chore(release): v3.8.43 — 2026-07-02

This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-02 09:28:34 -03:00
parent cbc161bfc3
commit dd6cb2023f
58 changed files with 9144 additions and 220 deletions

View File

@@ -2,30 +2,14 @@
## [Unreleased]
---
## [3.8.43] — 2026-07-02
### ✨ New Features
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
### 🔧 Bug Fixes
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
---
## [3.8.43] — TBD
### ✨ New Features
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
@@ -50,9 +34,70 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
@@ -87,7 +132,7 @@
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
@@ -147,12 +192,108 @@
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -339,12 +339,12 @@ Result: 4 layers of fallback = zero downtime
- **🤖 One-command CLI/agent setup** — a dedicated `setup-*` command configures each coding tool to route through OmniRoute (Claude Code, Codex, Cline, Continue, Cursor, Roo Code, Kilo Code, Crush, Goose, Qwen Code, Aider, OpenCode); `omniroute launch` / `omniroute launch-codex` are zero-config launchers. → [CLI Integrations](docs/guides/CLI-INTEGRATIONS.md)
- **🛰️ Remote mode** — drive a remote OmniRoute from any machine with scoped access tokens (`omniroute connect` / `omniroute contexts` / `omniroute tokens`), plus an `omniroute login antigravity` helper that runs Google "native/desktop" OAuth on your own machine and pastes a credential blob into a remote/VPS install (where the loopback redirect is unreachable). → [Remote Mode](docs/guides/REMOTE-MODE.md)
- **🧭 Smarter auto-routing** — OpenRouter-style `auto/<category>:<tier>` combos (e.g. `auto/coding:fast`, `auto/reasoning:pro`), a **Fusion** strategy (fan out to a panel of models in parallel, then synthesize via a judge), **task-aware routing** (best-fit connection per task type), per-request `X-Route-Model` override, live Arena-ELO + models.dev model intelligence, per-step account allowlists, provider-wildcard combo steps, nested combo-ref execution, sticky weighted selection, and `web_search`-aware routing. → [Auto-Combo](docs/routing/AUTO-COMBO.md)
- **🗜️ Pluggable compression** — an async pipeline of **10 composable engines** with Compression Studios, an LLMLingua-2 ONNX engine and a heuristic/SLM two-tier **Ultra**, RTK, delegated Anthropic Context Editing, **Output Styles** (output-axis steering: terse-prose / less-code / terse-CJK), an **adaptive context-budget dial** (escalate only as far as needed to fit the context window), per-request `x-omniroute-compression` control, an opt-in offline eval harness, one-click **Headroom** proxy lifecycle management from the dashboard (Docker sidecar supported), a synthetic **compression playground** (Play lanes + A/B Compare with USD-capped fidelity verdicts), an opt-in **per-step fidelity gate** that rejects a lossy engine before it degrades the prompt, a **best-of-N candidate encoder** (GCF vs TOON — keep whichever is shorter, with an A/B bytes/token table in the studio), **CCR ranged/grep/stats retrieval** (pull an exact byte/line slice or summary of a stored block instead of re-expanding it), and a unified panel with named profiles + an active-profile selector. → [Compression](docs/compression/COMPRESSION_ENGINES.md)
- **🗜️ Pluggable compression** — an async pipeline of **10 composable engines** with Compression Studios, an LLMLingua-2 ONNX engine and a heuristic/SLM two-tier **Ultra**, RTK, delegated Anthropic Context Editing, **Output Styles** (output-axis steering: terse-prose / less-code / terse-CJK), an **adaptive context-budget dial** (escalate only as far as needed to fit the context window), per-request `x-omniroute-compression` control, an opt-in offline eval harness, one-click **Headroom** proxy lifecycle management from the dashboard (Docker sidecar supported), a synthetic **compression playground** (Play lanes + A/B Compare with USD-capped fidelity verdicts), an opt-in **per-step fidelity gate** that rejects a lossy engine before it degrades the prompt, a **best-of-N candidate encoder** (GCF vs TOON — keep whichever is shorter, with an A/B bytes/token table in the studio), **CCR ranged/grep/stats retrieval** (pull an exact byte/line slice or summary of a stored block instead of re-expanding it), a unified panel with named profiles + an active-profile selector, an opt-in **per-engine pipeline circuit-breaker**, an opt-in **LLM-tier engine** (a model pass for higher-ratio semantic compression), a **read-lifecycle engine** that collapses superseded file reads, **usage-observed prefix freeze**, a graduated **CCR retrieval-feedback ramp**, a `preserveSystemPrompt` mode enum, and a **drag-reorder pipeline editor** in the studio. → [Compression](docs/compression/COMPRESSION_ENGINES.md)
- **🕵️ Transparent MITM decrypt (TPROXY)** — capture & translate traffic from CLIs that ignore proxy env vars, with a per-SNI certificate authority and a trust-store installer. → [MITM/TPROXY](docs/security/MITM-TPROXY-DECRYPT.md)
- **💸 Cost telemetry everywhere** — `X-OmniRoute-*` cost/usage headers on every endpoint (including media), a non-token cost engine, a cache-HIT `X-OmniRoute-Cost-Saved` header, and per-key USD spend quotas. → [API Reference](docs/reference/API_REFERENCE.md)
- **🧠 Memory you control** — opt-in int8 vector quantization (Qdrant + sqlite-vec), memory off by default, and a per-request `x-omniroute-no-memory` header. → [Memory](docs/frameworks/MEMORY.md)
- **🧠 Memory you control** — opt-in int8 vector quantization (Qdrant + sqlite-vec), opt-in **typed memory decay** (aged low-value memories fade on a per-type schedule), memory off by default, and a per-request `x-omniroute-no-memory` header. → [Memory](docs/frameworks/MEMORY.md)
- **🛡️ Security** — a prompt-injection guard across every LLM route (backed by a red-team suite), plus a free DuckDuckGo last-resort web search. → [Guardrails](docs/security/GUARDRAILS.md)
- **🤝 More providers & agents** — Cursor Cloud Agent (a 4th cloud agent), CodeBuddy CN (`copilot.tencent.com`), a Google Flow video-generation provider, new gateways **DGrid** and **Pioneer AI** (Fastino Labs), inbound **xAI Grok** translators plus **Grok Build (xAI)** with an OAuth import-token flow, GPT-4 / GPT-4o-mini on the GitHub Copilot provider, multi-model **Factory Droid**, **ZenMux Free** (session-cookie free tier), **Alibaba DashScope** text-to-video (`wan2.7-t2v`), a refreshed 237-provider catalog (OrcaRouter, Wafer AI, OpenAdapter, dit.ai, TokenRouter, …), Vertex AI media generation (speech/transcription/music/video), and one-click account import from CLIProxyAPI (`~/.cli-proxy-api/`). → [Providers](docs/reference/PROVIDER_REFERENCE.md)
- **🤝 More providers & agents** — Cursor Cloud Agent (a 4th cloud agent), CodeBuddy CN (`copilot.tencent.com`), a Google Flow video-generation provider, new gateways **DGrid** and **Pioneer AI** (Fastino Labs), inbound **xAI Grok** translators plus **Grok Build (xAI)** with an OAuth import-token flow, GPT-4 / GPT-4o-mini on the GitHub Copilot provider, multi-model **Factory Droid**, **ZenMux Free** (session-cookie free tier), **Alibaba DashScope** text-to-video (`wan2.7-t2v`), a refreshed 237-provider catalog (OrcaRouter, Wafer AI, OpenAdapter, dit.ai, TokenRouter, …), Vertex AI media generation (speech/transcription/music/video), a first-class **Ollama** local-provider card, the **SenseNova** free Token Plan (chat + text-to-image), and one-click account import from CLIProxyAPI (`~/.cli-proxy-api/`). → [Providers](docs/reference/PROVIDER_REFERENCE.md)
- **⚡ Local performance & infra** — a one-click local Redis launcher (`omniroute redis up`, plus a dashboard Redis panel), one-click **Cloudflare Workers** and **Deno Deploy** relay deployers wired into the proxy pool, and an optional Bifrost Go sidecar that offloads the hottest relay path (`BIFROST_BASE_URL`, with automatic fallback to the TypeScript path on timeout) — now with a relay-backend selector (`OMNIROUTE_RELAY_BACKEND=ts|bifrost|auto`) so the `/v1/relay` endpoint stays the stable surface while choosing the fastest backend internally. → [Environment](docs/reference/ENVIRONMENT.md)
<br/>

View File

@@ -2,7 +2,8 @@
"_comment": "Catraca de qualidade. 'down' = nao pode aumentar; 'up' = nao pode cair. Atualize via 'npm run quality:ratchet -- --update' (somente quando melhora). Cada valor e um numero REAL medido, nunca um chute. Cobertura entra na Fase 4 a partir de um run de cobertura mergeada no CI.",
"metrics": {
"eslintWarnings": {
"value": 4158,
"value": 4199,
"_rebaseline_2026_07_02_v3843_release_close": "4158->4199 (+41). v3.8.43 release-close drift measured by the release-green pre-flight (the Quality Ratchet does NOT run on PR->release fast-gates, so warnings accrued unmeasured across the ~120 commits merged after the mid-cycle 4158 rebaseline — the compression T02/T05/T06/T07/T08/T10 engine families, memory typed decay, provider adds Ollama/SenseNova, ~55 SSE/translator/kiro/oauth/dashboard fixes, and the god-file decomposition wave). Trust-but-verify: measured 4199 via `npm run lint` on the release-finalize working tree INCLUDING my changes (CHANGELOG/i18n/README docs + kiro pricing data entry + the 3 base-red CODE fixes: opencode fabrication removal, resolveEffectiveKey type-widen, openai-to-claude claudeFinishEmitted flag + 4 test-alignment files + golden snapshot regen) — the code fixes NET-REMOVE lines and add no `any`/unused, and lint reported 4199 both before and after them, so all +41 is inherited cycle drift (`any` warn-allowed in open-sse/ + tests/). Tighten via --require-tighten next cycle.",
"direction": "down",
"_rebaseline_2026_07_01_v3843_release": "4121->4158 (+37). v3.8.43 cycle drift surfaced by the release-green pre-flight; the Quality Ratchet does NOT run on PR->release fast-gates, so warnings accrued unmeasured across this cycle. 4158 = the value measured by the CI Quality Ratchet on the release tip fce85136c (release PR #5609). Trust-but-verify: the fix/release-v3843-ci-reds branch touches only test files (rtk-mcp-tools de-flake, compression-studio e2e anchor, oauth-error-linkify hardening test) + src/shared/utils/linkify.ts (eslint-clean, 0 warnings) + stryker.conf.json + this baseline -> 0 new warnings, so all +37 is inherited cycle drift (any warn-allowed in open-sse/ + tests/). Tighten via --require-tighten next cycle.",
"_rebaseline_2026_06_30_v3842_release": "4116->4121 (+5). v3.8.42 cycle drift surfaced by the release-green pre-flight (the Quality Ratchet does NOT run on PR->release fast-gates, so warnings accrued unmeasured across this cycle's 90 commits — chatgpt-web PoW sha3-512 BoringSSL fix #5540, provider baseUrl/i18n umbrella #5511, proxy union proxyUrlMap+acct.proxy #5521, dead-code + duplication waves #5468-#5495, tls-options packaging #5503, release-freeze + .npmrc fetch-retries #5506, dast-smoke spawn-prefix client-safe extraction #5546, plus ~30 SSE/translator/combo/dashboard fixes). Trust-but-verify: measured 4121 via `npm run check:release-green` on the working tree INCLUDING my reconciliation (CHANGELOG/i18n/golden snapshot + file-size baseline) — those touch only config JSON + a provider snapshot (eslint-ignored) and contribute 0 warnings; all +5 is inherited cycle drift (`any` warn-allowed in open-sse/ + tests/). Tighten via --require-tighten next cycle.",

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -6,11 +6,15 @@
## [3.8.31] — 2026-06-20
## [3.8.43] — TBD
## [3.8.43] — 2026-07-02
### ✨ New Features
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky))
- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles/<name>/settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737).
- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)).
@@ -32,13 +36,107 @@
- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7.
- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark)
- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21)
- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4)
- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw)
- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1)
- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw)
- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw)
- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw)
- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari)
- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw)
- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu)
- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw)
- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw)
- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw)
- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw)
- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw)
- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw)
- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw)
- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw)
- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw)
### 🔧 Bug Fixes
- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw)
- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw)
- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw)
- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw)
- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4).
- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 12 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)
- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)
- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842))
- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari))
- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))
- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887))
- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849))
- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554))
- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836))
- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871))
- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873))
- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872))
- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819))
- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer <token-id>:<token-secret>`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`.
- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself))
- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)
- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21)
- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)
- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo))
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))
- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1).
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) *responded* with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces *why* a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716))
- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)
- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692))
@@ -86,8 +184,118 @@
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic)
- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero)
- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)
- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)
- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)
- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3)
- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba)
- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4)
- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3)
- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3)
- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr)
- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw)
- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2)
- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2)
- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself)
- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong)
- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw)
- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127)
- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw)
- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw)
- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw)
- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw)
- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025)
- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden)
- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw)
- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself)
- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw)
- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn)
- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun)
- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira)
- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1)
- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape)
- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari)
- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw)
- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw)
- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw)
- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw)
- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw)
- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw)
- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw)
- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw)
- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw)
- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw)
- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw)
- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw)
- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw)
- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw)
### 📝 Maintenance
- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891))
- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari
- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7).
- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw
- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw)
- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw
- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw
- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw)
---
## [3.8.42] — 2026-06-30

View File

@@ -547,12 +547,12 @@ export class BaseExecutor {
* Resolve the effective API key via extra-keys round-robin rotation.
* Mutates `credentials.providerSpecificData.selectedKeyId` on rotation.
*/
protected resolveEffectiveKey(credentials: ProviderCredentials): string {
protected resolveEffectiveKey(credentials: ProviderCredentials): string | undefined {
const extraKeys =
(credentials.providerSpecificData?.extraApiKeys as string[] | undefined) ?? [];
const selectedKeyId = (credentials.providerSpecificData as Record<string, unknown> | undefined)
?.selectedKeyId as string | undefined;
let effectiveKey = credentials.apiKey ?? "";
let effectiveKey = credentials.apiKey;
if (extraKeys.length > 0 && credentials.connectionId && credentials.apiKey) {
const resolved = resolveKeyForRequest(
credentials.connectionId,
@@ -577,7 +577,7 @@ export class BaseExecutor {
protected buildHeadersPreamble(
credentials: ProviderCredentials,
stream: boolean
): { headers: Record<string, string>; effectiveKey: string } {
): { headers: Record<string, string>; effectiveKey: string | undefined } {
const headers: Record<string, string> = {
"Content-Type": "application/json",
...this.config.headers,
@@ -901,7 +901,11 @@ export class BaseExecutor {
const strippedFields = new Set<string>();
for (let urlIndex = 0; urlIndex < fallbackCount; urlIndex++) {
const requestCredentials = withForcedResponsesUpstream(this.provider, body, activeCredentials);
const requestCredentials = withForcedResponsesUpstream(
this.provider,
body,
activeCredentials
);
const url = this.buildUrl(model, stream, urlIndex, requestCredentials);
const headers = this.buildHeaders(requestCredentials, stream, clientHeaders, model);
applyConfiguredUserAgent(headers, requestCredentials?.providerSpecificData);

View File

@@ -254,13 +254,6 @@ export class OpencodeExecutor extends BaseExecutor {
});
}
if (!headers["User-Agent"] && !headers["user-agent"]) {
headers["User-Agent"] = "opencode/local";
}
if (!headers["x-opencode-client"]) {
headers["x-opencode-client"] = "cli";
}
void model;
return headers;

View File

@@ -225,8 +225,15 @@ export function openaiToClaudeResponse(chunk, state) {
}
}
// Finish — guard against duplicate finish_reason chunks (common with OpenAI-compatible models)
if (choice.finish_reason && !state.finishReason) {
// Finish — guard against duplicate finish_reason chunks (common with OpenAI-compatible models).
// Use a dedicated `claudeFinishEmitted` flag rather than `state.finishReason`: in the
// Responses→Claude hub path the shared `state` object is also written by the
// openai-responses→openai translator, which sets `state.finishReason` on
// `response.completed` BEFORE this openai→claude step runs. Reusing `finishReason` as the
// guard therefore misfired and silently dropped the terminal message_delta/message_stop
// for Responses→Claude streams (#5828 regression).
if (choice.finish_reason && !state.claudeFinishEmitted) {
state.claudeFinishEmitted = true;
stopThinkingBlock(state, results);
stopTextBlock(state, results);

View File

@@ -526,6 +526,13 @@ export const DEFAULT_PRICING_OAUTH = {
reasoning: 15.0,
cache_creation: 3.0,
},
"claude-sonnet-5": {
input: 3.0,
output: 15.0,
cached: 1.5,
reasoning: 15.0,
cache_creation: 3.0,
},
"deepseek-v3.2": {
input: 0.27,
output: 1.1,

View File

@@ -2277,27 +2277,23 @@
"format": "openai",
"headers": {
"apiKey": {
"Accept": "*/*",
"Accept": "text/event-stream",
"Authorization": "Bearer <TOK>",
"Content-Type": "application/connect+json",
"connect-protocol-version": "1"
"Content-Type": "application/json"
},
"nonStream": {
"Accept": "*/*",
"Authorization": "Bearer <TOK>",
"Content-Type": "application/connect+json",
"connect-protocol-version": "1"
"Content-Type": "application/json"
},
"oauth": {
"Accept": "*/*",
"Accept": "text/event-stream",
"Authorization": "Bearer <TOK>",
"Content-Type": "application/connect+json",
"connect-protocol-version": "1"
"Content-Type": "application/json"
}
},
"url": {
"nonStream": "https://www.kimi.com/apiv2/kimi.gateway.chat.v1.ChatService/Chat",
"stream": "https://www.kimi.com/apiv2/kimi.gateway.chat.v1.ChatService/Chat"
"nonStream": "https://www.kimi.com",
"stream": "https://www.kimi.com"
}
},
"kiro": {

View File

@@ -11,6 +11,11 @@ const core = await import("../../src/lib/db/core.ts");
const usageHistory = await import("../../src/lib/usage/usageHistory.ts");
const callLogs = await import("../../src/lib/usage/callLogs.ts");
// Captured stream chunks carry a per-chunk arrival-time prefix ("[HH:MM:SS.mmm] ")
// added by the request logger for streaming-latency observability (#5834). Strip it
// before comparing the raw chunk payload.
const stripChunkTs = (chunk: string): string => chunk.replace(/^\[\d{2}:\d{2}:\d{2}\.\d{3}\] /, "");
test.after(() => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
@@ -544,7 +549,7 @@ test("createRequestLogger and trackPendingRequest with matching model propagate
string,
string[]
>;
assert.equal(apiChunks?.provider[0], 'data: {"chunk":"hello"}');
assert.equal(stripChunkTs(apiChunks?.provider[0]), 'data: {"chunk":"hello"}');
});
test("finalizePendingRequestById completes the exact stream when same model requests overlap", () => {

View File

@@ -437,17 +437,19 @@ test("chatCore sanitization normalizes mixed content blocks and removes unsuppor
textBlocks.some((block) => block.text === "[draft.txt]\nDraft text"),
true
);
// Orphaned tool_result blocks (no matching tool_use anywhere in the request) are
// stripped by `stripOrphanedToolResults` (#5805) before reaching content normalization,
// to avoid strict-upstream 400s. They are therefore removed entirely — neither preserved
// as tool_result blocks nor inlined as "[Tool Result: …]" text.
assert.equal(
textBlocks.some((block) => block.text === "[Tool Result: tool-1]\ndone"),
true
content.some((block) => block.type === "tool_result"),
false
);
assert.equal(
textBlocks.some((block) => block.text === "[Tool Result: tool-2]\nstructured result"),
true
);
assert.equal(
textBlocks.some((block) => block.text === '[Tool Result: tool-3]\n{"status":"ok","count":2}'),
true
textBlocks.some(
(block) => typeof block.text === "string" && block.text.includes("[Tool Result:")
),
false
);
});

View File

@@ -75,10 +75,14 @@ test("OpencodeExecutor.buildHeaders: Content-Type always application/json", () =
assert.equal(headers["Content-Type"], "application/json");
});
test("OpencodeExecutor.buildHeaders: defaults User-Agent to opencode/local when no client UA", () => {
test("OpencodeExecutor.buildHeaders: omits User-Agent when no client UA (forward-only, not fabricated)", () => {
// Forward-only contract (see opencode-executor.test.ts): opencode client identity headers
// are opencode-internal — inventing them risks upstream rejection, so we never fabricate a
// default. A pure dedup refactor (#5720) briefly regressed this by defaulting to
// "opencode/local"; the executor forwards a client-sent User-Agent but adds none of its own.
const executor = new OpencodeExecutor("opencode");
const headers = executor.buildHeaders({ apiKey: "key-1" }, true);
assert.equal(headers["User-Agent"], "opencode/local");
assert.equal(headers["User-Agent"], undefined);
});
test("OpencodeExecutor.buildHeaders: preserves client User-Agent when provided", () => {
@@ -89,10 +93,12 @@ test("OpencodeExecutor.buildHeaders: preserves client User-Agent when provided",
assert.equal(headers["User-Agent"], "opencode/1.17.12");
});
test("OpencodeExecutor.buildHeaders: defaults x-opencode-client to cli when absent", () => {
test("OpencodeExecutor.buildHeaders: omits x-opencode-client when absent (forward-only, not fabricated)", () => {
// x-opencode-client / x-opencode-project valid values are opencode-internal; fabricating a
// default ("cli") risks upstream rejection, so they stay forward-only (see opencode-executor.test.ts).
const executor = new OpencodeExecutor("opencode");
const headers = executor.buildHeaders({ apiKey: "key-1" }, true);
assert.equal(headers["x-opencode-client"], "cli");
assert.equal(headers["x-opencode-client"], undefined);
});
test("OpencodeExecutor.buildHeaders: preserves x-opencode-client from client headers", () => {

View File

@@ -98,7 +98,9 @@ test("request log detail splits token badges into input and output groups", () =
const modelLabelIndex = html.indexOf(">Model<");
const requestedModelLabelIndex = html.indexOf(">Requested Model<");
assert.notEqual(html.indexOf(">Completed Time<"), -1);
// The completed-request detail grid renders "Started At" / "Ended At" columns
// (renamed from the old single "Completed Time" label in #5834).
assert.notEqual(html.indexOf(">Ended At<"), -1);
assert.equal(html.includes(">Time<"), false);
assert.notEqual(inputLabelIndex, -1);
assert.notEqual(outputLabelIndex, -1);
@@ -173,11 +175,7 @@ test("request log detail compression-summary badge shows positive saved%, never
// Original bug repro: totalIn=0, compressed=5286 → previously rendered "(100%)".
const fullyCompressed = make(0, 5286);
assert.match(fullyCompressed, /Compressed: 5,286 → 0 \(100% saved\)/);
assert.equal(
fullyCompressed.includes("(-100%)"),
false,
"literal '(-100%)' must never appear"
);
assert.equal(fullyCompressed.includes("(-100%)"), false, "literal '(-100%)' must never appear");
assert.equal(
fullyCompressed.includes("\u2212100%"),
false,

View File

@@ -40,7 +40,7 @@ test("event stream shows only when debugEnabled and appears above legacy respons
);
assert.notEqual(
html.indexOf(">Event Stream (Debug)<"),
html.indexOf(">Provider Event Stream<"),
-1,
"Event Stream should be present when debugEnabled"
);
@@ -51,7 +51,7 @@ test("event stream shows only when debugEnabled and appears above legacy respons
"Legacy response payload should be present"
);
assert(
html.indexOf(">Event Stream (Debug)<") < html.indexOf(">Response Payload (Legacy)<"),
html.indexOf(">Provider Event Stream<") < html.indexOf(">Response Payload (Legacy)<"),
"Event Stream should appear before Response Payload (Legacy)"
);
});
@@ -85,7 +85,7 @@ test("event stream hidden when debugEnabled is false", () => {
);
assert.equal(
html.indexOf(">Event Stream (Debug)<"),
html.indexOf(">Provider Event Stream<"),
-1,
"Event Stream should be hidden when debugEnabled is false"
);
@@ -163,15 +163,17 @@ test("request logger detail renders stream chunks correctly", () => {
const expectedFragment = "message_start";
assert.notEqual(
html.indexOf(">Event Stream (Debug)<"),
html.indexOf(">Provider Event Stream<"),
-1,
"Event Stream header should be present"
);
// The payload is HTML-escaped; check for the provider key token and the message content
// The new UI renders the provider stream under a "Provider Event Stream" section
// (the raw key is no longer dumped inline); match case-insensitively so the check
// still asserts the provider stream is referenced in the output.
assert.notEqual(
html.indexOf("provider"),
html.toLowerCase().indexOf("provider"),
-1,
"Stream chunks output should reference provider key"
"Stream chunks output should reference the provider stream"
);
assert.notEqual(
html.indexOf(expectedFragment),

View File

@@ -52,7 +52,10 @@ test("serializes truncated payloads as valid JSON objects", () => {
});
test("structured SSE collector preserves event order and marks truncation", () => {
const collector = createStructuredSSECollector({ maxEvents: 2, maxBytes: 200 });
// Each collected event now also carries an ISO `timestamp` field (#5834 observability),
// which enlarges per-event bytes. Give the byte budget enough headroom so truncation
// here is driven by maxEvents (drop 1 of 3), which is what this test verifies.
const collector = createStructuredSSECollector({ maxEvents: 2, maxBytes: 2000 });
collector.push({ type: "response.created", id: "r1" });
collector.push({ type: "response.output_text.delta", delta: "hi" });

View File

@@ -16,6 +16,12 @@ test.after(() => {
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
// Captured stream chunks are prefixed with a per-chunk arrival timestamp
// ("[HH:MM:SS.mmm] ") by the request logger for streaming-latency observability
// (added in #5834 / correlation-id observability). Strip it before comparing the
// raw payload so these assertions verify the chunk content + ordering, not the clock.
const stripChunkTs = (chunk: string): string => chunk.replace(/^\[\d{2}:\d{2}:\d{2}\.\d{3}\] /, "");
// ─── Active log endpoint removal ─────────────────────────────────────────
test("/api/logs/active route is removed", () => {
@@ -280,7 +286,7 @@ test("GET /api/usage/call-logs includes completed in-memory fallback rows", asyn
assert.equal(rows[0].duration, completedDetail!.durationMs);
});
test("GET /api/usage/call-logs rows are globally timestamp ordered", async () => {
test("GET /api/usage/call-logs rows are ordered by priority then newest-first", async () => {
const { buildCallLogListRows } = await import("../../src/app/api/usage/call-logs/route.ts");
const rows = buildCallLogListRows({
logs: [
@@ -311,9 +317,13 @@ test("GET /api/usage/call-logs rows are globally timestamp ordered", async () =>
now: 4000,
});
// Rows sort by priority first (active=0 > completed in-memory=1 > persisted=2),
// then newest-first within each priority (#5834 correlation-id observability).
// The completed in-memory entry therefore floats above the persisted DB rows,
// which are ordered newest-first among themselves.
assert.deepEqual(
rows.map((row) => row.id),
["persisted-newest", "completed-middle", "persisted-oldest"]
["completed-middle", "persisted-newest", "persisted-oldest"]
);
});
@@ -525,8 +535,8 @@ test("createRequestLogger with connectionId/model/provider populates streamChunk
assert.ok(detail.streamChunks, "streamChunks should be non-null after appendProviderChunk");
assert.ok(Array.isArray(detail.streamChunks.provider), "provider array should exist");
assert.equal(detail.streamChunks.provider.length, 2, "should have 2 provider chunks");
assert.equal(detail.streamChunks.provider[0], 'data: {"content":"hello"}');
assert.equal(detail.streamChunks.provider[1], 'data: {"content":" world"}');
assert.equal(stripChunkTs(detail.streamChunks.provider[0]), 'data: {"content":"hello"}');
assert.equal(stripChunkTs(detail.streamChunks.provider[1]), 'data: {"content":" world"}');
assert.deepEqual(detail.streamChunks.openai, []);
assert.deepEqual(detail.streamChunks.client, []);
});
@@ -550,7 +560,7 @@ test("createRequestLogger requestId binds streamChunks to the exact pending requ
const second = usageHistory.getPendingById().get(secondId);
assert.equal(first?.streamChunks, undefined);
assert.equal(second?.streamChunks?.provider[0], 'data: {"content":"second"}');
assert.equal(stripChunkTs(second?.streamChunks?.provider[0]), 'data: {"content":"second"}');
});
test("createRequestLogger fallback match does not cross connectionId boundaries", async () => {
@@ -571,7 +581,7 @@ test("createRequestLogger fallback match does not cross connectionId boundaries"
logger.appendProviderChunk('data: {"content":"conn-b"}');
const second = usageHistory.getPendingById().get(secondId);
assert.equal(second?.streamChunks?.provider[0], 'data: {"content":"conn-b"}');
assert.equal(stripChunkTs(second?.streamChunks?.provider[0]), 'data: {"content":"conn-b"}');
});
test("createRequestLogger without connectionId does not populate streamChunks", async () => {
@@ -624,9 +634,12 @@ test("createRequestLogger appendOpenAIChunk and appendConvertedChunk also popula
assert.ok(detail?.streamChunks, "streamChunks should be set");
assert.equal(detail.streamChunks.openai.length, 1);
assert.equal(detail.streamChunks.openai[0], 'data: {"role":"assistant","content":"hi"}');
assert.equal(
stripChunkTs(detail.streamChunks.openai[0]),
'data: {"role":"assistant","content":"hi"}'
);
assert.equal(detail.streamChunks.client.length, 1);
assert.equal(detail.streamChunks.client[0], 'data: {"content":"there"}');
assert.equal(stripChunkTs(detail.streamChunks.client[0]), 'data: {"content":"there"}');
});
test("createRequestLogger captures stream chunks even when enabled: false", async () => {
@@ -653,7 +666,7 @@ test("createRequestLogger captures stream chunks even when enabled: false", asyn
assert.ok(detail?.streamChunks, "streamChunks should be set even when logger is disabled");
assert.equal(detail.streamChunks.provider.length, 1);
assert.equal(detail.streamChunks.provider[0], 'data: {"content":"hello"}');
assert.equal(stripChunkTs(detail.streamChunks.provider[0]), 'data: {"content":"hello"}');
// But getPipelinePayloads should return null when disabled
assert.equal(

View File

@@ -18,6 +18,12 @@ const {
const { FORMATS } = await import("../../open-sse/translator/formats.ts");
const { createRequestLogger } = await import("../../open-sse/utils/requestLogger.ts");
// Retained stream chunks are prefixed with a fixed-width per-chunk arrival timestamp
// ("[HH:MM:SS.mmm] ", 15 chars) by the request logger for streaming-latency
// observability (added in #5834). Strip it before comparing raw chunk payloads.
const STREAM_CHUNK_TS_PREFIX_LEN = "[HH:MM:SS.mmm] ".length;
const stripChunkTs = (chunk: string): string => chunk.replace(/^\[\d{2}:\d{2}:\d{2}\.\d{3}\] /, "");
const textEncoder = new TextEncoder();
// PR #3399 intentionally changed the synthetic empty-response text to "" so that
// proxy internals no longer leak into chat history. Tests assert on the new behavior.
@@ -2093,16 +2099,21 @@ test("createRequestLogger skips disabled logs and caps retained stream chunk byt
const logger = await createRequestLogger("openai", "openai", "gpt-test", {
enabled: true,
captureStreamChunks: true,
maxStreamChunkBytes: 5,
// The byte budget now also counts the 15-char timestamp prefix, so size it as
// prefix + 5 to keep the original intent: cap the *payload* to 5 bytes ("abcde").
maxStreamChunkBytes: STREAM_CHUNK_TS_PREFIX_LEN + 5,
});
logger.appendProviderChunk("abcdef");
logger.appendProviderChunk("ghijkl");
const payloads = logger.getPipelinePayloads();
assert.deepEqual(payloads.streamChunks.provider, [
"abcde",
"[stream chunk log truncated after 5 bytes]",
]);
// First chunk retained but its payload truncated to 5 bytes; second replaced by marker.
assert.equal(stripChunkTs(payloads.streamChunks.provider[0]), "abcde");
assert.equal(
payloads.streamChunks.provider[1],
`[stream chunk log truncated after ${STREAM_CHUNK_TS_PREFIX_LEN + 5} bytes]`
);
assert.equal(payloads.streamChunks.provider.length, 2);
});
test("createRequestLogger caps retained stream chunk item count", async () => {
@@ -2118,10 +2129,9 @@ test("createRequestLogger caps retained stream chunk item count", async () => {
logger.appendProviderChunk("three");
const payloads = logger.getPipelinePayloads();
assert.deepEqual(payloads.streamChunks.provider, [
"one",
"[stream chunk log truncated after 2 chunks]",
]);
assert.equal(stripChunkTs(payloads.streamChunks.provider[0]), "one");
assert.equal(payloads.streamChunks.provider[1], "[stream chunk log truncated after 2 chunks]");
assert.equal(payloads.streamChunks.provider.length, 2);
});
// T-VERIFY: passthrough mode failure decrements pending requests