Skip to content

v3.8.28

Choose a tag to compare

@diegosouzapw diegosouzapw released this 17 Jun 22:28
dd5a3db

✨ 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). passthroughModels is 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_tokens fallback via tiktoken — the local count_tokens fallback 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 400 because 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 existing context_management retry 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-editing engine 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-uncapped and 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 a MITM_VERBOSE routing-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 cwd at the package root (global/systemd installs) — the standalone LiveWS launcher (scripts/start-ws-server.mjs) re-spawns itself with node --import tsx <self> but did not set cwd. When the WebSocket sidecar was launched from outside the package directory — a global npm/homebrew install, or a systemd/launchd unit started from $HOME — Node could not resolve the tsx package (ERR_MODULE_NOT_FOUND: Cannot find package 'tsx'), and even from the package directory tsx could not resolve the tsconfig @/* path aliases (e.g. @/types/databaseSettings), so the sidecar never booted. The spawn now pins cwd to the package root (the directory above scripts/, where package.json + tsconfig.json live), which resolves both tsx discovery 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 a visibilitychange event (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 real visibilitychange → 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 builder stage ran npm run build with 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 with FATAL ERROR: … JavaScript heap out of memory at [builder] npm run build. The builder stage now sets NODE_OPTIONS=--max-old-space-size (default 4096 MB, overridable via --build-arg OMNIROUTE_BUILD_MEMORY_MB=…) before the build; the value propagates to the spawned next build. Build-only — the runtime heap (OMNIROUTE_MEMORY_MB on 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's updateAvailable, which derived the latest version ONLY from npm info omniroute version --json via the npm CLI binary. When that binary is absent from the runtime PATH (Docker/desktop/locked-down installs) or the registry is unreachable, the call returned nullupdateAvailable=false → the banner silently never rendered even when a newer release existed. The route now resolves the latest version through resolveLatestVersion(): the fast npm CLI 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 tolerate v-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_MODE DB 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-ws upgrades and warn on an unset JWT_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 when JWT_SECRET is 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 dev boots reliably. (#4092 — thanks @chirag127)
  • fix(auto): resolve built-in auto/* catalog combos — referencing a built-in auto/* combo returned a premature 400 because 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 413 with 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/responses keep-alive timer on cancel/abort — a cancelled or aborted /v1/responses stream 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 finally to 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 a finally block. (#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 structuredClone instead of a JSON round-trip for per-chunk reasoning split — the per-chunk reasoning split now clones with structuredClone rather than JSON.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-sync was 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_DIR per test process; raise Stryker concurrency 1→4 — test processes now get an isolated DATA_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/transformers in 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_MS and MITM_VERBOSE in .env.example + ENVIRONMENT.md, allowlists the framework-internal TURBOPACK and the Claude Code ANTHROPIC_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-node 4 → 6 — (#4048)
  • ci(deps): bump actions/cache 4.3.0 → 5.0.5 — (#4047)
  • ci(deps): bump actions/github-script 7 → 9 — (#4046)
  • ci(deps): bump ossf/scorecard-action 2.4.0 → 2.4.3 — (#4045)
  • ci(deps): bump actions/upload-artifact 4 → 7 — (#4044)

What's Changed

Full Changelog: v3.8.27...v3.8.28