fix(oauth): antigravity login no longer hangs — fire-and-forget onboarding + bounded post-exchange - #5193
Conversation
…rding + bounded post-exchange The dashboard antigravity OAuth login 'just spun forever': postExchange `await`ed the onboardUser retry loop (up to 10×5s, each fetch un-timed) inline, so a slow/unreachable Antigravity upstream blocked the /exchange response indefinitely. Compared against the working 9router web flow, which runs onboarding fire-and-forget. Fix: - onboardUser now runs in the background (void + .catch) — it never gates the OAuth login response. Project onboarding is also done lazily at request time by antigravityProjectBootstrap.ts, so backgrounding it is safe. - userInfo + loadCodeAssist are AbortSignal.timeout(8s)-bounded (one shared deadline per fallback list, not per-endpoint) so a stalled upstream can never hang the login — worst case ~16s, never infinite. Validated by tests/unit/antigravity-oauth-postexchange-nonblocking.test.ts (2/2): postExchange returns in ~96ms with onboarding gated open (flip-proof: reverting to inline await hangs the test), and stays timeout-bounded when upstreams stall. Pending live VPS validation of the real login flow.
|
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 potential hang in the Antigravity OAuth login flow by making the onboarding process fire-and-forget and bounding all upstream requests with an 8-second timeout. It also adds unit tests to guard against regressions. The reviewer feedback highlights a potential crash if the user info response contains invalid JSON, points out that the background onboarding loop incorrectly terminates on any error instead of retrying, and suggests mocking AbortSignal.timeout in the unit tests to significantly speed up test execution.
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.
| const userInfoRes = await fetch(`${ANTIGRAVITY_CONFIG.userInfoUrl}?alt=json`, { | ||
| headers: { Authorization: `Bearer ${tokens.access_token}` }, | ||
| }); | ||
| const userInfo = userInfoRes.ok ? await userInfoRes.json() : {}; | ||
| signal: AbortSignal.timeout(POSTEXCHANGE_TIMEOUT_MS), | ||
| }).catch(() => null); | ||
| const userInfo = userInfoRes?.ok ? await userInfoRes.json() : {}; |
There was a problem hiding this comment.
If the userInfo request succeeds but the response body is invalid JSON or empty, calling userInfoRes.json() will throw an unhandled exception and crash the entire postExchange (and thus the OAuth login) flow. Wrapping both the fetch and the JSON parsing in a single try-catch block ensures that any network, timeout, or parsing errors are safely caught and handled gracefully.
let userInfo = {};
try {
const userInfoRes = await fetch(`${ANTIGRAVITY_CONFIG.userInfoUrl}?alt=json`, {
headers: { Authorization: `Bearer ${tokens.access_token}` },
signal: AbortSignal.timeout(POSTEXCHANGE_TIMEOUT_MS),
});
if (userInfoRes.ok) {
userInfo = await userInfoRes.json();
}
} catch (e) {
console.log("Failed to fetch user info:", e);
}| } catch { | ||
| break; | ||
| } |
There was a problem hiding this comment.
Breaking the loop on any error (catch { break; }) means that if the first onboarding attempt fails (e.g., due to a temporary network issue or a timeout), the background process will immediately stop retrying. Since the goal of this background loop is to retry up to 10 times, we should log the error and let the loop continue to the next iteration after the 5-second delay.
| } catch { | |
| break; | |
| } | |
| } catch (e) { | |
| console.log("Onboarding attempt failed:", e); | |
| } |
| const originalFetch = globalThis.fetch; | ||
|
|
||
| function jsonRes(body: unknown, status = 200): Response { | ||
| return new Response(JSON.stringify(body), { | ||
| status, | ||
| headers: { "Content-Type": "application/json" }, | ||
| }); | ||
| } | ||
|
|
||
| // A fetch that rejects when its AbortSignal fires, and otherwise never resolves. | ||
| // Mirrors real fetch: an already-aborted signal rejects immediately (so a shared | ||
| // deadline reused across fallback endpoints fails fast after the first abort). | ||
| function stalledFetch(init?: { signal?: AbortSignal }): Promise<Response> { | ||
| return new Promise((_resolve, reject) => { | ||
| const abortErr = () => new DOMException("The operation was aborted.", "AbortError"); | ||
| const signal = init?.signal; | ||
| if (signal?.aborted) { | ||
| reject(abortErr()); | ||
| return; | ||
| } | ||
| signal?.addEventListener("abort", () => reject(abortErr())); | ||
| }); | ||
| } | ||
|
|
||
| test.afterEach(() => { | ||
| globalThis.fetch = originalFetch; | ||
| }); |
There was a problem hiding this comment.
To prevent the unit tests from taking up to 16 seconds to run due to real timeouts, we can mock AbortSignal.timeout to use a much shorter duration during the test. Let's store the original AbortSignal.timeout and restore it in afterEach.
const originalFetch = globalThis.fetch;
const originalTimeout = AbortSignal.timeout;
function jsonRes(body: unknown, status = 200): Response {
return new Response(JSON.stringify(body), {
status,
headers: { "Content-Type": "application/json" },
});
}
// A fetch that rejects when its AbortSignal fires, and otherwise never resolves.
// Mirrors real fetch: an already-aborted signal rejects immediately (so a shared
// deadline reused across fallback endpoints fails fast after the first abort).
function stalledFetch(init?: { signal?: AbortSignal }): Promise<Response> {
return new Promise((_resolve, reject) => {
const abortErr = () => new DOMException("The operation was aborted.", "AbortError");
const signal = init?.signal;
if (signal?.aborted) {
reject(abortErr());
return;
}
signal?.addEventListener("abort", () => reject(abortErr()));
});
}
test.afterEach(() => {
globalThis.fetch = originalFetch;
AbortSignal.timeout = originalTimeout;
});| test("postExchange stays timeout-bounded when loadCodeAssist/userinfo stall (no infinite hang)", async () => { | ||
| globalThis.fetch = (async (url: unknown, init?: { signal?: AbortSignal }) => { | ||
| const u = String(url); | ||
| if (u.includes("userinfo") || u.includes("loadCodeAssist")) return stalledFetch(init); | ||
| return jsonRes({}); | ||
| }) as typeof fetch; | ||
|
|
||
| const start = Date.now(); | ||
| const result = await antigravity.postExchange({ access_token: "tok" } as never); | ||
| const elapsed = Date.now() - start; | ||
|
|
||
| // userInfo + loadCodeAssist are AbortSignal.timeout(8s)-bounded (one shared | ||
| // deadline each), so the worst case is ~16s — never an infinite hang. | ||
| assert.ok(elapsed < 22000, `postExchange must be timeout-bounded; took ${elapsed}ms`); | ||
| assert.equal(result.projectId, "", "no project when loadCodeAssist times out"); | ||
| }); |
There was a problem hiding this comment.
By mocking AbortSignal.timeout to a short duration (e.g., 50ms), we can speed up this test from ~16 seconds to ~100ms, and assert a much tighter timeout bound (e.g., < 1000ms instead of < 22000ms).
| test("postExchange stays timeout-bounded when loadCodeAssist/userinfo stall (no infinite hang)", async () => { | |
| globalThis.fetch = (async (url: unknown, init?: { signal?: AbortSignal }) => { | |
| const u = String(url); | |
| if (u.includes("userinfo") || u.includes("loadCodeAssist")) return stalledFetch(init); | |
| return jsonRes({}); | |
| }) as typeof fetch; | |
| const start = Date.now(); | |
| const result = await antigravity.postExchange({ access_token: "tok" } as never); | |
| const elapsed = Date.now() - start; | |
| // userInfo + loadCodeAssist are AbortSignal.timeout(8s)-bounded (one shared | |
| // deadline each), so the worst case is ~16s — never an infinite hang. | |
| assert.ok(elapsed < 22000, `postExchange must be timeout-bounded; took ${elapsed}ms`); | |
| assert.equal(result.projectId, "", "no project when loadCodeAssist times out"); | |
| }); | |
| test("postExchange stays timeout-bounded when loadCodeAssist/userinfo stall (no infinite hang)", async () => { | |
| AbortSignal.timeout = () => originalTimeout(50); | |
| globalThis.fetch = (async (url: unknown, init?: { signal?: AbortSignal }) => { | |
| const u = String(url); | |
| if (u.includes("userinfo") || u.includes("loadCodeAssist")) return stalledFetch(init); | |
| return jsonRes({}); | |
| }) as typeof fetch; | |
| const start = Date.now(); | |
| const result = await antigravity.postExchange({ access_token: "tok" } as never); | |
| const elapsed = Date.now() - start; | |
| // userInfo + loadCodeAssist are AbortSignal.timeout(8s)-bounded (one shared | |
| // deadline each), so the worst case is ~16s — never an infinite hang. | |
| // With AbortSignal.timeout mocked to 50ms, this runs in ~100ms. | |
| assert.ok(elapsed < 1000, `postExchange must be timeout-bounded; took ${elapsed}ms`); | |
| assert.equal(result.projectId, "", "no project when loadCodeAssist times out"); | |
| }); |
🚀 Deployed to VPS 192.168.0.15 for live validationThe fix bundle is live (BUILD_SHA matches this commit; auth-gated endpoints return the expected 307/401). Final step — operator login test: open |
…enid (match 9router) Operator hit a hang on the Google page `accounts.google.com/signin/oauth/firstparty/nativeapp?...requestPath=/signin/oauth/consent` — the consent never completed/redirected. Root cause: our antigravity flow sent a PKCE code_challenge AND requested the `openid` scope on a Google Desktop/native client, which routes into the hanging nativeapp consent. The working 9router web flow uses a plain authorization_code grant (client_secret, no code_challenge) and does NOT request openid. - antigravity (+ agy alias): flowType authorization_code_pkce → authorization_code; no code_challenge is emitted, exchange already sends client_secret + omits code_verifier. - ANTIGRAVITY_CONFIG.scopes: drop "openid" (agy inherits via spread). - OAuthModal: show the actionable "copy the callback URL and paste it" instruction for remote Google providers too (previously only non-Google got it), so a LAN/remote dashboard can finish the loopback callback manually. Tests: antigravity-oauth-no-pkce-no-openid.test.ts (2/2; flip-proof: restoring pkce fails it); oauth-providers-config.test.ts updated for fire-and-forget onboarding (projectId now from loadCodeAssist, matching 9router). Pending live VPS validation.
🚀 Deploy #2 to VPS 192.168.0.15 — no-PKCE/no-openid + fire-and-forget + paste UXRoot cause was the Google consent hang on |
…(no PKCE) After switching antigravity to a plain authorization_code grant, generateAuthData still mints a codeVerifier for every flow and the modal forwards it to /exchange. antigravity.exchangeToken was still attaching it as code_verifier — but the authorize URL no longer carries a code_challenge, so Google rejects the token exchange with invalid_grant, surfacing as a 500 "Internal server error" after the operator pastes the callback. Drop the code_verifier entirely (client_secret-only, matching 9router). Guarded by antigravity-oauth-no-pkce-no-openid.test.ts.
Deploy #3 (ea204bd) + definitive remote-OAuth findingsShipped: exchange-500 fix (stop forwarding code_verifier on the now-PKCE-less antigravity grant). VPS Live-validated root cause of the remote hang (via Claude-in-Chrome):
Working remote options: (1) SSH tunnel so the loopback is reachable ( The no-PKCE/no-openid + fire-and-forget + paste-instruction changes remain valid (align to 9router); they were necessary but not sufficient for remote. |
|
#5200 is ready to merge as the Fast Quality Gates unblocker. Evidence:
The patch only removes redundant comment-only lines from the four frozen file-size offenders and does not touch file-size-baseline.json. I tried both PR merge and direct fast-forward push, but KooshaPari does not have upstream merge/write permission here. Helper PR: #5200 |
Shrinks the 4 file-size freeze offenders (comment-only removals) so the antigravity-oauth-hang branch passes the file-size gate. Integrated into fix/antigravity-oauth-hang.
This reverts commit 22dd424.
…ting base-red Owner chose to rebaseline (keep documented comments) over the contributor comment-stripping (#5200, reverted in the previous commit). Bumps: - accountFallback.ts 1773->1777, providers/[id]/test/route.ts 924->940 (pre-existing release base-red, unrelated to antigravity) - OAuthModal.tsx 960->969 (#5193 + #5203 own growth, summed) - oauth-providers-config.test.ts 870->873 (#5193 test growth) Each with a _rebaseline_ justification entry.
* 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>
* 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>
* 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…
…uzapw#5193 regression of diegosouzapw#2541) PR diegosouzapw#5193 changed onboarding from inline await to fire-and-forget gated by if (projectId), which never fires when projectId is empty — the exact case that needs onboarding. This re-introduced the diegosouzapw#2541 catch-22. Add an else-if branch that attempts onboarding inline (bounded by AbortSignal.timeout) when projectId is empty, then retries loadCodeAssist to discover the newly created project. - Existing accounts with projectId: unchanged (fire-and-forget) - New accounts without projectId: now onboarded within login flow - Timeout bounded: +8s worst case (onboardUser + retry loadCodeAssist) - Graceful degradation: if onboarding fails, lazy retry handles it Tests: - 3 new tests covering empty-projectId onboarding path (RED→GREEN) - Existing 2 tests preserved and passing - Adjusted timeout assertion for the stall test (now includes onboardUser stall) - 5/5 passing on Node 24 Fixes diegosouzapw#7814 Related: diegosouzapw#5193, diegosouzapw#2569, diegosouzapw#2541, diegosouzapw#2219
…uzapw#5193 regression of diegosouzapw#2541) PR diegosouzapw#5193 changed onboarding from inline await to fire-and-forget gated by if (projectId), which never fires when projectId is empty — the exact case that needs onboarding. This re-introduced the diegosouzapw#2541 catch-22. Add an else-if branch that attempts onboarding inline (bounded by AbortSignal.timeout) when projectId is empty, then retries loadCodeAssist to discover the newly created project. - Existing accounts with projectId: unchanged (fire-and-forget) - New accounts without projectId: now onboarded within login flow - Timeout bounded: +8s worst case (onboardUser + retry loadCodeAssist) - Graceful degradation: if onboarding fails, lazy retry handles it Tests: - 3 new tests covering empty-projectId onboarding path (RED→GREEN) - Existing 2 tests preserved and passing - Adjusted timeout assertion for the stall test (now includes onboardUser stall) - 5/5 passing on Node 24 Fixes diegosouzapw#7814 Related: diegosouzapw#5193, diegosouzapw#2569, diegosouzapw#2541, diegosouzapw#2219
…iegosouzapw#5193 regression of diegosouzapw#2541)" This reverts commit b36d3b6. That commit is a byte-identical duplicate of the antigravity onboarding fix shipped separately in PR diegosouzapw#7815 (same author, same diff to src/lib/oauth/providers/antigravity.ts and its test file). Keeping it here would ship and credit the same fix twice across two PRs. diegosouzapw#7808 is scoped to the diegosouzapw#7791 global-install alias-resolver fix only; the antigravity fix stays in diegosouzapw#7815. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
…egression of #2541) (#7815) * fix(antigravity): attempt onboarding when projectId is empty (#5193 regression of #2541) PR #5193 changed onboarding from inline await to fire-and-forget gated by if (projectId), which never fires when projectId is empty — the exact case that needs onboarding. This re-introduced the #2541 catch-22. Add an else-if branch that attempts onboarding inline (bounded by AbortSignal.timeout) when projectId is empty, then retries loadCodeAssist to discover the newly created project. - Existing accounts with projectId: unchanged (fire-and-forget) - New accounts without projectId: now onboarded within login flow - Timeout bounded: +8s worst case (onboardUser + retry loadCodeAssist) - Graceful degradation: if onboarding fails, lazy retry handles it Tests: - 3 new tests covering empty-projectId onboarding path (RED→GREEN) - Existing 2 tests preserved and passing - Adjusted timeout assertion for the stall test (now includes onboardUser stall) - 5/5 passing on Node 24 Fixes #7814 Related: #5193, #2569, #2541, #2219 * docs(changelog): add fragment for antigravity onboarding empty-projectId fix (#7814) Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouzapw@users.noreply.github.com> Co-authored-by: Rafael Dias Zendron <rafaumeu@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 (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>
…uzapw#5193 regression of diegosouzapw#2541) (diegosouzapw#7815) * fix(antigravity): attempt onboarding when projectId is empty (diegosouzapw#5193 regression of diegosouzapw#2541) PR diegosouzapw#5193 changed onboarding from inline await to fire-and-forget gated by if (projectId), which never fires when projectId is empty — the exact case that needs onboarding. This re-introduced the diegosouzapw#2541 catch-22. Add an else-if branch that attempts onboarding inline (bounded by AbortSignal.timeout) when projectId is empty, then retries loadCodeAssist to discover the newly created project. - Existing accounts with projectId: unchanged (fire-and-forget) - New accounts without projectId: now onboarded within login flow - Timeout bounded: +8s worst case (onboardUser + retry loadCodeAssist) - Graceful degradation: if onboarding fails, lazy retry handles it Tests: - 3 new tests covering empty-projectId onboarding path (RED→GREEN) - Existing 2 tests preserved and passing - Adjusted timeout assertion for the stall test (now includes onboardUser stall) - 5/5 passing on Node 24 Fixes diegosouzapw#7814 Related: diegosouzapw#5193, diegosouzapw#2569, diegosouzapw#2541, diegosouzapw#2219 * docs(changelog): add fragment for antigravity onboarding empty-projectId fix (diegosouzapw#7814) Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouzapw@users.noreply.github.com> Co-authored-by: Rafael Dias Zendron <rafaumeu@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>
…rding + 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.
Problem
The dashboard Antigravity OAuth login "just spun forever" and never completed. Validated against the working 9router web flow (
_references/_sistemas_proxys/9router).Root cause
postExchangeinsrc/lib/oauth/providers/antigravity.tsawaited theonboardUserretry loop inline (up to 10×5s, each fetch with no timeout), so a slow/unreachable Antigravity upstream blocked the/exchangeresponse indefinitely. 9router's web flow runs the same onboarding fire-and-forget.Fix
onboardUser→ fire-and-forget (void onboardInBackground().catch(...)): it never gates the OAuth login response. Project onboarding is also done lazily at request time byantigravityProjectBootstrap.ts, so backgrounding it is safe (matches 9router).userInfo+loadCodeAssist→AbortSignal.timeout(8s)-bounded (one shared deadline per fallback list, not per-endpoint — per-endpoint re-introduced a ~40s wait). Worst case ~16s, never infinite. Mirrors the existingantigravityProjectBootstrap.tspattern.Validation
tests/unit/antigravity-oauth-postexchange-nonblocking.test.ts(2/2).postExchangereturns in ~96ms with onboarding gated open; flip-proof: reverting to inlineawaithangs the test (EXIT 124). Timeout-bounded test confirms no infinite hang when upstreams stall.192.168.0.15for a real login validation (recorded below once done).