fix(security): exact-host Anthropic baseUrl check (CodeQL #674) - #5129
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
Code Review
This pull request addresses a CodeQL security vulnerability by replacing a substring-based check for the official Anthropic host with a robust hostname-based validation using the URL API, and adds comprehensive unit tests. The review feedback recommends enhancing the robustness of the new isOfficialAnthropicBaseUrl function to handle non-string inputs and trim leading/trailing whitespaces, as well as expanding the test suite to cover these edge cases.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| export function isOfficialAnthropicBaseUrl(baseUrl: string): boolean { | ||
| if (!baseUrl) return true; | ||
| let host: string | null = null; | ||
| try { | ||
| host = new URL(baseUrl).hostname; | ||
| } catch { | ||
| try { | ||
| host = new URL(`https://${baseUrl}`).hostname; | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
| return host === "api.anthropic.com"; | ||
| } |
There was a problem hiding this comment.
Robustness and Defensive Programming in isOfficialAnthropicBaseUrl
Currently, isOfficialAnthropicBaseUrl assumes baseUrl is always a string and does not trim leading or trailing whitespaces. If baseUrl contains leading/trailing spaces (e.g., " api.anthropic.com "), the new URL constructor will throw on both attempts, incorrectly treating it as a third-party host.
Furthermore, since credentials is implicitly typed as any in buildHeaders, baseUrl could potentially be a non-string at runtime (e.g., if providerSpecificData contains unexpected types). Calling .trim() or passing a non-string to new URL without a type guard could lead to runtime exceptions (e.g., TypeError).
We can make this function fully robust by:
- Adding a runtime type check to ensure
baseUrlis a string (returningtrueif falsy, andfalseif truthy but not a string). - Trimming the string to handle any accidental leading/trailing whitespaces.
export function isOfficialAnthropicBaseUrl(baseUrl: unknown): boolean {
if (typeof baseUrl !== "string") {
return !baseUrl;
}
const trimmed = baseUrl.trim();
if (!trimmed) return true;
let host: string | null = null;
try {
host = new URL(trimmed).hostname;
} catch {
try {
host = new URL("https://" + trimmed).hostname;
} catch {
return false;
}
}
return host === "api.anthropic.com";
}| test("official endpoints are recognized (empty / scheme / scheme-less / path)", () => { | ||
| assert.equal(isOfficialAnthropicBaseUrl(""), true, "empty baseUrl = default official"); | ||
| assert.equal(isOfficialAnthropicBaseUrl("https://api.anthropic.com"), true); | ||
| assert.equal(isOfficialAnthropicBaseUrl("https://api.anthropic.com/v1"), true); | ||
| assert.equal(isOfficialAnthropicBaseUrl("https://api.anthropic.com/"), true); | ||
| // scheme-less host (operator may omit the protocol) is parsed with an assumed https:// | ||
| assert.equal(isOfficialAnthropicBaseUrl("api.anthropic.com"), true); | ||
| assert.equal(isOfficialAnthropicBaseUrl("api.anthropic.com/v1"), true); | ||
| }); |
There was a problem hiding this comment.
Add test cases for whitespace-padded inputs
To ensure that leading and trailing whitespaces are correctly handled and trimmed, we should add test cases for whitespace-padded inputs.
test("official endpoints are recognized (empty / scheme / scheme-less / path / whitespace)", () => {
assert.equal(isOfficialAnthropicBaseUrl(""), true, "empty baseUrl = default official");
assert.equal(isOfficialAnthropicBaseUrl("https://api.anthropic.com"), true);
assert.equal(isOfficialAnthropicBaseUrl("https://api.anthropic.com/v1"), true);
assert.equal(isOfficialAnthropicBaseUrl("https://api.anthropic.com/"), true);
// scheme-less host (operator may omit the protocol) is parsed with an assumed https://
assert.equal(isOfficialAnthropicBaseUrl("api.anthropic.com"), true);
assert.equal(isOfficialAnthropicBaseUrl("api.anthropic.com/v1"), true);
// whitespace-padded inputs
assert.equal(isOfficialAnthropicBaseUrl(" https://api.anthropic.com "), true);
assert.equal(isOfficialAnthropicBaseUrl(" api.anthropic.com/v1 "), true);
});| test("unparseable baseUrl falls back to third-party (Bearer emitted)", () => { | ||
| assert.equal(isOfficialAnthropicBaseUrl("http://"), false); | ||
| assert.equal(isOfficialAnthropicBaseUrl(":::"), false); | ||
| }); |
There was a problem hiding this comment.
Add test cases for non-string inputs
To verify that non-string inputs are handled gracefully without throwing runtime exceptions, we should add test cases for them.
test("unparseable or non-string baseUrl falls back to third-party (Bearer emitted)", () => {
assert.equal(isOfficialAnthropicBaseUrl("http://"), false);
assert.equal(isOfficialAnthropicBaseUrl(":::"), false);
assert.equal(isOfficialAnthropicBaseUrl(true as any), false);
assert.equal(isOfficialAnthropicBaseUrl({} as any), false);
});…te-url-substring-sanitization #674) The anthropic-compatible Bearer-fallback gate decided whether a configured baseUrl targeted the official api.anthropic.com host via a substring `.includes("api.anthropic.com")`. A look-alike upstream such as `https://api.anthropic.com.evil.test` or `https://evil.test/?x=api.anthropic.com` matched the substring and was wrongly treated as official, suppressing the Bearer fallback meant for third-party gateways (CodeQL #674, js/incomplete-url-substring-sanitization, high). Replace the substring test with an exported `isOfficialAnthropicBaseUrl()` helper that parses the URL and compares the hostname for exact equality. Empty baseUrl stays official; scheme-less hosts are parsed with an assumed https://; an unparseable baseUrl falls back to third-party (Bearer emitted) as the safer default. Behavior for legitimate official/third-party baseUrls is unchanged. Adds tests/unit/anthropic-official-baseurl-host.test.ts covering official, look-alike, scheme-less, and unparseable inputs plus a static guard that the substring pattern is gone.
a082f3f to
ca62ec1
Compare
CI Coverage Report
Coverage artifact was not available for this run. |
…te-url-substring-sanitization diegosouzapw#674) (diegosouzapw#5129) The anthropic-compatible Bearer-fallback gate decided whether a configured baseUrl targeted the official api.anthropic.com host via a substring `.includes("api.anthropic.com")`. A look-alike upstream such as `https://api.anthropic.com.evil.test` or `https://evil.test/?x=api.anthropic.com` matched the substring and was wrongly treated as official, suppressing the Bearer fallback meant for third-party gateways (CodeQL diegosouzapw#674, js/incomplete-url-substring-sanitization, high). Replace the substring test with an exported `isOfficialAnthropicBaseUrl()` helper that parses the URL and compares the hostname for exact equality. Empty baseUrl stays official; scheme-less hosts are parsed with an assumed https://; an unparseable baseUrl falls back to third-party (Bearer emitted) as the safer default. Behavior for legitimate official/third-party baseUrls is unchanged. Adds tests/unit/anthropic-official-baseurl-host.test.ts covering official, look-alike, scheme-less, and unparseable inputs plus a static guard that the substring pattern is gone.
Reconcile the security hotfix #5129 (exact-host Anthropic baseUrl check, CodeQL js/incomplete-url-substring-sanitization #674) landed on main into the active release branch. Only conflict: open-sse/executors/default.ts import block. Resolved to the release superset — release already independently carries the #5129 logic (isOfficialAnthropicBaseUrl import + exact-host check, auto-merged) and adds normalizeAnthropicHeaderVariants on top, so both imports are kept. No security change is dropped; anthropicHost.ts and its test are byte-identical on both branches.
…lize These PRs squash-merged into release/v3.8.38 between the CHANGELOG finalize (ff57be3) and the merge-to-main (ae6e234), so they shipped in the v3.8.38 tag but had no bullet: - feat(compression): Ionizer engine (lossy JSON-array sampling + CCR) (#5148) - fix(sse): preserve non-stream reasoning fields (#5155, @rdself) - fix(i18n): add missing English UI labels (#5153, @rdself) - test(combo): gated live smoke (#5151) + release-expectations refresh (#5150, @KooshaPari) (#5129 exact-host Anthropic baseUrl is already covered by the #5130 bullet — same CodeQL #674.) Synced 41 i18n CHANGELOG mirrors.
* chore(release): open v3.8.39 development cycle * docs(changelog): backfill 5 v3.8.38 bullets merged after release finalize These PRs squash-merged into release/v3.8.38 between the CHANGELOG finalize (ff57be3) and the merge-to-main (ae6e234), so they shipped in the v3.8.38 tag but had no bullet: - feat(compression): Ionizer engine (lossy JSON-array sampling + CCR) (#5148) - fix(sse): preserve non-stream reasoning fields (#5155, @rdself) - fix(i18n): add missing English UI labels (#5153, @rdself) - test(combo): gated live smoke (#5151) + release-expectations refresh (#5150, @KooshaPari) (#5129 exact-host Anthropic baseUrl is already covered by the #5130 bullet — same CodeQL #674.) Synced 41 i18n CHANGELOG mirrors. * feat(compression): TOON best-of-N candidate encoder + encoder A/B table (#5163) Integrated into release/v3.8.39. TOON best-of-N candidate encoder (GCF default, fail-open). 17/17 unit tests pass on merge result; CI reds were base-stale + Quality Ratchet DRIFT. * fix(zenmux): normalize vendor-prefixed GLM system roles (#5158) Integrated into release/v3.8.39. ZenMux vendor-prefixed GLM system-role normalization; 12/12 role-normalizer tests pass on merge result. CI reds base-stale. * [codex] fix xAI OAuth test and reasoning effort (#5157) Integrated into release/v3.8.39. xAI reasoning-effort normalization (max/xhigh→high) + OAuth test config; 46/46 xai-translator tests pass on merge result. CI reds base-stale. * docs(i18n): add Traditional Chinese (zh-TW) README and update zh-CN to latest (#5162) Integrated into release/v3.8.39. Traditional Chinese (zh-TW) README + zh-CN refresh; docs-only. * test(security): guard PII redaction stays opt-in (default off) + Hard Rule #20 (#5159) Integrated into release/v3.8.39. PII opt-in regression guard + Hard Rule #20; rebased to strip base-drift (+81/-1). 5/5 guard tests pass; flip-proof verified. * test(combo): deterministic context-relay universal-handoff coverage (closes phase-2 TODO) (#5168) Integrated into release/v3.8.39. Deterministic context-relay universal-handoff coverage (3 tests); 3/3 pass on merge result. * docs(i18n): full sync zh-TW and zh-CN README with canonical English v3.8.39 (#5171) Integrated into release/v3.8.39. Full zh-TW docs tree + zh-CN sync with canonical English v3.8.39; docs-only. * fix(serve): honour HOSTNAME from .env instead of hardcoding 0.0.0.0 (#5134) (#5170) Integrated into release/v3.8.39. HOSTNAME env override in serve (#5134) + regression test (4/4, TDD flip-proof verified). * fix(sse): resolve nameless deepseek-web tool blocks via parameter-schema match (#5154) (#5173) Integrated into release/v3.8.39. Schema-based nameless deepseek-web tool-block resolution (#5154); 6/6 tests pass on merge result (incl. ambiguous/no-match negatives + named-tag no-regression). * fix(sse): normalize array user content for Command Code to avoid upstream 400 (#5166) (#5174) Integrated into release/v3.8.39. Normalize array user content for Command Code (#5166, user-array/400 symptom); 4/4 tests pass on merge result. * fix(sse): defer </think> close so it never leaks before tool_calls (#5123) (#5175) Integrated into release/v3.8.39. Defer </think> close so it never leaks before tool_calls (#5123); 4/4 tests pass (incl. #4633 no-regression). CHANGELOG synced to keep all 3 v3.8.39 fixes. * fix(dashboard): use amber for home update-step warning icon (#5176) Integrated into release/v3.8.39. Amber for home update-step warning icon; 1/1 UI test. * fix(api): LAN/Tailscale dashboard — host-aware CSP + GET-exempt version route + combo field errors (#5083) (#5177) Integrated into release/v3.8.39. Host-aware CSP (ReDoS/injection-safe host validation) + GET-exempt /api/system/version (POST/spawn stays LOCAL_ONLY, exact-match safe-methods-only) + COMBO_002 firstField. 44/44 tests + route-guard membership gate green. CHANGELOG synced to keep all 4 v3.8.39 fixes. * fix(api): replace #5083 global middleware CSP with declarative ws: scheme (#5083) Follow-up to PR #5177 (merged): that version implemented the LAN-CSP fix (Bug 1) with a new global `src/middleware.ts` + `src/server/csp.ts`, which contradicts the project's documented architecture — 'No global Next.js middleware — interception is route-specific' (CLAUDE.md / AGENTS.md) — and was merged unverified (middleware vs next.config header precedence was never confirmed in a real build). This replaces that approach with the minimal, declarative equivalent: • next.config.mjs: connect-src now permits the bare `ws:` scheme (symmetric with the bare `wss:` already allowed) so the dashboard can reach its own Live WS server from a LAN/Tailscale host. No middleware. • Removes src/middleware.ts, src/server/csp.ts, and tests/unit/csp-host-aware.test.ts. • Adds tests/unit/csp-lan-ws-5083.test.ts (incl. a guard asserting src/middleware.ts does NOT exist, so the global-middleware approach cannot silently return). Bugs 2 (GET-exempt /api/system/version) and 3 (COMBO_002 field surfacing) from #5177 are unaffected and remain in place. Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com> * test(combo): end-to-end quota-share DRR routing-decision coverage (matrix parity) (#5179) Integrated into release/v3.8.39. Quota-share DRR routing-decision coverage (matrix parity); 2/2 pass on merge result. * feat(agent-bridge): graceful cert-install fallback with manual guide for containers (#4546) (#5178) Integrated into release/v3.8.39. Agent-bridge graceful cert-install fallback + manual guide (#4546); 6/6 tests pass on merge result. * fix(antigravity): family-scoped quota lockout (gemini/claude buckets) (#5180) Integrated into release/v3.8.39 — family-scoped antigravity quota lockout. Rebased from v3.8.37 + validated (vitest 5/5, typecheck clean, full combo-matrix green, model-lockout 99/0). Same-model cross-account retry (chat.ts) deferred pending live antigravity VPS validation. * fix(cli): force NODE_ENV to match dev/start run mode in custom Next server (#5189) Integrated into release/v3.8.39. Force NODE_ENV to match dev/start run mode in custom Next server; 2/2 source-scan+ordering tests pass on merge result. * feat(compression): CCR ranged/grep/stats retrieval (ReDoS-safe, backward-compat) (#5187) Integrated into release/v3.8.39. CCR ranged/grep/stats retrieval (safe-regex ReDoS guard + length/match caps); 17/17 tests pass on merge result. * docs(combo): sync all combo/routing-strategy docs to current state + document test coverage (#5185) Integrated into release/v3.8.39. Combo/routing-strategy docs sync; docs-only. * fix(mcp): return 404 (not 400) for unknown Streamable HTTP session id (#5169) (#5191) * fix(api): respect blocked Auto (Zero-Config) provider in /v1/models catalog (#5192) (#5194) * test(combo): deterministic context-relay codex quota-handoff coverage (closes last gap) (#5195) * test(ci): wire antigravity-quota-family under test:vitest (fix test-discovery orphan) (#5196) * fix(oauth): antigravity login no longer hangs — fire-and-forget onboarding + bounded post-exchange (#5193) Antigravity OAuth hang fix (no-PKCE/no-openid + bounded post-exchange + exchange-500 fix). Includes #5200 (Koosha) revert + owner rebaseline to keep documented comments. Integrated into release/v3.8.39. * feat(oauth): remote Antigravity login via local helper + paste-credentials (#5203) Remote Antigravity login: local helper (omniroute login antigravity) + paste-credentials. Integrated into release/v3.8.39. * fix(translator): accept Claude Messages shape in non-stream malformed-200 guard (#5156) Integrated into release/v3.8.39 * fix(cli): default dev bundler to Turbopack (16.2.x panic no longer reproduces) (#5206) Integrated into release/v3.8.39 * fix(cli): auto-calibrate server V8 heap from physical RAM (#5172) (#5213) The server was spawned with a fixed --max-old-space-size=512 (omniroute serve) or no heap flag at all (Electron), so RAM-rich boxes still OOM-crashed under load (Ineffective mark-compacts near heap limit ~500MB) with many providers/ accounts and large model catalogs. New calibrateHeapFallbackMb(os.totalmem()) defaults the heap to ~35% of RAM clamped [512,4096], wired into serve.mjs and electron/main.js. Explicit OMNIROUTE_MEMORY_MB still wins (#2939 unchanged). Also addresses #5160 (same OOM root); #5152 (docker) benefits via the same knob. Closes #5172 * fix(proxy): coalesce fast-fail health probes (#5208) Integrated into release/v3.8.39 * fix(proxy): close dispatchers when clearing cache (#5202) Integrated into release/v3.8.39 * fix(cli): raise dev server Node heap limit to 8GB to prevent OOM (#5198) Integrated into release/v3.8.39 * fix(auth): allow synthetic no-auth fallback for mimocode (#5205) Integrated into release/v3.8.39 * fix(oauth): preserve Antigravity refresh_token on empty/omitted upstream response (#3850) (#5214) Google's OAuth refresh tokens are non-rotating: the refresh response usually omits refresh_token and occasionally returns it as an empty string. The Antigravity executor used `typeof tokens.refresh_token === "string" ? ... ` which accepts "" (typeof "" === "string") and overwrote the stored token with empty, nulling it on first refresh. Now treats non-string OR empty as absent and preserves credentials.refreshToken, matching refreshGoogleToken semantics. Closes #3850 * fix(responses): normalize non-array input (#5204) Integrated into release/v3.8.39 * fix(stream): normalize safety finish reasons via shared helper (#5197) Integrated into release/v3.8.39 * fix(request-logger): never render negative '(-100%)' compression badge (#5201) Integrated into release/v3.8.39 * fix(combo): reject empty responses api output (#5207) Integrated into release/v3.8.39 — combo failover now rejects empty Responses API output (validateQuality). Baseline rebaseline dropped (main-measured drift; maintainer rebaselines at release). * fix(pwa): prefer cached navigation before offline page (#5209) Integrated into release/v3.8.39 — PWA service worker prefers cached navigation before offline page (#5165). * chore(release): v3.8.39 — 2026-06-28 * chore(release): rebaseline openapi+i18n coverage ratchet drift for v3.8.39 --------- Co-authored-by: Arthur Bodera <abodera@gmail.com> Co-authored-by: Nguyen Minh <lop123thcs@gmail.com> Co-authored-by: lunkerchen <labanchen@gmail.com> Co-authored-by: Ankit <177378174+anki1kr@users.noreply.github.com> Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com> Co-authored-by: Ardem2025 <ardemb22@gmail.com> Co-authored-by: backryun <bakryun0718@proton.me> Co-authored-by: Anton <39598727+NomenAK@users.noreply.github.com> Co-authored-by: KooshaPari <42529354+KooshaPari@users.noreply.github.com> Co-authored-by: Wilson <pedbookmed@gmail.com> Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
* chore(release): open v3.8.39 development cycle * docs(changelog): backfill 5 v3.8.38 bullets merged after release finalize These PRs squash-merged into release/v3.8.38 between the CHANGELOG finalize (ff57be3) and the merge-to-main (ae6e234), so they shipped in the v3.8.38 tag but had no bullet: - feat(compression): Ionizer engine (lossy JSON-array sampling + CCR) (diegosouzapw#5148) - fix(sse): preserve non-stream reasoning fields (diegosouzapw#5155, @rdself) - fix(i18n): add missing English UI labels (diegosouzapw#5153, @rdself) - test(combo): gated live smoke (diegosouzapw#5151) + release-expectations refresh (diegosouzapw#5150, @KooshaPari) (diegosouzapw#5129 exact-host Anthropic baseUrl is already covered by the diegosouzapw#5130 bullet — same CodeQL diegosouzapw#674.) Synced 41 i18n CHANGELOG mirrors. * feat(compression): TOON best-of-N candidate encoder + encoder A/B table (diegosouzapw#5163) Integrated into release/v3.8.39. TOON best-of-N candidate encoder (GCF default, fail-open). 17/17 unit tests pass on merge result; CI reds were base-stale + Quality Ratchet DRIFT. * fix(zenmux): normalize vendor-prefixed GLM system roles (diegosouzapw#5158) Integrated into release/v3.8.39. ZenMux vendor-prefixed GLM system-role normalization; 12/12 role-normalizer tests pass on merge result. CI reds base-stale. * [codex] fix xAI OAuth test and reasoning effort (diegosouzapw#5157) Integrated into release/v3.8.39. xAI reasoning-effort normalization (max/xhigh→high) + OAuth test config; 46/46 xai-translator tests pass on merge result. CI reds base-stale. * docs(i18n): add Traditional Chinese (zh-TW) README and update zh-CN to latest (diegosouzapw#5162) Integrated into release/v3.8.39. Traditional Chinese (zh-TW) README + zh-CN refresh; docs-only. * test(security): guard PII redaction stays opt-in (default off) + Hard Rule diegosouzapw#20 (diegosouzapw#5159) Integrated into release/v3.8.39. PII opt-in regression guard + Hard Rule diegosouzapw#20; rebased to strip base-drift (+81/-1). 5/5 guard tests pass; flip-proof verified. * test(combo): deterministic context-relay universal-handoff coverage (closes phase-2 TODO) (diegosouzapw#5168) Integrated into release/v3.8.39. Deterministic context-relay universal-handoff coverage (3 tests); 3/3 pass on merge result. * docs(i18n): full sync zh-TW and zh-CN README with canonical English v3.8.39 (diegosouzapw#5171) Integrated into release/v3.8.39. Full zh-TW docs tree + zh-CN sync with canonical English v3.8.39; docs-only. * fix(serve): honour HOSTNAME from .env instead of hardcoding 0.0.0.0 (diegosouzapw#5134) (diegosouzapw#5170) Integrated into release/v3.8.39. HOSTNAME env override in serve (diegosouzapw#5134) + regression test (4/4, TDD flip-proof verified). * fix(sse): resolve nameless deepseek-web tool blocks via parameter-schema match (diegosouzapw#5154) (diegosouzapw#5173) Integrated into release/v3.8.39. Schema-based nameless deepseek-web tool-block resolution (diegosouzapw#5154); 6/6 tests pass on merge result (incl. ambiguous/no-match negatives + named-tag no-regression). * fix(sse): normalize array user content for Command Code to avoid upstream 400 (diegosouzapw#5166) (diegosouzapw#5174) Integrated into release/v3.8.39. Normalize array user content for Command Code (diegosouzapw#5166, user-array/400 symptom); 4/4 tests pass on merge result. * fix(sse): defer </think> close so it never leaks before tool_calls (diegosouzapw#5123) (diegosouzapw#5175) Integrated into release/v3.8.39. Defer </think> close so it never leaks before tool_calls (diegosouzapw#5123); 4/4 tests pass (incl. diegosouzapw#4633 no-regression). CHANGELOG synced to keep all 3 v3.8.39 fixes. * fix(dashboard): use amber for home update-step warning icon (diegosouzapw#5176) Integrated into release/v3.8.39. Amber for home update-step warning icon; 1/1 UI test. * fix(api): LAN/Tailscale dashboard — host-aware CSP + GET-exempt version route + combo field errors (diegosouzapw#5083) (diegosouzapw#5177) Integrated into release/v3.8.39. Host-aware CSP (ReDoS/injection-safe host validation) + GET-exempt /api/system/version (POST/spawn stays LOCAL_ONLY, exact-match safe-methods-only) + COMBO_002 firstField. 44/44 tests + route-guard membership gate green. CHANGELOG synced to keep all 4 v3.8.39 fixes. * fix(api): replace diegosouzapw#5083 global middleware CSP with declarative ws: scheme (diegosouzapw#5083) Follow-up to PR diegosouzapw#5177 (merged): that version implemented the LAN-CSP fix (Bug 1) with a new global `src/middleware.ts` + `src/server/csp.ts`, which contradicts the project's documented architecture — 'No global Next.js middleware — interception is route-specific' (CLAUDE.md / AGENTS.md) — and was merged unverified (middleware vs next.config header precedence was never confirmed in a real build). This replaces that approach with the minimal, declarative equivalent: • next.config.mjs: connect-src now permits the bare `ws:` scheme (symmetric with the bare `wss:` already allowed) so the dashboard can reach its own Live WS server from a LAN/Tailscale host. No middleware. • Removes src/middleware.ts, src/server/csp.ts, and tests/unit/csp-host-aware.test.ts. • Adds tests/unit/csp-lan-ws-5083.test.ts (incl. a guard asserting src/middleware.ts does NOT exist, so the global-middleware approach cannot silently return). Bugs 2 (GET-exempt /api/system/version) and 3 (COMBO_002 field surfacing) from diegosouzapw#5177 are unaffected and remain in place. Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com> * test(combo): end-to-end quota-share DRR routing-decision coverage (matrix parity) (diegosouzapw#5179) Integrated into release/v3.8.39. Quota-share DRR routing-decision coverage (matrix parity); 2/2 pass on merge result. * feat(agent-bridge): graceful cert-install fallback with manual guide for containers (diegosouzapw#4546) (diegosouzapw#5178) Integrated into release/v3.8.39. Agent-bridge graceful cert-install fallback + manual guide (diegosouzapw#4546); 6/6 tests pass on merge result. * fix(antigravity): family-scoped quota lockout (gemini/claude buckets) (diegosouzapw#5180) Integrated into release/v3.8.39 — family-scoped antigravity quota lockout. Rebased from v3.8.37 + validated (vitest 5/5, typecheck clean, full combo-matrix green, model-lockout 99/0). Same-model cross-account retry (chat.ts) deferred pending live antigravity VPS validation. * fix(cli): force NODE_ENV to match dev/start run mode in custom Next server (diegosouzapw#5189) Integrated into release/v3.8.39. Force NODE_ENV to match dev/start run mode in custom Next server; 2/2 source-scan+ordering tests pass on merge result. * feat(compression): CCR ranged/grep/stats retrieval (ReDoS-safe, backward-compat) (diegosouzapw#5187) Integrated into release/v3.8.39. CCR ranged/grep/stats retrieval (safe-regex ReDoS guard + length/match caps); 17/17 tests pass on merge result. * docs(combo): sync all combo/routing-strategy docs to current state + document test coverage (diegosouzapw#5185) Integrated into release/v3.8.39. Combo/routing-strategy docs sync; docs-only. * fix(mcp): return 404 (not 400) for unknown Streamable HTTP session id (diegosouzapw#5169) (diegosouzapw#5191) * fix(api): respect blocked Auto (Zero-Config) provider in /v1/models catalog (diegosouzapw#5192) (diegosouzapw#5194) * test(combo): deterministic context-relay codex quota-handoff coverage (closes last gap) (diegosouzapw#5195) * test(ci): wire antigravity-quota-family under test:vitest (fix test-discovery orphan) (diegosouzapw#5196) * fix(oauth): antigravity login no longer hangs — fire-and-forget onboarding + bounded post-exchange (diegosouzapw#5193) Antigravity OAuth hang fix (no-PKCE/no-openid + bounded post-exchange + exchange-500 fix). Includes diegosouzapw#5200 (Koosha) revert + owner rebaseline to keep documented comments. Integrated into release/v3.8.39. * feat(oauth): remote Antigravity login via local helper + paste-credentials (diegosouzapw#5203) Remote Antigravity login: local helper (omniroute login antigravity) + paste-credentials. Integrated into release/v3.8.39. * fix(translator): accept Claude Messages shape in non-stream malformed-200 guard (diegosouzapw#5156) Integrated into release/v3.8.39 * fix(cli): default dev bundler to Turbopack (16.2.x panic no longer reproduces) (diegosouzapw#5206) Integrated into release/v3.8.39 * fix(cli): auto-calibrate server V8 heap from physical RAM (diegosouzapw#5172) (diegosouzapw#5213) The server was spawned with a fixed --max-old-space-size=512 (omniroute serve) or no heap flag at all (Electron), so RAM-rich boxes still OOM-crashed under load (Ineffective mark-compacts near heap limit ~500MB) with many providers/ accounts and large model catalogs. New calibrateHeapFallbackMb(os.totalmem()) defaults the heap to ~35% of RAM clamped [512,4096], wired into serve.mjs and electron/main.js. Explicit OMNIROUTE_MEMORY_MB still wins (diegosouzapw#2939 unchanged). Also addresses diegosouzapw#5160 (same OOM root); diegosouzapw#5152 (docker) benefits via the same knob. Closes diegosouzapw#5172 * fix(proxy): coalesce fast-fail health probes (diegosouzapw#5208) Integrated into release/v3.8.39 * fix(proxy): close dispatchers when clearing cache (diegosouzapw#5202) Integrated into release/v3.8.39 * fix(cli): raise dev server Node heap limit to 8GB to prevent OOM (diegosouzapw#5198) Integrated into release/v3.8.39 * fix(auth): allow synthetic no-auth fallback for mimocode (diegosouzapw#5205) Integrated into release/v3.8.39 * fix(oauth): preserve Antigravity refresh_token on empty/omitted upstream response (diegosouzapw#3850) (diegosouzapw#5214) Google's OAuth refresh tokens are non-rotating: the refresh response usually omits refresh_token and occasionally returns it as an empty string. The Antigravity executor used `typeof tokens.refresh_token === "string" ? ... ` which accepts "" (typeof "" === "string") and overwrote the stored token with empty, nulling it on first refresh. Now treats non-string OR empty as absent and preserves credentials.refreshToken, matching refreshGoogleToken semantics. Closes diegosouzapw#3850 * fix(responses): normalize non-array input (diegosouzapw#5204) Integrated into release/v3.8.39 * fix(stream): normalize safety finish reasons via shared helper (diegosouzapw#5197) Integrated into release/v3.8.39 * fix(request-logger): never render negative '(-100%)' compression badge (diegosouzapw#5201) Integrated into release/v3.8.39 * fix(combo): reject empty responses api output (diegosouzapw#5207) Integrated into release/v3.8.39 — combo failover now rejects empty Responses API output (validateQuality). Baseline rebaseline dropped (main-measured drift; maintainer rebaselines at release). * fix(pwa): prefer cached navigation before offline page (diegosouzapw#5209) Integrated into release/v3.8.39 — PWA service worker prefers cached navigation before offline page (diegosouzapw#5165). * chore(release): v3.8.39 — 2026-06-28 * chore(release): rebaseline openapi+i18n coverage ratchet drift for v3.8.39 --------- Co-authored-by: Arthur Bodera <abodera@gmail.com> Co-authored-by: Nguyen Minh <lop123thcs@gmail.com> Co-authored-by: lunkerchen <labanchen@gmail.com> Co-authored-by: Ankit <177378174+anki1kr@users.noreply.github.com> Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com> Co-authored-by: Ardem2025 <ardemb22@gmail.com> Co-authored-by: backryun <bakryun0718@proton.me> Co-authored-by: Anton <39598727+NomenAK@users.noreply.github.com> Co-authored-by: KooshaPari <42529354+KooshaPari@users.noreply.github.com> Co-authored-by: Wilson <pedbookmed@gmail.com> Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
…te-url-substring-sanitization #674) (diegosouzapw#5129) The anthropic-compatible Bearer-fallback gate decided whether a configured baseUrl targeted the official api.anthropic.com host via a substring `.includes("api.anthropic.com")`. A look-alike upstream such as `https://api.anthropic.com.evil.test` or `https://evil.test/?x=api.anthropic.com` matched the substring and was wrongly treated as official, suppressing the Bearer fallback meant for third-party gateways (CodeQL #674, js/incomplete-url-substring-sanitization, high). Replace the substring test with an exported `isOfficialAnthropicBaseUrl()` helper that parses the URL and compares the hostname for exact equality. Empty baseUrl stays official; scheme-less hosts are parsed with an assumed https://; an unparseable baseUrl falls back to third-party (Bearer emitted) as the safer default. Behavior for legitimate official/third-party baseUrls is unchanged. Adds tests/unit/anthropic-official-baseurl-host.test.ts covering official, look-alike, scheme-less, and unparseable inputs plus a static guard that the substring pattern is gone.
* chore(release): open v3.8.39 development cycle * docs(changelog): backfill 5 v3.8.38 bullets merged after release finalize These PRs squash-merged into release/v3.8.38 between the CHANGELOG finalize (ff57be3) and the merge-to-main (ae6e234), so they shipped in the v3.8.38 tag but had no bullet: - feat(compression): Ionizer engine (lossy JSON-array sampling + CCR) (diegosouzapw#5148) - fix(sse): preserve non-stream reasoning fields (diegosouzapw#5155, @rdself) - fix(i18n): add missing English UI labels (diegosouzapw#5153, @rdself) - test(combo): gated live smoke (diegosouzapw#5151) + release-expectations refresh (diegosouzapw#5150, @KooshaPari) (diegosouzapw#5129 exact-host Anthropic baseUrl is already covered by the diegosouzapw#5130 bullet — same CodeQL #674.) Synced 41 i18n CHANGELOG mirrors. * feat(compression): TOON best-of-N candidate encoder + encoder A/B table (diegosouzapw#5163) Integrated into release/v3.8.39. TOON best-of-N candidate encoder (GCF default, fail-open). 17/17 unit tests pass on merge result; CI reds were base-stale + Quality Ratchet DRIFT. * fix(zenmux): normalize vendor-prefixed GLM system roles (diegosouzapw#5158) Integrated into release/v3.8.39. ZenMux vendor-prefixed GLM system-role normalization; 12/12 role-normalizer tests pass on merge result. CI reds base-stale. * [codex] fix xAI OAuth test and reasoning effort (diegosouzapw#5157) Integrated into release/v3.8.39. xAI reasoning-effort normalization (max/xhigh→high) + OAuth test config; 46/46 xai-translator tests pass on merge result. CI reds base-stale. * docs(i18n): add Traditional Chinese (zh-TW) README and update zh-CN to latest (diegosouzapw#5162) Integrated into release/v3.8.39. Traditional Chinese (zh-TW) README + zh-CN refresh; docs-only. * test(security): guard PII redaction stays opt-in (default off) + Hard Rule #20 (diegosouzapw#5159) Integrated into release/v3.8.39. PII opt-in regression guard + Hard Rule #20; rebased to strip base-drift (+81/-1). 5/5 guard tests pass; flip-proof verified. * test(combo): deterministic context-relay universal-handoff coverage (closes phase-2 TODO) (diegosouzapw#5168) Integrated into release/v3.8.39. Deterministic context-relay universal-handoff coverage (3 tests); 3/3 pass on merge result. * docs(i18n): full sync zh-TW and zh-CN README with canonical English v3.8.39 (diegosouzapw#5171) Integrated into release/v3.8.39. Full zh-TW docs tree + zh-CN sync with canonical English v3.8.39; docs-only. * fix(serve): honour HOSTNAME from .env instead of hardcoding 0.0.0.0 (diegosouzapw#5134) (diegosouzapw#5170) Integrated into release/v3.8.39. HOSTNAME env override in serve (diegosouzapw#5134) + regression test (4/4, TDD flip-proof verified). * fix(sse): resolve nameless deepseek-web tool blocks via parameter-schema match (diegosouzapw#5154) (diegosouzapw#5173) Integrated into release/v3.8.39. Schema-based nameless deepseek-web tool-block resolution (diegosouzapw#5154); 6/6 tests pass on merge result (incl. ambiguous/no-match negatives + named-tag no-regression). * fix(sse): normalize array user content for Command Code to avoid upstream 400 (diegosouzapw#5166) (diegosouzapw#5174) Integrated into release/v3.8.39. Normalize array user content for Command Code (diegosouzapw#5166, user-array/400 symptom); 4/4 tests pass on merge result. * fix(sse): defer </think> close so it never leaks before tool_calls (diegosouzapw#5123) (diegosouzapw#5175) Integrated into release/v3.8.39. Defer </think> close so it never leaks before tool_calls (diegosouzapw#5123); 4/4 tests pass (incl. diegosouzapw#4633 no-regression). CHANGELOG synced to keep all 3 v3.8.39 fixes. * fix(dashboard): use amber for home update-step warning icon (diegosouzapw#5176) Integrated into release/v3.8.39. Amber for home update-step warning icon; 1/1 UI test. * fix(api): LAN/Tailscale dashboard — host-aware CSP + GET-exempt version route + combo field errors (diegosouzapw#5083) (diegosouzapw#5177) Integrated into release/v3.8.39. Host-aware CSP (ReDoS/injection-safe host validation) + GET-exempt /api/system/version (POST/spawn stays LOCAL_ONLY, exact-match safe-methods-only) + COMBO_002 firstField. 44/44 tests + route-guard membership gate green. CHANGELOG synced to keep all 4 v3.8.39 fixes. * fix(api): replace diegosouzapw#5083 global middleware CSP with declarative ws: scheme (diegosouzapw#5083) Follow-up to PR diegosouzapw#5177 (merged): that version implemented the LAN-CSP fix (Bug 1) with a new global `src/middleware.ts` + `src/server/csp.ts`, which contradicts the project's documented architecture — 'No global Next.js middleware — interception is route-specific' (CLAUDE.md / AGENTS.md) — and was merged unverified (middleware vs next.config header precedence was never confirmed in a real build). This replaces that approach with the minimal, declarative equivalent: • next.config.mjs: connect-src now permits the bare `ws:` scheme (symmetric with the bare `wss:` already allowed) so the dashboard can reach its own Live WS server from a LAN/Tailscale host. No middleware. • Removes src/middleware.ts, src/server/csp.ts, and tests/unit/csp-host-aware.test.ts. • Adds tests/unit/csp-lan-ws-5083.test.ts (incl. a guard asserting src/middleware.ts does NOT exist, so the global-middleware approach cannot silently return). Bugs 2 (GET-exempt /api/system/version) and 3 (COMBO_002 field surfacing) from diegosouzapw#5177 are unaffected and remain in place. Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com> * test(combo): end-to-end quota-share DRR routing-decision coverage (matrix parity) (diegosouzapw#5179) Integrated into release/v3.8.39. Quota-share DRR routing-decision coverage (matrix parity); 2/2 pass on merge result. * feat(agent-bridge): graceful cert-install fallback with manual guide for containers (diegosouzapw#4546) (diegosouzapw#5178) Integrated into release/v3.8.39. Agent-bridge graceful cert-install fallback + manual guide (diegosouzapw#4546); 6/6 tests pass on merge result. * fix(antigravity): family-scoped quota lockout (gemini/claude buckets) (diegosouzapw#5180) Integrated into release/v3.8.39 — family-scoped antigravity quota lockout. Rebased from v3.8.37 + validated (vitest 5/5, typecheck clean, full combo-matrix green, model-lockout 99/0). Same-model cross-account retry (chat.ts) deferred pending live antigravity VPS validation. * fix(cli): force NODE_ENV to match dev/start run mode in custom Next server (diegosouzapw#5189) Integrated into release/v3.8.39. Force NODE_ENV to match dev/start run mode in custom Next server; 2/2 source-scan+ordering tests pass on merge result. * feat(compression): CCR ranged/grep/stats retrieval (ReDoS-safe, backward-compat) (diegosouzapw#5187) Integrated into release/v3.8.39. CCR ranged/grep/stats retrieval (safe-regex ReDoS guard + length/match caps); 17/17 tests pass on merge result. * docs(combo): sync all combo/routing-strategy docs to current state + document test coverage (diegosouzapw#5185) Integrated into release/v3.8.39. Combo/routing-strategy docs sync; docs-only. * fix(mcp): return 404 (not 400) for unknown Streamable HTTP session id (diegosouzapw#5169) (diegosouzapw#5191) * fix(api): respect blocked Auto (Zero-Config) provider in /v1/models catalog (diegosouzapw#5192) (diegosouzapw#5194) * test(combo): deterministic context-relay codex quota-handoff coverage (closes last gap) (diegosouzapw#5195) * test(ci): wire antigravity-quota-family under test:vitest (fix test-discovery orphan) (diegosouzapw#5196) * fix(oauth): antigravity login no longer hangs — fire-and-forget onboarding + bounded post-exchange (diegosouzapw#5193) Antigravity OAuth hang fix (no-PKCE/no-openid + bounded post-exchange + exchange-500 fix). Includes diegosouzapw#5200 (Koosha) revert + owner rebaseline to keep documented comments. Integrated into release/v3.8.39. * feat(oauth): remote Antigravity login via local helper + paste-credentials (diegosouzapw#5203) Remote Antigravity login: local helper (omniroute login antigravity) + paste-credentials. Integrated into release/v3.8.39. * fix(translator): accept Claude Messages shape in non-stream malformed-200 guard (diegosouzapw#5156) Integrated into release/v3.8.39 * fix(cli): default dev bundler to Turbopack (16.2.x panic no longer reproduces) (diegosouzapw#5206) Integrated into release/v3.8.39 * fix(cli): auto-calibrate server V8 heap from physical RAM (diegosouzapw#5172) (diegosouzapw#5213) The server was spawned with a fixed --max-old-space-size=512 (omniroute serve) or no heap flag at all (Electron), so RAM-rich boxes still OOM-crashed under load (Ineffective mark-compacts near heap limit ~500MB) with many providers/ accounts and large model catalogs. New calibrateHeapFallbackMb(os.totalmem()) defaults the heap to ~35% of RAM clamped [512,4096], wired into serve.mjs and electron/main.js. Explicit OMNIROUTE_MEMORY_MB still wins (diegosouzapw#2939 unchanged). Also addresses diegosouzapw#5160 (same OOM root); diegosouzapw#5152 (docker) benefits via the same knob. Closes diegosouzapw#5172 * fix(proxy): coalesce fast-fail health probes (diegosouzapw#5208) Integrated into release/v3.8.39 * fix(proxy): close dispatchers when clearing cache (diegosouzapw#5202) Integrated into release/v3.8.39 * fix(cli): raise dev server Node heap limit to 8GB to prevent OOM (diegosouzapw#5198) Integrated into release/v3.8.39 * fix(auth): allow synthetic no-auth fallback for mimocode (diegosouzapw#5205) Integrated into release/v3.8.39 * fix(oauth): preserve Antigravity refresh_token on empty/omitted upstream response (diegosouzapw#3850) (diegosouzapw#5214) Google's OAuth refresh tokens are non-rotating: the refresh response usually omits refresh_token and occasionally returns it as an empty string. The Antigravity executor used `typeof tokens.refresh_token === "string" ? ... ` which accepts "" (typeof "" === "string") and overwrote the stored token with empty, nulling it on first refresh. Now treats non-string OR empty as absent and preserves credentials.refreshToken, matching refreshGoogleToken semantics. Closes diegosouzapw#3850 * fix(responses): normalize non-array input (diegosouzapw#5204) Integrated into release/v3.8.39 * fix(stream): normalize safety finish reasons via shared helper (diegosouzapw#5197) Integrated into release/v3.8.39 * fix(request-logger): never render negative '(-100%)' compression badge (diegosouzapw#5201) Integrated into release/v3.8.39 * fix(combo): reject empty responses api output (diegosouzapw#5207) Integrated into release/v3.8.39 — combo failover now rejects empty Responses API output (validateQuality). Baseline rebaseline dropped (main-measured drift; maintainer rebaselines at release). * fix(pwa): prefer cached navigation before offline page (diegosouzapw#5209) Integrated into release/v3.8.39 — PWA service worker prefers cached navigation before offline page (diegosouzapw#5165). * chore(release): v3.8.39 — 2026-06-28 * chore(release): rebaseline openapi+i18n coverage ratchet drift for v3.8.39 --------- Co-authored-by: Arthur Bodera <abodera@gmail.com> Co-authored-by: Nguyen Minh <lop123thcs@gmail.com> Co-authored-by: lunkerchen <labanchen@gmail.com> Co-authored-by: Ankit <177378174+anki1kr@users.noreply.github.com> Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com> Co-authored-by: Ardem2025 <ardemb22@gmail.com> Co-authored-by: backryun <bakryun0718@proton.me> Co-authored-by: Anton <39598727+NomenAK@users.noreply.github.com> Co-authored-by: KooshaPari <42529354+KooshaPari@users.noreply.github.com> Co-authored-by: Wilson <pedbookmed@gmail.com> Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
…te-url-substring-sanitization #674) (diegosouzapw#5129) The anthropic-compatible Bearer-fallback gate decided whether a configured baseUrl targeted the official api.anthropic.com host via a substring `.includes("api.anthropic.com")`. A look-alike upstream such as `https://api.anthropic.com.evil.test` or `https://evil.test/?x=api.anthropic.com` matched the substring and was wrongly treated as official, suppressing the Bearer fallback meant for third-party gateways (CodeQL #674, js/incomplete-url-substring-sanitization, high). Replace the substring test with an exported `isOfficialAnthropicBaseUrl()` helper that parses the URL and compares the hostname for exact equality. Empty baseUrl stays official; scheme-less hosts are parsed with an assumed https://; an unparseable baseUrl falls back to third-party (Bearer emitted) as the safer default. Behavior for legitimate official/third-party baseUrls is unchanged. Adds tests/unit/anthropic-official-baseurl-host.test.ts covering official, look-alike, scheme-less, and unparseable inputs plus a static guard that the substring pattern is gone.
* fix(security): exact-host Anthropic baseUrl check (CodeQL js/incomplete-url-substring-sanitization #674) (#5129)
The anthropic-compatible Bearer-fallback gate decided whether a configured baseUrl
targeted the official api.anthropic.com host via a substring `.includes("api.anthropic.com")`.
A look-alike upstream such as `https://api.anthropic.com.evil.test` or
`https://evil.test/?x=api.anthropic.com` matched the substring and was wrongly treated as
official, suppressing the Bearer fallback meant for third-party gateways
(CodeQL #674, js/incomplete-url-substring-sanitization, high).
Replace the substring test with an exported `isOfficialAnthropicBaseUrl()` helper that
parses the URL and compares the hostname for exact equality. Empty baseUrl stays official;
scheme-less hosts are parsed with an assumed https://; an unparseable baseUrl falls back to
third-party (Bearer emitted) as the safer default. Behavior for legitimate official/third-party
baseUrls is unchanged.
Adds tests/unit/anthropic-official-baseurl-host.test.ts covering official, look-alike,
scheme-less, and unparseable inputs plus a static guard that the substring pattern is gone.
* Release v3.8.38 (#5078)
* chore(release): open v3.8.38 development cycle
* fix(executors): strip client_metadata for cerebras and mistral (#4727)
Integrated into release/v3.8.38 (leva 5)
* fix(codebuddy): only send reasoning params when client requests reasoning (#5019)
Integrated into release/v3.8.38 (leva 5)
* fix(sse): keep streaming for forceStream providers when client requests JSON (#5021)
Integrated into release/v3.8.38 (leva 5)
* fix(sse): guard non-JSON SSE lines and duplicate [DONE] (#4937)
Integrated into release/v3.8.38 (leva 5)
* feat(blackbox): refresh provider model catalog (#4935)
Integrated into release/v3.8.38 (leva 5)
* fix(sse): dedupe case-variant Anthropic version/beta headers (#4846)
Integrated into release/v3.8.38 (leva 5)
* feat(sse): Kiro inline <thinking> stream splitter (#4911)
Integrated into release/v3.8.38 (leva 5)
* feat(cursor): parse Composer DeepSeek-style inline tool calls (#4912)
Integrated into release/v3.8.38 (leva 5)
* feat(proxy): auth-less host:port batch import (#4938)
Integrated into release/v3.8.38 (leva 5)
* fix(oauth): support Kiro IDC (organization) token import (#4944)
Integrated into release/v3.8.38 (leva 5)
* fix(translator): preserve cache_control for DashScope OpenAI-compat providers (port from 9router#2069) (#5013)
Integrated into release/v3.8.38 (leva 5)
* fix(tts): resolve Gemini TTS models from catalog (#4934)
Integrated into release/v3.8.38 (leva 5)
* fix(sse): don't cool down the connection on a self-inflicted upstream timeout (504) (#5064)
Integrated into release/v3.8.38 (leva 5)
* fix(sse): robust Anthropic /v1/messages streaming — real ping keepalive + client-disconnect guard (#5063)
Integrated into release/v3.8.38 (leva 5)
* feat(video): add Alibaba DashScope (wan2.7-t2v) provider (#5051)
Integrated into release/v3.8.38 (leva 5)
* fix: preserve model hidden flags (isHidden) across model sync (#5086)
Integrated into release/v3.8.38 (leva 5)
* fix(models): derive model discovery config from registry modelsUrl (#5087)
Integrated into release/v3.8.38 (leva 5)
* fix(compression): replace fileURLToPath(import.meta.url) with runtime anchors for standalone bundle (#5089)
Integrated into release/v3.8.38 (leva 5)
* feat(cc): add summarized thinking display toggle (#5055)
Integrated into release/v3.8.38 (leva 5)
* Harden selected API error responses (#5032)
Integrated into release/v3.8.38 (leva 5)
* chore(quality): rebaseline file-size for leva 5 PR batch drift
6 frozen files grew from merged leva-5 PRs (cursor #4912, kiro #4911,
videoGeneration #5051, default #4727, base #4846, chat #5064); all covered
by per-PR tests. See _rebaseline_2026_06_26_leva5 in the baseline.
* feat(compression): compression playground (Play + Compare tabs) in the studio (#5080)
Integrated into release/v3.8.38
* fix(combo): fail over on empty-content 502 instead of exhausting the provider (#5085) (#5104)
* fix(dashboard): surface detailed credential-validation error in add-connection modal (#5088) (#5106)
* feat(providers): allow local/private provider URLs by default with scoped metadata-safe guard (#5066) (#5107)
* fix(diagnostics): treat non-streaming Claude messages shape as valid output (#5108) (#5116)
* fix(db): translate pt-BR SQLite driver-fallback log lines to English (#5103) (#5115)
* fix(sse): repair release base-reds — malformed-response false positives + header casing + stale tests (#5117)
Repairs the release/v3.8.38 base-reds; unblocks #5078.
* chore(quality): rebaseline file-size for responseSanitizer (#5117) + AddApiKeyModal drift
* fix(translator): forward image tool_result blocks as image_url (#5100)
Base-reds fixed (#5117); image tool_result→image_url. Integrated into release/v3.8.38.
* fix(responses): default text.format for openai-compatible responses providers (#5101)
Base-reds fixed (#5117); default text.format + file-size rebaseline. Integrated into release/v3.8.38.
* feat(dashboard): expose Fusion judgeModel + fusionTuning in the combo editor (#5074)
Base-reds fixed (#5117); Fusion editor + file-size rebaseline. Integrated into release/v3.8.38.
* feat(quota): add opt-in Codex/Claude auto-ping keepalive (#5102)
Base-reds fixed (#5117); auto-ping keepalive + file-size rebaseline. Integrated into release/v3.8.38.
* test(release): relocate 2 orphan test files into the collected flat tests/unit dir (#5120)
Unblocks Lint (test-discovery) on #5078. Integrated into release/v3.8.38.
* fix(translator): preserve reasoning-replay reasoning_content + repair 3 release-green test reds (#5122)
Repairs 3 release-green test reds + test-masking; unblocks #5078.
* test(golden): redact live Node version from provider translate-path snapshot (#5125)
Final golden unblock for #5078.
* test(golden): redact OmniRoute app version from translate-path snapshot (#5126)
Coverage shard golden unblock for #5078.
* Ignore disconnect races during in-band stream error handling (#5007)
Integrated into release/v3.8.38
* Track final connection IDs in failover logs (#5016)
Integrated into release/v3.8.38
* fix(sse): convert Gemini body to OpenAI format in antigravity MITM handler (#4845)
Integrated into release/v3.8.38 (rebased on tip, CHANGELOG re-injected)
* feat(providers): add ZenMux Free session-cookie provider (#5105)
Integrated into release/v3.8.38 (rebased on tip, CHANGELOG re-injected)
* feat(dashboard): click-to-edit model alias in provider page (#5119)
Integrated into release/v3.8.38 (rebased on tip, i18n scope verified, CHANGELOG re-injected)
* feat(mcp): web-session robustness — cookie dedup (PR6) + browser-pool observability (PR7) (#3368) (#5121)
Integrated into release/v3.8.38 (rebased on tip; cookie-dedup branch extracted to findExistingCookieConnection helper → complexity-neutral; CHANGELOG added)
* fix(usage): dedupe request-usage logging and debounce stats (#4940)
Integrated into release/v3.8.38 (rebased on tip; DB-handle hang was stale-base artifact — resetDbInstance already closes the handle, test green 5/5; file-size drift consolidated at release; CHANGELOG re-injected)
* fix(dashboard): key model visibility toggle on canonical providerId (#5091)
Integrated into release/v3.8.38 (retargeted main→release; .tsx visibility-key test green 2/2)
* chore(deps): bump actions/cache from 5.0.5 to 6.0.0 (#5112)
Integrated into release/v3.8.38 (retargeted main→release; workflow-only actions/cache bump — unit failures were stale main base-reds)
* fix(streaming): harden long OpenAI-compatible SSE streams (#5124)
Integrated into release/v3.8.38 (rebased on tip; streamHandler conflict with #5007 disconnect-guard resolved — both coexist, stream-handler 22/22 green)
* feat: Add Grok Build (xAI) provider with OAuth import-token flow (#5020)
Integrated into release/v3.8.38 (rebased on tip; Hard Rule #11 fix — Grok public client_id now via resolvePublicCred(grok_id), 3 literals removed; grok-oauth 7/7 + check:public-creds green)
* feat(providers): add Factory (factory.ai) as a subscription gateway provider (#5065)
Integrated into release/v3.8.38 (rebased on tip; added factory registry test for PR Test Policy + fixed check:env-doc-sync phantom FACTORY_API_KEY; factory loads in PROVIDERS, no Zod issue — that flag was a false positive)
* chore(test): reconcile golden snapshot + apikey count for new providers
#5020 (grok-cli), #5065 (factory), #5105 (zenmux-free) added providers but did
not regenerate tests/snapshots/provider/translate-path.json (now +3 entries) nor
bump the APIKEY_PROVIDERS count (159->160 for the factory gateway). Test-only
reconciliation; no production change.
* fix(resilience): harden quota and model lockout edge cases (#5093)
Integrated into release/v3.8.38 (rebased on tip). TRUST-BUT-VERIFY: dropped the PR's 0dd7df641 'fix unit gates' commit which reverted #5122 reasoning-replay (preserveReasoningContent) + re-introduced #4849 O(n^2) growth, and restored 5 tests it had realigned. Kept only the 3 declared resilience fixes (quota cutoff guard, gemini MIME, model-lockout maxCooldownMs); 23/23 green.
* Hydrate quota cache and scope auto combo candidates (#5015)
Integrated into release/v3.8.38 (rebased on tip). Kept core quota-cache hydration + auto-combo candidate scoping + combos UI; dropped out-of-scope toolCloaking refactor (conflicted with #4813 stripEnumDescriptions — took tip) and the unrelated sse-auth test split. Added quota-cache-hydrate-5015 regression test (Rule #18); combo-account-allowlist 8/8 + hydration 2/2 green.
* chore(quality): reconcile complexity + file-size baselines for v3.8.38 owner-PR batch
complexity 1972->1978 (+6) and file-size providers.ts 1093->1107 / usageHistory.ts
934->983 — drift from the /review-prs merge batch (#4845/#5105/#5020/#4940/#5093/
#5015 + #5121 cookie-dedup helper extraction). check:complexity/check:file-size do
not run on the PR->release fast-path, so the branch accrued unmeasured; all legit
feature/fix growth, not regression. See per-key justifications in each baseline.
* fix(security): exact-host Anthropic baseUrl check (CodeQL js/incomplete-url-substring-sanitization #674) (#5130)
The anthropic-compatible Bearer-fallback gate decided whether a configured baseUrl
targeted the official api.anthropic.com host via a substring `.includes("api.anthropic.com")`.
A look-alike upstream such as `https://api.anthropic.com.evil.test` or
`https://evil.test/?x=api.anthropic.com` matched the substring and was wrongly treated as
official, suppressing the Bearer fallback meant for third-party gateways
(CodeQL #674, js/incomplete-url-substring-sanitization, high).
Replace the substring test with an exported `isOfficialAnthropicBaseUrl()` helper that
parses the URL and compares the hostname for exact equality. Empty baseUrl stays official;
scheme-less hosts are parsed with an assumed https://; an unparseable baseUrl falls back to
third-party (Bearer emitted) as the safer default. Behavior for legitimate official/third-party
baseUrls is unchanged.
Adds tests/unit/anthropic-official-baseurl-host.test.ts covering official, look-alike,
scheme-less, and unparseable inputs plus a static guard that the substring pattern is gone.
* fix(proxy): repair one-click Deno & Cloudflare relay deployments (#5128) (#5132)
* fix(services): embed WS proxy honours LIVE_WS_HOST; reject empty messages early (#5110) (#5133)
* fix(api): resolve /v1/models/{id} case-insensitively (#5082) (#5135)
* fix(providers): add MiniMax M3 & Nemotron 3 Ultra to Cline catalog (#3321) (#5136)
* fix(proxy): make SOCKS5 handshake timeout tunable via SOCKS_HANDSHAKE_TIMEOUT_MS (#5109) (#5137)
* feat(sidebar): add support for colored menu icons (#3812)
Integrated into release/v3.8.38 (recreated on tip — fork had unrelated history; added getSidebarIconAccent regression test, Rule #18). Clean 2-file UI feature.
* fix(providers): complete grok-cli OAuth wiring + zenmux-free web-session metadata
Base-red repair for #5020 (grok-cli) and #5105 (zenmux-free), surfaced by the
full CI on the release PR (#5078) — the PR->release fast-path does not run the
oauth-providers-config / web-session-credentials / provider-consistency gates.
- grok-cli: register in OAUTH_PROVIDERS (providers.ts canonical list, fixes
check:provider-consistency), add OAUTH_PROVIDER_IDS.GROK_CLI + GROK_CLI_CONFIG
in oauth constants (provider config now sourced there, not a local literal),
align oauth-providers-config.test.ts (EXPECTED_PROVIDER_KEYS + config map).
- zenmux-free: declare its web-session credential requirement (full Cookie header)
in WEB_SESSION_CREDENTIAL_REQUIREMENTS.
Local: oauth-providers-config 27/27, web-session-credentials 4/4, grok-cli-oauth
7/7, check:provider-consistency OK, +115 OAUTH_PROVIDERS tests green.
* Fix resilience settings page response mapping (#5139)
Integrated into release/v3.8.38. Thanks @rdself for the fix and the regression test.
* fix(kiro): retire claude-sonnet-4.5 from catalog + pin 400 model-unavailable test (#5140)
Extracted the real change from #5140 (the bot PR regenerated the entire
freeModelCatalog.data.ts + touched package-lock.json; only the targeted
edits are kept here):
- remove claude-sonnet-4.5 from the Kiro registry entry
- remove the matching kiro free-model catalog row
- pin Kiro's verbatim 400 "Invalid model..." to isModelUnavailableError
Closes #4484
* fix(sidebar): drop orphan `settings` accent color (typecheck:core red) (#5142)
SIDEBAR_ICON_ACCENTS is typed Partial<Record<HideableSidebarItemId, string>>,
but `settings` is not a hideable item id (only `settings-general`,
`settings-appearance`, … and `context-settings` exist; there is no item with
`id: "settings"`), so the accent was unreachable. It broke `typecheck:core`
on the release tip ("'settings' does not exist in type …", introduced by
#3812 colored menu icons). Removing the orphan key restores a clean
typecheck:core (rc=0).
* feat: salvage batch 2 — diagnostics null-guard (#5096) + observed quota reset windows (#5025) (#5141)
* fix(diagnostics): null-guard content blocks in detectMalformedNonStream
A null (or non-object) entry in a Claude-native `content` array made the
non-stream classifier throw `TypeError: Cannot read properties of null
(reading 'type')`, crashing the malformed-response detection path. Guard
before type-asserting each block: a null/non-object block is simply skipped.
Two regression tests added (null block among valid blocks → null; only-null
blocks → empty_choices).
Salvaged from closed PR #5096 (base-stale; only the defensive guard — the
Claude-shape recognition it also carried already landed via #5108).
Co-authored-by: herjarsa <herjarsa@users.noreply.github.com>
* feat(quota): persist observed provider quota reset windows
Adds `provider_quota_reset_events` (migration 108) + `db/quotaResetEvents.ts`
to record real upstream weekly-quota window transitions whenever a quota
refresh shows the reset rolling to a new cycle (different day, later resetAt).
`apiKeyUsageLimits` now prefers the observed window start over the inferred
`resetAt − 7d`, falling back to snapshot inference when no event is recorded
yet. `quotaCache.setQuotaCache` records the transition opportunistically.
`recordProviderQuotaResetEventIfChanged` only fires for the primary weekly
window (not daily/sonnet), is idempotent (INSERT OR IGNORE on the unique
window key), and no-ops when the reset didn't actually roll. 4 unit tests
(tests/unit/lib/quota-reset-events.test.ts).
Salvaged from closed PR #5025 (which bundled this with two unrelated
features + a colliding migration 104). Renumbered to 108; module re-exported
from localDb (Rule #2).
Co-authored-by: Witroch4 <175152067+Witroch4@users.noreply.github.com>
---------
Co-authored-by: herjarsa <herjarsa@users.noreply.github.com>
Co-authored-by: Witroch4 <175152067+Witroch4@users.noreply.github.com>
* docs(i18n): sync 3.8.38 CHANGELOG section to 41 mirrors (unblock docs-accuracy) (#5144)
The root CHANGELOG [3.8.38] section grew with this cycle's merged PRs, but the
docs/i18n/<lang>/CHANGELOG.md mirrors were not re-synced — drifting >25% in body
size and failing check:docs-sync (the "Docs accuracy" fast-gate step) for every
open PR against the release.
Ran scripts/release/sync-changelog-i18n.mjs 3.8.38 3.8.37 to copy the root
[3.8.38] section into all 41 mirrors. check:docs-all now passes (exit 0).
Sections are copied verbatim; the per-language translation pass runs at release
time via i18n:run — this only restores the size-sync the gate enforces.
* feat(compression): pure per-step fidelity checker (4 invariants, fail-open)
* feat(compression): fidelityGate config + rejected breakdown fields
* feat(compression): wire per-step fidelity gate into stacked pipeline (opt-in)
* feat(compression): preview route accepts fidelityGate flag (playground)
* feat(compression): playground fidelity-gate toggle + lane rejection display
* docs(compression): note fidelityGate advanced thresholds are intentionally API-omitted
* refactor(compression): extract fidelity-gate step helpers to shrink strategySelector (file-size gate)
bodyToText and gateAdvance moved to fidelityGateStep.ts; StackAccumulator exported.
strategySelector: 889->854 (-35). Residual +6 vs pre-Milestone-B frozen 848 is the
irreducible StackOptions.fidelityGate field + two stacked-loop dispatch reads + import.
Baseline updated to 854 with justification. No cycle introduced (import type only).
940 compression tests pass; typecheck clean.
* test(usage): wire usageHistoryDedup under unit runner brace-list (#5145)
Integrated into release/v3.8.38.
* feat: salvage batch from closed stale PRs (#5038, #5057, #5076) (#5138)
Integrated into release/v3.8.38.
* test(combo): deterministic routing-decision matrix for all 17 strategies (#5146)
Integrated into release/v3.8.38.
* feat(compression): fuzzy near-duplicate dedup (session-dedup 2nd pass + playground toggle) (#5143)
Integrated into release/v3.8.38.
* chore(quality): rebaseline file-size for sidebarVisibility.ts + chat.ts drift (#5147)
Mid-cycle drift on release/v3.8.38 from already-merged PRs that the fast-path
(PR->release skips check:file-size) let accumulate without a bump:
- src/shared/constants/sidebarVisibility.ts 1100->1198 (#3812 colored menu
icons, per-item accent map; #5142 dropped one orphan, net still above frozen)
- src/sse/handlers/chat.ts 1560->1575 (#5064 self-inflicted-timeout cooldown
skip + #5124 long OpenAI-compatible SSE hardening + #5110 embed-WS
LIVE_WS_HOST honour / early empty-message reject)
Each covered by its own PR tests; structural shrink of chat.ts tracked in #3501.
Unblocks the Fast Quality Gates for PRs targeting release/v3.8.38.
* chore(release): finalize v3.8.38 CHANGELOG + cycle reconciliation
- Reconcile [3.8.38]: +18 bullets (compression fidelity-gate/fuzzy-dedup #5143,
quota keepalive #5102, web-session robustness #5121, MiniMax/Nemotron #5136,
model-visibility #5091, failover logs #5016, disconnect races #5007, sidebar
orphan #5142, SRE playbooks salvage #5138, new Security #5130 + Maintenance roll-up)
- Credit salvaged-PR authors (@JxnLexn / @KooshaPari / @herjarsa / @Witroch4)
- Remove phantom bullet for CLOSED-not-merged #5092 (setup aggregator never landed)
- Fix isHidden bullet PR citation #4389 -> #5086 (@herjarsa)
- Back-fill forgotten v3.8.36 bullet: #5026 crypto.randomUUID ID-gen (@hamsa0x7)
- Sync 41 i18n CHANGELOG mirrors; README What's New -> v3.8.38
- Rebaseline cycle drift: eslint 3987->4002, cognitive 833->841, dead-exports
345->346, cyclomatic 1978->1980 (file-size handled by #5147)
* fix(i18n): add missing English UI labels (#5153)
Integrated into release/v3.8.38
* Preserve non-stream reasoning fields for compatible clients (#5155)
Integrated into release/v3.8.38
* feat(compression): ionizer engine — lossy JSON-array sampling reversible via CCR (#5148)
Integrated into release/v3.8.38
* test(combo): gated live smoke for combo strategies (in-process + VPS HTTP) (#5151)
Integrated into release/v3.8.38
* test: refresh release expectations to match current code (#5150)
Integrated into release/v3.8.38 (test-only base-red alignment extracted from #5150)
---------
Co-authored-by: Éder Costa <eder.almeida.costa@gmail.com>
Co-authored-by: José Victor Ferreira <root@josevictor.me>
Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com>
Co-authored-by: fulorgnas <46461624+fulorgnas@users.noreply.github.com>
Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
Co-authored-by: Jan Leon <Jan.gaschler@gmail.com>
Co-authored-by: R. Beltran <rbeltran8000@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: KooshaPari <42529354+KooshaPari@users.noreply.github.com>
Co-authored-by: Ramel Tecnologia - Rafa Martins <146174365+rafacpti23@users.noreply.github.com>
Co-authored-by: herjarsa <herjarsa@users.noreply.github.com>
Co-authored-by: Witroch4 <175152067+Witroch4@users.noreply.github.com>
* Release v3.8.39 (#5164)
* chore(release): open v3.8.39 development cycle
* docs(changelog): backfill 5 v3.8.38 bullets merged after release finalize
These PRs squash-merged into release/v3.8.38 between the CHANGELOG finalize
(ff57be32f) and the merge-to-main (ae6e2342d), so they shipped in the v3.8.38
tag but had no bullet:
- feat(compression): Ionizer engine (lossy JSON-array sampling + CCR) (#5148)
- fix(sse): preserve non-stream reasoning fields (#5155, @rdself)
- fix(i18n): add missing English UI labels (#5153, @rdself)
- test(combo): gated live smoke (#5151) + release-expectations refresh (#5150, @KooshaPari)
(#5129 exact-host Anthropic baseUrl is already covered by the #5130 bullet — same CodeQL #674.)
Synced 41 i18n CHANGELOG mirrors.
* feat(compression): TOON best-of-N candidate encoder + encoder A/B table (#5163)
Integrated into release/v3.8.39. TOON best-of-N candidate encoder (GCF default, fail-open). 17/17 unit tests pass on merge result; CI reds were base-stale + Quality Ratchet DRIFT.
* fix(zenmux): normalize vendor-prefixed GLM system roles (#5158)
Integrated into release/v3.8.39. ZenMux vendor-prefixed GLM system-role normalization; 12/12 role-normalizer tests pass on merge result. CI reds base-stale.
* [codex] fix xAI OAuth test and reasoning effort (#5157)
Integrated into release/v3.8.39. xAI reasoning-effort normalization (max/xhigh→high) + OAuth test config; 46/46 xai-translator tests pass on merge result. CI reds base-stale.
* docs(i18n): add Traditional Chinese (zh-TW) README and update zh-CN to latest (#5162)
Integrated into release/v3.8.39. Traditional Chinese (zh-TW) README + zh-CN refresh; docs-only.
* test(security): guard PII redaction stays opt-in (default off) + Hard Rule #20 (#5159)
Integrated into release/v3.8.39. PII opt-in regression guard + Hard Rule #20; rebased to strip base-drift (+81/-1). 5/5 guard tests pass; flip-proof verified.
* test(combo): deterministic context-relay universal-handoff coverage (closes phase-2 TODO) (#5168)
Integrated into release/v3.8.39. Deterministic context-relay universal-handoff coverage (3 tests); 3/3 pass on merge result.
* docs(i18n): full sync zh-TW and zh-CN README with canonical English v3.8.39 (#5171)
Integrated into release/v3.8.39. Full zh-TW docs tree + zh-CN sync with canonical English v3.8.39; docs-only.
* fix(serve): honour HOSTNAME from .env instead of hardcoding 0.0.0.0 (#5134) (#5170)
Integrated into release/v3.8.39. HOSTNAME env override in serve (#5134) + regression test (4/4, TDD flip-proof verified).
* fix(sse): resolve nameless deepseek-web tool blocks via parameter-schema match (#5154) (#5173)
Integrated into release/v3.8.39. Schema-based nameless deepseek-web tool-block resolution (#5154); 6/6 tests pass on merge result (incl. ambiguous/no-match negatives + named-tag no-regression).
* fix(sse): normalize array user content for Command Code to avoid upstream 400 (#5166) (#5174)
Integrated into release/v3.8.39. Normalize array user content for Command Code (#5166, user-array/400 symptom); 4/4 tests pass on merge result.
* fix(sse): defer </think> close so it never leaks before tool_calls (#5123) (#5175)
Integrated into release/v3.8.39. Defer </think> close so it never leaks before tool_calls (#5123); 4/4 tests pass (incl. #4633 no-regression). CHANGELOG synced to keep all 3 v3.8.39 fixes.
* fix(dashboard): use amber for home update-step warning icon (#5176)
Integrated into release/v3.8.39. Amber for home update-step warning icon; 1/1 UI test.
* fix(api): LAN/Tailscale dashboard — host-aware CSP + GET-exempt version route + combo field errors (#5083) (#5177)
Integrated into release/v3.8.39. Host-aware CSP (ReDoS/injection-safe host validation) + GET-exempt /api/system/version (POST/spawn stays LOCAL_ONLY, exact-match safe-methods-only) + COMBO_002 firstField. 44/44 tests + route-guard membership gate green. CHANGELOG synced to keep all 4 v3.8.39 fixes.
* fix(api): replace #5083 global middleware CSP with declarative ws: scheme (#5083)
Follow-up to PR #5177 (merged): that version implemented the LAN-CSP fix (Bug 1)
with a new global `src/middleware.ts` + `src/server/csp.ts`, which contradicts the
project's documented architecture — 'No global Next.js middleware — interception is
route-specific' (CLAUDE.md / AGENTS.md) — and was merged unverified (middleware vs
next.config header precedence was never confirmed in a real build).
This replaces that approach with the minimal, declarative equivalent:
• next.config.mjs: connect-src now permits the bare `ws:` scheme (symmetric with the
bare `wss:` already allowed) so the dashboard can reach its own Live WS server from
a LAN/Tailscale host. No middleware.
• Removes src/middleware.ts, src/server/csp.ts, and tests/unit/csp-host-aware.test.ts.
• Adds tests/unit/csp-lan-ws-5083.test.ts (incl. a guard asserting src/middleware.ts
does NOT exist, so the global-middleware approach cannot silently return).
Bugs 2 (GET-exempt /api/system/version) and 3 (COMBO_002 field surfacing) from #5177
are unaffected and remain in place.
Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com>
* test(combo): end-to-end quota-share DRR routing-decision coverage (matrix parity) (#5179)
Integrated into release/v3.8.39. Quota-share DRR routing-decision coverage (matrix parity); 2/2 pass on merge result.
* feat(agent-bridge): graceful cert-install fallback with manual guide for containers (#4546) (#5178)
Integrated into release/v3.8.39. Agent-bridge graceful cert-install fallback + manual guide (#4546); 6/6 tests pass on merge result.
* fix(antigravity): family-scoped quota lockout (gemini/claude buckets) (#5180)
Integrated into release/v3.8.39 — family-scoped antigravity quota lockout. Rebased from v3.8.37 + validated (vitest 5/5, typecheck clean, full combo-matrix green, model-lockout 99/0). Same-model cross-account retry (chat.ts) deferred pending live antigravity VPS validation.
* fix(cli): force NODE_ENV to match dev/start run mode in custom Next server (#5189)
Integrated into release/v3.8.39. Force NODE_ENV to match dev/start run mode in custom Next server; 2/2 source-scan+ordering tests pass on merge result.
* feat(compression): CCR ranged/grep/stats retrieval (ReDoS-safe, backward-compat) (#5187)
Integrated into release/v3.8.39. CCR ranged/grep/stats retrieval (safe-regex ReDoS guard + length/match caps); 17/17 tests pass on merge result.
* docs(combo): sync all combo/routing-strategy docs to current state + document test coverage (#5185)
Integrated into release/v3.8.39. Combo/routing-strategy docs sync; docs-only.
* fix(mcp): return 404 (not 400) for unknown Streamable HTTP session id (#5169) (#5191)
* fix(api): respect blocked Auto (Zero-Config) provider in /v1/models catalog (#5192) (#5194)
* test(combo): deterministic context-relay codex quota-handoff coverage (closes last gap) (#5195)
* test(ci): wire antigravity-quota-family under test:vitest (fix test-discovery orphan) (#5196)
* fix(oauth): antigravity login no longer hangs — fire-and-forget onboarding + bounded post-exchange (#5193)
Antigravity OAuth hang fix (no-PKCE/no-openid + bounded post-exchange + exchange-500 fix). Includes #5200 (Koosha) revert + owner rebaseline to keep documented comments. Integrated into release/v3.8.39.
* feat(oauth): remote Antigravity login via local helper + paste-credentials (#5203)
Remote Antigravity login: local helper (omniroute login antigravity) + paste-credentials. Integrated into release/v3.8.39.
* fix(translator): accept Claude Messages shape in non-stream malformed-200 guard (#5156)
Integrated into release/v3.8.39
* fix(cli): default dev bundler to Turbopack (16.2.x panic no longer reproduces) (#5206)
Integrated into release/v3.8.39
* fix(cli): auto-calibrate server V8 heap from physical RAM (#5172) (#5213)
The server was spawned with a fixed --max-old-space-size=512 (omniroute serve)
or no heap flag at all (Electron), so RAM-rich boxes still OOM-crashed under
load (Ineffective mark-compacts near heap limit ~500MB) with many providers/
accounts and large model catalogs. New calibrateHeapFallbackMb(os.totalmem())
defaults the heap to ~35% of RAM clamped [512,4096], wired into serve.mjs and
electron/main.js. Explicit OMNIROUTE_MEMORY_MB still wins (#2939 unchanged).
Also addresses #5160 (same OOM root); #5152 (docker) benefits via the same knob.
Closes #5172
* fix(proxy): coalesce fast-fail health probes (#5208)
Integrated into release/v3.8.39
* fix(proxy): close dispatchers when clearing cache (#5202)
Integrated into release/v3.8.39
* fix(cli): raise dev server Node heap limit to 8GB to prevent OOM (#5198)
Integrated into release/v3.8.39
* fix(auth): allow synthetic no-auth fallback for mimocode (#5205)
Integrated into release/v3.8.39
* fix(oauth): preserve Antigravity refresh_token on empty/omitted upstream response (#3850) (#5214)
Google's OAuth refresh tokens are non-rotating: the refresh response usually
omits refresh_token and occasionally returns it as an empty string. The
Antigravity executor used `typeof tokens.refresh_token === "string" ? ... `
which accepts "" (typeof "" === "string") and overwrote the stored token with
empty, nulling it on first refresh. Now treats non-string OR empty as absent and
preserves credentials.refreshToken, matching refreshGoogleToken semantics.
Closes #3850
* fix(responses): normalize non-array input (#5204)
Integrated into release/v3.8.39
* fix(stream): normalize safety finish reasons via shared helper (#5197)
Integrated into release/v3.8.39
* fix(request-logger): never render negative '(-100%)' compression badge (#5201)
Integrated into release/v3.8.39
* fix(combo): reject empty responses api output (#5207)
Integrated into release/v3.8.39 — combo failover now rejects empty Responses API output (validateQuality). Baseline rebaseline dropped (main-measured drift; maintainer rebaselines at release).
* fix(pwa): prefer cached navigation before offline page (#5209)
Integrated into release/v3.8.39 — PWA service worker prefers cached navigation before offline page (#5165).
* chore(release): v3.8.39 — 2026-06-28
* chore(release): rebaseline openapi+i18n coverage ratchet drift for v3.8.39
---------
Co-authored-by: Arthur Bodera <abodera@gmail.com>
Co-authored-by: Nguyen Minh <lop123thcs@gmail.com>
Co-authored-by: lunkerchen <labanchen@gmail.com>
Co-authored-by: Ankit <177378174+anki1kr@users.noreply.github.com>
Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com>
Co-authored-by: Ardem2025 <ardemb22@gmail.com>
Co-authored-by: backryun <bakryun0718@proton.me>
Co-authored-by: Anton <39598727+NomenAK@users.noreply.github.com>
Co-authored-by: KooshaPari <42529354+KooshaPari@users.noreply.github.com>
Co-authored-by: Wilson <pedbookmed@gmail.com>
Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
* fix(docker): copy open-sse workspace manifest before npm ci (v3.8.39 image build) (#5223)
* chore(docker): harden base image against container-scan CVEs (#5228)
apt-get upgrade -y in the base stage pulls security-patched trixie
packages at build time, and npm install -g npm@latest refreshes the
globally-bundled undici/tar inside the npm CLI. Together these clear the
subset of GitHub container-scan CVE alerts that have an upstream fix
available.
None of the flagged CVEs are in the application dependency tree (app
already resolves undici@8.5.0 / tar@7.5.16, both fixed); they live in
the node:24-trixie-slim base layer and npm's own internals, and none are
reachable from the proxy request surface at runtime. CVEs without a
published fix (local-only TOCTOU, etc.) remain until the distro patches
them and the image is rebuilt.
* chore(ci): Trivy advisory scan ignores unfixed CVEs (Security-tab noise) (#5234)
The advisory Trivy image scan uploaded every HIGH/CRITICAL into the
Security tab without ignore-unfixed, flooding it with ~150 unfixable
base-image OS CVEs (Debian trixie packages with no upstream patch,
overwhelmingly local-only and not reachable from the proxy request
surface). Operators cannot act on those, so they are pure noise.
Add ignore-unfixed:true to the advisory step so it mirrors the existing
CRITICAL blocking gate and surfaces only actionable, fixable
vulnerabilities. Wire trivyignores to a new repo-root .trivyignore that
documents the accepted-risk policy and is the single auditable home for
the rare fixable CVE we must temporarily accept (none at present).
Takes effect on the next release image build (Trivy only runs on tag
builds, not main pushes); fixed CVEs drop out of the SARIF and GitHub
auto-resolves the corresponding alerts.
* fix: centralize public origin checks for proxied dashboards (#5278)
Centralizes browser-mutation origin validation into `src/server/origin/publicOrigin.ts` and wires it through the authz pipeline, replacing the per-route same-origin-only check that 403'd dashboard mutations when served behind a reverse proxy on a different public origin. The new module resolves the allowed public origin from configured base-URL env vars or trusted forwarded headers (only when OMNIROUTE_TRUST_PROXY is set AND the peer is loopback/LAN via peer-stamp), validates Sec-Fetch-Site metadata, and sanitizes Host/Forwarded inputs (rejects control chars, userinfo, path/query in Host).
Reviewed sound; validated locally: authz/public-origin + pipeline suites 27/27 green (incl. invalid-origin reject + configured-origin accept), typecheck clean. Maintainer fix-up: moved the new test from tests/unit/server/ (not collected by any runner — orphan-test gate fail) into tests/unit/authz/. Remaining red CI shards are the pre-existing #4076 Dockerfile heap base-red on `main` (unrelated; de-brittled in the v3.8.40 release line).
Co-authored-by: Thinkscape <Thinkscape@users.noreply.github.com>
* Release v3.8.40
v3.8.40 cycle integration → main. All test gates green (Unit/Integration/Coverage/Node-compat/Quality-Ratchet). The only red check, 'PR Test Policy', is the test-masking heuristic firing on the cumulative ~57-commit release diff (legitimate assert consolidations already reviewed per-PR — Gemini CLI removal #5246, retired GPT models #5280, provider catalog refreshes); overridden with --admin per the documented release-PR convention. CodeQL/SonarQube advisory scans non-blocking; #5278's code already passed CodeQL on main. Homologated on VPS 192.168.0.15 (v3.8.40 healthy).
* Release v3.8.41 (#5327)
Release v3.8.41 — 52 commits since v3.8.40 (19 CHANGELOG bullets, 11 contributors).
All gating CI green: Unit×8, Coverage×8, Vitest, Package Artifact, Quality Ratchet, CodeQL, Lint, Docs Sync (Strict), Node 24/26 compat, E2E×9, Integration, Electron smoke.
Advisory checks overridden (main unprotected): PR Test Policy = test-masking heuristic on the cumulative 52-commit assert delta (legitimate dead-code-sweep removals + consolidations, reviewed per-PR); SonarCloud/SonarQube = new-code maintainability/coverage quality gate (CodeQL/Semgrep/Security/npm-audit/Dependabot all clean — not a security finding).
* deps: bump the development group across 1 directory with 9 updates (#5415)
Bumps the development group with 8 updates in the / directory:
| Package | From | To |
| --- | --- | --- |
| [@axe-core/playwright](https://github.com/dequelabs/axe-core-npm) | `4.11.3` | `4.12.1` |
| [@playwright/test](https://github.com/microsoft/playwright) | `1.61.0` | `1.61.1` |
| [@tailwindcss/postcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-postcss) | `4.3.1` | `4.3.2` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `26.0.0` | `26.0.1` |
| [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) | `6.0.2` | `6.0.3` |
| [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) | `6.18.0` | `6.23.0` |
| [prettier](https://github.com/prettier/prettier) | `3.8.4` | `3.9.4` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.61.1` | `8.62.1` |
Updates `@axe-core/playwright` from 4.11.3 to 4.12.1
- [Release notes](https://github.com/dequelabs/axe-core-npm/releases)
- [Changelog](https://github.com/dequelabs/axe-core-npm/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/dequelabs/axe-core-npm/commits)
Updates `@playwright/test` from 1.61.0 to 1.61.1
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.61.0...v1.61.1)
Updates `@tailwindcss/postcss` from 4.3.1 to 4.3.2
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.2/packages/@tailwindcss-postcss)
Updates `@types/node` from 26.0.0 to 26.0.1
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)
Updates `@vitejs/plugin-react` from 6.0.2 to 6.0.3
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.0.3/packages/plugin-react)
Updates `knip` from 6.18.0 to 6.23.0
- [Release notes](https://github.com/webpro-nl/knip/releases)
- [Commits](https://github.com/webpro-nl/knip/commits/knip@6.23.0/packages/knip)
Updates `prettier` from 3.8.4 to 3.9.4
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/3.9.4/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.8.4...3.9.4)
Updates `tailwindcss` from 4.3.1 to 4.3.2
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.2/packages/tailwindcss)
Updates `typescript-eslint` from 8.61.1 to 8.62.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.62.1/packages/typescript-eslint)
---
updated-dependencies:
- dependency-name: "@axe-core/playwright"
dependency-version: 4.12.1
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: development
- dependency-name: "@playwright/test"
dependency-version: 1.61.1
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: development
- dependency-name: "@tailwindcss/postcss"
dependency-version: 4.3.2
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: development
- dependency-name: "@types/node"
dependency-version: 26.0.1
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: development
- dependency-name: "@vitejs/plugin-react"
dependency-version: 6.0.3
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: development
- dependency-name: knip
dependency-version: 6.23.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: development
- dependency-name: prettier
dependency-version: 3.9.3
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: development
- dependency-name: tailwindcss
dependency-version: 4.3.2
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: development
- dependency-name: typescript-eslint
dependency-version: 8.62.1
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: development
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* docs: add relay backend strategy guide (#5533)
* Release v3.8.42 (#5459)
Release v3.8.42 — full CHANGELOG in CHANGELOG.md.
CI: 103 checks green incl. CodeQL (all languages), Semgrep, all 8 unit shards,
coverage, Node 24 compat, and integration tests. Full unit suite validated
locally: 19437 pass / 0 fail. The 3 red checks are advisory and do not gate
main (no required status checks): SonarCloud/SonarQube new-code coverage gate,
and PR Test Policy (test-masking detector flagging the legitimate dead-Phind
provider removal in #5530 — reviewed, correct).
Includes cycle-close reconciliation + repair of inherited base-red tests from
#5480/#5527/#5427/#5521 that the PR->release fast-path did not exercise.
* Fix grammatical errors in readme (#5738)
* Release v3.8.43 (#5609)
* chore(release): open v3.8.43 development cycle
* docs(relay): clarify backend routing contract (#5621)
Integrated into release/v3.8.43 (drift-shed: cherry-picked the real change onto the release tip; stale-base drift dropped).
* fix(security): avoid rendering error stacks (#5624)
Integrated into release/v3.8.43 (drift-shed: cherry-picked the real change onto the release tip; stale-base drift dropped).
* fix(chatgpt-web): restore dot-form Pro model ids (#5549)
Integrated into release/v3.8.43 (drift-shed: cherry-picked the real change onto the release tip; stale-base drift dropped).
* feat(commandCode): add multimodal image support for CC vision models (#5557)
Integrated into release/v3.8.43 (drift-shed: cherry-picked the real change onto the release tip; stale-base drift dropped).
* fix(providers): validate M365 Copilot web credentials (#5432)
Integrated into release/v3.8.43 (drift-shed: cherry-picked the real change onto the release tip; stale-base drift dropped).
* fix(sse): bound chat hot-path heap — pressure-aware admission + response cap + clone reductions (#5152) (#5425)
Integrated into release/v3.8.43 (drift-shed: cherry-picked the real change onto the release tip; stale-base drift dropped).
* fix: model lockout not recording for 429 rate_limit_exceeded from Antigravity
## Problem
When Antigravity returns HTTP 429 with `rate_limit_exceeded` error code,
the model lockout system never records the failure, so the model is not
cooled down despite being rate-limited.
### Root Cause
Antigravity's 429 error text is: `"Resource has been exhausted (e.g. check
quota)."`
The QUOTA_PATTERNS in `classify429.ts` contained overly broad regexes:
- `/resource.*exhaust/i` — matches "Resource has been exhausted"
- `/check.*quota/i` — matches "check quota"
This caused `classifyErrorText()` to return `QUOTA_EXHAUSTED` (wrong),
which set `providerExhausted = true` in the combo target exhaustion logic.
With `providerExhausted`, the retry path was skipped entirely, and while
the "done retrying" path should still record lockout, the misclassification
cascaded into incorrect provider-level exhaustion state.
Additionally, `targetExhaustion.ts` used the raw error text string instead
of the structured error code (`rate_limit_exceeded`) that was already
parsed from the response body.
## Fix
1. **classify429.ts** — Removed overly broad `/resource.*exhaust/i` and
`/check.*quota/i` from QUOTA_PATTERNS. Antigravity's rate-limit wording
is not a true quota exhaustion signal.
2. **targetExhaustion.ts** — Added optional `structuredError` to
`ApplyComboTargetExhaustionOptions`. When available, the structured
error code (e.g. `rate_limit_exceeded`) takes precedence over raw error
text for exhaustion classification.
3. **combo.ts** — Passes `structuredError` to both `applyComboTargetExhaustion`
call sites (dispatch path + retry-or-rotate path).
## Effect
`structuredError.code = "rate_limit_exceeded"` → classified as rate-limit
(not quota) → `providerExhausted = false` → retry proceeds →
`recordModelLockoutFailure` called → model enters lockout with proper
cooldown (120s base, exponential backoff).
## Tests
Added 2 new tests for `structuredError.code` precedence in exhaustion
classification. All 28 related tests pass.
* fix(checks): normalize route paths on windows (#5613)
Integrated into release/v3.8.43. Windows path-normalization fix for the route-guard membership gate + regression test (Rule #18). Co-authored test added by maintainer.
* fix: truncate tool list when provider limit exceeds MAX_TOOLS_LIMIT (grok-cli 200)
- Add proactive PROVIDER_TOOL_LIMITS map with grok-cli: 200
- Fix regex to capture 'maximum is 200' (not '427 tools provided')
- Remove broken truncation gate that skipped limits >= MAX_TOOLS_LIMIT (128)
- Add tests for Grok regex, proactive limits, and limits above threshold
Refs #5563
* test(chatcore): cover grok-cli tool-list truncation via prepareUpstreamBody (#5563)
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
* fix(security): v3.8.15 hardening follow-ups (Seg2/Seg3/Seg4/Bug3) (#5512)
Security v3.8.15 hardening follow-ups: Seg2 (CHANGEME boot warn), Seg3 (auth_token cookie maxAge 30d), Seg4 (VS Code path-token once-per-process warning), Bug3 (real global install path resolution), Bug1 (segment-match node_modules in auto-update detection). All 5 carry TDD regression guards.
* Fix HuggingChat web session routing (#5592) (#5592)
Integrated into release/v3.8.43. HuggingChat web session-routing fix (root parent-message fetch + cookie propagation + encrypted-credential guard) + 24-model catalog refresh. Maintainer adjustments (co-authored): reverted the freeModelCatalog.data.ts whole-file reformat down to the surgical 24-record huggingchat change (preserving the auto-generated compact format), and added a 502 regression test for the null parent-message-id path (Rule #18).
* fix: preserve system role for GLM 5.1/5.2 (#5610) (#5663)
* fix: restore Codex Responses WS TLS profile + apply proxy (#5591, #5611) (#5668)
* fix: allow saving providers without a live validator (#5565, #5567) (#5669)
* fix: static model catalog for jules/linkup/ollama/searchapi search providers (#5569, #5571, #5573, #5575) (#5672)
* fix: live AI/ML API catalog + deprecate dead CablyAI (#5570, #5568) (#5673)
* fix: correct 404 provider setup links for ollama/searchapi/you.com (#5572, #5574, #5576) (#5674)
* fix: page call_logs cleanup queries to avoid startup OOM on large DBs (#5618) (#5675)
* fix: use PowerShell Expand-Archive on Windows for embedded-service install (#5590) (#5678)
* fix: treat array content blocks as valid output in detectMalformedNonStream (#5559) (#5680)
* fix: render memory engine status detail strings in English (#5596) (#5685)
* fix: free proxy pool silent sync failure — iplocate txt + per-source isolation + surface errors (#5595) (#5686)
* chore(quality): close QG v2 tail — drop orphan semcheck.yaml + Fase 9 maturity re-eval (#5681)
- Remove semcheck.yaml: orphan config (zero workflow/script wiring) with stale
rule counts; deterministic doc-accuracy coverage already exists
(check:fabricated-docs --strict + docs-counts-sync + docs-symbols). Drop the
REPOSITORY_MAP row referencing it.
- Add docs/ops/MATURITY_REEVAL.md (Fase 9): re-measures maturity post-Ondas 0-3.
The two biggest structural weaknesses from QUALITY_GATE_PLAYBOOK (2026-06-16) are
now closed: fast-gates hole (quality.yml runs typecheck:core + impacted TIA unit
tests + vitest + shards) and mutation-score-as-ratchet (check-mutation-ratchet.mjs
+ seeded baseline + nightly blocking job). Residual gap is owner/infra-gated
(branch-protection main, SLSA L3, CodeQL advanced).
- Record agent-lsp as deferred/opt-in (doc-only scaffold, no wiring).
* fix(ci): stabilize nightly-mutation — guard tap.testFiles drift + anti-flake eps (#5682)
Root cause (NOT a timeout): the nightly-mutation run fails on cold-cache nights
because the blocking mutation-ratchet job measures modules below baseline, while
warm-cache nights pass — the verdict tracked GitHub Actions cache state, not code
quality. Proven via a local Stryker probe on headers.ts: covering unit tests
(no-memory-header, strip-reasoning) had drifted OUT of stryker.conf.json
tap.testFiles, so their mutants went covered-but-unkilled = Survived on a cold
full run (COVERED score 61.73 vs 94.29 baseline); adding them restores the kills.
- Add scripts/check/check-mutation-test-coverage.mjs: guards that every UNIT test
importing a Stryker-mutated module is listed in tap.testFiles. Advisory by
default, --strict in CI (wired in quality.yml fast-gates). Prevents recurrence.
- Add the 38 drifted covering unit tests to stryker.conf.json tap.testFiles
(138 -> 176). Monotonically safe: more covering tests only raise/hold the score.
- Add MUTATION_RATCHET_EPS (1.0pt) anti-flake tolerance to check-mutation-ratchet
so sub-point tap-runner jitter no longer false-fails the gate. Lowers no baseline.
- Tests: check-mutation-test-coverage (3) + eps cases in check-mutation-ratchet.
Residual: a clean post-merge nightly confirms scores return to/above baseline;
any marginal residual gets a baseline re-seed (operator).
* refactor(dashboard): split sidebarVisibility god-file into types + sections leaves (#5683)
Behavior-preserving decomposition: src/shared/constants/sidebarVisibility.ts
1197 -> 291 LOC by extracting two leaves under sidebarVisibility/:
- types.ts (160): HIDEABLE_SIDEBAR_ITEM_IDS + all sidebar types (self-contained).
- sections.ts (762): section building-block consts + SIDEBAR_SECTIONS (imports
types only — cycle-safe). COMPRESSION_CONTEXT_GROUP + SIDEBAR_SECTIONS stay
exported; host re-exports both + 'export *' of types, so every consumer import
path is unchanged.
Byte-identical data verified via JSON.stringify of HIDEABLE_SIDEBAR_ITEM_IDS /
SIDEBAR_ICON_ACCENTS / COMPRESSION_CONTEXT_GROUP / SIDEBAR_SECTIONS / SIDEBAR_PRESETS
+ getSectionItems output (identical before/after). typecheck:core, check:cycles
(no cycles), check:file-size (3 files <800), and the 3 sidebar suites (20/20) pass.
No logic changed.
Note: file-size frozen baseline for sidebarVisibility.ts (1198) can ratchet to 291
to lock the shrink (left for the release ratchet / operator).
* fix: surface fusion-specific config on the Global Routing tab (#5598) (#5688)
* fix(executor): route OpenAI-compatible MCP Responses requests to /responses (#5483)
Closes #5483. OpenAI-compatible providers receiving a Responses-shaped request carrying MCP / tool_search tools now route to the upstream /responses endpoint instead of downgrading to /chat/completions, preserving Codex deferred tool discovery. Detection helpers extracted to open-sse/executors/forceResponsesUpstream.ts. Thanks to @KooshaPari.
* fix(ci): make release-green pre-flight gates visible + bounded so unit reds are not missed (#5644)
Integrated into release/v3.8.43.
* fix(body-size): raise LLM API payload limit for responses routes (#5652)
Integrated into release/v3.8.43. Thanks @JxnLexn!
* fix(test): use lightweight health probe for batch e2e (#5651)
Integrated into release/v3.8.43. Thanks @KooshaPari!
* feat(compression): T05/C5 — preserveSystemPrompt mode enum + legacy back-compat (#5653)
Integrated into release/v3.8.43. Includes the legacy-boolean back-compat derivation so existing preserveSystemPrompt=false installs keep whenNoCache behavior.
* routing: optimize latency strategy with perf metrics (#5629)
Integrated into release/v3.8.43. Thanks @KooshaPari!
* feat(db): models/5004 — self-correcting model context-window overrides (#5667)
Integrated into release/v3.8.43.
* feat(providers): complete SenseNova free Token Plan — chat + Text-to-Image (port from 9router#2233) (#5679)
Integrated into release/v3.8.43.
* feat(api): routing/4985 — configurable response-body validation + failover (#5684)
Integrated into release/v3.8.43.
* fix(chatcore): default Claude tool type to "custom" when missing (#5662)
Integrated into release/v3.8.43. Port from 9router#2196.
Co-authored-by: warelik <warelik@users.noreply.github.com>
* fix(translator): merge consecutive same-role contents for Gemini (port from 9router#2191) (#5661)
Integrated into release/v3.8.43. Port from 9router#2191.
* chore(bun): add locked bun runtime dependency (#5615)
Integrated into release/v3.8.43. Bun 1.3.10 pinned via npm lockfile (adopt-partial decision). Thanks @KooshaPari!
* chore(bun): run validated ts scripts with bun (#5612)
Integrated into release/v3.8.43. Thanks @KooshaPari!
* chore(bun): run CI script checks with bun (#5617)
Integrated into release/v3.8.43. Validated bun==node output for all 3 gates (provider-consistency, compression-budget, known-symbols). Thanks @KooshaPari!
* fix(build): make pack validator bun safe (#5643)
Integrated into release/v3.8.43. Forward-compat guard; node/npm path unchanged. Thanks @KooshaPari!
* docs: document Bun as the allow-listed build/dev script runner (Node stays the published runtime) (#5703)
Integrated into release/v3.8.43.
* feat(analytics): show $0 cost for flat-rate subscription/cookie providers (#5552) (#5704)
* refactor(api): extract unified-catalog helpers into cohesive leaf modules (#5699)
BLOCO E2 of the god-files campaign. The module-level pure/standalone helpers in
src/app/api/v1/models/catalog.ts (1611 LOC) were lifted out verbatim into five
cohesive leaf modules so the catalog host shrinks toward the 800-LOC file-size cap
without any behavior change (host now 1345 LOC; the heavy getUnifiedModelsResponse
orchestrator is untouched — its in-function closures stay put):
- catalogHelpers.ts — pure numeric/array/shape helpers + shared catalog types
- catalogOpenrouter.ts — OpenRouter id/modality/free-model/display-name helpers
- catalogVision.ts — vision-capability field derivation (+ isVisionModelId re-export)
- catalogProviderMaps.ts — alias<->providerId resolution maps (buildAliasMaps)
- catalogRequest.ts — /v1/models API-key auth gating + Codex CLI client detection
The host re-exports getCustomVisionCapabilityFields and isVisionModelId so the public
API consumed by other tests (llm-selector-custom-vision-models, vision-detection-
consistency) is unchanged; all 9 catalog/vision suites stay green.
Adds tests/unit/catalog-helpers-extraction.test.ts: characterization tests for every
extracted helper + a guard asserting the host preserves its public exports.
Validated: typecheck:core, 50 catalog characterization tests, 12 new leaf tests,
integration-wiring, check:cycles, check:file-size (no new violations), ESLint, Prettier.
* feat(mcp): T07 — expose RTK learn/discover as MCP tools (#5691)
Adds two read-only MCP tools wrapping the existing RTK discovery primitives: omniroute_rtk_discover (discoverRepeatedNoise/suggestFilter over recently captured raw tool output → candidate noise patterns + suggested filter) and omniroute_rtk_learn (listRtkCommandSamples + commandToId). Scope read:compression, MCP audit-logged, no new engine logic. Regression guard: tests/unit/compression/rtk-mcp-tools.test.ts. gaps v3.8.42 — T07.
* feat(compression): T05/C3 — opt-in LLM-tier compression engine (#5702)
Adds an opt-in, default-off LLM-tier compression engine ('llm') that condenses non-system message prose via a pluggable chat-completion backend, mirroring the llmlingua contract. Safe by construction: no-op default backend (pass-through out of the box), not in the default stacked pipeline, enabled defaults false, fenced code blocks + system messages never sent to the model, fail-open everywhere, minTokens floor. Real production backend is a VPS-validated follow-up (Hard Rule #18). Regression guard: tests/unit/compression/llm-compressor-engine.test.ts (8). gaps v3.8.42 — T05/C3.
* refactor(db): extract compat/aliases/mitm helpers from db/models.ts into leaf modules (#5705)
BLOCO E3 of the god-files campaign. db/models.ts (1250 LOC) mixed six concerns; the
three cleanly-separable ones plus the shared key_value helpers were lifted out verbatim
into a new src/lib/db/models/ subdirectory, leaving the tightly-coupled custom/synced/
flags trio in the host (host now 936 LOC). The host re-exports every moved public symbol
so the module's public API (consumed by ~29 test files + localDb) is unchanged.
- models/shared.ts — asRecord / toNonEmptyString / getKeyValue + JsonRecord (19 LOC)
- models/compat.ts — model-compat overrides + sanitizeUpstreamHeadersMap (249 LOC)
- models/aliases.ts — model-alias CRUD + cascade delete (61 LOC)
- models/mitmAlias.ts — MITM alias get/set (32 LOC)
The custom/synced/flags trio stays in the host because it is genuinely coupled
(flags->getCustomModelRow, flags->readCompatList, custom->removeModelCompatOverride,
synced->getModelIsDeleted, setModelIsHidden->updateCustomModel) — splitting it cleanly
is a follow-up. Dependency DAG is acyclic (verified by check:cycles).
Adds tests/unit/db-models-split.test.ts: characterization of the pure extracted helpers
+ a guard asserting the host preserves its full public export surface.
Validated: typecheck:core, check:cycles (no cycles), 77 existing db/models consumer
tests (db-models-crud/extended/aliases-cascade + 7 more) green, 7 new tests, ESLint,
Prettier, check:file-size (host 936 < frozen 1259; no new violations).
* refactor(db): extract pricing/lkgp/cache-metrics from db/settings.ts into leaf modules (#5709)
BLOCO E3 of the god-files campaign. db/settings.ts (1154 LOC) mixed five concerns; the
three cleanly-separable ones plus the shared toRecord/JsonRecord helper were lifted out
verbatim into a new src/lib/db/settings/ subdirectory, leaving the Settings-core + Proxy
config concerns in the host (host now 646 LOC). The host re-exports every moved public
symbol so the module's public API (consumed by ~93 test files + localDb) is unchanged.
- settings/shared.ts — toRecord + JsonRecord (9 LOC)
- settings/pricing.ts — pricing layers/sources/per-model + update/reset (254 LOC)
- settings/lkgp.ts — Last-Known-Good-Provider get/set/clear (49 LOC)
- settings/cacheMetrics.ts — cache metrics + trend (235 LOC)
Settings-core + the Proxy-config concern stay in the host: proxy is the most tangled
(245-line resolveProxyForConnection, resolution cache, imports from ./proxies) and
getSettings is the most central function — leaving them is the correct coupled-core stop.
Pricing/LKGP/Cache have NO dependency on Settings/Proxy helpers (verified); the
dependency DAG is acyclic (check:cycles).
Adds tests/unit/db-settings-split.test.ts: characterization of the shared toRecord helper
+ a guard asserting the host preserves its full public export surface.
Validated: typecheck:core, check:cycles (no cycles), 149 existing+new db/settings consumer
tests green (db-settings-crud/extended, 8 pricing suites, cache-metrics, 2 proxy-resolution
suites + 29 new), ESLint, Prettier, check:file-size (host 646 < frozen 1155).
* fix(translator): re-apply lost defensive hardening for Gemini merge + Claude tool defaults (#5706)
Re-applies two dropped gemini-code-assist hardening fixes (defaultClaudeToolType non-object passthrough; mergeConsecutiveSameRoleContents shallow-copy) with regression tests. Follow-up to #5661/#5662. Integrated into release/v3.8.43.
* feat(codex): generate fallback profiles for compatible models (#5701)
setup-codex now generates Codex profiles for compatible text models from the live /v1/models catalog when the model id doesn't match a hand-tuned pattern, skipping media/embedding models. Integrated into release/v3.8.43.
* docs(changelog): credit @Chewji9875 for #5563 + #5579
Add CHANGELOG credit bullets for grok-cli tool-limit (#5563) and Antigravity 429 lockout (#5579). Documentation-only.
* test(dashboard): repoint sidebar quota-share placement scan to sections.ts (#5711)
The D1 god-file split (#5683) moved the nav-item id definitions out of
src/shared/constants/sidebarVisibility.ts into the extracted leaf
src/shared/constants/sidebarVisibility/sections.ts. This source-scan test
still read the old monolith path, so it found 0 occurrences of
id: "costs-quota-share" and failed (base-red on release/v3.8.43).
Repoint SIDEBAR_PATH to sections.ts where the ids now live. All four
placement assertions (quota-share after quota, same array, far from
costs-budget, exactly one occurrence) hold against the new source.
* refactor(db): extract columns/nodes/rate-limit leaves from db/providers.ts (#5714)
db/providers.ts was a 1106-line god-file mixing four concerns. Extract the
three acyclic, cohesive slices into sibling leaf modules under
src/lib/db/providers/, leaving the tightly-coupled connection-CRUD core in
the host:
- providers/columns.ts (116) 10 pure column-normalizer helpers (DB-free)
- providers/nodes.ts (163) 6 provider-node CRUD functions
- providers/rateLimit.ts (177) 6 rate-limit/quota runtime helpers + formatResetCountdown
Host providers.ts: 1106 -> 719 lines. The connection-CRUD core does not call
any node or rate-limit function (verified), so the host re-exports the 12
moved public symbols via `export { ... } from './providers/<leaf>'` — the
module's public API stays IDENTICAL (23 symbols). Bodies moved verbatim
(byte-identical); the only edit to a moved line is the added `export` on the
10 previously-private normalizers.
Behavior-preserving: 122 existing provider/quota/rate-limit consumer tests
stay green; new tests/unit/db-providers-split.test.ts guards the re-export
barrel + characterizes the pure column helpers (38 assertions).
Refs #3501 (god-file structural shrink).
* refactor(db): extract types + pure mappers from db/proxies.ts (#5717)
db/proxies.ts was a 1059-line god-file. Extract the two acyclic, DB-free
slices into sibling leaf modules under src/lib/db/proxies/, leaving the
tightly-coupled CRUD + assignment + resolution core in the host:
- proxies/types.ts (65) 10 proxy type/interface declarations
- proxies/mappers.ts (180) pure row mappers / scope normalizers / payload
coercers (toRecord, mapProxyRow, mapAssignmentRow,
isRelayProxyType, extractRelayAuth,
toRegistryProxyResolution, normalizeScope,
normalizeAssignmentScopeId, toLegacyProxyLevel,
coerceProxyPayload, redactProxySecrets)
Host proxies.ts: 1059 -> 847 lines. The resolution functions call
createProxy/assignProxyToScope, so the CRUD+resolution core CANNOT be
extracted without an import cycle and stays in the host. The host re-exports
the 2 moved public functions (extractRelayAuth, redactProxySecrets) via
`export { ... } from './proxies/mappers'` — the public API stays IDENTICAL
(20 functions; no types were ever publicly exported). Bodies moved verbatim;
the only host edits are the new leaf imports, the re-export, dropping the now
unused `import { decrypt }`, and two prettier line-wrap reflows of retained
ternary/union lines (token-identical).
Behavior-preserving: 69 existing proxy/registry/relay/family consumer tests
stay green; new tests/unit/db-proxies-split.test.ts guards the re-export
barrel + characterizes the pure mappers (35 assertions).
Refs #3501.
* refactor(db): extract static migration data tables from migrationRunner.ts (#5721)
migrationRunner.ts (1124 lines, frozen-baselined) is the startup migration
orchestrator. As a conservative, zero-behaviour-risk first slice, extract the
six static migration-compatibility DATA tables (verbatim) into a pure-data
leaf, leaving the entire orchestrator + all SQL-running helpers in the host:
- migrationRunner/constants.ts (118) RENAMED_MIGRATION_COMPATIBILITY,
LEGACY_VERSION_SLOT_MIGRATIONS, SUPERSEDED_DUPLICATE_MIGRATIONS,
PHYSICAL_SCHEMA_SENTINELS, INITIAL_SCHEMA_SENTINELS,
OPTIONAL_FTS5_MIGRATION_VERSIONS
Host migrationRunner.ts: 1124 -> 1023. The runtime fts5SupportCache (a
WeakMap, mutable state) stays in the host. No public …
…resolve (#216) * fix(security): exact-host Anthropic baseUrl check (CodeQL js/incomplete-url-substring-sanitization #674) (#5129) The anthropic-compatible Bearer-fallback gate decided whether a configured baseUrl targeted the official api.anthropic.com host via a substring `.includes("api.anthropic.com")`. A look-alike upstream such as `https://api.anthropic.com.evil.test` or `https://evil.test/?x=api.anthropic.com` matched the substring and was wrongly treated as official, suppressing the Bearer fallback meant for third-party gateways (CodeQL #674, js/incomplete-url-substring-sanitization, high). Replace the substring test with an exported `isOfficialAnthropicBaseUrl()` helper that parses the URL and compares the hostname for exact equality. Empty baseUrl stays official; scheme-less hosts are parsed with an assumed https://; an unparseable baseUrl falls back to third-party (Bearer emitted) as the safer default. Behavior for legitimate official/third-party baseUrls is unchanged. Adds tests/unit/anthropic-official-baseurl-host.test.ts covering official, look-alike, scheme-less, and unparseable inputs plus a static guard that the substring pattern is gone. * Release v3.8.38 (#5078) * chore(release): open v3.8.38 development cycle * fix(executors): strip client_metadata for cerebras and mistral (#4727) Integrated into release/v3.8.38 (leva 5) * fix(codebuddy): only send reasoning params when client requests reasoning (#5019) Integrated into release/v3.8.38 (leva 5) * fix(sse): keep streaming for forceStream providers when client requests JSON (#5021) Integrated into release/v3.8.38 (leva 5) * fix(sse): guard non-JSON SSE lines and duplicate [DONE] (#4937) Integrated into release/v3.8.38 (leva 5) * feat(blackbox): refresh provider model catalog (#4935) Integrated into release/v3.8.38 (leva 5) * fix(sse): dedupe case-variant Anthropic version/beta headers (#4846) Integrated into release/v3.8.38 (leva 5) * feat(sse): Kiro inline <thinking> stream splitter (#4911) Integrated into release/v3.8.38 (leva 5) * feat(cursor): parse Composer DeepSeek-style inline tool calls (#4912) Integrated into release/v3.8.38 (leva 5) * feat(proxy): auth-less host:port batch import (#4938) Integrated into release/v3.8.38 (leva 5) * fix(oauth): support Kiro IDC (organization) token import (#4944) Integrated into release/v3.8.38 (leva 5) * fix(translator): preserve cache_control for DashScope OpenAI-compat providers (port from 9router#2069) (#5013) Integrated into release/v3.8.38 (leva 5) * fix(tts): resolve Gemini TTS models from catalog (#4934) Integrated into release/v3.8.38 (leva 5) * fix(sse): don't cool down the connection on a self-inflicted upstream timeout (504) (#5064) Integrated into release/v3.8.38 (leva 5) * fix(sse): robust Anthropic /v1/messages streaming — real ping keepalive + client-disconnect guard (#5063) Integrated into release/v3.8.38 (leva 5) * feat(video): add Alibaba DashScope (wan2.7-t2v) provider (#5051) Integrated into release/v3.8.38 (leva 5) * fix: preserve model hidden flags (isHidden) across model sync (#5086) Integrated into release/v3.8.38 (leva 5) * fix(models): derive model discovery config from registry modelsUrl (#5087) Integrated into release/v3.8.38 (leva 5) * fix(compression): replace fileURLToPath(import.meta.url) with runtime anchors for standalone bundle (#5089) Integrated into release/v3.8.38 (leva 5) * feat(cc): add summarized thinking display toggle (#5055) Integrated into release/v3.8.38 (leva 5) * Harden selected API error responses (#5032) Integrated into release/v3.8.38 (leva 5) * chore(quality): rebaseline file-size for leva 5 PR batch drift 6 frozen files grew from merged leva-5 PRs (cursor #4912, kiro #4911, videoGeneration #5051, default #4727, base #4846, chat #5064); all covered by per-PR tests. See _rebaseline_2026_06_26_leva5 in the baseline. * feat(compression): compression playground (Play + Compare tabs) in the studio (#5080) Integrated into release/v3.8.38 * fix(combo): fail over on empty-content 502 instead of exhausting the provider (#5085) (#5104) * fix(dashboard): surface detailed credential-validation error in add-connection modal (#5088) (#5106) * feat(providers): allow local/private provider URLs by default with scoped metadata-safe guard (#5066) (#5107) * fix(diagnostics): treat non-streaming Claude messages shape as valid output (#5108) (#5116) * fix(db): translate pt-BR SQLite driver-fallback log lines to English (#5103) (#5115) * fix(sse): repair release base-reds — malformed-response false positives + header casing + stale tests (#5117) Repairs the release/v3.8.38 base-reds; unblocks #5078. * chore(quality): rebaseline file-size for responseSanitizer (#5117) + AddApiKeyModal drift * fix(translator): forward image tool_result blocks as image_url (#5100) Base-reds fixed (#5117); image tool_result→image_url. Integrated into release/v3.8.38. * fix(responses): default text.format for openai-compatible responses providers (#5101) Base-reds fixed (#5117); default text.format + file-size rebaseline. Integrated into release/v3.8.38. * feat(dashboard): expose Fusion judgeModel + fusionTuning in the combo editor (#5074) Base-reds fixed (#5117); Fusion editor + file-size rebaseline. Integrated into release/v3.8.38. * feat(quota): add opt-in Codex/Claude auto-ping keepalive (#5102) Base-reds fixed (#5117); auto-ping keepalive + file-size rebaseline. Integrated into release/v3.8.38. * test(release): relocate 2 orphan test files into the collected flat tests/unit dir (#5120) Unblocks Lint (test-discovery) on #5078. Integrated into release/v3.8.38. * fix(translator): preserve reasoning-replay reasoning_content + repair 3 release-green test reds (#5122) Repairs 3 release-green test reds + test-masking; unblocks #5078. * test(golden): redact live Node version from provider translate-path snapshot (#5125) Final golden unblock for #5078. * test(golden): redact OmniRoute app version from translate-path snapshot (#5126) Coverage shard golden unblock for #5078. * Ignore disconnect races during in-band stream error handling (#5007) Integrated into release/v3.8.38 * Track final connection IDs in failover logs (#5016) Integrated into release/v3.8.38 * fix(sse): convert Gemini body to OpenAI format in antigravity MITM handler (#4845) Integrated into release/v3.8.38 (rebased on tip, CHANGELOG re-injected) * feat(providers): add ZenMux Free session-cookie provider (#5105) Integrated into release/v3.8.38 (rebased on tip, CHANGELOG re-injected) * feat(dashboard): click-to-edit model alias in provider page (#5119) Integrated into release/v3.8.38 (rebased on tip, i18n scope verified, CHANGELOG re-injected) * feat(mcp): web-session robustness — cookie dedup (PR6) + browser-pool observability (PR7) (#3368) (#5121) Integrated into release/v3.8.38 (rebased on tip; cookie-dedup branch extracted to findExistingCookieConnection helper → complexity-neutral; CHANGELOG added) * fix(usage): dedupe request-usage logging and debounce stats (#4940) Integrated into release/v3.8.38 (rebased on tip; DB-handle hang was stale-base artifact — resetDbInstance already closes the handle, test green 5/5; file-size drift consolidated at release; CHANGELOG re-injected) * fix(dashboard): key model visibility toggle on canonical providerId (#5091) Integrated into release/v3.8.38 (retargeted main→release; .tsx visibility-key test green 2/2) * chore(deps): bump actions/cache from 5.0.5 to 6.0.0 (#5112) Integrated into release/v3.8.38 (retargeted main→release; workflow-only actions/cache bump — unit failures were stale main base-reds) * fix(streaming): harden long OpenAI-compatible SSE streams (#5124) Integrated into release/v3.8.38 (rebased on tip; streamHandler conflict with #5007 disconnect-guard resolved — both coexist, stream-handler 22/22 green) * feat: Add Grok Build (xAI) provider with OAuth import-token flow (#5020) Integrated into release/v3.8.38 (rebased on tip; Hard Rule #11 fix — Grok public client_id now via resolvePublicCred(grok_id), 3 literals removed; grok-oauth 7/7 + check:public-creds green) * feat(providers): add Factory (factory.ai) as a subscription gateway provider (#5065) Integrated into release/v3.8.38 (rebased on tip; added factory registry test for PR Test Policy + fixed check:env-doc-sync phantom FACTORY_API_KEY; factory loads in PROVIDERS, no Zod issue — that flag was a false positive) * chore(test): reconcile golden snapshot + apikey count for new providers #5020 (grok-cli), #5065 (factory), #5105 (zenmux-free) added providers but did not regenerate tests/snapshots/provider/translate-path.json (now +3 entries) nor bump the APIKEY_PROVIDERS count (159->160 for the factory gateway). Test-only reconciliation; no production change. * fix(resilience): harden quota and model lockout edge cases (#5093) Integrated into release/v3.8.38 (rebased on tip). TRUST-BUT-VERIFY: dropped the PR's 0dd7df641 'fix unit gates' commit which reverted #5122 reasoning-replay (preserveReasoningContent) + re-introduced #4849 O(n^2) growth, and restored 5 tests it had realigned. Kept only the 3 declared resilience fixes (quota cutoff guard, gemini MIME, model-lockout maxCooldownMs); 23/23 green. * Hydrate quota cache and scope auto combo candidates (#5015) Integrated into release/v3.8.38 (rebased on tip). Kept core quota-cache hydration + auto-combo candidate scoping + combos UI; dropped out-of-scope toolCloaking refactor (conflicted with #4813 stripEnumDescriptions — took tip) and the unrelated sse-auth test split. Added quota-cache-hydrate-5015 regression test (Rule #18); combo-account-allowlist 8/8 + hydration 2/2 green. * chore(quality): reconcile complexity + file-size baselines for v3.8.38 owner-PR batch complexity 1972->1978 (+6) and file-size providers.ts 1093->1107 / usageHistory.ts 934->983 — drift from the /review-prs merge batch (#4845/#5105/#5020/#4940/#5093/ #5015 + #5121 cookie-dedup helper extraction). check:complexity/check:file-size do not run on the PR->release fast-path, so the branch accrued unmeasured; all legit feature/fix growth, not regression. See per-key justifications in each baseline. * fix(security): exact-host Anthropic baseUrl check (CodeQL js/incomplete-url-substring-sanitization #674) (#5130) The anthropic-compatible Bearer-fallback gate decided whether a configured baseUrl targeted the official api.anthropic.com host via a substring `.includes("api.anthropic.com")`. A look-alike upstream such as `https://api.anthropic.com.evil.test` or `https://evil.test/?x=api.anthropic.com` matched the substring and was wrongly treated as official, suppressing the Bearer fallback meant for third-party gateways (CodeQL #674, js/incomplete-url-substring-sanitization, high). Replace the substring test with an exported `isOfficialAnthropicBaseUrl()` helper that parses the URL and compares the hostname for exact equality. Empty baseUrl stays official; scheme-less hosts are parsed with an assumed https://; an unparseable baseUrl falls back to third-party (Bearer emitted) as the safer default. Behavior for legitimate official/third-party baseUrls is unchanged. Adds tests/unit/anthropic-official-baseurl-host.test.ts covering official, look-alike, scheme-less, and unparseable inputs plus a static guard that the substring pattern is gone. * fix(proxy): repair one-click Deno & Cloudflare relay deployments (#5128) (#5132) * fix(services): embed WS proxy honours LIVE_WS_HOST; reject empty messages early (#5110) (#5133) * fix(api): resolve /v1/models/{id} case-insensitively (#5082) (#5135) * fix(providers): add MiniMax M3 & Nemotron 3 Ultra to Cline catalog (#3321) (#5136) * fix(proxy): make SOCKS5 handshake timeout tunable via SOCKS_HANDSHAKE_TIMEOUT_MS (#5109) (#5137) * feat(sidebar): add support for colored menu icons (#3812) Integrated into release/v3.8.38 (recreated on tip — fork had unrelated history; added getSidebarIconAccent regression test, Rule #18). Clean 2-file UI feature. * fix(providers): complete grok-cli OAuth wiring + zenmux-free web-session metadata Base-red repair for #5020 (grok-cli) and #5105 (zenmux-free), surfaced by the full CI on the release PR (#5078) — the PR->release fast-path does not run the oauth-providers-config / web-session-credentials / provider-consistency gates. - grok-cli: register in OAUTH_PROVIDERS (providers.ts canonical list, fixes check:provider-consistency), add OAUTH_PROVIDER_IDS.GROK_CLI + GROK_CLI_CONFIG in oauth constants (provider config now sourced there, not a local literal), align oauth-providers-config.test.ts (EXPECTED_PROVIDER_KEYS + config map). - zenmux-free: declare its web-session credential requirement (full Cookie header) in WEB_SESSION_CREDENTIAL_REQUIREMENTS. Local: oauth-providers-config 27/27, web-session-credentials 4/4, grok-cli-oauth 7/7, check:provider-consistency OK, +115 OAUTH_PROVIDERS tests green. * Fix resilience settings page response mapping (#5139) Integrated into release/v3.8.38. Thanks @rdself for the fix and the regression test. * fix(kiro): retire claude-sonnet-4.5 from catalog + pin 400 model-unavailable test (#5140) Extracted the real change from #5140 (the bot PR regenerated the entire freeModelCatalog.data.ts + touched package-lock.json; only the targeted edits are kept here): - remove claude-sonnet-4.5 from the Kiro registry entry - remove the matching kiro free-model catalog row - pin Kiro's verbatim 400 "Invalid model..." to isModelUnavailableError Closes #4484 * fix(sidebar): drop orphan `settings` accent color (typecheck:core red) (#5142) SIDEBAR_ICON_ACCENTS is typed Partial<Record<HideableSidebarItemId, string>>, but `settings` is not a hideable item id (only `settings-general`, `settings-appearance`, … and `context-settings` exist; there is no item with `id: "settings"`), so the accent was unreachable. It broke `typecheck:core` on the release tip ("'settings' does not exist in type …", introduced by #3812 colored menu icons). Removing the orphan key restores a clean typecheck:core (rc=0). * feat: salvage batch 2 — diagnostics null-guard (#5096) + observed quota reset windows (#5025) (#5141) * fix(diagnostics): null-guard content blocks in detectMalformedNonStream A null (or non-object) entry in a Claude-native `content` array made the non-stream classifier throw `TypeError: Cannot read properties of null (reading 'type')`, crashing the malformed-response detection path. Guard before type-asserting each block: a null/non-object block is simply skipped. Two regression tests added (null block among valid blocks → null; only-null blocks → empty_choices). Salvaged from closed PR #5096 (base-stale; only the defensive guard — the Claude-shape recognition it also carried already landed via #5108). Co-authored-by: herjarsa <herjarsa@users.noreply.github.com> * feat(quota): persist observed provider quota reset windows Adds `provider_quota_reset_events` (migration 108) + `db/quotaResetEvents.ts` to record real upstream weekly-quota window transitions whenever a quota refresh shows the reset rolling to a new cycle (different day, later resetAt). `apiKeyUsageLimits` now prefers the observed window start over the inferred `resetAt − 7d`, falling back to snapshot inference when no event is recorded yet. `quotaCache.setQuotaCache` records the transition opportunistically. `recordProviderQuotaResetEventIfChanged` only fires for the primary weekly window (not daily/sonnet), is idempotent (INSERT OR IGNORE on the unique window key), and no-ops when the reset didn't actually roll. 4 unit tests (tests/unit/lib/quota-reset-events.test.ts). Salvaged from closed PR #5025 (which bundled this with two unrelated features + a colliding migration 104). Renumbered to 108; module re-exported from localDb (Rule #2). Co-authored-by: Witroch4 <175152067+Witroch4@users.noreply.github.com> --------- Co-authored-by: herjarsa <herjarsa@users.noreply.github.com> Co-authored-by: Witroch4 <175152067+Witroch4@users.noreply.github.com> * docs(i18n): sync 3.8.38 CHANGELOG section to 41 mirrors (unblock docs-accuracy) (#5144) The root CHANGELOG [3.8.38] section grew with this cycle's merged PRs, but the docs/i18n/<lang>/CHANGELOG.md mirrors were not re-synced — drifting >25% in body size and failing check:docs-sync (the "Docs accuracy" fast-gate step) for every open PR against the release. Ran scripts/release/sync-changelog-i18n.mjs 3.8.38 3.8.37 to copy the root [3.8.38] section into all 41 mirrors. check:docs-all now passes (exit 0). Sections are copied verbatim; the per-language translation pass runs at release time via i18n:run — this only restores the size-sync the gate enforces. * feat(compression): pure per-step fidelity checker (4 invariants, fail-open) * feat(compression): fidelityGate config + rejected breakdown fields * feat(compression): wire per-step fidelity gate into stacked pipeline (opt-in) * feat(compression): preview route accepts fidelityGate flag (playground) * feat(compression): playground fidelity-gate toggle + lane rejection display * docs(compression): note fidelityGate advanced thresholds are intentionally API-omitted * refactor(compression): extract fidelity-gate step helpers to shrink strategySelector (file-size gate) bodyToText and gateAdvance moved to fidelityGateStep.ts; StackAccumulator exported. strategySelector: 889->854 (-35). Residual +6 vs pre-Milestone-B frozen 848 is the irreducible StackOptions.fidelityGate field + two stacked-loop dispatch reads + import. Baseline updated to 854 with justification. No cycle introduced (import type only). 940 compression tests pass; typecheck clean. * test(usage): wire usageHistoryDedup under unit runner brace-list (#5145) Integrated into release/v3.8.38. * feat: salvage batch from closed stale PRs (#5038, #5057, #5076) (#5138) Integrated into release/v3.8.38. * test(combo): deterministic routing-decision matrix for all 17 strategies (#5146) Integrated into release/v3.8.38. * feat(compression): fuzzy near-duplicate dedup (session-dedup 2nd pass + playground toggle) (#5143) Integrated into release/v3.8.38. * chore(quality): rebaseline file-size for sidebarVisibility.ts + chat.ts drift (#5147) Mid-cycle drift on release/v3.8.38 from already-merged PRs that the fast-path (PR->release skips check:file-size) let accumulate without a bump: - src/shared/constants/sidebarVisibility.ts 1100->1198 (#3812 colored menu icons, per-item accent map; #5142 dropped one orphan, net still above frozen) - src/sse/handlers/chat.ts 1560->1575 (#5064 self-inflicted-timeout cooldown skip + #5124 long OpenAI-compatible SSE hardening + #5110 embed-WS LIVE_WS_HOST honour / early empty-message reject) Each covered by its own PR tests; structural shrink of chat.ts tracked in #3501. Unblocks the Fast Quality Gates for PRs targeting release/v3.8.38. * chore(release): finalize v3.8.38 CHANGELOG + cycle reconciliation - Reconcile [3.8.38]: +18 bullets (compression fidelity-gate/fuzzy-dedup #5143, quota keepalive #5102, web-session robustness #5121, MiniMax/Nemotron #5136, model-visibility #5091, failover logs #5016, disconnect races #5007, sidebar orphan #5142, SRE playbooks salvage #5138, new Security #5130 + Maintenance roll-up) - Credit salvaged-PR authors (@JxnLexn / @KooshaPari / @herjarsa / @Witroch4) - Remove phantom bullet for CLOSED-not-merged #5092 (setup aggregator never landed) - Fix isHidden bullet PR citation #4389 -> #5086 (@herjarsa) - Back-fill forgotten v3.8.36 bullet: #5026 crypto.randomUUID ID-gen (@hamsa0x7) - Sync 41 i18n CHANGELOG mirrors; README What's New -> v3.8.38 - Rebaseline cycle drift: eslint 3987->4002, cognitive 833->841, dead-exports 345->346, cyclomatic 1978->1980 (file-size handled by #5147) * fix(i18n): add missing English UI labels (#5153) Integrated into release/v3.8.38 * Preserve non-stream reasoning fields for compatible clients (#5155) Integrated into release/v3.8.38 * feat(compression): ionizer engine — lossy JSON-array sampling reversible via CCR (#5148) Integrated into release/v3.8.38 * test(combo): gated live smoke for combo strategies (in-process + VPS HTTP) (#5151) Integrated into release/v3.8.38 * test: refresh release expectations to match current code (#5150) Integrated into release/v3.8.38 (test-only base-red alignment extracted from #5150) --------- Co-authored-by: Éder Costa <eder.almeida.costa@gmail.com> Co-authored-by: José Victor Ferreira <root@josevictor.me> Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com> Co-authored-by: fulorgnas <46461624+fulorgnas@users.noreply.github.com> Co-authored-by: Randi <55005611+rdself@users.noreply.github.com> Co-authored-by: Jan Leon <Jan.gaschler@gmail.com> Co-authored-by: R. Beltran <rbeltran8000@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: KooshaPari <42529354+KooshaPari@users.noreply.github.com> Co-authored-by: Ramel Tecnologia - Rafa Martins <146174365+rafacpti23@users.noreply.github.com> Co-authored-by: herjarsa <herjarsa@users.noreply.github.com> Co-authored-by: Witroch4 <175152067+Witroch4@users.noreply.github.com> * Release v3.8.39 (#5164) * chore(release): open v3.8.39 development cycle * docs(changelog): backfill 5 v3.8.38 bullets merged after release finalize These PRs squash-merged into release/v3.8.38 between the CHANGELOG finalize (ff57be32f) and the merge-to-main (ae6e2342d), so they shipped in the v3.8.38 tag but had no bullet: - feat(compression): Ionizer engine (lossy JSON-array sampling + CCR) (#5148) - fix(sse): preserve non-stream reasoning fields (#5155, @rdself) - fix(i18n): add missing English UI labels (#5153, @rdself) - test(combo): gated live smoke (#5151) + release-expectations refresh (#5150, @KooshaPari) (#5129 exact-host Anthropic baseUrl is already covered by the #5130 bullet — same CodeQL #674.) Synced 41 i18n CHANGELOG mirrors. * feat(compression): TOON best-of-N candidate encoder + encoder A/B table (#5163) Integrated into release/v3.8.39. TOON best-of-N candidate encoder (GCF default, fail-open). 17/17 unit tests pass on merge result; CI reds were base-stale + Quality Ratchet DRIFT. * fix(zenmux): normalize vendor-prefixed GLM system roles (#5158) Integrated into release/v3.8.39. ZenMux vendor-prefixed GLM system-role normalization; 12/12 role-normalizer tests pass on merge result. CI reds base-stale. * [codex] fix xAI OAuth test and reasoning effort (#5157) Integrated into release/v3.8.39. xAI reasoning-effort normalization (max/xhigh→high) + OAuth test config; 46/46 xai-translator tests pass on merge result. CI reds base-stale. * docs(i18n): add Traditional Chinese (zh-TW) README and update zh-CN to latest (#5162) Integrated into release/v3.8.39. Traditional Chinese (zh-TW) README + zh-CN refresh; docs-only. * test(security): guard PII redaction stays opt-in (default off) + Hard Rule #20 (#5159) Integrated into release/v3.8.39. PII opt-in regression guard + Hard Rule #20; rebased to strip base-drift (+81/-1). 5/5 guard tests pass; flip-proof verified. * test(combo): deterministic context-relay universal-handoff coverage (closes phase-2 TODO) (#5168) Integrated into release/v3.8.39. Deterministic context-relay universal-handoff coverage (3 tests); 3/3 pass on merge result. * docs(i18n): full sync zh-TW and zh-CN README with canonical English v3.8.39 (#5171) Integrated into release/v3.8.39. Full zh-TW docs tree + zh-CN sync with canonical English v3.8.39; docs-only. * fix(serve): honour HOSTNAME from .env instead of hardcoding 0.0.0.0 (#5134) (#5170) Integrated into release/v3.8.39. HOSTNAME env override in serve (#5134) + regression test (4/4, TDD flip-proof verified). * fix(sse): resolve nameless deepseek-web tool blocks via parameter-schema match (#5154) (#5173) Integrated into release/v3.8.39. Schema-based nameless deepseek-web tool-block resolution (#5154); 6/6 tests pass on merge result (incl. ambiguous/no-match negatives + named-tag no-regression). * fix(sse): normalize array user content for Command Code to avoid upstream 400 (#5166) (#5174) Integrated into release/v3.8.39. Normalize array user content for Command Code (#5166, user-array/400 symptom); 4/4 tests pass on merge result. * fix(sse): defer </think> close so it never leaks before tool_calls (#5123) (#5175) Integrated into release/v3.8.39. Defer </think> close so it never leaks before tool_calls (#5123); 4/4 tests pass (incl. #4633 no-regression). CHANGELOG synced to keep all 3 v3.8.39 fixes. * fix(dashboard): use amber for home update-step warning icon (#5176) Integrated into release/v3.8.39. Amber for home update-step warning icon; 1/1 UI test. * fix(api): LAN/Tailscale dashboard — host-aware CSP + GET-exempt version route + combo field errors (#5083) (#5177) Integrated into release/v3.8.39. Host-aware CSP (ReDoS/injection-safe host validation) + GET-exempt /api/system/version (POST/spawn stays LOCAL_ONLY, exact-match safe-methods-only) + COMBO_002 firstField. 44/44 tests + route-guard membership gate green. CHANGELOG synced to keep all 4 v3.8.39 fixes. * fix(api): replace #5083 global middleware CSP with declarative ws: scheme (#5083) Follow-up to PR #5177 (merged): that version implemented the LAN-CSP fix (Bug 1) with a new global `src/middleware.ts` + `src/server/csp.ts`, which contradicts the project's documented architecture — 'No global Next.js middleware — interception is route-specific' (CLAUDE.md / AGENTS.md) — and was merged unverified (middleware vs next.config header precedence was never confirmed in a real build). This replaces that approach with the minimal, declarative equivalent: • next.config.mjs: connect-src now permits the bare `ws:` scheme (symmetric with the bare `wss:` already allowed) so the dashboard can reach its own Live WS server from a LAN/Tailscale host. No middleware. • Removes src/middleware.ts, src/server/csp.ts, and tests/unit/csp-host-aware.test.ts. • Adds tests/unit/csp-lan-ws-5083.test.ts (incl. a guard asserting src/middleware.ts does NOT exist, so the global-middleware approach cannot silently return). Bugs 2 (GET-exempt /api/system/version) and 3 (COMBO_002 field surfacing) from #5177 are unaffected and remain in place. Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com> * test(combo): end-to-end quota-share DRR routing-decision coverage (matrix parity) (#5179) Integrated into release/v3.8.39. Quota-share DRR routing-decision coverage (matrix parity); 2/2 pass on merge result. * feat(agent-bridge): graceful cert-install fallback with manual guide for containers (#4546) (#5178) Integrated into release/v3.8.39. Agent-bridge graceful cert-install fallback + manual guide (#4546); 6/6 tests pass on merge result. * fix(antigravity): family-scoped quota lockout (gemini/claude buckets) (#5180) Integrated into release/v3.8.39 — family-scoped antigravity quota lockout. Rebased from v3.8.37 + validated (vitest 5/5, typecheck clean, full combo-matrix green, model-lockout 99/0). Same-model cross-account retry (chat.ts) deferred pending live antigravity VPS validation. * fix(cli): force NODE_ENV to match dev/start run mode in custom Next server (#5189) Integrated into release/v3.8.39. Force NODE_ENV to match dev/start run mode in custom Next server; 2/2 source-scan+ordering tests pass on merge result. * feat(compression): CCR ranged/grep/stats retrieval (ReDoS-safe, backward-compat) (#5187) Integrated into release/v3.8.39. CCR ranged/grep/stats retrieval (safe-regex ReDoS guard + length/match caps); 17/17 tests pass on merge result. * docs(combo): sync all combo/routing-strategy docs to current state + document test coverage (#5185) Integrated into release/v3.8.39. Combo/routing-strategy docs sync; docs-only. * fix(mcp): return 404 (not 400) for unknown Streamable HTTP session id (#5169) (#5191) * fix(api): respect blocked Auto (Zero-Config) provider in /v1/models catalog (#5192) (#5194) * test(combo): deterministic context-relay codex quota-handoff coverage (closes last gap) (#5195) * test(ci): wire antigravity-quota-family under test:vitest (fix test-discovery orphan) (#5196) * fix(oauth): antigravity login no longer hangs — fire-and-forget onboarding + bounded post-exchange (#5193) Antigravity OAuth hang fix (no-PKCE/no-openid + bounded post-exchange + exchange-500 fix). Includes #5200 (Koosha) revert + owner rebaseline to keep documented comments. Integrated into release/v3.8.39. * feat(oauth): remote Antigravity login via local helper + paste-credentials (#5203) Remote Antigravity login: local helper (omniroute login antigravity) + paste-credentials. Integrated into release/v3.8.39. * fix(translator): accept Claude Messages shape in non-stream malformed-200 guard (#5156) Integrated into release/v3.8.39 * fix(cli): default dev bundler to Turbopack (16.2.x panic no longer reproduces) (#5206) Integrated into release/v3.8.39 * fix(cli): auto-calibrate server V8 heap from physical RAM (#5172) (#5213) The server was spawned with a fixed --max-old-space-size=512 (omniroute serve) or no heap flag at all (Electron), so RAM-rich boxes still OOM-crashed under load (Ineffective mark-compacts near heap limit ~500MB) with many providers/ accounts and large model catalogs. New calibrateHeapFallbackMb(os.totalmem()) defaults the heap to ~35% of RAM clamped [512,4096], wired into serve.mjs and electron/main.js. Explicit OMNIROUTE_MEMORY_MB still wins (#2939 unchanged). Also addresses #5160 (same OOM root); #5152 (docker) benefits via the same knob. Closes #5172 * fix(proxy): coalesce fast-fail health probes (#5208) Integrated into release/v3.8.39 * fix(proxy): close dispatchers when clearing cache (#5202) Integrated into release/v3.8.39 * fix(cli): raise dev server Node heap limit to 8GB to prevent OOM (#5198) Integrated into release/v3.8.39 * fix(auth): allow synthetic no-auth fallback for mimocode (#5205) Integrated into release/v3.8.39 * fix(oauth): preserve Antigravity refresh_token on empty/omitted upstream response (#3850) (#5214) Google's OAuth refresh tokens are non-rotating: the refresh response usually omits refresh_token and occasionally returns it as an empty string. The Antigravity executor used `typeof tokens.refresh_token === "string" ? ... ` which accepts "" (typeof "" === "string") and overwrote the stored token with empty, nulling it on first refresh. Now treats non-string OR empty as absent and preserves credentials.refreshToken, matching refreshGoogleToken semantics. Closes #3850 * fix(responses): normalize non-array input (#5204) Integrated into release/v3.8.39 * fix(stream): normalize safety finish reasons via shared helper (#5197) Integrated into release/v3.8.39 * fix(request-logger): never render negative '(-100%)' compression badge (#5201) Integrated into release/v3.8.39 * fix(combo): reject empty responses api output (#5207) Integrated into release/v3.8.39 — combo failover now rejects empty Responses API output (validateQuality). Baseline rebaseline dropped (main-measured drift; maintainer rebaselines at release). * fix(pwa): prefer cached navigation before offline page (#5209) Integrated into release/v3.8.39 — PWA service worker prefers cached navigation before offline page (#5165). * chore(release): v3.8.39 — 2026-06-28 * chore(release): rebaseline openapi+i18n coverage ratchet drift for v3.8.39 --------- Co-authored-by: Arthur Bodera <abodera@gmail.com> Co-authored-by: Nguyen Minh <lop123thcs@gmail.com> Co-authored-by: lunkerchen <labanchen@gmail.com> Co-authored-by: Ankit <177378174+anki1kr@users.noreply.github.com> Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com> Co-authored-by: Ardem2025 <ardemb22@gmail.com> Co-authored-by: backryun <bakryun0718@proton.me> Co-authored-by: Anton <39598727+NomenAK@users.noreply.github.com> Co-authored-by: KooshaPari <42529354+KooshaPari@users.noreply.github.com> Co-authored-by: Wilson <pedbookmed@gmail.com> Co-authored-by: Randi <55005611+rdself@users.noreply.github.com> * fix(docker): copy open-sse workspace manifest before npm ci (v3.8.39 image build) (#5223) * chore(docker): harden base image against container-scan CVEs (#5228) apt-get upgrade -y in the base stage pulls security-patched trixie packages at build time, and npm install -g npm@latest refreshes the globally-bundled undici/tar inside the npm CLI. Together these clear the subset of GitHub container-scan CVE alerts that have an upstream fix available. None of the flagged CVEs are in the application dependency tree (app already resolves undici@8.5.0 / tar@7.5.16, both fixed); they live in the node:24-trixie-slim base layer and npm's own internals, and none are reachable from the proxy request surface at runtime. CVEs without a published fix (local-only TOCTOU, etc.) remain until the distro patches them and the image is rebuilt. * chore(ci): Trivy advisory scan ignores unfixed CVEs (Security-tab noise) (#5234) The advisory Trivy image scan uploaded every HIGH/CRITICAL into the Security tab without ignore-unfixed, flooding it with ~150 unfixable base-image OS CVEs (Debian trixie packages with no upstream patch, overwhelmingly local-only and not reachable from the proxy request surface). Operators cannot act on those, so they are pure noise. Add ignore-unfixed:true to the advisory step so it mirrors the existing CRITICAL blocking gate and surfaces only actionable, fixable vulnerabilities. Wire trivyignores to a new repo-root .trivyignore that documents the accepted-risk policy and is the single auditable home for the rare fixable CVE we must temporarily accept (none at present). Takes effect on the next release image build (Trivy only runs on tag builds, not main pushes); fixed CVEs drop out of the SARIF and GitHub auto-resolves the corresponding alerts. * fix: centralize public origin checks for proxied dashboards (#5278) Centralizes browser-mutation origin validation into `src/server/origin/publicOrigin.ts` and wires it through the authz pipeline, replacing the per-route same-origin-only check that 403'd dashboard mutations when served behind a reverse proxy on a different public origin. The new module resolves the allowed public origin from configured base-URL env vars or trusted forwarded headers (only when OMNIROUTE_TRUST_PROXY is set AND the peer is loopback/LAN via peer-stamp), validates Sec-Fetch-Site metadata, and sanitizes Host/Forwarded inputs (rejects control chars, userinfo, path/query in Host). Reviewed sound; validated locally: authz/public-origin + pipeline suites 27/27 green (incl. invalid-origin reject + configured-origin accept), typecheck clean. Maintainer fix-up: moved the new test from tests/unit/server/ (not collected by any runner — orphan-test gate fail) into tests/unit/authz/. Remaining red CI shards are the pre-existing #4076 Dockerfile heap base-red on `main` (unrelated; de-brittled in the v3.8.40 release line). Co-authored-by: Thinkscape <Thinkscape@users.noreply.github.com> * Release v3.8.40 v3.8.40 cycle integration → main. All test gates green (Unit/Integration/Coverage/Node-compat/Quality-Ratchet). The only red check, 'PR Test Policy', is the test-masking heuristic firing on the cumulative ~57-commit release diff (legitimate assert consolidations already reviewed per-PR — Gemini CLI removal #5246, retired GPT models #5280, provider catalog refreshes); overridden with --admin per the documented release-PR convention. CodeQL/SonarQube advisory scans non-blocking; #5278's code already passed CodeQL on main. Homologated on VPS 192.168.0.15 (v3.8.40 healthy). * Release v3.8.41 (#5327) Release v3.8.41 — 52 commits since v3.8.40 (19 CHANGELOG bullets, 11 contributors). All gating CI green: Unit×8, Coverage×8, Vitest, Package Artifact, Quality Ratchet, CodeQL, Lint, Docs Sync (Strict), Node 24/26 compat, E2E×9, Integration, Electron smoke. Advisory checks overridden (main unprotected): PR Test Policy = test-masking heuristic on the cumulative 52-commit assert delta (legitimate dead-code-sweep removals + consolidations, reviewed per-PR); SonarCloud/SonarQube = new-code maintainability/coverage quality gate (CodeQL/Semgrep/Security/npm-audit/Dependabot all clean — not a security finding). * deps: bump the development group across 1 directory with 9 updates (#5415) Bumps the development group with 8 updates in the / directory: | Package | From | To | | --- | --- | --- | | [@axe-core/playwright](https://github.com/dequelabs/axe-core-npm) | `4.11.3` | `4.12.1` | | [@playwright/test](https://github.com/microsoft/playwright) | `1.61.0` | `1.61.1` | | [@tailwindcss/postcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-postcss) | `4.3.1` | `4.3.2` | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `26.0.0` | `26.0.1` | | [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) | `6.0.2` | `6.0.3` | | [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) | `6.18.0` | `6.23.0` | | [prettier](https://github.com/prettier/prettier) | `3.8.4` | `3.9.4` | | [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.61.1` | `8.62.1` | Updates `@axe-core/playwright` from 4.11.3 to 4.12.1 - [Release notes](https://github.com/dequelabs/axe-core-npm/releases) - [Changelog](https://github.com/dequelabs/axe-core-npm/blob/develop/CHANGELOG.md) - [Commits](https://github.com/dequelabs/axe-core-npm/commits) Updates `@playwright/test` from 1.61.0 to 1.61.1 - [Release notes](https://github.com/microsoft/playwright/releases) - [Commits](https://github.com/microsoft/playwright/compare/v1.61.0...v1.61.1) Updates `@tailwindcss/postcss` from 4.3.1 to 4.3.2 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.2/packages/@tailwindcss-postcss) Updates `@types/node` from 26.0.0 to 26.0.1 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `@vitejs/plugin-react` from 6.0.2 to 6.0.3 - [Release notes](https://github.com/vitejs/vite-plugin-react/releases) - [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.0.3/packages/plugin-react) Updates `knip` from 6.18.0 to 6.23.0 - [Release notes](https://github.com/webpro-nl/knip/releases) - [Commits](https://github.com/webpro-nl/knip/commits/knip@6.23.0/packages/knip) Updates `prettier` from 3.8.4 to 3.9.4 - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/3.9.4/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/3.8.4...3.9.4) Updates `tailwindcss` from 4.3.1 to 4.3.2 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.2/packages/tailwindcss) Updates `typescript-eslint` from 8.61.1 to 8.62.1 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.62.1/packages/typescript-eslint) --- updated-dependencies: - dependency-name: "@axe-core/playwright" dependency-version: 4.12.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development - dependency-name: "@playwright/test" dependency-version: 1.61.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development - dependency-name: "@tailwindcss/postcss" dependency-version: 4.3.2 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development - dependency-name: "@types/node" dependency-version: 26.0.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development - dependency-name: "@vitejs/plugin-react" dependency-version: 6.0.3 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development - dependency-name: knip dependency-version: 6.23.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development - dependency-name: prettier dependency-version: 3.9.3 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development - dependency-name: tailwindcss dependency-version: 4.3.2 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development - dependency-name: typescript-eslint dependency-version: 8.62.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * docs: add relay backend strategy guide (#5533) * Release v3.8.42 (#5459) Release v3.8.42 — full CHANGELOG in CHANGELOG.md. CI: 103 checks green incl. CodeQL (all languages), Semgrep, all 8 unit shards, coverage, Node 24 compat, and integration tests. Full unit suite validated locally: 19437 pass / 0 fail. The 3 red checks are advisory and do not gate main (no required status checks): SonarCloud/SonarQube new-code coverage gate, and PR Test Policy (test-masking detector flagging the legitimate dead-Phind provider removal in #5530 — reviewed, correct). Includes cycle-close reconciliation + repair of inherited base-red tests from #5480/#5527/#5427/#5521 that the PR->release fast-path did not exercise. * chore(release): open v3.8.43 development cycle * docs(relay): clarify backend routing contract (#5621) Integrated into release/v3.8.43 (drift-shed: cherry-picked the real change onto the release tip; stale-base drift dropped). * fix(security): avoid rendering error stacks (#5624) Integrated into release/v3.8.43 (drift-shed: cherry-picked the real change onto the release tip; stale-base drift dropped). * fix(chatgpt-web): restore dot-form Pro model ids (#5549) Integrated into release/v3.8.43 (drift-shed: cherry-picked the real change onto the release tip; stale-base drift dropped). * feat(commandCode): add multimodal image support for CC vision models (#5557) Integrated into release/v3.8.43 (drift-shed: cherry-picked the real change onto the release tip; stale-base drift dropped). * fix(providers): validate M365 Copilot web credentials (#5432) Integrated into release/v3.8.43 (drift-shed: cherry-picked the real change onto the release tip; stale-base drift dropped). * fix(sse): bound chat hot-path heap — pressure-aware admission + response cap + clone reductions (#5152) (#5425) Integrated into release/v3.8.43 (drift-shed: cherry-picked the real change onto the release tip; stale-base drift dropped). * fix: model lockout not recording for 429 rate_limit_exceeded from Antigravity ## Problem When Antigravity returns HTTP 429 with `rate_limit_exceeded` error code, the model lockout system never records the failure, so the model is not cooled down despite being rate-limited. ### Root Cause Antigravity's 429 error text is: `"Resource has been exhausted (e.g. check quota)."` The QUOTA_PATTERNS in `classify429.ts` contained overly broad regexes: - `/resource.*exhaust/i` — matches "Resource has been exhausted" - `/check.*quota/i` — matches "check quota" This caused `classifyErrorText()` to return `QUOTA_EXHAUSTED` (wrong), which set `providerExhausted = true` in the combo target exhaustion logic. With `providerExhausted`, the retry path was skipped entirely, and while the "done retrying" path should still record lockout, the misclassification cascaded into incorrect provider-level exhaustion state. Additionally, `targetExhaustion.ts` used the raw error text string instead of the structured error code (`rate_limit_exceeded`) that was already parsed from the response body. ## Fix 1. **classify429.ts** — Removed overly broad `/resource.*exhaust/i` and `/check.*quota/i` from QUOTA_PATTERNS. Antigravity's rate-limit wording is not a true quota exhaustion signal. 2. **targetExhaustion.ts** — Added optional `structuredError` to `ApplyComboTargetExhaustionOptions`. When available, the structured error code (e.g. `rate_limit_exceeded`) takes precedence over raw error text for exhaustion classification. 3. **combo.ts** — Passes `structuredError` to both `applyComboTargetExhaustion` call sites (dispatch path + retry-or-rotate path). ## Effect `structuredError.code = "rate_limit_exceeded"` → classified as rate-limit (not quota) → `providerExhausted = false` → retry proceeds → `recordModelLockoutFailure` called → model enters lockout with proper cooldown (120s base, exponential backoff). ## Tests Added 2 new tests for `structuredError.code` precedence in exhaustion classification. All 28 related tests pass. * fix(checks): normalize route paths on windows (#5613) Integrated into release/v3.8.43. Windows path-normalization fix for the route-guard membership gate + regression test (Rule #18). Co-authored test added by maintainer. * fix: truncate tool list when provider limit exceeds MAX_TOOLS_LIMIT (grok-cli 200) - Add proactive PROVIDER_TOOL_LIMITS map with grok-cli: 200 - Fix regex to capture 'maximum is 200' (not '427 tools provided') - Remove broken truncation gate that skipped limits >= MAX_TOOLS_LIMIT (128) - Add tests for Grok regex, proactive limits, and limits above threshold Refs #5563 * test(chatcore): cover grok-cli tool-list truncation via prepareUpstreamBody (#5563) Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> * fix(security): v3.8.15 hardening follow-ups (Seg2/Seg3/Seg4/Bug3) (#5512) Security v3.8.15 hardening follow-ups: Seg2 (CHANGEME boot warn), Seg3 (auth_token cookie maxAge 30d), Seg4 (VS Code path-token once-per-process warning), Bug3 (real global install path resolution), Bug1 (segment-match node_modules in auto-update detection). All 5 carry TDD regression guards. * Fix HuggingChat web session routing (#5592) (#5592) Integrated into release/v3.8.43. HuggingChat web session-routing fix (root parent-message fetch + cookie propagation + encrypted-credential guard) + 24-model catalog refresh. Maintainer adjustments (co-authored): reverted the freeModelCatalog.data.ts whole-file reformat down to the surgical 24-record huggingchat change (preserving the auto-generated compact format), and added a 502 regression test for the null parent-message-id path (Rule #18). * fix: preserve system role for GLM 5.1/5.2 (#5610) (#5663) * fix: restore Codex Responses WS TLS profile + apply proxy (#5591, #5611) (#5668) * fix: allow saving providers without a live validator (#5565, #5567) (#5669) * fix: static model catalog for jules/linkup/ollama/searchapi search providers (#5569, #5571, #5573, #5575) (#5672) * fix: live AI/ML API catalog + deprecate dead CablyAI (#5570, #5568) (#5673) * fix: correct 404 provider setup links for ollama/searchapi/you.com (#5572, #5574, #5576) (#5674) * fix: page call_logs cleanup queries to avoid startup OOM on large DBs (#5618) (#5675) * fix: use PowerShell Expand-Archive on Windows for embedded-service install (#5590) (#5678) * fix: treat array content blocks as valid output in detectMalformedNonStream (#5559) (#5680) * fix: render memory engine status detail strings in English (#5596) (#5685) * fix: free proxy pool silent sync failure — iplocate txt + per-source isolation + surface errors (#5595) (#5686) * chore(quality): close QG v2 tail — drop orphan semcheck.yaml + Fase 9 maturity re-eval (#5681) - Remove semcheck.yaml: orphan config (zero workflow/script wiring) with stale rule counts; deterministic doc-accuracy coverage already exists (check:fabricated-docs --strict + docs-counts-sync + docs-symbols). Drop the REPOSITORY_MAP row referencing it. - Add docs/ops/MATURITY_REEVAL.md (Fase 9): re-measures maturity post-Ondas 0-3. The two biggest structural weaknesses from QUALITY_GATE_PLAYBOOK (2026-06-16) are now closed: fast-gates hole (quality.yml runs typecheck:core + impacted TIA unit tests + vitest + shards) and mutation-score-as-ratchet (check-mutation-ratchet.mjs + seeded baseline + nightly blocking job). Residual gap is owner/infra-gated (branch-protection main, SLSA L3, CodeQL advanced). - Record agent-lsp as deferred/opt-in (doc-only scaffold, no wiring). * fix(ci): stabilize nightly-mutation — guard tap.testFiles drift + anti-flake eps (#5682) Root cause (NOT a timeout): the nightly-mutation run fails on cold-cache nights because the blocking mutation-ratchet job measures modules below baseline, while warm-cache nights pass — the verdict tracked GitHub Actions cache state, not code quality. Proven via a local Stryker probe on headers.ts: covering unit tests (no-memory-header, strip-reasoning) had drifted OUT of stryker.conf.json tap.testFiles, so their mutants went covered-but-unkilled = Survived on a cold full run (COVERED score 61.73 vs 94.29 baseline); adding them restores the kills. - Add scripts/check/check-mutation-test-coverage.mjs: guards that every UNIT test importing a Stryker-mutated module is listed in tap.testFiles. Advisory by default, --strict in CI (wired in quality.yml fast-gates). Prevents recurrence. - Add the 38 drifted covering unit tests to stryker.conf.json tap.testFiles (138 -> 176). Monotonically safe: more covering tests only raise/hold the score. - Add MUTATION_RATCHET_EPS (1.0pt) anti-flake tolerance to check-mutation-ratchet so sub-point tap-runner jitter no longer false-fails the gate. Lowers no baseline. - Tests: check-mutation-test-coverage (3) + eps cases in check-mutation-ratchet. Residual: a clean post-merge nightly confirms scores return to/above baseline; any marginal residual gets a baseline re-seed (operator). * refactor(dashboard): split sidebarVisibility god-file into types + sections leaves (#5683) Behavior-preserving decomposition: src/shared/constants/sidebarVisibility.ts 1197 -> 291 LOC by extracting two leaves under sidebarVisibility/: - types.ts (160): HIDEABLE_SIDEBAR_ITEM_IDS + all sidebar types (self-contained). - sections.ts (762): section building-block consts + SIDEBAR_SECTIONS (imports types only — cycle-safe). COMPRESSION_CONTEXT_GROUP + SIDEBAR_SECTIONS stay exported; host re-exports both + 'export *' of types, so every consumer import path is unchanged. Byte-identical data verified via JSON.stringify of HIDEABLE_SIDEBAR_ITEM_IDS / SIDEBAR_ICON_ACCENTS / COMPRESSION_CONTEXT_GROUP / SIDEBAR_SECTIONS / SIDEBAR_PRESETS + getSectionItems output (identical before/after). typecheck:core, check:cycles (no cycles), check:file-size (3 files <800), and the 3 sidebar suites (20/20) pass. No logic changed. Note: file-size frozen baseline for sidebarVisibility.ts (1198) can ratchet to 291 to lock the shrink (left for the release ratchet / operator). * fix: surface fusion-specific config on the Global Routing tab (#5598) (#5688) * fix(executor): route OpenAI-compatible MCP Responses requests to /responses (#5483) Closes #5483. OpenAI-compatible providers receiving a Responses-shaped request carrying MCP / tool_search tools now route to the upstream /responses endpoint instead of downgrading to /chat/completions, preserving Codex deferred tool discovery. Detection helpers extracted to open-sse/executors/forceResponsesUpstream.ts. Thanks to @KooshaPari. * fix(ci): make release-green pre-flight gates visible + bounded so unit reds are not missed (#5644) Integrated into release/v3.8.43. * fix(body-size): raise LLM API payload limit for responses routes (#5652) Integrated into release/v3.8.43. Thanks @JxnLexn! * fix(test): use lightweight health probe for batch e2e (#5651) Integrated into release/v3.8.43. Thanks @KooshaPari! * feat(compression): T05/C5 — preserveSystemPrompt mode enum + legacy back-compat (#5653) Integrated into release/v3.8.43. Includes the legacy-boolean back-compat derivation so existing preserveSystemPrompt=false installs keep whenNoCache behavior. * routing: optimize latency strategy with perf metrics (#5629) Integrated into release/v3.8.43. Thanks @KooshaPari! * feat(db): models/5004 — self-correcting model context-window overrides (#5667) Integrated into release/v3.8.43. * feat(providers): complete SenseNova free Token Plan — chat + Text-to-Image (port from 9router#2233) (#5679) Integrated into release/v3.8.43. * feat(api): routing/4985 — configurable response-body validation + failover (#5684) Integrated into release/v3.8.43. * fix(chatcore): default Claude tool type to "custom" when missing (#5662) Integrated into release/v3.8.43. Port from 9router#2196. Co-authored-by: warelik <warelik@users.noreply.github.com> * fix(translator): merge consecutive same-role contents for Gemini (port from 9router#2191) (#5661) Integrated into release/v3.8.43. Port from 9router#2191. * chore(bun): add locked bun runtime dependency (#5615) Integrated into release/v3.8.43. Bun 1.3.10 pinned via npm lockfile (adopt-partial decision). Thanks @KooshaPari! * chore(bun): run validated ts scripts with bun (#5612) Integrated into release/v3.8.43. Thanks @KooshaPari! * chore(bun): run CI script checks with bun (#5617) Integrated into release/v3.8.43. Validated bun==node output for all 3 gates (provider-consistency, compression-budget, known-symbols). Thanks @KooshaPari! * fix(build): make pack validator bun safe (#5643) Integrated into release/v3.8.43. Forward-compat guard; node/npm path unchanged. Thanks @KooshaPari! * docs: document Bun as the allow-listed build/dev script runner (Node stays the published runtime) (#5703) Integrated into release/v3.8.43. * feat(analytics): show $0 cost for flat-rate subscription/cookie providers (#5552) (#5704) * refactor(api): extract unified-catalog helpers into cohesive leaf modules (#5699) BLOCO E2 of the god-files campaign. The module-level pure/standalone helpers in src/app/api/v1/models/catalog.ts (1611 LOC) were lifted out verbatim into five cohesive leaf modules so the catalog host shrinks toward the 800-LOC file-size cap without any behavior change (host now 1345 LOC; the heavy getUnifiedModelsResponse orchestrator is untouched — its in-function closures stay put): - catalogHelpers.ts — pure numeric/array/shape helpers + shared catalog types - catalogOpenrouter.ts — OpenRouter id/modality/free-model/display-name helpers - catalogVision.ts — vision-capability field derivation (+ isVisionModelId re-export) - catalogProviderMaps.ts — alias<->providerId resolution maps (buildAliasMaps) - catalogRequest.ts — /v1/models API-key auth gating + Codex CLI client detection The host re-exports getCustomVisionCapabilityFields and isVisionModelId so the public API consumed by other tests (llm-selector-custom-vision-models, vision-detection- consistency) is unchanged; all 9 catalog/vision suites stay green. Adds tests/unit/catalog-helpers-extraction.test.ts: characterization tests for every extracted helper + a guard asserting the host preserves its public exports. Validated: typecheck:core, 50 catalog characterization tests, 12 new leaf tests, integration-wiring, check:cycles, check:file-size (no new violations), ESLint, Prettier. * feat(mcp): T07 — expose RTK learn/discover as MCP tools (#5691) Adds two read-only MCP tools wrapping the existing RTK discovery primitives: omniroute_rtk_discover (discoverRepeatedNoise/suggestFilter over recently captured raw tool output → candidate noise patterns + suggested filter) and omniroute_rtk_learn (listRtkCommandSamples + commandToId). Scope read:compression, MCP audit-logged, no new engine logic. Regression guard: tests/unit/compression/rtk-mcp-tools.test.ts. gaps v3.8.42 — T07. * feat(compression): T05/C3 — opt-in LLM-tier compression engine (#5702) Adds an opt-in, default-off LLM-tier compression engine ('llm') that condenses non-system message prose via a pluggable chat-completion backend, mirroring the llmlingua contract. Safe by construction: no-op default backend (pass-through out of the box), not in the default stacked pipeline, enabled defaults false, fenced code blocks + system messages never sent to the model, fail-open everywhere, minTokens floor. Real production backend is a VPS-validated follow-up (Hard Rule #18). Regression guard: tests/unit/compression/llm-compressor-engine.test.ts (8). gaps v3.8.42 — T05/C3. * refactor(db): extract compat/aliases/mitm helpers from db/models.ts into leaf modules (#5705) BLOCO E3 of the god-files campaign. db/models.ts (1250 LOC) mixed six concerns; the three cleanly-separable ones plus the shared key_value helpers were lifted out verbatim into a new src/lib/db/models/ subdirectory, leaving the tightly-coupled custom/synced/ flags trio in the host (host now 936 LOC). The host re-exports every moved public symbol so the module's public API (consumed by ~29 test files + localDb) is unchanged. - models/shared.ts — asRecord / toNonEmptyString / getKeyValue + JsonRecord (19 LOC) - models/compat.ts — model-compat overrides + sanitizeUpstreamHeadersMap (249 LOC) - models/aliases.ts — model-alias CRUD + cascade delete (61 LOC) - models/mitmAlias.ts — MITM alias get/set (32 LOC) The custom/synced/flags trio stays in the host because it is genuinely coupled (flags->getCustomModelRow, flags->readCompatList, custom->removeModelCompatOverride, synced->getModelIsDeleted, setModelIsHidden->updateCustomModel) — splitting it cleanly is a follow-up. Dependency DAG is acyclic (verified by check:cycles). Adds tests/unit/db-models-split.test.ts: characterization of the pure extracted helpers + a guard asserting the host preserves its full public export surface. Validated: typecheck:core, check:cycles (no cycles), 77 existing db/models consumer tests (db-models-crud/extended/aliases-cascade + 7 more) green, 7 new tests, ESLint, Prettier, check:file-size (host 936 < frozen 1259; no new violations). * refactor(db): extract pricing/lkgp/cache-metrics from db/settings.ts into leaf modules (#5709) BLOCO E3 of the god-files campaign. db/settings.ts (1154 LOC) mixed five concerns; the three cleanly-separable ones plus the shared toRecord/JsonRecord helper were lifted out verbatim into a new src/lib/db/settings/ subdirectory, leaving the Settings-core + Proxy config concerns in the host (host now 646 LOC). The host re-exports every moved public symbol so the module's public API (consumed by ~93 test files + localDb) is unchanged. - settings/shared.ts — toRecord + JsonRecord (9 LOC) - settings/pricing.ts — pricing layers/sources/per-model + update/reset (254 LOC) - settings/lkgp.ts — Last-Known-Good-Provider get/set/clear (49 LOC) - settings/cacheMetrics.ts — cache metrics + trend (235 LOC) Settings-core + the Proxy-config concern stay in the host: proxy is the most tangled (245-line resolveProxyForConnection, resolution cache, imports from ./proxies) and getSettings is the most central function — leaving them is the correct coupled-core stop. Pricing/LKGP/Cache have NO dependency on Settings/Proxy helpers (verified); the dependency DAG is acyclic (check:cycles). Adds tests/unit/db-settings-split.test.ts: characterization of the shared toRecord helper + a guard asserting the host preserves its full public export surface. Validated: typecheck:core, check:cycles (no cycles), 149 existing+new db/settings consumer tests green (db-settings-crud/extended, 8 pricing suites, cache-metrics, 2 proxy-resolution suites + 29 new), ESLint, Prettier, check:file-size (host 646 < frozen 1155). * fix(translator): re-apply lost defensive hardening for Gemini merge + Claude tool defaults (#5706) Re-applies two dropped gemini-code-assist hardening fixes (defaultClaudeToolType non-object passthrough; mergeConsecutiveSameRoleContents shallow-copy) with regression tests. Follow-up to #5661/#5662. Integrated into release/v3.8.43. * feat(codex): generate fallback profiles for compatible models (#5701) setup-codex now generates Codex profiles for compatible text models from the live /v1/models catalog when the model id doesn't match a hand-tuned pattern, skipping media/embedding models. Integrated into release/v3.8.43. * docs(changelog): credit @Chewji9875 for #5563 + #5579 Add CHANGELOG credit bullets for grok-cli tool-limit (#5563) and Antigravity 429 lockout (#5579). Documentation-only. * test(dashboard): repoint sidebar quota-share placement scan to sections.ts (#5711) The D1 god-file split (#5683) moved the nav-item id definitions out of src/shared/constants/sidebarVisibility.ts into the extracted leaf src/shared/constants/sidebarVisibility/sections.ts. This source-scan test still read the old monolith path, so it found 0 occurrences of id: "costs-quota-share" and failed (base-red on release/v3.8.43). Repoint SIDEBAR_PATH to sections.ts where the ids now live. All four placement assertions (quota-share after quota, same array, far from costs-budget, exactly one occurrence) hold against the new source. * refactor(db): extract columns/nodes/rate-limit leaves from db/providers.ts (#5714) db/providers.ts was a 1106-line god-file mixing four concerns. Extract the three acyclic, cohesive slices into sibling leaf modules under src/lib/db/providers/, leaving the tightly-coupled connection-CRUD core in the host: - providers/columns.ts (116) 10 pure column-normalizer helpers (DB-free) - providers/nodes.ts (163) 6 provider-node CRUD functions - providers/rateLimit.ts (177) 6 rate-limit/quota runtime helpers + formatResetCountdown Host providers.ts: 1106 -> 719 lines. The connection-CRUD core does not call any node or rate-limit function (verified), so the host re-exports the 12 moved public symbols via `export { ... } from './providers/<leaf>'` — the module's public API stays IDENTICAL (23 symbols). Bodies moved verbatim (byte-identical); the only edit to a moved line is the added `export` on the 10 previously-private normalizers. Behavior-preserving: 122 existing provider/quota/rate-limit consumer tests stay green; new tests/unit/db-providers-split.test.ts guards the re-export barrel + characterizes the pure column helpers (38 assertions). Refs #3501 (god-file structural shrink). * refactor(db): extract types + pure mappers from db/proxies.ts (#5717) db/proxies.ts was a 1059-line god-file. Extract the two acyclic, DB-free slices into sibling leaf modules under src/lib/db/proxies/, leaving the tightly-coupled CRUD + assignment + resolution core in the host: - proxies/types.ts (65) 10 proxy type/interface declarations - proxies/mappers.ts (180) pure row mappers / scope normalizers / payload coercers (toRecord, mapProxyRow, mapAssignmentRow, isRelayProxyType, extractRelayAuth, toRegistryProxyResolution, normalizeScope, normalizeAssignmentScopeId, toLegacyProxyLevel, coerceProxyPayload, redactProxySecrets) Host proxies.ts: 1059 -> 847 lines. The resolution functions call createProxy/assignProxyToScope, so the CRUD+resolution core CANNOT be extracted without an import cycle and stays in the host. The host re-exports the 2 moved public functions (extractRelayAuth, redactProxySecrets) via `export { ... } from './proxies/mappers'` — the public API stays IDENTICAL (20 functions; no types were ever publicly exported). Bodies moved verbatim; the only host edits are the new leaf imports, the re-export, dropping the now unused `import { decrypt }`, and two prettier line-wrap reflows of retained ternary/union lines (token-identical). Behavior-preserving: 69 existing proxy/registry/relay/family consumer tests stay green; new tests/unit/db-proxies-split.test.ts guards the re-export barrel + characterizes the pure mappers (35 assertions). Refs #3501. * refactor(db): extract static migration data tables from migrationRunner.ts (#5721) migrationRunner.ts (1124 lines, frozen-baselined) is the startup migration orchestrator. As a conservative, zero-behaviour-risk first slice, extract the six static migration-compatibility DATA tables (verbatim) into a pure-data leaf, leaving the entire orchestrator + all SQL-running helpers in the host: - migrationRunner/constants.ts (118) RENAMED_MIGRATION_COMPATIBILITY, LEGACY_VERSION_SLOT_MIGRATIONS, SUPERSEDED_DUPLICATE_MIGRATIONS, PHYSICAL_SCHEMA_SENTINELS, INITIAL_SCHEMA_SENTINELS, OPTIONAL_FTS5_MIGRATION_VERSIONS Host migrationRunner.ts: 1124 -> 1023. The runtime fts5SupportCache (a WeakMap, mutable state) stays in the host. No public API change (these consts were module-internal). Data moved byte-ident…
…te-url-substring-sanitization diegosouzapw#674) (diegosouzapw#5129) The anthropic-compatible Bearer-fallback gate decided whether a configured baseUrl targeted the official api.anthropic.com host via a substring `.includes("api.anthropic.com")`. A look-alike upstream such as `https://api.anthropic.com.evil.test` or `https://evil.test/?x=api.anthropic.com` matched the substring and was wrongly treated as official, suppressing the Bearer fallback meant for third-party gateways (CodeQL diegosouzapw#674, js/incomplete-url-substring-sanitization, high). Replace the substring test with an exported `isOfficialAnthropicBaseUrl()` helper that parses the URL and compares the hostname for exact equality. Empty baseUrl stays official; scheme-less hosts are parsed with an assumed https://; an unparseable baseUrl falls back to third-party (Bearer emitted) as the safer default. Behavior for legitimate official/third-party baseUrls is unchanged. Adds tests/unit/anthropic-official-baseurl-host.test.ts covering official, look-alike, scheme-less, and unparseable inputs plus a static guard that the substring pattern is gone.
* chore(release): open v3.8.39 development cycle * docs(changelog): backfill 5 v3.8.38 bullets merged after release finalize These PRs squash-merged into release/v3.8.38 between the CHANGELOG finalize (f6aa6c3) and the merge-to-main (e2efe2c), so they shipped in the v3.8.38 tag but had no bullet: - feat(compression): Ionizer engine (lossy JSON-array sampling + CCR) (diegosouzapw#5148) - fix(sse): preserve non-stream reasoning fields (diegosouzapw#5155, @rdself) - fix(i18n): add missing English UI labels (diegosouzapw#5153, @rdself) - test(combo): gated live smoke (diegosouzapw#5151) + release-expectations refresh (diegosouzapw#5150, @KooshaPari) (diegosouzapw#5129 exact-host Anthropic baseUrl is already covered by the diegosouzapw#5130 bullet — same CodeQL diegosouzapw#674.) Synced 41 i18n CHANGELOG mirrors. * feat(compression): TOON best-of-N candidate encoder + encoder A/B table (diegosouzapw#5163) Integrated into release/v3.8.39. TOON best-of-N candidate encoder (GCF default, fail-open). 17/17 unit tests pass on merge result; CI reds were base-stale + Quality Ratchet DRIFT. * fix(zenmux): normalize vendor-prefixed GLM system roles (diegosouzapw#5158) Integrated into release/v3.8.39. ZenMux vendor-prefixed GLM system-role normalization; 12/12 role-normalizer tests pass on merge result. CI reds base-stale. * [codex] fix xAI OAuth test and reasoning effort (diegosouzapw#5157) Integrated into release/v3.8.39. xAI reasoning-effort normalization (max/xhigh→high) + OAuth test config; 46/46 xai-translator tests pass on merge result. CI reds base-stale. * docs(i18n): add Traditional Chinese (zh-TW) README and update zh-CN to latest (diegosouzapw#5162) Integrated into release/v3.8.39. Traditional Chinese (zh-TW) README + zh-CN refresh; docs-only. * test(security): guard PII redaction stays opt-in (default off) + Hard Rule diegosouzapw#20 (diegosouzapw#5159) Integrated into release/v3.8.39. PII opt-in regression guard + Hard Rule diegosouzapw#20; rebased to strip base-drift (+81/-1). 5/5 guard tests pass; flip-proof verified. * test(combo): deterministic context-relay universal-handoff coverage (closes phase-2 TODO) (diegosouzapw#5168) Integrated into release/v3.8.39. Deterministic context-relay universal-handoff coverage (3 tests); 3/3 pass on merge result. * docs(i18n): full sync zh-TW and zh-CN README with canonical English v3.8.39 (diegosouzapw#5171) Integrated into release/v3.8.39. Full zh-TW docs tree + zh-CN sync with canonical English v3.8.39; docs-only. * fix(serve): honour HOSTNAME from .env instead of hardcoding 0.0.0.0 (diegosouzapw#5134) (diegosouzapw#5170) Integrated into release/v3.8.39. HOSTNAME env override in serve (diegosouzapw#5134) + regression test (4/4, TDD flip-proof verified). * fix(sse): resolve nameless deepseek-web tool blocks via parameter-schema match (diegosouzapw#5154) (diegosouzapw#5173) Integrated into release/v3.8.39. Schema-based nameless deepseek-web tool-block resolution (diegosouzapw#5154); 6/6 tests pass on merge result (incl. ambiguous/no-match negatives + named-tag no-regression). * fix(sse): normalize array user content for Command Code to avoid upstream 400 (diegosouzapw#5166) (diegosouzapw#5174) Integrated into release/v3.8.39. Normalize array user content for Command Code (diegosouzapw#5166, user-array/400 symptom); 4/4 tests pass on merge result. * fix(sse): defer </think> close so it never leaks before tool_calls (diegosouzapw#5123) (diegosouzapw#5175) Integrated into release/v3.8.39. Defer </think> close so it never leaks before tool_calls (diegosouzapw#5123); 4/4 tests pass (incl. diegosouzapw#4633 no-regression). CHANGELOG synced to keep all 3 v3.8.39 fixes. * fix(dashboard): use amber for home update-step warning icon (diegosouzapw#5176) Integrated into release/v3.8.39. Amber for home update-step warning icon; 1/1 UI test. * fix(api): LAN/Tailscale dashboard — host-aware CSP + GET-exempt version route + combo field errors (diegosouzapw#5083) (diegosouzapw#5177) Integrated into release/v3.8.39. Host-aware CSP (ReDoS/injection-safe host validation) + GET-exempt /api/system/version (POST/spawn stays LOCAL_ONLY, exact-match safe-methods-only) + COMBO_002 firstField. 44/44 tests + route-guard membership gate green. CHANGELOG synced to keep all 4 v3.8.39 fixes. * fix(api): replace diegosouzapw#5083 global middleware CSP with declarative ws: scheme (diegosouzapw#5083) Follow-up to PR diegosouzapw#5177 (merged): that version implemented the LAN-CSP fix (Bug 1) with a new global `src/middleware.ts` + `src/server/csp.ts`, which contradicts the project's documented architecture — 'No global Next.js middleware — interception is route-specific' (CLAUDE.md / AGENTS.md) — and was merged unverified (middleware vs next.config header precedence was never confirmed in a real build). This replaces that approach with the minimal, declarative equivalent: • next.config.mjs: connect-src now permits the bare `ws:` scheme (symmetric with the bare `wss:` already allowed) so the dashboard can reach its own Live WS server from a LAN/Tailscale host. No middleware. • Removes src/middleware.ts, src/server/csp.ts, and tests/unit/csp-host-aware.test.ts. • Adds tests/unit/csp-lan-ws-5083.test.ts (incl. a guard asserting src/middleware.ts does NOT exist, so the global-middleware approach cannot silently return). Bugs 2 (GET-exempt /api/system/version) and 3 (COMBO_002 field surfacing) from diegosouzapw#5177 are unaffected and remain in place. Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com> * test(combo): end-to-end quota-share DRR routing-decision coverage (matrix parity) (diegosouzapw#5179) Integrated into release/v3.8.39. Quota-share DRR routing-decision coverage (matrix parity); 2/2 pass on merge result. * feat(agent-bridge): graceful cert-install fallback with manual guide for containers (diegosouzapw#4546) (diegosouzapw#5178) Integrated into release/v3.8.39. Agent-bridge graceful cert-install fallback + manual guide (diegosouzapw#4546); 6/6 tests pass on merge result. * fix(antigravity): family-scoped quota lockout (gemini/claude buckets) (diegosouzapw#5180) Integrated into release/v3.8.39 — family-scoped antigravity quota lockout. Rebased from v3.8.37 + validated (vitest 5/5, typecheck clean, full combo-matrix green, model-lockout 99/0). Same-model cross-account retry (chat.ts) deferred pending live antigravity VPS validation. * fix(cli): force NODE_ENV to match dev/start run mode in custom Next server (diegosouzapw#5189) Integrated into release/v3.8.39. Force NODE_ENV to match dev/start run mode in custom Next server; 2/2 source-scan+ordering tests pass on merge result. * feat(compression): CCR ranged/grep/stats retrieval (ReDoS-safe, backward-compat) (diegosouzapw#5187) Integrated into release/v3.8.39. CCR ranged/grep/stats retrieval (safe-regex ReDoS guard + length/match caps); 17/17 tests pass on merge result. * docs(combo): sync all combo/routing-strategy docs to current state + document test coverage (diegosouzapw#5185) Integrated into release/v3.8.39. Combo/routing-strategy docs sync; docs-only. * fix(mcp): return 404 (not 400) for unknown Streamable HTTP session id (diegosouzapw#5169) (diegosouzapw#5191) * fix(api): respect blocked Auto (Zero-Config) provider in /v1/models catalog (diegosouzapw#5192) (diegosouzapw#5194) * test(combo): deterministic context-relay codex quota-handoff coverage (closes last gap) (diegosouzapw#5195) * test(ci): wire antigravity-quota-family under test:vitest (fix test-discovery orphan) (diegosouzapw#5196) * fix(oauth): antigravity login no longer hangs — fire-and-forget onboarding + bounded post-exchange (diegosouzapw#5193) Antigravity OAuth hang fix (no-PKCE/no-openid + bounded post-exchange + exchange-500 fix). Includes diegosouzapw#5200 (Koosha) revert + owner rebaseline to keep documented comments. Integrated into release/v3.8.39. * feat(oauth): remote Antigravity login via local helper + paste-credentials (diegosouzapw#5203) Remote Antigravity login: local helper (omniroute login antigravity) + paste-credentials. Integrated into release/v3.8.39. * fix(translator): accept Claude Messages shape in non-stream malformed-200 guard (diegosouzapw#5156) Integrated into release/v3.8.39 * fix(cli): default dev bundler to Turbopack (16.2.x panic no longer reproduces) (diegosouzapw#5206) Integrated into release/v3.8.39 * fix(cli): auto-calibrate server V8 heap from physical RAM (diegosouzapw#5172) (diegosouzapw#5213) The server was spawned with a fixed --max-old-space-size=512 (omniroute serve) or no heap flag at all (Electron), so RAM-rich boxes still OOM-crashed under load (Ineffective mark-compacts near heap limit ~500MB) with many providers/ accounts and large model catalogs. New calibrateHeapFallbackMb(os.totalmem()) defaults the heap to ~35% of RAM clamped [512,4096], wired into serve.mjs and electron/main.js. Explicit OMNIROUTE_MEMORY_MB still wins (diegosouzapw#2939 unchanged). Also addresses diegosouzapw#5160 (same OOM root); diegosouzapw#5152 (docker) benefits via the same knob. Closes diegosouzapw#5172 * fix(proxy): coalesce fast-fail health probes (diegosouzapw#5208) Integrated into release/v3.8.39 * fix(proxy): close dispatchers when clearing cache (diegosouzapw#5202) Integrated into release/v3.8.39 * fix(cli): raise dev server Node heap limit to 8GB to prevent OOM (diegosouzapw#5198) Integrated into release/v3.8.39 * fix(auth): allow synthetic no-auth fallback for mimocode (diegosouzapw#5205) Integrated into release/v3.8.39 * fix(oauth): preserve Antigravity refresh_token on empty/omitted upstream response (diegosouzapw#3850) (diegosouzapw#5214) Google's OAuth refresh tokens are non-rotating: the refresh response usually omits refresh_token and occasionally returns it as an empty string. The Antigravity executor used `typeof tokens.refresh_token === "string" ? ... ` which accepts "" (typeof "" === "string") and overwrote the stored token with empty, nulling it on first refresh. Now treats non-string OR empty as absent and preserves credentials.refreshToken, matching refreshGoogleToken semantics. Closes diegosouzapw#3850 * fix(responses): normalize non-array input (diegosouzapw#5204) Integrated into release/v3.8.39 * fix(stream): normalize safety finish reasons via shared helper (diegosouzapw#5197) Integrated into release/v3.8.39 * fix(request-logger): never render negative '(-100%)' compression badge (diegosouzapw#5201) Integrated into release/v3.8.39 * fix(combo): reject empty responses api output (diegosouzapw#5207) Integrated into release/v3.8.39 — combo failover now rejects empty Responses API output (validateQuality). Baseline rebaseline dropped (main-measured drift; maintainer rebaselines at release). * fix(pwa): prefer cached navigation before offline page (diegosouzapw#5209) Integrated into release/v3.8.39 — PWA service worker prefers cached navigation before offline page (diegosouzapw#5165). * chore(release): v3.8.39 — 2026-06-28 * chore(release): rebaseline openapi+i18n coverage ratchet drift for v3.8.39 --------- Co-authored-by: Arthur Bodera <abodera@gmail.com> Co-authored-by: Nguyen Minh <lop123thcs@gmail.com> Co-authored-by: lunkerchen <labanchen@gmail.com> Co-authored-by: Ankit <177378174+anki1kr@users.noreply.github.com> Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com> Co-authored-by: Ardem2025 <ardemb22@gmail.com> Co-authored-by: backryun <bakryun0718@proton.me> Co-authored-by: Anton <39598727+NomenAK@users.noreply.github.com> Co-authored-by: KooshaPari <42529354+KooshaPari@users.noreply.github.com> Co-authored-by: Wilson <pedbookmed@gmail.com> Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
…te-url-substring-sanitization diegosouzapw#674) (diegosouzapw#5129) The anthropic-compatible Bearer-fallback gate decided whether a configured baseUrl targeted the official api.anthropic.com host via a substring `.includes("api.anthropic.com")`. A look-alike upstream such as `https://api.anthropic.com.evil.test` or `https://evil.test/?x=api.anthropic.com` matched the substring and was wrongly treated as official, suppressing the Bearer fallback meant for third-party gateways (CodeQL diegosouzapw#674, js/incomplete-url-substring-sanitization, high). Replace the substring test with an exported `isOfficialAnthropicBaseUrl()` helper that parses the URL and compares the hostname for exact equality. Empty baseUrl stays official; scheme-less hosts are parsed with an assumed https://; an unparseable baseUrl falls back to third-party (Bearer emitted) as the safer default. Behavior for legitimate official/third-party baseUrls is unchanged. Adds tests/unit/anthropic-official-baseurl-host.test.ts covering official, look-alike, scheme-less, and unparseable inputs plus a static guard that the substring pattern is gone.
* chore(release): open v3.8.39 development cycle * docs(changelog): backfill 5 v3.8.38 bullets merged after release finalize These PRs squash-merged into release/v3.8.38 between the CHANGELOG finalize (ff57be3) and the merge-to-main (ae6e234), so they shipped in the v3.8.38 tag but had no bullet: - feat(compression): Ionizer engine (lossy JSON-array sampling + CCR) (diegosouzapw#5148) - fix(sse): preserve non-stream reasoning fields (diegosouzapw#5155, @rdself) - fix(i18n): add missing English UI labels (diegosouzapw#5153, @rdself) - test(combo): gated live smoke (diegosouzapw#5151) + release-expectations refresh (diegosouzapw#5150, @KooshaPari) (diegosouzapw#5129 exact-host Anthropic baseUrl is already covered by the diegosouzapw#5130 bullet — same CodeQL diegosouzapw#674.) Synced 41 i18n CHANGELOG mirrors. * feat(compression): TOON best-of-N candidate encoder + encoder A/B table (diegosouzapw#5163) Integrated into release/v3.8.39. TOON best-of-N candidate encoder (GCF default, fail-open). 17/17 unit tests pass on merge result; CI reds were base-stale + Quality Ratchet DRIFT. * fix(zenmux): normalize vendor-prefixed GLM system roles (diegosouzapw#5158) Integrated into release/v3.8.39. ZenMux vendor-prefixed GLM system-role normalization; 12/12 role-normalizer tests pass on merge result. CI reds base-stale. * [codex] fix xAI OAuth test and reasoning effort (diegosouzapw#5157) Integrated into release/v3.8.39. xAI reasoning-effort normalization (max/xhigh→high) + OAuth test config; 46/46 xai-translator tests pass on merge result. CI reds base-stale. * docs(i18n): add Traditional Chinese (zh-TW) README and update zh-CN to latest (diegosouzapw#5162) Integrated into release/v3.8.39. Traditional Chinese (zh-TW) README + zh-CN refresh; docs-only. * test(security): guard PII redaction stays opt-in (default off) + Hard Rule diegosouzapw#20 (diegosouzapw#5159) Integrated into release/v3.8.39. PII opt-in regression guard + Hard Rule diegosouzapw#20; rebased to strip base-drift (+81/-1). 5/5 guard tests pass; flip-proof verified. * test(combo): deterministic context-relay universal-handoff coverage (closes phase-2 TODO) (diegosouzapw#5168) Integrated into release/v3.8.39. Deterministic context-relay universal-handoff coverage (3 tests); 3/3 pass on merge result. * docs(i18n): full sync zh-TW and zh-CN README with canonical English v3.8.39 (diegosouzapw#5171) Integrated into release/v3.8.39. Full zh-TW docs tree + zh-CN sync with canonical English v3.8.39; docs-only. * fix(serve): honour HOSTNAME from .env instead of hardcoding 0.0.0.0 (diegosouzapw#5134) (diegosouzapw#5170) Integrated into release/v3.8.39. HOSTNAME env override in serve (diegosouzapw#5134) + regression test (4/4, TDD flip-proof verified). * fix(sse): resolve nameless deepseek-web tool blocks via parameter-schema match (diegosouzapw#5154) (diegosouzapw#5173) Integrated into release/v3.8.39. Schema-based nameless deepseek-web tool-block resolution (diegosouzapw#5154); 6/6 tests pass on merge result (incl. ambiguous/no-match negatives + named-tag no-regression). * fix(sse): normalize array user content for Command Code to avoid upstream 400 (diegosouzapw#5166) (diegosouzapw#5174) Integrated into release/v3.8.39. Normalize array user content for Command Code (diegosouzapw#5166, user-array/400 symptom); 4/4 tests pass on merge result. * fix(sse): defer </think> close so it never leaks before tool_calls (diegosouzapw#5123) (diegosouzapw#5175) Integrated into release/v3.8.39. Defer </think> close so it never leaks before tool_calls (diegosouzapw#5123); 4/4 tests pass (incl. diegosouzapw#4633 no-regression). CHANGELOG synced to keep all 3 v3.8.39 fixes. * fix(dashboard): use amber for home update-step warning icon (diegosouzapw#5176) Integrated into release/v3.8.39. Amber for home update-step warning icon; 1/1 UI test. * fix(api): LAN/Tailscale dashboard — host-aware CSP + GET-exempt version route + combo field errors (diegosouzapw#5083) (diegosouzapw#5177) Integrated into release/v3.8.39. Host-aware CSP (ReDoS/injection-safe host validation) + GET-exempt /api/system/version (POST/spawn stays LOCAL_ONLY, exact-match safe-methods-only) + COMBO_002 firstField. 44/44 tests + route-guard membership gate green. CHANGELOG synced to keep all 4 v3.8.39 fixes. * fix(api): replace diegosouzapw#5083 global middleware CSP with declarative ws: scheme (diegosouzapw#5083) Follow-up to PR diegosouzapw#5177 (merged): that version implemented the LAN-CSP fix (Bug 1) with a new global `src/middleware.ts` + `src/server/csp.ts`, which contradicts the project's documented architecture — 'No global Next.js middleware — interception is route-specific' (CLAUDE.md / AGENTS.md) — and was merged unverified (middleware vs next.config header precedence was never confirmed in a real build). This replaces that approach with the minimal, declarative equivalent: • next.config.mjs: connect-src now permits the bare `ws:` scheme (symmetric with the bare `wss:` already allowed) so the dashboard can reach its own Live WS server from a LAN/Tailscale host. No middleware. • Removes src/middleware.ts, src/server/csp.ts, and tests/unit/csp-host-aware.test.ts. • Adds tests/unit/csp-lan-ws-5083.test.ts (incl. a guard asserting src/middleware.ts does NOT exist, so the global-middleware approach cannot silently return). Bugs 2 (GET-exempt /api/system/version) and 3 (COMBO_002 field surfacing) from diegosouzapw#5177 are unaffected and remain in place. Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com> * test(combo): end-to-end quota-share DRR routing-decision coverage (matrix parity) (diegosouzapw#5179) Integrated into release/v3.8.39. Quota-share DRR routing-decision coverage (matrix parity); 2/2 pass on merge result. * feat(agent-bridge): graceful cert-install fallback with manual guide for containers (diegosouzapw#4546) (diegosouzapw#5178) Integrated into release/v3.8.39. Agent-bridge graceful cert-install fallback + manual guide (diegosouzapw#4546); 6/6 tests pass on merge result. * fix(antigravity): family-scoped quota lockout (gemini/claude buckets) (diegosouzapw#5180) Integrated into release/v3.8.39 — family-scoped antigravity quota lockout. Rebased from v3.8.37 + validated (vitest 5/5, typecheck clean, full combo-matrix green, model-lockout 99/0). Same-model cross-account retry (chat.ts) deferred pending live antigravity VPS validation. * fix(cli): force NODE_ENV to match dev/start run mode in custom Next server (diegosouzapw#5189) Integrated into release/v3.8.39. Force NODE_ENV to match dev/start run mode in custom Next server; 2/2 source-scan+ordering tests pass on merge result. * feat(compression): CCR ranged/grep/stats retrieval (ReDoS-safe, backward-compat) (diegosouzapw#5187) Integrated into release/v3.8.39. CCR ranged/grep/stats retrieval (safe-regex ReDoS guard + length/match caps); 17/17 tests pass on merge result. * docs(combo): sync all combo/routing-strategy docs to current state + document test coverage (diegosouzapw#5185) Integrated into release/v3.8.39. Combo/routing-strategy docs sync; docs-only. * fix(mcp): return 404 (not 400) for unknown Streamable HTTP session id (diegosouzapw#5169) (diegosouzapw#5191) * fix(api): respect blocked Auto (Zero-Config) provider in /v1/models catalog (diegosouzapw#5192) (diegosouzapw#5194) * test(combo): deterministic context-relay codex quota-handoff coverage (closes last gap) (diegosouzapw#5195) * test(ci): wire antigravity-quota-family under test:vitest (fix test-discovery orphan) (diegosouzapw#5196) * fix(oauth): antigravity login no longer hangs — fire-and-forget onboarding + bounded post-exchange (diegosouzapw#5193) Antigravity OAuth hang fix (no-PKCE/no-openid + bounded post-exchange + exchange-500 fix). Includes diegosouzapw#5200 (Koosha) revert + owner rebaseline to keep documented comments. Integrated into release/v3.8.39. * feat(oauth): remote Antigravity login via local helper + paste-credentials (diegosouzapw#5203) Remote Antigravity login: local helper (omniroute login antigravity) + paste-credentials. Integrated into release/v3.8.39. * fix(translator): accept Claude Messages shape in non-stream malformed-200 guard (diegosouzapw#5156) Integrated into release/v3.8.39 * fix(cli): default dev bundler to Turbopack (16.2.x panic no longer reproduces) (diegosouzapw#5206) Integrated into release/v3.8.39 * fix(cli): auto-calibrate server V8 heap from physical RAM (diegosouzapw#5172) (diegosouzapw#5213) The server was spawned with a fixed --max-old-space-size=512 (omniroute serve) or no heap flag at all (Electron), so RAM-rich boxes still OOM-crashed under load (Ineffective mark-compacts near heap limit ~500MB) with many providers/ accounts and large model catalogs. New calibrateHeapFallbackMb(os.totalmem()) defaults the heap to ~35% of RAM clamped [512,4096], wired into serve.mjs and electron/main.js. Explicit OMNIROUTE_MEMORY_MB still wins (diegosouzapw#2939 unchanged). Also addresses diegosouzapw#5160 (same OOM root); diegosouzapw#5152 (docker) benefits via the same knob. Closes diegosouzapw#5172 * fix(proxy): coalesce fast-fail health probes (diegosouzapw#5208) Integrated into release/v3.8.39 * fix(proxy): close dispatchers when clearing cache (diegosouzapw#5202) Integrated into release/v3.8.39 * fix(cli): raise dev server Node heap limit to 8GB to prevent OOM (diegosouzapw#5198) Integrated into release/v3.8.39 * fix(auth): allow synthetic no-auth fallback for mimocode (diegosouzapw#5205) Integrated into release/v3.8.39 * fix(oauth): preserve Antigravity refresh_token on empty/omitted upstream response (diegosouzapw#3850) (diegosouzapw#5214) Google's OAuth refresh tokens are non-rotating: the refresh response usually omits refresh_token and occasionally returns it as an empty string. The Antigravity executor used `typeof tokens.refresh_token === "string" ? ... ` which accepts "" (typeof "" === "string") and overwrote the stored token with empty, nulling it on first refresh. Now treats non-string OR empty as absent and preserves credentials.refreshToken, matching refreshGoogleToken semantics. Closes diegosouzapw#3850 * fix(responses): normalize non-array input (diegosouzapw#5204) Integrated into release/v3.8.39 * fix(stream): normalize safety finish reasons via shared helper (diegosouzapw#5197) Integrated into release/v3.8.39 * fix(request-logger): never render negative '(-100%)' compression badge (diegosouzapw#5201) Integrated into release/v3.8.39 * fix(combo): reject empty responses api output (diegosouzapw#5207) Integrated into release/v3.8.39 — combo failover now rejects empty Responses API output (validateQuality). Baseline rebaseline dropped (main-measured drift; maintainer rebaselines at release). * fix(pwa): prefer cached navigation before offline page (diegosouzapw#5209) Integrated into release/v3.8.39 — PWA service worker prefers cached navigation before offline page (diegosouzapw#5165). * chore(release): v3.8.39 — 2026-06-28 * chore(release): rebaseline openapi+i18n coverage ratchet drift for v3.8.39 --------- Co-authored-by: Arthur Bodera <abodera@gmail.com> Co-authored-by: Nguyen Minh <lop123thcs@gmail.com> Co-authored-by: lunkerchen <labanchen@gmail.com> Co-authored-by: Ankit <177378174+anki1kr@users.noreply.github.com> Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com> Co-authored-by: Ardem2025 <ardemb22@gmail.com> Co-authored-by: backryun <bakryun0718@proton.me> Co-authored-by: Anton <39598727+NomenAK@users.noreply.github.com> Co-authored-by: KooshaPari <42529354+KooshaPari@users.noreply.github.com> Co-authored-by: Wilson <pedbookmed@gmail.com> Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
…te-url-substring-sanitization #674) (diegosouzapw#5129)
Reconcile the security hotfix diegosouzapw#5129 (exact-host Anthropic baseUrl check, CodeQL js/incomplete-url-substring-sanitization diegosouzapw#674) landed on main into the active release branch. Only conflict: open-sse/executors/default.ts import block. Resolved to the release superset — release already independently carries the diegosouzapw#5129 logic (isOfficialAnthropicBaseUrl import + exact-host check, auto-merged) and adds normalizeAnthropicHeaderVariants on top, so both imports are kept. No security change is dropped; anthropicHost.ts and its test are byte-identical on both branches.
…lize These PRs squash-merged into release/v3.8.38 between the CHANGELOG finalize (f6aa6c3) and the merge-to-main (e2efe2c), so they shipped in the v3.8.38 tag but had no bullet: - feat(compression): Ionizer engine (lossy JSON-array sampling + CCR) (diegosouzapw#5148) - fix(sse): preserve non-stream reasoning fields (diegosouzapw#5155, @rdself) - fix(i18n): add missing English UI labels (diegosouzapw#5153, @rdself) - test(combo): gated live smoke (diegosouzapw#5151) + release-expectations refresh (diegosouzapw#5150, @KooshaPari) (diegosouzapw#5129 exact-host Anthropic baseUrl is already covered by the diegosouzapw#5130 bullet — same CodeQL diegosouzapw#674.) Synced 41 i18n CHANGELOG mirrors.
CodeQL alert #674 —
js/incomplete-url-substring-sanitization(high)Location:
open-sse/executors/default.ts(anthropic-compatible Bearer-fallback gate)Problem
The check that decides whether a configured
baseUrltargets the officialapi.anthropic.comhost used a substring match:A look-alike upstream like
https://api.anthropic.com.evil.testorhttps://evil.test/?x=api.anthropic.commatches the substring and is wrongly treated as official, suppressing theAuthorization: Bearerfallback that third-party Anthropic-compatible gateways require.Fix
Replace it with an exported
isOfficialAnthropicBaseUrl()helper that parses the URL and compares the hostname for exact equality (same house pattern used for the priorjs/incomplete-url-substring-sanitizationfixes #658/#659/#660):api.anthropic.com/v1) → parsed with assumedhttps://Behavior for all legitimate official/third-party baseUrls is unchanged.
Tests (Rule #18 TDD)
tests/unit/anthropic-official-baseurl-host.test.ts— official / look-alike / scheme-less / unparseable inputs + a static guard asserting the substring pattern is gone (RED→GREEN regression guard).Validation
node --testnew suite: 4/4 ✅npm run typecheck:core: clean ✅eslint(changed files): clean ✅Closes the alert on
mainafter merge + JS/TS re-scan. Cherry-picked torelease/v3.8.38in a follow-up PR.