Skip to content

v3.8.45

Latest

Choose a tag to compare

@diegosouzapw diegosouzapw released this 06 Jul 06:03
3ddcee6

✨ New Features

  • feat(providers): add Yuanbao (web) as a cookie-session provider (#6196) — yuanbao-web (Tencent Yuanbao, yuanbao.tencent.com) with cookie-only auth (hy_user/hy_token + public agent id), SSE→OpenAI translation incl. reasoning_content, exposing DeepSeek V3/R1 + Hunyuan / Hunyuan-T1. Regression guard: tests/unit/providers-yuanbao-web.test.ts. together-web was deferred (no verifiable web-session endpoint — needs a captured request) and huggingchat-web dropped (the existing huggingchat already is a web-cookie provider). (thanks @chirag127)
  • feat(providers): route the built-in agentrouter through the dynamic Claude-Code wire image (#6056) — a small static allow-set (CC_WIRE_IMAGE_BUILTINS in open-sse/services/ccWireImageBuiltins.ts), consulted by isClaudeCodeCompatible / isClaudeCodeCompatibleProvider / applyFingerprint, makes agentrouter adopt the CC wire-image headers + fingerprint while guarding the CC baseUrl/auth branches so it keeps its own registry baseUrl and x-api-key auth. Regression guard: tests/unit/agentrouter-cc-wire-image.test.ts (asserts the wire image is applied AND agentrouter's baseUrl/auth are preserved). Live WAF-acceptance against agentrouter.org is a VPS validation follow-up (Hard Rule #18).
  • feat(providers): bulk-add API keys for Cloudflare Workers AI (#6174) — cloudflare-ai is removed from the bulk-add exclusion list and the bulk parser gains a 3-field name|accountId|apiKey mode; the bulk route now builds a per-entry providerSpecificData so each key carries its own accountId (fixing the previous shared-object reuse), and both the create + key-validation paths receive it. Regression guard: tests/unit/bulk-api-key-parser-cloudflare.test.ts. (thanks @muflifadla38)
  • feat(dashboard): routing/settings UX clarity (#6147) — (1) weighted combos show the effective routing share % next to each weight when weights don't sum to 100 (WeightTotalBar.tsx); (2) the status widget's user-facing "Cloud Sync" label is renamed to "Remote Settings Sync" (CloudSyncStatus.tsx; internal ids/state untouched); (3) built-in providers gain an opt-in advanced base-URL override (isBaseUrlOverrideEligibleProvider, hidden behind an "Advanced" toggle, reusing the existing providerSpecificData.baseUrl persistence — not globally widened). Regression guard: tests/unit/routing-settings-ux-6147.test.ts.
  • feat(combo): add an option to disable session stickiness, per-combo or globally — round-robin / random combos can rotate to a different connection on every request instead of pinning a whole conversation to one connection by its first-message hash. Resolution precedence per-combo config.disableSessionStickiness → global settings.disableSessionStickiness → default false (preserves the #3825 prompt-cache/504 fix); gates both stickiness call sites in open-sse/services/combo.ts. Exposed as a global toggle (Combo Defaults) and a per-combo Inherit/on/off control. (#6168) Regression guard: tests/unit/combo-disable-session-stickiness.test.ts. (thanks @RCrushMe)
  • feat(docker): add the OMNIROUTE_NO_SUDO env flag for root-less / user-namespaced deployments — the MITM cert-trust command path (resolveSudoSpawn in src/mitm/systemCommands.ts) now strips the leading sudo when the flag is truthy, in addition to the existing root / sudo-missing cases, so the Proxy Agent runs without sudo (the operator trusts the CA manually, e.g. via NODE_EXTRA_CA_CERTS). Argv-array spawn preserved — no shell interpolation (Hard Rule #13). (#6122) Regression guard: tests/unit/mitm-systemCommands-no-sudo.test.ts. (thanks @powellnorma)
  • feat(providers): add Requesty as an OpenAI-compatible gateway provider (BYOK, base https://router.requesty.ai/v1, ~200 free requests/day) — wired through the shared OpenAI-compatible registry with full model passthrough (open-sse/config/providers/registry/requesty/, src/shared/constants/providers/apikey/gateways.ts). (#6120) Regression guard: tests/unit/requesty-provider.test.ts. (thanks @chirag127)
  • feat(dashboard): add configured-only / available-only filters to the Free Provider Rankings page (#6150) — hide providers you haven't configured, or whose connections are all rate-limited / out of quota, via server-side query params (?configuredOnly / ?availableOnly on GET /api/free-provider-rankings) backed by a testable lib helper reusing the in-process connection state (no Redis). Both filters default off, so the default view is unchanged; this supersedes the earlier client-side "Configured Only" toggle (#6245) with an available-only dimension and unit-tested logic. Regression guard: tests/unit/freeProviderRankings-filters.test.ts.
  • feat(rankings): add a 'Configured Only' filter to the Free Provider Rankings page, so the table can be narrowed to just the providers you have configured connections for (with an empty-state hint when none are configured). New en.json keys and a pure filter helper covered by tests/unit/free-provider-rankings-configured-filter.test.ts. (#6245, closes #6150 — thanks @Iammilansoni)

🔧 Bug Fixes

  • fix(mitm): the test suite and CI can never mutate the OS trust store again — OMNIROUTE_SKIP_SYSTEM_TRUST=1 (set by the global test setup and all CI workflows) makes installCert/uninstallCert/installTproxyCa skip the privileged OS dispatch while preserving the #4546 environment-skip contract. Root cause of the self-hosted runner incident: a cert-flow integration test installed a 105-byte fake PEM into /usr/local/share/ca-certificates, breaking ALL system TLS on the VM. Regression guard: tests/unit/system-trust-test-guard.test.ts. (#6310)

  • fix(security): /api/keys/{id}/devices answers a clean method-first 405 for undocumented HTTP methods (e.g. the new QUERY) via a dedicated http-method-guard rule — the auth layer was answering 401 first, failing schemathesis's unsupported-methods check. Same pattern as the v3.8.44 TRACE fix. Regression guard: tests/unit/dast-method-not-allowed.test.ts.

  • fix(combo): the #6216 empty-stream failover is restricted to truly empty bodies (zero bytes — the Gemini HTTP-200-empty case), restoring the #3399/#3685 pass-through contracts for [DONE]-terminated empty streams and incomplete Claude lifecycles. New guard: #5976 truly EMPTY streaming body → invalid for combo failover (87/87 across both suites).

  • fix(combo): 5 streaming-path fixes — locked-stream 500, error-frame-only-if-no-content, Gemini MALFORMED_RESPONSE→content_filter failover, correlationId substring search, per-model-500 lockout skip + request-logger UI detail. Maintainer follow-up: releaseQualityClone cancels the abandoned quality-check tee branch (per-request memory) + regression test. (#6216 — thanks @hartmark)

  • fix(skills): generate the missing omni-github-skills registry entry (the #6186 catalog addition never ran the generator — 8 integration assertions split between old/new counts) and align the agent-skills catalog counts across integration + unit suites (43 = 23 API + 20 CLI; 44 with config).

  • fix(a2a): finish the #6186 catalog-count update — listCapabilities metadata reported coverage.api.total: 22 (type literal + value) and SkillCoverageSchema pinned z.literal(22), so the schema would REJECT the correct runtime value with 23 API skills. All three aligned to 23.

  • fix(github-skills): add a missing import, unit tests and a settings JSON-parse fix for the GitHub agent-skill discovery/import flow. (#6186 — thanks @Moseyuh333)

  • fix(api): POST /api/github-skills validates its body with a Zod schema (validateBody) instead of blind request.json() destructuring — a non-array targets would crash .map. Regression guard: tests/unit/github-skills-route-validation.test.ts.

  • fix(docker): add id= to the BuildKit cache mounts so strict builders (e.g. buildkitd with strict frontend parsing) accept the Dockerfile. (#6291 — thanks @karimalsalah)

  • fix(oauth): register zed in the OAuth PROVIDERS map (fixes "Unknown provider" on the Zed sign-in flow) (#6078 — thanks @anki1kr), and align zed in OAUTH_PROVIDER_IDS + the config enum after the merge.

  • fix(doubao-web): switch the Doubao web provider to the Dola global endpoint. (#6235 — thanks @backryun)

  • fix(doctor): resolve two false-positive WARNs in the doctor diagnostics (#6163, closes #6162 — thanks @arssnndr)

  • fix(providers): refresh the GitHub Copilot model catalog to the current upstream set. (#6154 — thanks @backryun)

  • fix(providers): correct the Kiro model catalog to real upstream ids — fabricated claude-opus-4.7/claude-sonnet-4.6 entries removed, real claude-sonnet-5/claude-sonnet-4.5/claude-haiku-4.5 kept. (#6170)

  • feat(sse): surface Kiro adaptive-thinking reasoning frames as reasoning_content in the OpenAI-shaped stream. (#6213 — thanks @VXNCXNX)

  • fix(cli): use OMNIROUTE_SERVER_HOST instead of the POSIX auto-set HOSTNAME for the bind address (fixes wrong bind on POSIX shells that export HOSTNAME). (#6195, closes #6194 — thanks @Theadd)

  • feat(provider): add Claude 5 Sonnet to the Claude Web provider catalog. (#6209, closes #6200 — thanks @Iammilansoni)

  • fix(providers): add nvidia to PROVIDER_TOOL_LIMITS (1536) to prevent silent tool-list truncation. (#6177 — thanks @LuisAlejandroVega)

  • fix(translator): strip the reasoning param for nvidia z-ai/glm-5.2 (upstream 400s on it). (#6181 — thanks @kanztu)

  • fix(dashboard): providers page gains a data-timeout guard and the live-WS standalone wiring (no more indefinite spinner when the data fetch stalls). (#6211)

  • fix(sse): surface the ChatGPT-web image silent-drop as an accurate error instead of an empty success. (#6208)

  • fix(cline): force upstream streaming for Cline/ClinePass (streaming-only API) — non-stream client requests are served from the buffered SSE. (#6165)

  • fix(dashboard): remove the always-on Auto-Routing (combo) banner from the home page — it did not reflect live routing state and reappeared on every fresh browser. Replacement guard: tests/unit/home-no-autorouting-banner.test.ts. (#6164)

  • fix(dashboard): stop a model-test error from freezing the page (React #31 object-as-child toast) — errors go through extractApiErrorMessage. (#6161)

  • fix(oauth): extract the keychain-import-only guard to its own module, restoring the oauth file-size freeze. (#6158)

  • fix(sse): strip zero-width markers from streamed tool-call arguments — a follow-up to #5857. That PR removed injected zero-width joiners (U+200D) from streamed assistant text/reasoning but deliberately left tool-call argument JSON byte-exact. The request-side obfuscation (open-sse/services/claudeCodeObfuscation.ts) injects ZWJ into agent words — including the temp path inside the Bash tool description — and Claude models copy that verbatim into generated commands, which are delivered as tool-call arguments rather than assistant text. As a result the ZWJ survived and corrupted code blocks (e.g. a temp path rendered with an invisible joiner). Now open-sse/handlers/responseSanitizer.ts strips zero-width code points from tool-call argument strings at every emit site (OpenAI non-stream/stream chat tool_calls + legacy function_call, native Responses function_call items, the OpenAI→Responses conversion, and the native Responses streaming response.function_call_arguments.delta/.done events). Only zero-width code points are removed; JSON structure and all other bytes stay identical (no parse/restringify), so normal arguments remain byte-exact. Regression guard: 6 new cases in tests/unit/response-sanitizer.test.ts (suite 50/50).

  • fix(nodejs): the default app log path now resolves under DATA_DIR (~/.omniroute/logs/application/app.log) instead of process.cwd() (#6197) — the globally-installed CLI runs from an arbitrary working directory, so anchoring the default to cwd made file logging silently write to (or no-op under) an unrelated directory, contradicting the documented .env.example default. getAppLogFilePath() now computes the default lazily via the pure resolveDataDir() resolver (honours a per-process DATA_DIR, no directory-creation side effect); an explicit APP_LOG_FILE_PATH still wins. Regression guard: tests/unit/logenv-datadir-path-6197.test.ts (3). (root cause independently diagnosed by @subhansh-dev in #6298 — thanks!)

  • fix(docker): AgentBridge/startMitm no longer aborts in containers/headless when the Antigravity-default DNS step can't write /etc/hosts (#6127), and the privileged command's stderr now reaches app.log instead of only a bare exit code hitting the toast (#6198). The default DNS step (addDNSEntry) was called unguarded while cert install and the two sibling DNS steps were each best-effort — in the runtime Docker image (USER node, no sudo, read-only /etc/hosts) it threw Command failed with code 1 out of startMitmInternal and killed the whole start, discarding the stderr. The three DNS steps are extracted into a best-effort provisionDnsEntries() where each failure is logged with the full err (stderr included, folded in by systemCommands.ts) and never aborts the start. Regression guard: tests/unit/mitm-dns-graceful-degrade-6127.test.ts (4).

  • fix(providers): copilot-m365-web now supports the M365 Education "Starter / OfficeWebIncludedCopilot" tier and no longer returns an empty content:null stream (#6210). Two gaps: (1) buildWsUrl() hardcoded the individual-consumer scenario (OfficeWebPaidConsumerCopilot, isEdu=false) — the EDU tier is now opt-in via providerSpecificData.tier="edu", emitting scenario=OfficeWebIncludedCopilot/isEdu=true (the individual path is unchanged); (2) the EDU/GPT-5.5 path streams deltas via arguments[0].writeAtCursor (incremental) instead of only messages[].text (accumulated snapshots), which the parser dropped — a new accumulateBotContent() folds both formats, with type:2 item.result.message as a last-resort fallback. Regression guard: tests/unit/copilot-m365-edu-writeatcursor-6210.test.ts (10). (thanks @qpeyba)

  • fix(providers): GitLab Duo executor now feeds tool results back into the prompt instead of looping (#6220) — buildPrompt() branched only on system/user and took userParts.at(-1), silently dropping the assistant{tool_calls} + tool{result} turns the client appended, so the reconstructed prompt was byte-identical to turn 1 and the model re-emitted the same <tool> call forever. When a tool exchange is present the full conversation is now serialized, folding each tool result back keyed by its tool_call_id; simple conversations keep the legacy shape. Complements the tool_call emission from #6051 (the kilo-duplicate label was a false positive — different, sequential defect). Regression guard: tests/unit/gitlab-tool-result-feedback-6220.test.ts (4).

  • fix(providers): opencode-go/opencode-zen can now synthesize the OpenCode CLI identity headers Cloudflare requires on VPS egress (#5997) — on a datacenter VPS, opencode.ai/zen/go/v1/chat/completions 403s (HTML challenge) requests lacking CLI identity, while the reporter's control curl proved that User-Agent: opencode-cli/1.0.0 + x-opencode-client: cli + x-opencode-project: default + fresh request/session UUIDs succeed. Opt-in via OPENCODE_SYNTHESIZE_CLI_HEADERS=true (values overridable via OPENCODE_GO_USER_AGENT/OPENCODE_USER_AGENT/OPENCODE_CLIENT/OPENCODE_PROJECT); it fills only headers the client did not already send. Kept off by default — the forward-only path is deliberate (fabricating a wrong value risks upstream rejection; a prior dedup regressed with opencode/local), so this replaces the fragile local header-injection shim without changing default behavior. Regression guard: tests/unit/opencode-cli-headers-synthesis-5997.test.ts (6). (thanks @aleksesipenko)

  • fix(resilience): sticky session affinity now evicts and fails over to another account when the pinned account is exhausted/unavailable (#6219)

  • fix(sse): Responses API passthrough now drops internal commentary-phase output before forwarding to clients (gated by RESPONSES_PASSTHROUGH_DROP_COMMENTARY, default on) (#6199)

  • fix(proxy): stop the v3.8.44 proxy regression that leaked the real IP and disabled healthy proxies (#6246). Two coupled defects from the new health scheduler: (1) IP leak — when a proxy assigned to a connection was marked inactive, resolution fell through to a direct egress instead of blocking, exposing the operator's real IP; (2) over-deactivation — the sweep flipped a proxy to inactive on the first failed probe and counted our own 5s timeout / a probe-target 5xx as the proxy's fault, so healthy paid proxies vanished from egress selection ("my proxies are not being used anymore"). Fix: the sweep decision is extracted into a pure, network-free decideProxyHealthAction (src/lib/proxyHealth/decision.ts) — by default the health check now only counts/logs and never downgrades status (a proxy is downgraded/removed only with PROXY_AUTO_REMOVE=true, after PROXY_AUTO_REMOVE_AFTER consecutive conclusive failures); probes are classified tri-state so an inconclusive result (our timeout, or a 5xx from the probe target) never penalizes the proxy, and the probe timeout is raised 5s→15s. Separately, safeResolveProxy now fails closed via the existing policy: a connection whose assigned proxy is dead is blocked instead of leaking direct (hasBlockingProxyAssignment), honoring the explicit proxy off toggles and the PROXY_FAIL_OPEN=true opt-out. Existing proxies stuck inactive by the old behavior need a one-time manual re-activate (the operator owns proxy status). Regression guards: tests/unit/proxy-health-decide-action-6246.test.ts, tests/unit/proxy-assigned-unavailable-6246.test.ts.

  • fix(proxy): make "Test All" read-only and add bulk enable/disable (#6246). Complements the core fail-closed / scheduler fix (#6296) with the two remaining reporter asks. (1) The "Test All" button (POST /api/settings/proxies/auto-test) used to flip a proxy to inactive on a failed reachability probe; since the egress selector excludes inactive proxies, a flaky probe (an unreachable httpbin.org, a proxy that blocks HEAD, or a slow paid proxy) silently disabled every proxy that failed — "Test All" is now read-only by default (only the operator sets a proxy active/inactive; opt back into the legacy test-and-set with PROXY_HEALTH_AUTO_DEACTIVATE=true). (2) Adds a bulk enable/disable proxies endpoint + toolbar action (POST /api/settings/proxies/batch-activate) so an operator can re-activate proxies in one click. Regression guard: tests/unit/proxy-health-6246.test.ts. (thanks @tenshiak)

  • chatcore (tools): stop the default 128-tool cap from silently dropping opencode's task/MCP tools. opencode (used as an MCP/agent host) sends a large tool list; when it exceeds the speculative MAX_TOOLS_LIMIT (128) default, truncateToolList did a blind tools.slice(0, 128), dropping every tool past index 128 — including opencode's built-in task tool (subagent launch) and many MCP tools, so models routed through OmniRoute could no longer spawn subagents or reach part of their tools. The cap exists to avoid upstream 400s for providers with real hard limits (e.g. grok-cli 200), so it is kept for those: detection of the opencode client (isOpencodeClient — any x-opencode-* header, or opencode in the user-agent) now only bypasses the speculative 128 default, never a known provider ceiling. Precedence is explicit — a proactive/detected provider limit always truncates (even for opencode); otherwise opencode forwards its full tool list; otherwise the unchanged 128 default applies to every other client. Refactors getEffectiveToolLimit into getKnownToolLimit(provider) ?? DEFAULT_LIMIT (byte-identical for existing callers) and fixes a cosmetic debug-log that reported the truncated count instead of the original. Regression guard: tests/unit/tool-limit-detector.test.ts. (#6193 — thanks @DKotsyuba)

  • fix(mitm): the macOS MITM-cert install check now matches the system keychain again. security find-certificate -a -Z prints the SHA-1 as a colon-less hex string, but the installed-check compared it against getCertFingerprint()'s colon-separated form, so the substring match never hit — the cert was reported as not-installed and re-prompted for the sudo install on every run. Fingerprints are now normalized (colons stripped, upper-cased) on both sides via the extracted macCertOutputHasFingerprint helper. Regression guard: tests/unit/mitm-cert-mac-fingerprint.test.ts. (#6204, closes #6134 — thanks @rianonehub)

  • fix(api): /v1/messages/count_tokens now counts tool_use, tool_result and thinking content blocks (and array-form system prompts) in the local-estimation path, instead of only text. Real agentic conversations keep ~95% of their tokens inside tool results; the previous estimate returned near-zero for them, which silently broke Claude Code's auto-compaction (context grew past the window with no compaction until the upstream API rejected the request). The real provider-side count path is unchanged. Regression guard: tests/unit/messages-count-tokens-route.test.ts. (#6221 — thanks @luweiCN)

  • fix(antigravity): strip a trailing assistant prefill turn for Vertex Claude models to avoid upstream 400s (#6114). Regression guard: tests/unit/antigravity-claude-prefill-strip.test.ts. (thanks @anki1kr)

  • fix(security): the mutable cloud-agent routes (/api/cloud/credentials/update, /api/cloud/models/alias) now require management auth instead of being treated as public. They were classified as public API routes, so a request without management credentials could update stored cloud-agent credentials and model aliases. They are removed from the public-route set, classified as management routes in the authz pipeline, and gated by requireManagementAuth; cloud read/auth routes stay public. Regression guards: tests/unit/cloud-write-auth.test.ts, tests/unit/authz/classify.test.ts, tests/unit/public-api-routes.test.ts. (#6233 — thanks @vittoroliveira-dev)

  • refactor(dashboard): extract the onboarding-wizard "Open provider details" link target into a pure, unit-tested buildProviderDetailsHref(connection) helper. The wizard already routes by connection.id (the node UUID) rather than the provider category slug (#6144/#6145); this hardens that behavior behind a tested helper that guards a missing id/connection. Regression guard: tests/unit/provider-onboarding-href.test.ts. (#6166 — thanks @KooshaPari)

  • fix(api): relay worker now binds the SSRF guard to a stable const name so minified standalone (Docker) builds resolve it (#6149) — the Vercel/Deno relay generators embedded the shared resolveRelayTarget guard as a bare ${fn.toString()} declaration while the worker body called the hardcoded literal name; SWC minification mangled the source function's name, so the deployed worker defined <mangled> but still called resolveRelayTargetReferenceError. Both templates now emit const resolveRelayTarget = ${fn.toString()}; (the const name is a template literal, immune to minification). Regression guard: tests/unit/relay-minified-fn-6149.test.ts (4). (thanks @SeaXen)

  • fix(providers): refresh the stale NVIDIA NIM model registry — drop EOL z-ai/glm-5.1, add z-ai/glm-5.2 and nvidia/nemotron-3-ultra-550b-a55b (#6108). Regression guard: tests/unit/nvidia-nim-registry-6108.test.ts. (thanks @andrea-kingautomation)

  • fix(backend): GPT-family (codex) models now report a distinct max_input_tokens (272000) below their 400K context_length via an optional maxInputTokens on RegistryModel, so coding agents auto-compact correctly instead of overflowing the real input cap (#6191). Regression guard: tests/unit/gpt-max-input-tokens-6191.test.ts. (thanks @luweiCN)

  • fix(backend): call logs now record a reasoning source/char-count (migration 116, reasoning_source/reasoning_chars) for models that emit reasoning_content/<think> but report zero reasoning tokens in usage, so tokens_reasoning no longer silently under-represents reasoning — cost math is unchanged (the priced tokens_reasoning stays usage-derived) (#6187). Regression guard: tests/unit/reasoning-token-source-6187.test.ts. (thanks @andrea-kingautomation)

  • fix(auth): a stale/changed STORAGE_ENCRYPTION_KEY now surfaces as a clear 424 storage_encryption_stale ("re-enter the API key") instead of a misleading "Auth failed: 401" — the connection's ciphertext failed to decrypt and was coerced to an empty Bearer, hiding the real cause (#6148). Regression guard: tests/unit/decrypt-stale-key-hint-6148.test.ts. (thanks @chirag127)

  • fix(backend): memory injection now keeps the injected system message first for providers that require it (via a PROVIDERS_SYSTEM_MUST_BE_FIRST capability), instead of the cache-safe mid-array splice that made strict providers reject the request with a 400 (#6135). Regression guard: tests/unit/memory-system-first-6135.test.ts.

  • fix(services): 9Router embed panel no longer 404s (optional catch-all route) and the supervisor probes the port before spawning to avoid raw EADDRINUSE (#6205). Regression guards: tests/unit/ninerouter-embed-port-6205.test.ts, tests/unit/services/ServiceSupervisor.test.ts. (thanks @jonlwheat2-gif)

  • fix(mcp): forward the MCP request extra context through static tool loops so stdio callers keep their scope/identity (#6178)

⚡ Performance & Infrastructure

  • perf(test): test-suite loader quick wins (#6214) — the 19 test scripts switch --import tsx--import tsx/esm (the repo is pure ESM; the unused CJS hook cost ~1.3s per test process × 2,462 processes — CI fast-path unit shards dropped 14.8→7.5 min, −49%), tsx bumped to ^4.23.0 (tsx#809 startup-regression fix), 37 orphan .test.mjs files (224 cases) recovered into the canonical glob (they matched no runner and never ran in any CI job; check:test-discovery now scans .mjs too), and ci.yml/quality.yml unit jobs now call the canonical npm script test:unit:ci:shard (single source of truth — closes two silent drifts: missing setupPolyfill import in CI and memory/+usage/ dirs absent from the fast-path glob). tests/unit/dashboard/** keeps the full tsx hook in its own invocation (@lobehub/icons es/ build internally require()s ESM-syntax files).
  • ci: heavy-pipeline dedup (#6215) — the release-PR pipeline ran the unit suite 4× per sync (95 jobs, 208 machine-min; the v3.8.44 cycle fired 123 such runs, 88 cancelled). Now: Node 24/26 compat matrices move to a daily nightly-compat.yml (−28%/run; resolves the active release branch, opens a tracking issue on failure), coverage is collected inside the unit shards themselves via c8/NODE_V8_COVERAGE (−18%/run; the Coverage Shard ×8 matrix is gone — nodejs/node's own CI pattern), the ~40-job per-language i18n matrix becomes 1 job (the account has 20 concurrent-job slots total), and heavy jobs skip draft PRs — paired with /generate-release now opening the living release PR as draft (flipped ready at the new Phase 0a.0a), killing the per-merge churn for the whole cycle. Validated by a full workflow_dispatch of the new pipeline: 35 jobs, 0 failures, 23 min, merged coverage 80.16% (> ratchet baseline).
  • feat(quality): no-new-warnings per PR (#6218) — native ESLint bulk suppressions (≥9.24) freeze the pre-existing debt (476 files / 4,273 violations in config/quality/eslint-suppressions.json); npm run lint, lint-staged (pre-commit) and a new fork-aware lint-guard job in quality.yml all run suppressions-aware, so a NEW warning goes red in the PR that introduces it instead of accruing invisibly (+41/+88 per cycle) and being blind-rebaselined at release. 3 warn rules promoted to error in src/** (react-hooks/exhaustive-deps, @next/next/no-img-element, import/no-anonymous-default-export); collect-metrics measures under the frozen baseline (ratchet metric = net-NEW debt; baseline tightened 4,279→0 in-PR per require-tighten); fork PRs run report-only (contributors are never blocked — the maintainer campaigns fix via co-authorship). Baseline stock shrinks via --prune-suppressions at release reconciliation.
  • ci: test jobs no longer wait on the Build gate (#6275) — test-unit×8, vitest, integration×2 and security declared needs: build but never download the next-build artifact; they now start at minute 0 (needs: changes, same if as Build), cutting ~15–20 min of wall-clock per heavy run. e2e/package-artifact/electron-smoke keep needs: build (they consume the artifact for real).
  • ci(build): the ci.yml Build job compiles Next.js with Turbopack (OMNIROUTE_USE_TURBOPACK=1) (#6273) — Build job 20 min → 6 min 59 s (~2.9×) on ubuntu-latest; the webpack actions/cache step is removed. Validated end-to-end pre-merge via gh workflow run ci.yml --ref <branch>.
  • feat(build): Turbopack becomes the default bundler for next build and next dev (#6283) — build-next-isolated.mjs, run-next.mjs and the playwright-runner default to Turbopack; OMNIROUTE_USE_TURBOPACK=0 is the explicit webpack escape hatch. nightly-compat.yml/npm-publish.yml inherit the default. Regression guard: tests/unit/build-bundler-default-turbopack.test.ts.
  • feat(docker): the Docker image builds with Turbopack (ENV OMNIROUTE_USE_TURBOPACK=1) (#6285) — the v3.8.27 ImportTracer panic ("unreachable: there must be a path to a root") does not reproduce on Next 16.2.9: amd64 (659 s) and arm64 (qemu) build clean, 0 panics, smoke health 200.
  • ci: opt-in self-hosted VPS runners for the release window (#6284) — scripts/vps/release-runner-up.sh/down.sh manage the runner VM, and build/test-unit/vitest pick a dynamic runs-on gated by vars.USE_VPS_RUNNER == 'true' and own-origin (fork PRs never reach self-hosted runners). Wired into /generate-release (VM up at Phase 1, mandatory down at Phase 3).

📝 Maintenance

  • quality(release-green): full pre-flight hardening for this release — the cycle's 11 net-new ESLint errors typed/fixed and validate-release-green made suppressions-aware with per-gate logs (_artifacts/release-green/) and a --hermetic mode; test-masking allowlist entries for the cycle's verified-legitimate assert reductions; stale ESLint suppressions pruned (4,273 → 4,233); the 7 net-new as any casts from #6292 typed; githubSkillTools MCP errors routed through sanitizeErrorMessage(); combo-provider-cooldown-sibling added to the Stryker tap set; executors/env docs count fixes.
  • ci(quality): merge-integrity fast-gates per PR — check:changelog-integrity (no base CHANGELOG bullet may vanish in the merge result — the auto-resolve "CHANGELOG-eat" pattern) and check:agent-skills-sync (generated SKILL.md ≡ catalog), blocking for own-origin branches and report-only for forks (Princípio Zero). (#6300)
  • ci(vps): hermetic nightly-release-green pre-flight on the dedicated omni-release self-hosted runner (dynamic runs-on, clean env); e2e/integration/electron stay on hosted runners (per-VM port collision + concurrent artifact-download limits documented in the PR). (#6305)
  • chore(quality): v3.8.45 cycle-close drift rebaselines — file-size (13 files grown by merged cycle PRs), cognitive 867→877, cyclomatic 2028→2035, kiro-translator test debt from #6213; all with dated justification keys.
  • docs(architecture): sync stale DB-layer counts (45+/55 → 95+/110+) in REPOSITORY_MAP, the db-schema diagram and llm.txt (+42 i18n mirrors). (#6167)
  • chore(release): parallel-cycle flow — sync-next-cycle.mjs + Hard Rule #21 semantics (#6203); v3.8.45 development cycle opened.
  • i18n(it): add 118 missing Italian (it) translations (net-additive — no existing keys dropped, valid JSON), improving Italian UI coverage. (#6212 — thanks @serverless83)
  • chore(providers): remove deprecated MiMo V2 model entries from the catalogs (xiaomi-mimo, opencode-go, zenmux-free, audio TTS) — the upstream V2 line is superseded by MiMo V2.5; drops mimo-v2-tts, mimo-v2-pro, mimo-v2-omni, mimo-v2-flash, mimo-v2-flash-free and realigns the provider-catalog tests. (#6248 — thanks @backryun)

🙌 Contributors

Thanks to everyone whose work landed in v3.8.45:

Contributor PRs / Issues
@aleksesipenko direct commit / report
@andrea-kingautomation direct commit / report
@anki1kr #6078
@arssnndr #6162, #6163
@backryun #6154, #6235, #6248
@chirag127 direct commit / report
@DKotsyuba #6193
@hartmark #6216
@Iammilansoni #6150, #6200, #6209, #6245
@jonlwheat2-gif direct commit / report
@kanztu #6181
@karimalsalah #6291
@KooshaPari #6166
@LuisAlejandroVega #6177
@luweiCN #6221
@Moseyuh333 #6186
@muflifadla38 direct commit / report
@powellnorma direct commit / report
@qpeyba direct commit / report
@RCrushMe direct commit / report
@rianonehub #6134, #6204
@SeaXen direct commit / report
@serverless83 #6212
@subhansh-dev #6298 (diagnosis, landed via #6234)
@tenshiak direct commit / report
@Theadd #6194, #6195
@vittoroliveira-dev #6233
@VXNCXNX #6213
@diegosouzapw maintainer

What's Changed

Full Changelog: v3.8.44...v3.8.45