v3.8.28
✨ New Features
- feat(providers): add OrcaRouter (OpenAI-compatible routing gateway) — OrcaRouter is now registered as an API-key provider. Its adaptive router is exposed as
orcarouter/auto(smart routing across 150+ upstream models), alongside a curated flagship set (GPT-5.5, Gemini 3.5 Flash, Claude Opus 4.8, Grok 4.3, DeepSeek V4 Pro, MiniMax M2.7, Qwen3.7 Max).passthroughModelsis enabled so any OrcaRouter model id works. OpenAI-compatible endpoint (https://api.orcarouter.ai/v1), Bearer (sk-orca-…) auth — no custom executor or translator required. (#4070 — thanks @jinhaosong-source) - feat(providers): add Wafer AI (Anthropic-compatible, Bearer auth) — Wafer AI is now a built-in provider speaking the Anthropic Messages format with Bearer authentication, registered with its model catalog so it works out of the box. (#4098 — thanks @diegosouzapw)
- feat(cli):
omniroute launch— zero-config Claude Code launcher — a new CLI subcommand that boots OmniRoute (if not already running) and launches Claude Code pre-wired to it, with no manual env/settings editing. (#4097 — thanks @diegosouzapw) - feat(api): exact offline token counting for the
count_tokensfallback via tiktoken — the localcount_tokensfallback now uses a real tiktoken (BPE) tokenizer for exact offline counts instead of a heuristic estimate, so token budgeting is accurate even when no upstream count endpoint is reachable. (#4087 — thanks @diegosouzapw) - feat(sse): Claude Code quota-probe bypass + command meta-request helpers — ported from free-claude-code: OmniRoute now recognizes Claude Code's quota-probe and command meta-requests and serves them locally instead of burning an upstream call, reducing wasted quota during CLI sessions. (#4083 — thanks @diegosouzapw)
- feat(sse): generic 400 field-downgrade retry + Groq field stripping — when an upstream rejects a request with
400because of an unsupported field, OmniRoute now strips the offending field and retries (a generic downgrade path), with Groq-specific field stripping wired in. Aligned with the existingcontext_managementretry handling. (#4096 — thanks @diegosouzapw) - feat(sse): delegated Anthropic Context Editing — relay coverage + 400-fallback — extends the Claude server-side Context Editing delegation (#4021) with broader relay coverage and a
400-fallback so a request the upstream rejects for the context-management beta degrades gracefully instead of failing. (#4065 — thanks @diegosouzapw) - feat(compression): record per-engine Context Editing telemetry — the compression pipeline now records a
context-editingengine entry so the dashboard attributes server-side Context Editing savings alongside the local compression engines. (#4062 — thanks @diegosouzapw) - feat(compression): RTK learn/discover (sample source + API + UI) — the rule-based RTK compression engine gains a learn/discover workflow: sample a source, surface candidate rules through a new API, and review/apply them in the dashboard. (#4088 — thanks @diegosouzapw)
- feat(dashboard): 2026-06-17 free-tier refresh — honest catalog, uncapped + boost tiers, Layout A budget table — the free-tier page was refreshed with an honest, deep-researched catalog (pooled/realistic figures rather than inflated 24/7 RPM math), new
recurring-uncappedand boost tiers, new providers, and a KPI + budget table (Layout A). (#4089 — thanks @diegosouzapw) - feat(dashboard): Combo Studio connection-cooldown badge (U1b Slice 2) — the Combo Live cascade now surfaces each connection's cooldown state as a badge, complementing the circuit-breaker badge shipped in 3.8.27. (#4068 — thanks @diegosouzapw)
- feat(mitm): attribute intercepted requests to the originating process (Gap 1) — the Traffic Inspector now resolves each intercepted connection back to the originating local process (via
/proc), so captured traffic can be attributed to the app that produced it. (ProxyBridge-inspired hardening.) (#4085 — thanks @diegosouzapw) - feat(mitm): capture-pipeline self-test route (Gap 12) — a diagnostic route that exercises the MITM capture pipeline end-to-end so operators can confirm interception is working without crafting a real upstream call. (#4093 — thanks @diegosouzapw)
- feat(mitm): loop-guard self-check + verbosity control in
server.cjs(Gaps 14+15) — the MITM proxy gains a self-referential loop guard (so it never proxies its own traffic into an infinite loop) and aMITM_VERBOSErouting-decision log level. (#4101 — thanks @diegosouzapw) - feat(agent-bridge): portable JSON import/export of config (Gap 4) — the Agent Bridge / MITM configuration can now be exported to and imported from a portable JSON file, so a working setup can be backed up or moved between machines. (#4094 — thanks @diegosouzapw)
🐛 Fixed
- fix(ws): start the LiveWS sidecar with
cwdat the package root (global/systemd installs) — the standalone LiveWS launcher (scripts/start-ws-server.mjs) re-spawns itself withnode --import tsx <self>but did not setcwd. When the WebSocket sidecar was launched from outside the package directory — a global npm/homebrew install, or asystemd/launchdunit started from$HOME— Node could not resolve thetsxpackage (ERR_MODULE_NOT_FOUND: Cannot find package 'tsx'), and even from the package directorytsxcould not resolve the tsconfig@/*path aliases (e.g.@/types/databaseSettings), so the sidecar never booted. The spawn now pinscwdto the package root (the directory abovescripts/, wherepackage.json+tsconfig.jsonlive), which resolves bothtsxdiscovery and the@/*aliases regardless of launch directory. (#4055 — thanks @Rahulsharma0810) - fix(dashboard): Logs page auto-refresh now works in embedded/proxied dashboards — the Request Logger gated each auto-refresh tick on a static
document.visibilityState === "visible"read. Hosts that report a permanent non-"visible"state without ever firing avisibilitychangeevent (Docker dashboard wrappers, embedded webviews) froze auto-refresh entirely — only the manual Refresh button worked, a regression from 3.8.24's unconditional polling. The pause is now event-driven and fail-open: polling starts enabled and only pauses after a realvisibilitychange→ hidden transition (still preserving the backgrounded-tab optimization for normal browser tabs). (#4054 — thanks @tjengbudi) - fix(docker): raise the build-stage Node heap to stop the production-build OOM — the Docker
builderstage rannpm run buildwith V8's default heap ceiling (~2 GB). After #4052 forced the heavier webpack engine (Turbopack panics on this Next.js version), the production optimization pass exceeded that ceiling and the build died withFATAL ERROR: … JavaScript heap out of memoryat[builder] npm run build. The builder stage now setsNODE_OPTIONS=--max-old-space-size(default 4096 MB, overridable via--build-arg OMNIROUTE_BUILD_MEMORY_MB=…) before the build; the value propagates to the spawnednext build. Build-only — the runtime heap (OMNIROUTE_MEMORY_MBon the runner stage) is unchanged. (#4076 — thanks @kamenkadmitry) - fix(dashboard): "Update Available" banner reappears reliably across Docker/npm/desktop installs — the home-page banner is gated on
GET /api/system/version'supdateAvailable, which derived the latest version ONLY fromnpm info omniroute version --jsonvia thenpmCLI binary. When that binary is absent from the runtime PATH (Docker/desktop/locked-down installs) or the registry is unreachable, the call returnednull→updateAvailable=false→ the banner silently never rendered even when a newer release existed. The route now resolves the latest version throughresolveLatestVersion(): the fastnpmCLI path first, then an npm-binary-free fallback over the registry HTTP API (registry.npmjs.org/omniroute/latest), and a logged warning instead of silent degradation when both fail. Version comparison was also hardened to toleratev-prefixed and pre-release version strings. (#4100) - fix(sse): route image requests only to confirmed-vision combo targets — a combo could route an image-bearing request to a member that doesn't actually support vision. Routing now requires
supportsVision === true(plus a model-id heuristic) before sending images to a target, so multimodal requests land only on members that can handle them. (#4071 — thanks @diego-anselmo) - fix(security): injection guard respects the
INJECTION_GUARD_MODEDB feature flag — the prompt-injection guard ignored the database feature-flag, so operators couldn't change its mode at runtime; it now reads the flag and honors the configured mode. (#4077 — thanks @zhiru) - fix(ws): proxy LAN
/live-wsupgrades and warn on an unsetJWT_SECRET— WebSocket upgrade requests arriving over the LAN proxy path were not forwarded to the LiveWS sidecar; they are now proxied correctly, and the server logs a clear warning whenJWT_SECRETis unset. (#4079 — thanks @Rahulsharma0810) - fix(dev): force webpack in the custom dev server (Turbopack 16.2.x panics) — the custom dev server now forces the webpack engine because Turbopack panics on this Next.js version, so
npm run devboots reliably. (#4092 — thanks @chirag127) - fix(auto): resolve built-in
auto/*catalog combos — referencing a built-inauto/*combo returned a premature400because the catalog entry wasn't resolved; the built-in auto catalog is now resolved before validation so those combos work. (#4058 — thanks @megamen32) - fix(sse): friendly 413 message for ChatGPT-web payload-too-large — an oversized ChatGPT-web payload returned an opaque error; it now returns a clear
413with a human-readable message. (#4080 — thanks @diegosouzapw) - fix(ws): warm the SSE auth import on LiveWS startup; relocate the boot test to integration — the LiveWS sidecar now pre-imports the SSE auth module at startup to avoid a first-request stall, and its boot test was moved to the integration suite. (#4063 — thanks @diegosouzapw)
- fix(mitm): crash-safe system-state teardown + socket timeouts (ProxyBridge-inspired hardening) — the MITM proxy could leave the host's system proxy settings applied if it crashed mid-teardown, and long-lived tunnels could leak as half-open sockets. Teardown is now crash-safe (system state is always restored) and proxied sockets get an idle timeout (
MITM_IDLE_TIMEOUT_MS, default 60s). (#4084 — thanks @diegosouzapw) - fix(responses): clear the
/v1/responseskeep-alive timer on cancel/abort — a cancelled or aborted/v1/responsesstream left its keep-alive timer running, leaking a timer and burning CPU; the timer is now cleared on cancel/abort. (#4105 — thanks @artickc) - fix(usage): reap orphaned pending-request details (unbounded memory leak) — pending-request detail entries whose request never completed accumulated without bound; they are now reaped, closing a slow memory leak. (#4107 — thanks @artickc)
- fix(auth): prune expired entries from the login brute-force guard map (unbounded growth) — the login brute-force guard map grew without bound because expired entries were never removed; expired entries are now pruned. (#4111 — thanks @artickc)
- fix(logger): hard-cap the error-dedup map to bound memory under unique-message bursts — a burst of unique error messages could grow the dedup map without limit; it is now hard-capped. (#4113 — thanks @artickc)
- fix(circuit-breaker): enforce
MAX_REGISTRY_SIZE(declared but never applied) — the circuit-breaker registry declared a maximum size that was never enforced, so it could grow unbounded; the cap is now applied. (#4114 — thanks @artickc) - fix(webhook): clear the abort timer in
finallyto avoid dangling timers on fetch error — a webhook dispatch that threw before clearing its abort timer left the timer dangling; it is now cleared in afinallyblock. (#4115 — thanks @artickc) - fix(combo): detach the per-target listener from the shared hedge abort signal — combo hedging attached a per-target listener to a shared abort signal without detaching it, leaking listeners across requests; the listener is now detached. (#4116 — thanks @artickc)
- fix(timers): unref background interval timers so they don't block clean shutdown — long-lived background interval timers kept the event loop alive and blocked a clean process exit; they are now
unref'd. (#4117 — thanks @artickc)
⚡ Performance
- perf(registry): precompute the model→provider index in
parseModelFromRegistry— model→provider lookups now use a precomputed index instead of scanning the registry on every call. (#4110 — thanks @artickc) - perf(obfuscation): cache per-word regexes instead of recompiling every request — the obfuscation pass now caches its per-word regexes rather than recompiling them on each request. (#4109 — thanks @artickc)
- perf(stream): use
structuredCloneinstead of a JSON round-trip for per-chunk reasoning split — the per-chunk reasoning split now clones withstructuredClonerather thanJSON.parse(JSON.stringify(...)). (#4108 — thanks @artickc) - perf(gemini): cache the reasoning close-tag regex instead of recompiling per token — the Gemini reasoning close-tag regex is now compiled once and reused instead of per token. (#4106 — thanks @artickc)
📝 Maintenance
- ci(quality): flip the TIA impacted-unit-tests gate from advisory to blocking (Fase 9) — the test-impact-analysis gate that runs the unit tests impacted by a diff is now blocking on PRs. (#4069 — thanks @diegosouzapw)
- ci(quality): dedup the doubly-run
check:docs-sync+ record the validated ROI backlog (Fase 9) —check:docs-syncwas running twice in CI; the duplicate was removed and the validated quality-gate ROI backlog recorded. (#4099 — thanks @diegosouzapw) - docs(quality-gates): reconcile the gate inventory with
ci.yml+ add the ROI rationalization backlog — the quality-gate inventory doc was reconciled against the actual CI jobs and a rationalization backlog added. (#4095 — thanks @diegosouzapw) - test(infra): isolate
DATA_DIRper test process; raise Stryker concurrency 1→4 — test processes now get an isolatedDATA_DIR(no shared-DB cross-talk) and the mutation runner's concurrency was raised. (#4078 — thanks @diegosouzapw) - test(dashboard): smoke e2e for the Combo Live Studio page — adds a Playwright smoke test covering the Combo Live Studio page. (#4075 — thanks @diegosouzapw)
- docs(compression): document LLMLingua optional deps + on-demand install — documents the optional LLMLingua dependencies and how they are installed on demand. (#4061 — thanks @diegosouzapw)
- chore(deps): freeze
@huggingface/transformersin dependabot (hard-pin) — the transformers dependency is hard-pinned and frozen in dependabot to protect the VPS-validated LLMLingua + memory-embeddings stack from a breaking major bump. (#4066 — thanks @diegosouzapw) - chore(docs): update the Discord invite link to a non-expiring one — replaces the expiring Discord invite with a permanent link. (#4067 — thanks @diegosouzapw)
- chore(docs): document the new MITM env vars + reconcile the env-doc contract — documents
MITM_IDLE_TIMEOUT_MSandMITM_VERBOSEin.env.example+ENVIRONMENT.md, allowlists the framework-internalTURBOPACKand the Claude CodeANTHROPIC_AUTH_TOKEN, and relocates/prunes stale provider/guide docs. (thanks @diegosouzapw)
🔧 Dependencies
- deps: bump the development group with 10 updates — routine dependabot dev-dependency bumps. (#4051)
- deps(electron): bump electron 42.4.0 → 42.4.1 — (#4049)
- ci(deps): bump
actions/setup-node4 → 6 — (#4048) - ci(deps): bump
actions/cache4.3.0 → 5.0.5 — (#4047) - ci(deps): bump
actions/github-script7 → 9 — (#4046) - ci(deps): bump
ossf/scorecard-action2.4.0 → 2.4.3 — (#4045) - ci(deps): bump
actions/upload-artifact4 → 7 — (#4044)
What's Changed
- Release v3.8.28 by @diegosouzapw in #4053
- fix(docs): unblock release build — move DOCUMENTATION_OVERHAUL_PLAN out of fumadocs collection by @diegosouzapw in #4123
Full Changelog: v3.8.27...v3.8.28