Skip to content

Release v3.8.43#5609

Merged
diegosouzapw merged 181 commits into
mainfrom
release/v3.8.43
Jul 2, 2026
Merged

Release v3.8.43#5609
diegosouzapw merged 181 commits into
mainfrom
release/v3.8.43

Conversation

@diegosouzapw

@diegosouzapw diegosouzapw commented Jun 30, 2026

Copy link
Copy Markdown
Owner

[3.8.43] — 2026-07-02

✨ New Features

  • usage (quota percentages + provider USD drilldown): @@om-usage and the HTTP usage endpoint now report personal API-key quotas as remaining percentages (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a provider USD cost drilldown (/api/usage/provider-window-costs + ProviderUsdCostModal, management-auth gated). Also honors observed provider quota resets: a same-resetAt reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New src/lib/usage/providerWindowCosts.ts. Regression guards: tests/unit/provider-window-costs.test.ts, tests/unit/internal-usage-command.test.ts, tests/unit/api-key-usage-limits.test.ts, tests/unit/lib/quota-reset-events.test.ts. Extracted from #5863 by @Witroch4.

  • dashboard (live WS behind reverse proxy): the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via NEXT_PUBLIC_LIVE_WS_PUBLIC_URL (e.g. wss://ws.my-ai.com/live-ws). The URL is honored both at build time (env inlined into the bundle) and at runtime for prebuilt Docker/npm images: the /api/v1/ws?handshake=1 handshake now echoes a lazily-read live.publicUrl (only ws:///wss:// values are accepted; anything else is rejected to null), and useLiveDashboard resolves the URL from that handshake before connecting, falling back to the previous ws(s)://hostname:20129 default. Also documents LIVE_WS_ALLOWED_HOSTS and aligns the GitLab Duo OAuth scopes line in .env.example with the live config (ai_features read_user). Regression guard: tests/unit/live-ws-public-url.test.ts (5). (#5877 by @ianriizky)

  • providers (CLI profile auto-sync): opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (~/.codex/*.config.toml) and now Claude Code (~/.claude/profiles/<name>/settings.json, via an extracted syncClaudeProfilesFromModels + a new claudeProfileAutoSync.ts mirroring the Codex path). Both are off by default and never touch the active/default CLI config; they are backed by the OMNIROUTE_AUTO_SYNC_CODEX_PROFILES / OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing CLI_ALLOW_CONFIG_WRITES write-guard. A "CLI profile auto-sync" card on the CLI Code dashboard toggles each (moved from the providers dashboard in #5778 — thanks @rdself). Regression guards: tests/unit/claude-profile-auto-sync-gate.test.ts, tests/unit/codex-profile-auto-sync-gate.test.ts, tests/unit/cli/setup-claude.test.ts (follow-up to feat(codex): auto-sync profiles after model discovery #5737).

  • cli (startup banner): the serve startup banner now prints the running OmniRoute version (v3.8.x) beneath the ASCII logo, so the active version is visible at a glance without a separate --version call. Regression guard: tests/unit/cli-serve-version-banner.test.ts. Thanks @chirag127 (#5752).

  • analytics (subscription cost): flat-rate providers now show $0 in cost analytics instead of an inflated per-token estimate. Subscription / coding-plan providers (every cookie-web provider — ChatGPT Web, grok-web, … — plus the dedicated Minimax Coding, Kimi Coding, GLM Coding, Alibaba Coding Plan, and Xiaomi MiMo plans) bill a flat fee, not per token, yet still carry per-token pricing rows used for estimates — so the analytics dashboard over-reported their cost. A new flat-rate classifier (src/lib/usage/flatRateProviders.ts) is consulted by the analytics surfaces (analytics route, usage stats, usage analytics) via an opt-in flatRateAsZero cost option, so those providers read $0 while budget / quota / routing keep estimating unchanged. Deliberately NOT zeroed: codex/cx (OmniRoute actively tracks Codex token cost — Fast-tier multipliers, GPT-5.x pricing — and Codex can be a metered account), byteplus (metered ModelArk), minimax-cn (metered China API). Regression guard: tests/unit/flat-rate-cost-5552.test.ts. (#5552)

  • mcp (RTK): expose the RTK tool-output learn/discover workflow as two new MCP tools so an agent can grow the RTK filter catalog without leaving the protocol. omniroute_rtk_discover analyzes recently captured raw tool output (discoverRepeatedNoise / suggestFilter) and returns candidate noise patterns plus a suggested filter; omniroute_rtk_learn lists the captured command samples (listRtkCommandSamples) and resolves a command to its RTK filter id (commandToId). Both are read-only (scope read:compression), wrap the existing RTK discovery primitives (no new logic in the engine), and log to the MCP audit trail. Regression guard: tests/unit/compression/rtk-mcp-tools.test.ts (4). gaps v3.8.42 — T07.

  • compression (LLM tier): add an opt-in, default-off LLM-tier compression engine (llm) that condenses the prose of non-system messages via a pluggable chat-completion backend. It mirrors the llmlingua engine's contract but is safe by construction: the default backend is a no-op pass-through (the engine never mutates the payload until an operator both enables it and wires a real backend via setLlmCompressorBackend()), it is not part of the default stacked pipeline, enabled defaults to false, fenced code blocks and system messages are never sent to the model, and every backend error fails open (the original segment/body is kept, never thrown). A minTokens floor skips small prompts. The real production backend is intentionally a VPS-validated follow-up (Hard Rule fix(ci): add environment for npm token access #18), exactly as the llmlingua worker backend is gated. New open-sse/services/compression/engines/llm/index.ts. Regression guard: tests/unit/compression/llm-compressor-engine.test.ts (8). gaps v3.8.42 — T05/C3.

  • memory (typed decay): add opt-in typed memory decay (TV6) so the conversational memory store stops accumulating stale episodic noise. Each injected memory now tracks an access_count + last_accessed_at (always-on, non-destructive telemetry; migration 111_memory_typed_decay), and an opt-in, default-off sweep (MEMORY_TYPED_DECAY_ENABLED, default false) deletes memories that are past a per-type TTL and not immune. Only episodic decays by default (30d, env-tunable); factual/procedural/semantic are immune, and any memory accessed >= 3 times earns access immunity (mirroring "guardrail/convention/decision never decay"). The decay clock re-bases on the last access, so used memories survive. Deletions reuse deleteMemory (SQLite + sqlite-vec + Qdrant stay in sync) and fail open; an optional periodic sweep is doubly opt-in (also needs MEMORY_TYPED_DECAY_SWEEP_INTERVAL>0). With the flag off nothing is ever deleted (Rule feat: v0.2.0 — advanced routing services, cost analytics, pricing overhaul #20 spirit). New src/lib/memory/typedDecay.ts. Regression guard: tests/unit/memory/typed-decay.test.ts (15). gaps v3.8.42 — T10/TV6.

  • dashboard (combos): the named-combos editor now lets you drag to reorder the stacked-compression pipeline instead of only editing fixed-position steps. A new pure model (src/shared/components/compression/compressionPipelineModel.ts) owns add/remove/move/update with the engine→intensity invariant and a never-empty guarantee, and a @dnd-kit/sortable editor (CompressionPipelineEditor.tsx, matching the sidebar reorder pattern) replaces the inline list in CompressionCombosPageClient. Order persists through the existing combos endpoint. Regression guards: tests/unit/compression-pipeline-model.test.ts (11) + tests/unit/ui/compression-pipeline-editor.test.tsx (4). A dedicated tests/e2e/compression-studio.spec.ts (Tela A render + tab switch) closes the studios e2e gap the combo-live spec did not cover. gaps v3.8.42 — T06 + T03.

  • compression (pipeline): add an opt-in, default-off per-engine circuit-breaker to the stacked compression pipeline (T02). When an engine throws repeatedly across requests, its breaker opens and the stacked loops skip that engine (keeping the body verbatim for that step — fail-open) for a cooldown, then probe once (lazy half-open); success closes it, a failed probe re-opens it. This is distinct from the provider circuit-breaker (src/shared/utils/circuitBreaker.ts, provider-scoped + DB-persisted) — the new pipelineEngineBreaker.ts is engine-scoped, process-local, and adds zero DB/IO on the hot path. It composes with the existing per-request TV1 bail-out (which skips within a single request); the breaker adds cross-request memory. Default off (COMPRESSION_PIPELINE_BREAKER_ENABLED=false) → byte-identical to the pre-breaker pipeline (a throwing engine still propagates unless TV1 is separately enabled). Configurable per-call, per-CompressionConfig, or via env (_THRESHOLD/_COOLDOWN_MS). Regression guard: tests/unit/compression/pipeline-circuit-breaker.test.ts (9, incl. a throwing-engine integration); existing strategySelector/bail-out suites stay green. gaps v3.8.42 — T02 (2.2).

  • compression (CCR): the CCR retrieval-feedback (H8) is now graduated instead of a binary cliff. Previously a block retrieved >= 3 times was flagged do-not-compress and everything below that stayed fully compressible. Now each prior retrieval raises a block's effective minChars linearly (effectiveMinChars), so frequently-retrieved content is compressed progressively less; the >= 3 exclusion is preserved (as Infinity). The ramp is controlled by a retrievalRampFactor (default 2, per-combo config or COMPRESSION_CCR_RETRIEVAL_RAMP_FACTOR); 1 reproduces the exact legacy binary behavior. Per-(principal, hash) isolation is unchanged. Regression guard: tests/unit/compression/ccr-retrieval-ramp.test.ts (12); existing CCR suites (51) stay green. gaps v3.8.42 — T08/H8.

  • compression (cache-aware): add an opt-in, default-off usage-observed prefix freeze (H5). The cache-aware guard previously preserved the system prompt only for providers a static heuristic recognized as caching. It now also learns which system prompts actually recur: once a system prompt has been observed >= a threshold across requests, it is treated as a stable cacheable prefix and preserved from compression even for providers the static check misses — recovering prompt-cache hits that a prefix-compressing mode would otherwise bust. Content-addressed by a hash of the system prompt (OpenAI / Claude / Gemini shapes), in-memory + bounded, zero DB/IO; a "freeze" only preserves the prefix, so it never mutates a payload. Default OFF (COMPRESSION_PREFIX_FREEZE_ENABLED, threshold _THRESHOLD); respects the never preserve-mode (never freezes). New open-sse/services/compression/prefixFreeze.ts, wired into resolveCacheAwareConfig. Regression guard: tests/unit/compression/prefix-freeze.test.ts (10); 44 existing cache-aware / preserve-mode tests stay green. gaps v3.8.42 — T08/H5.

  • compression (read-lifecycle): add a new opt-in, default-off read-lifecycle engine (H7) that collapses stale/superseded file-Read tool results. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is re-read (superseded by a newer view) or modified by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike session-dedup (identical-content) or ccr (reversible markers), this is semantic + lossy, so it is opt-in (enabled defaults false). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and fail-opens on any unexpected shape. Supports both the Anthropic (tool_use/tool_result) and OpenAI (tool_calls + role:"tool") shapes. New open-sse/services/compression/engines/readLifecycle/index.ts. Regression guard: tests/unit/compression/read-lifecycle.test.ts (10). gaps v3.8.42 — T08/H7.

  • observability (correlation IDs): requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. (#5834 — thanks @hartmark)

  • cli (startup banner — boot time): the serve ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. (#5799 — thanks @ishatiwari21)

  • api (quota-policy bypass scope): add an opt-in API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. (#5731 — thanks @Witroch4)

  • providers (Ollama local): add a first-class Ollama local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. (#5712 — thanks @diegosouzapw)

  • codex (fallback profiles): generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. (#5701 — thanks @skyzea1)

  • api (response-body validation + failover): add a configurable response-body validation step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/[Feature] Configurable response-body/schema validation for combo failover (fail model on bad 200 payload)  #4985). (#5684 — thanks @diegosouzapw)

  • providers (SenseNova): complete the SenseNova free Token Plan — chat completions plus Text-to-Image (ported from 9router#2233). (#5679 — thanks @diegosouzapw)

  • db (self-correcting context windows): add self-correcting model context-window overrides so a model whose advertised context length is wrong is corrected automatically (models/[Feature] Auto-detect/correct a model's real context window from the provider API instead of trusting advertised/static values #5004). (#5667 — thanks @diegosouzapw)

  • routing (latency strategy): optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. (#5629 — thanks @KooshaPari)

  • compression (preserveSystemPrompt mode): add a preserveSystemPrompt mode enum (always | whenNoCache | never) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). (#5653 — thanks @diegosouzapw)

  • commandCode (vision): add multimodal image support for Command Code vision models. (#5557 — thanks @Stazyu)

  • compression (read-lifecycle engine): T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. (#5754 — thanks @diegosouzapw)

  • compression (usage-observed prefix freeze): T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. (#5744 — thanks @diegosouzapw)

  • compression (CCR retrieval-feedback ramp): T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. (#5739 — thanks @diegosouzapw)

  • compression (per-engine circuit breaker): T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. (#5735 — thanks @diegosouzapw)

  • compression (LLM-tier engine): T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. (#5702 — thanks @diegosouzapw)

  • dashboard (compression pipeline editor): T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. (#5727 — thanks @diegosouzapw)

  • memory (typed decay): T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. (#5723 — thanks @diegosouzapw)

  • mcp (RTK tools): T07 — expose the RTK learn/discover capabilities as first-class MCP tools. (#5691 — thanks @diegosouzapw)

  • providers (CLI profile auto-sync): opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. (#5755 — thanks @diegosouzapw)

🔧 Bug Fixes

  • fix(opencode): stop fabricating User-Agent: opencode/local and x-opencode-client: cli headers when the client sends none — the executor-dedup refactor (#5720) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: tests/unit/opencode-executor.test.ts. (thanks @diegosouzapw)

  • fix(executors): resolveEffectiveKey returns undefined (not "") when no API key is present — a type-coercion cleanup (#5798) changed apiKey ?? "" to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to string | undefined and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: tests/unit/refactor-buildHeaders-preamble.test.ts. (thanks @diegosouzapw)

  • fix(translator): restore the terminal message_delta + message_stop on Responses→Claude streams — the doubled-tool-args dedup (#5828) guarded the finish handler on the shared state.finishReason, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after content_block_delta. The dedup now uses a dedicated state.claudeFinishEmitted flag. Regression guard: tests/unit/claude-code-rendering-fixes.test.ts. (thanks @diegosouzapw)

  • fix(pricing): add the Kiro claude-sonnet-5 pricing row so the newly-catalogued model (#5796) no longer reports $0.00 usage. Regression guard: tests/unit/catalog-updates-v3x.test.ts. (thanks @diegosouzapw)

  • fix(github): keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token without a refresh token; the proactive health check treated that as terminal no_refresh_token and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale no_refresh_token state, and refreshes the Copilot sub-token when needed. Regression guard: tests/unit/token-health-no-refresh-token-expired-5326.test.ts. Extracted from #5863 by @Witroch4.

  • fix(kiro): bound the Claude model-id dash→dot normalization to a 1–2 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl)

  • fix(usage): preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017)

  • fix(providers): route OpenAI responses-only models to /v1/responses instead of 404ing on /v1/chat/completions. The curated gpt-5.5-pro / gpt-5.4-pro entries never worked (OpenAI only serves *-pro reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry targetFormat: "openai-responses" (reusing the existing per-model translation plumbing shared with gh/codex), DefaultExecutor.buildUrl swaps the openai endpoint to /responses in lockstep (honoring custom base URLs), and a -pro suffix heuristic covers dynamically-synced ids such as o1-pro / gpt-5.2-pro (same spirit as the gh executor's /codex/i routing, 9router#102). Legacy completions-only ids (e.g. gpt-3.5-turbo-instruct) are out of scope — they are not in the catalog and OmniRoute has no legacy /v1/completions upstream. Regression guard: tests/unit/openai-responses-only-models-5842.test.ts (8). Thanks @maikokan. (#5842)

  • fix(image): keep bare codex image aliases (e.g. gpt-5.5) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named gpt-5.5 used to shadow the bare image alias in resolveImageRouteModel, hijacking /v1/images/* requests to a chat target (regression path adjacent to #5887); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. gpt-image-2) remain user-shadowable (OpenAI-compatible custom image providers are not supported by /v1/images/edits #3214/Image routes should resolve combo aliases and support OpenAI-compatible image edits #3215 behavior preserved). Regression guard: tests/unit/image-routes-combo-edits-3214-3215.test.ts (9). (#5902 by @KooshaPari)

  • fix(ci): re-green the release/v3.8.43 fast-gates queue — every PR→release was inheriting base-reds (#5798). Five distinct blockers cleared: (1) stale modelContextOverrides entry in the check:db-rules intentionally-internal allowlist (#5827 allowlisted it while the #5609 fix re-exported it from localDb.ts; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) LIVE_WS_ALLOWED_HOSTS / NEXT_PUBLIC_LIVE_WS_PUBLIC_URL documented in docs/reference/ENVIRONMENT.md (env/docs contract, from #5877); (3) the Router Backends ADR's references to the not-yet-merged registry (#5868) marked as landing-with-PR so check:fabricated-docs --strict passes; (4) antigravity-429-quota-tdd + middleware-header-strip-5849 added to stryker tap.testFiles (check:mutation-test-coverage); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: tests/unit/check-db-rules-classification.test.ts. (#5798)

  • providers (codex image auto-routing regression): an unprefixed gpt-5.5 request from a codex-only setup (no OpenAI connection) now correctly infers the codex provider again — the OpenAI static-catalog short-circuit in resolveModelByProviderInference was preempting the codex-preference block, so gpt-5.5 (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: tests/unit/codex-gpt55-routing-5887.test.ts. (#5887)

  • api (proxy header hygiene): upstream x-middleware-* control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding x-middleware-rewrite made Next 16 throw NextResponse.rewrite() was used in a app route handler and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: tests/unit/middleware-header-strip-5849.test.ts. (#5849)

  • docs (pnpm global install): replaced the unsupported pnpm approve-builds -g step with the install-time pnpm add -g omniroute@latest --allow-build=better-sqlite3 flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. (#5554)

  • dashboard (token badge): the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (testStatus === "expired"). Continuation of [BUG] Antigravity "Token Expired Badge" continuation of #3679 #3850 #5326. Regression guard: tests/unit/ui/connection-row-token-badge-5836.test.tsx. (#5836)

  • db (auto backup toggle): full pre-write SQLite backups now honor the persisted backup.autoBackupEnabled dashboard setting — previously only the DISABLE_SQLITE_AUTO_BACKUP env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: tests/unit/db-backup-autobackup-setting-5871.test.ts. (#5871)

  • providers (auto/ routing for custom providers): custom OpenAI-/Anthropic-compatible providers (dynamic *-compatible-* connection IDs) are no longer excluded from auto/ routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's defaultModel. Regression guard: tests/unit/auto-custom-provider-5873.test.ts. (#5873)

  • middleware (hook sandbox): operator-authored pre-request hook code now runs inside a hardened Node vm sandbox (minimal context, no ambient globals/process.env, execution timeout, no require) instead of new Function() in the main process — closing the Hard Rule deps: bump eslint from 9.39.2 to 10.0.0 in the development group #3 / SonarCloud S1523 exposure. Regression guard: tests/unit/middleware-hook-sandbox-5872.test.ts. (#5872)

  • mcp-server (auth forwarding): the per-caller MCP identity forwarded via withMcpHttpAuthContext now wins over the static OMNIROUTE_API_KEY env fallback in the internal-fetch helpers (apiFetch, omniRouteFetch) — previously the env key was spread after the forwarded headers and clobbered the caller's Authorization. Regression guard: open-sse/mcp-server/__tests__/httpAuthContext.test.ts. (#5819)

  • dashboard (Modal provider — two-field auth): the Modal provider connection form now exposes two fields — Token ID + Token Secret — instead of a single API-key input, since Modal authenticates with Authorization: Bearer <token-id>:<token-secret>. The dashboard combines the two fields into the id:secret credential before saving (combineModalCredential, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: tests/unit/modal-credential-combine.test.ts (5). (#5881, closes #5446) Follow-up: the Validation Model Id field is now pre-filled for Modal with the same model the server-side validator probes (Qwen/Qwen3-4B-Thinking-2507-FP8, shared via MODAL_DEFAULT_VALIDATION_MODEL_ID in src/shared/constants/modal.ts), closing the last checklist item of question: Modal — requires both Token ID and Token Secret, not a single API key #5446. Regression guard: tests/unit/modal-validation-model-prefill.test.ts.

  • api (chat completions — early SSE keepalive gate): the /v1/chat/completions route wrapped the response in the early-stream keepalive whenever stream was not explicitly false, so a client that omitted stream and asked for JSON (Accept: application/json) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit stream: true in the body or an Accept header that forces SSE (acceptHeaderForcesStream); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by chatCore/resolveStreamFlag — preserving OmniRoute's legacy streaming default when stream is omitted and the per-key streamDefaultMode: "json" opt-in. Regression guard: tests/unit/chat-combo-live-test.test.ts ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). (#5866 by @rdself)

  • fix(github): drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr)

  • fix(oauth): prevent cross-IdP account overwrites by disambiguating OAuth connections on username when present, not email alone. (thanks @KunN-21)

  • fix(mitm): best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz)

  • providers (Kiro — Claude Sonnet 5): the Kiro provider's model catalog was missing claude-sonnet-5, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (open-sse/config/providers/registry/kiro/index.ts) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry models[] feeds both the model selector and the live CodeWhisperer ListAvailableModels fallback, so the model is now selectable and routable. Regression guard: tests/unit/kiro-claude-sonnet-5-2267.test.ts. (thanks @openbioinfo)

  • settings (model aliases — self-heal after restart): the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local _customAliases map in modelDeprecation.ts that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as [BUG] Claude adaptive-thinking corrupts tool calls & leaks </think> on Cursor/OpenAI path; Thinking Budget UI is a no-op #5312), so the GET /api/settings/model-aliases handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads settings.modelAliases from the DB (via the existing getSettings() db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the _customAliases store in modelDeprecation.ts is backed by globalThis (key __omniroute_customAliases__), so the startup and app-route module graphs share one store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same globalThis singleton pattern already applied to thinkingBudget.ts/backgroundTaskDetector.ts ([BUG] Claude adaptive-thinking corrupts tool calls & leaks </think> on Cursor/OpenAI path; Thinking Budget UI is a no-op #5312). Regression guards: tests/unit/model-aliases-settings-route-selfheal.test.ts + tests/unit/model-aliases-globalthis-5777.test.ts. (#5777 — thanks @jleonar2)

  • providers (grok-cli token auto-refresh): grok-cli OAuth tokens were never proactively refreshed before their real expiry. mapTokens hardcoded expiresIn: 21600 (6 h) regardless of the token's actual lifetime, so the persisted expiresAt was always "now + 6 h" and the proactive tokenHealthCheck sweep (refresh when expiresAt - now < 5 min) fired 6 h after import instead of shortly before the token really expired. mapTokens now computes expiresIn from the authoritative expires_at field in ~/.grok/auth.json (ISO → epoch-seconds) with a fallback to the JWT exp claim (payload-only decode, no signature trust); the hardcoded 21600 is kept only when neither is present. An already-expired token (real expires_at/exp in the past) is now clamped to a positive expiresIn via Math.max(1, …), so the import route stores a near-future expiresAt and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in tests/unit/grok-cli-oauth.test.ts (JWT exp, JSON expires_at, the 21600 fallback, and the two expired-token clamps). (#5775 — thanks @Chewji9875)

  • compression (CCR retrieve via MCP HTTP): the omniroute_ccr_retrieve MCP tool returned "CCR block not found" for blocks stored earlier in the same session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (String(apiKeyInfo.id)), but the tool resolved the caller via extra.authInfo.clientId — which the MCP SDK never populates for API-key auth — so it fell back to "anonymous" and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (httpAuthContext) using the same getApiKeyMetadata lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: tests/unit/compression/ccr-mcp-principal-5649.test.ts (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). (#5649)

  • compression (context-editing telemetry): streaming responses now record Context Editing savings. Anthropic surfaces context_management.applied_edits[] on the final message_delta snapshot of an SSE stream, but the streaming reconstruction (buildStreamSummaryFromEvents → Claude branch) dropped context_management entirely and no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (cleared_input_tokens / cleared_tool_uses) surfaced under engine context-editing in compression analytics only for non-streaming responses. The collector now preserves context_management from the final snapshot (last-writer-wins), and onStreamComplete mirrors the non-streaming recordContextEditingTelemetryHook (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no context_management. Regression guard: tests/unit/context-editing-streaming-telemetry.test.ts (3). gaps v3.8.42 — T01 (5.1).

  • proxy (relay test diagnostics): the Proxy Pool "Test" button showed a bare "failed" with nothing in the server logs when a relay (Vercel / Deno / Cloudflare) responded with a non-200 — e.g. a 401 from an auth-token mismatch after a STORAGE_ENCRYPTION_KEY rotation. The relay success-path response set success: false but carried no error field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable error (the HTTP status, plus an auth/encryption-key hint on 401/403) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to buildRelayTestResult with a regression guard (tests/unit/proxy-relay-test-error-5716.test.ts). Note: this surfaces why a relay fails — it does not repair a genuinely broken/misconfigured relay. (#5716)

  • fix(dashboard): add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero)

  • providers (onboarding wizard — unsupported validation): adding a provider whose credentials have no live validator (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The /api/providers/validate endpoint returns HTTP 400 + { unsupported: true } for these ([BUG] LMArena (Free) session cookie cannot be saved — UI blocks on 'Invalid / Provider validation not supported' #5565/[BUG] PiAPI API key cannot be saved — UI blocks on 'Invalid / Provider validation not supported' (same as #5565) #5567), but the wizard's validateOnboardingApiKey ran it through expectOk, which threw on the non-200 — so the flow jumped to the error step and the connection was never created. The wizard now treats unsupported: true as a non-blocking "can't verify" and proceeds to save, mirroring AddApiKeyModal. Regression guard added to tests/unit/provider-onboarding-wizard.test.ts. (related to #5692)

  • dashboard (Quick Start step 1): the Quick Start "Create API key" step told users to "Go to Endpoint → Registered Keys" and linked to /dashboard/endpoint, but API keys are created on the API Manager page (/dashboard/api-manager, sidebar "API Keys") — the Endpoint page has no "Registered Keys" section, so users followed the link and could not find where to create a key. Step 1 now reads "Go to API Keys" and links to /dashboard/api-manager. Regression guard: tests/unit/ui/quick-start-api-keys-link-5695.test.ts. (#5695)

  • providers (DashScope/Alibaba setup link): the "Get API key" link for the Alibaba and Alibaba (China) providers pointed at the bare API host (dashscope-intl.aliyuncs.com / dashscope.aliyuncs.com), which returns 404 in a browser — API hostnames have no homepage. Repointed to the consoles where keys are actually issued: bailian.console.alibabacloud.com (international) and dashscope.console.aliyun.com (China). Same class as [BUG] Ollama setup links to 404 page (ollama.com/settings/api-keys) — Ollama is local-only, no API key exists #5572/[BUG] SearchAPI provider setup links to 404 docs page (searchapi.io/docs not found) #5574/[BUG] You.com provider — you.com/platform shows 'Error loading your API subscription / temporarily unavailable' #5576; regression guard added to tests/unit/provider-setup-links-5572.test.ts. (#5665)

  • thinking / runtime-config (module-graph fix): operator-configured proxy settings that are hydrated at boot but read per-request were silently ignored in production. Next.js compiles instrumentation.ts (boot hydration via applyRuntimeSettings / restore hooks) as a separate webpack module graph from the app-route / open-sse executors, so a module-local let _config singleton is duplicated — the boot copy is hydrated but the request path reads a different, un-hydrated copy. Live VPS validation proved the Thinking-Budget hydration ran to completion at boot yet base.ts still saw the passthrough default (this is why [BUG] Claude adaptive-thinking corrupts tool calls & leaks </think> on Cursor/OpenAI path; Thinking Budget UI is a no-op #5312 fix A stayed broken even after the boot-wiring fix). Fixed by backing the singletons with globalThis (the pattern systemPrompt.ts already uses for the Global System Prompt, [BUG] Global System Prompt export/import loses enabled state and prompt restore #2470), so all module-graph copies share one instance: thinkingBudget.ts (the dashboard Thinking-Budget mode now reaches the executor), backgroundTaskDetector.ts (the opt-in background-model degradation now actually fires on requests), and systemTransforms.ts (operator pipeline overrides now reach the request path). payloadRules.ts was already safe (it lazily self-loads from the DB per request, [BUG] Payload Rules not persisting across server restart #2986). Regression guards: tests/unit/thinking-budget-globalthis-5312.test.ts + tests/unit/runtime-config-globalthis-5312.test.ts (assert globalThis-backed sharing; a module-local let fails them). (#5312)

  • thinking (Claude OAuth): restore the proxy-level Thinking-Budget config on startup. The dashboard mode (auto/custom/adaptive) is persisted under settings.thinkingBudget, but the boot-time hydration (hydrateThinkingBudgetConfig) was only wired into src/server-init.ts — an unused module that never runs in production — so the operator's choice silently reverted to the passthrough default on every restart ([BUG] Claude adaptive-thinking corrupts tool calls & leaks </think> on Cursor/OpenAI path; Thinking Budget UI is a no-op #5312 fix A was non-functional, even though its direct unit test passed). The hydration now runs in the real boot path (src/instrumentation-node.ts), alongside the Global System Prompt restore. Surfaced by live Anthropic-OAuth validation on the VPS. Regression guard: tests/unit/thinking-budget-boot-wiring-5312.test.ts (asserts the production boot module calls the hydration, not just the function in isolation). (#5312)

  • translator/chatcore (hardening): re-apply two defensive review-fixes that were dropped in a branch rebuild before fix(translator): merge consecutive same-role contents for Gemini #5661 / fix(chatcore): default Claude tool type to "custom" when missing #5662 landed. (1) mergeConsecutiveSameRoleContents (OpenAI→Gemini) now shallow-copies each entry and its parts array instead of pushing the input reference, so the consecutive-same-role merge never mutates the caller's objects. (2) defaultClaudeToolType (Claude tool defaults) now passes any non-object array entry (null / primitive) through unchanged instead of spreading it into a fabricated { type: "custom", … } tool. No behavior change on real payloads (Gemini contents are freshly built; Claude tools are always objects); both properties are now locked by regression tests in tests/unit/translator-gemini-consecutive-role-2191.test.ts and tests/unit/claude-tool-type-default-2195.test.ts.

  • providers (grok-cli): truncate the tool list when it exceeds a provider's hard limit, so grok-cli (cli-chat-proxy.grok.com, max 200 tools) no longer rejects requests with Maximum tools limit reached. Adds a proactive PROVIDER_TOOL_LIMITS map (grok-cli: 200, consulted before the reactive cache), a corrected limit-parsing regex that captures the stated maximum (200) instead of the supplied count (427), and removes the broken < MAX_TOOLS_LIMIT truncation gate so truncation now fires whenever tools.length exceeds the effective limit. Regression guard: tests/unit/tool-limit-detector.test.ts. (#5563 — thanks @Chewji9875)

  • resilience (antigravity): record model lockout for Antigravity 429 rate_limit_exceeded errors. Antigravity's "Resource has been exhausted (e.g. check quota)." text was matched by overly broad QUOTA_PATTERNS and misclassified as QUOTA_EXHAUSTED, so the combo retry path was skipped (providerExhausted) and the model was never cooled down. Classification now prefers the structured error code — classifyErrorText(structuredError?.code || errorText) — so a rate_limit_exceeded code is treated as a transient rate-limit (not quota), and the two broad patterns (/resource.*exhaust/i, /check.*quota/i) were replaced with Antigravity-specific ones (individual quota reached, enable overages). (#5579 — thanks @Chewji9875)

  • providers (OpenAI-compatible): Codex MCP / tool_search deferred discovery (and apply_patch) now works through a Custom OpenAI-compatible provider. When such a provider received a Responses-API-shaped request that carried MCP / tool_search tools, OmniRoute downgraded it to /chat/completions, which drops the deferred tool-discovery mechanism — so the MCP namespaces never surfaced to the model and apply_patch was mis-handled as a JSON tool. The executor now detects a Responses-shaped request (input / previous_response_id / max_output_tokens / reasoning) that carries namespace / tool_search* tools and routes it to the upstream /responses endpoint natively instead of downgrading (it can also be forced via providerSpecificData._omnirouteForceResponsesUpstream). This is a distinct code path from the official Codex OAuth backend ([BUG]Codex MCP tools unavailable through OmniRoute because tool_search is dropped before namespace discovery #3033 / [BUG] Codex MCP/plugin tools are not exposed when using OmniRoute with FreeModel/OpenAI-compatible providers #4539, which the earlier fix never touched). Regression guard: tests/unit/executor-default-base.test.ts. Thanks to @KooshaPari for the fix. (#5483)

  • dashboard (routing): selecting the fusion strategy on the Global Routing defaults tab now reveals fusion-specific config instead of only the generic resilience fields. Fusion's engine knobs — judgeModel (the model that synthesizes the panel answers) and fusionTuning (minPanel / stragglerGraceMs / panelHardTimeoutMs) — already existed in the schema and the per-combo editor, but the Global Routing tab never surfaced them, so picking "fusion" there was effectively a no-op. The fields are now shown (extracted into a new FusionDefaultsFields component). Voting / aggregation-mode / per-provider-weight are intentionally not shown — those don't exist in the fusion engine. Regression guard: tests/unit/ui/combo-defaults-fusion-5598.test.tsx. (#5598)

  • dashboard (free proxy pool): the free proxy pool "Sync All" no longer fails silently with Total: 0. Three fixes: (1) the IPLocate source fetched …/protocols/<proto>.json and parsed it as JSON, but the upstream list is plain text (<proto>.txt, one ip:port per line) — every protocol 404'd / failed to parse; it now fetches .txt and parses the line list. (2) The sync route isolates each source in its own try/catch, so one provider throwing (e.g. a TLS handshake failure) no longer aborts the whole sync — the working sources still populate the pool. (3) The UI now surfaces the per-source errors the route already returns, instead of discarding the response, so a partial/empty sync explains itself. Regression guards: tests/unit/free-proxy-providers.test.ts, tests/unit/proxy-pool-sync-4878.test.ts, tests/unit/free-pool-tab.test.tsx. (#5595)

  • dashboard (memory engine): the memory engine status page no longer mixes English and Portuguese. The embedding / vector-store / rerank status detail strings were hardcoded in Portuguese in the backend (resolveEmbeddingSource, engineStatus), e.g. auto: nenhuma fonte de embedding disponível and sqlite-vec ativo, dim=…, while the surrounding UI labels render from the English i18n bundle — so an English user saw a half-translated page. The backend detail strings are now English (auto: no embedding source available, sqlite-vec active, dim=…, etc.), matching the rest of the page. Regression guard: tests/unit/memory-engine-status.test.ts. (#5596)

  • providers (cline): stop falsely mapping valid Cline (OAuth) responses to 502 empty_choices + account cooldown. detectMalformedNonStream only recognized choices[].message.content as a string, but some OpenAI-compatible upstreams — Cline via OAuth among them — return content as an array of Anthropic-style text blocks inside an OpenAI envelope. A non-empty response (recvBytes > 0) was therefore classified as empty_choices and turned into a 502 that also cooled the account down. The malformed-response detector now also treats a content array carrying at least one non-empty text block as real output. Regression guard: tests/unit/diagnostics.test.ts. (#5559)

  • embedded services (Windows): fix CLIProxyAPI install failing instantly with spawn unzip ENOENT on Windows. The binary extractor spawned unzip, which is not a Windows system command — it only ships inside Git for Windows' usr/bin, a directory Node's spawn PATH never sees, so even users with Git installed hit the error. On Windows the extractor now uses PowerShell's built-in Expand-Archive (via execFileAsync, no shell — paths pass as a single non-interpreted arg, with ''-escaping + -LiteralPath as defense in depth); other platforms keep using unzip. This is distinct from [BUG] spawn EINVAL installing embedded services (9Router/CLIProxy) on Windows + Node.js 24+ — runNpm execFile('npm.cmd') needs shell #5379 (that was npm.cmd needing shell: true). Regression guard: tests/unit/binary-manager-extract-zip-5590.test.ts. (#5590)

  • storage (daemon): fix a Node.js out-of-memory crash on startup when storage.sqlite grows large (~170 MB+). The boot-time call-log cleanup (cleanupExpiredLogsrotateCallLogs) ran two unbounded SELECT … FROM call_logs … .all() queries — listReferencedArtifacts (every artifact path) and deleteCallLogsBefore (every id before the retention cutoff). node:sqlite's StatementSync.all() materializes the entire result set as JS objects at once, so on a large table the V8 heap blew up and the process crashed before binding (FATAL ERROR: … heap out of memory, native frame node::sqlite::StatementSync::All). Both queries now page through call_logs in bounded 5 000-row chunks (new src/lib/usage/callLogsBoundedQueries.ts), keeping peak memory flat regardless of table size — no more manual --max-old-space-size bump required. Regression guard: tests/unit/call-log-oom-unbounded-5618.test.ts. (#5618)

  • dashboard (provider setup): fix three provider setup links that pointed at 404 pages. Ollama Cloud / ollama-search linked to ollama.com/settings/api-keys → corrected to ollama.com/settings/keys (the page moved; Ollama Cloud is a real keyed service, so the field stays). SearchAPI linked to the bare searchapi.io/docs (404) → searchapi.io/docs/google. You.com linked to you.com/docs/search/overview (404) → you.com/business/api/ (the developer portal). All three replacements were verified live. Regression guard: tests/unit/provider-setup-links-5572.test.ts. (#5572, #5574, #5576)

  • providers (AI/ML API): the model-import step now loads the live AI/ML API catalog (400+ models) instead of falling back to a stale 6-model seed. The registry had no modelsUrl, so the route silently used the bundled catalog with an "API unavailable — using local catalog" warning even when the key was valid. AI/ML API exposes its full catalog at the public, auth-free https://api.aimlapi.com/models endpoint (a bare array of { id, type, info }, distinct from the OpenAI-compat /v1/models); it's now wired into the models route's discovery config, with the bundled catalog kept as the offline fallback. Regression guard: tests/unit/provider-models-route.test.ts. (#5570)

  • providers (CablyAI): mark CablyAI deprecated — cablyai.com no longer resolves (DNS NXDOMAIN, verified 2026-06-30); the domain is gone. The provider is removed from the models-route discovery config so the import step returns a clean error instead of an unhandled 500 crash (the dead-domain fetch threw with no local-catalog fallback), and the registry entry now carries deprecated: true / riskNoticeVariant: "deprecated" so the dashboard flags existing connections (same treatment as the shut-down glhf/kluster.ai gateways). Regression guard: tests/unit/provider-models-route.test.ts. (#5568)

  • dashboard (provider add): non-LLM search/agent providers no longer fail the model-import step with a red Provider <id> does not support models listing. Jules (Google Labs coding agent), linkup-search (Linkup web search), ollama-search (Ollama Cloud web search — distinct from the local Ollama LLM), and searchapi-search (SearchAPI SERP) have no /v1/models endpoint, so the import surfaced a failure for expected behavior. Each now ships a small static catalog of its selectable capability ids — Linkup's fast/standard/deep search depths, SearchAPI's google/bing/youtube/… engines, a single Jules/Ollama-web-search entry — so the import step returns a usable list (source: local_catalog) instead of an error. Regression guard: tests/unit/provider-models-route.test.ts. (#5569, #5571, #5573, #5575)

  • dashboard (provider add): providers without a live key/cookie validator (e.g. LMArena (Free), PiAPI) can now be saved. The Add-connection modal treated the backend's "Provider validation not supported" response as a hard Invalid state and blocked Save entirely, leaving those providers impossible to add. The validate route now returns unsupported: true alongside the message, and the modal treats that as a non-blocking warning — the "Check" badge still shows "validation not supported" (informational), but Save persists the credential as-is. Regression guards: tests/unit/ui/add-api-key-modal-unsupported-save-5565.test.tsx (Save proceeds) and tests/unit/providers-validate-route.test.ts (wire-format). (#5565, #5567)

  • providers (codex): fix the Codex Responses WebSocket path (/v1/responses), which regressed in v3.8.40 with a client-visible Invalid JSON body and bypassed the configured proxy. (1) [BUG] Regression: Codex Responses WebSocket fails in v3.8.40+ with "Invalid JSON body"; v3.8.39 works #5591 — PR fix(providers): refresh impersonation User-Agents + TLS profiles to current client versions #5237 bumped the impersonation TLS profile to chrome_149, but wreq-js@2.3.1 only supports up to chrome_147; the unknown profile produced a degenerate fingerprint and ChatGPT rejected the upstream upgrade. The Codex WS path is reverted to the proven chrome_142 (the v3.8.39 value), and the over-bumped grok-web/claude-web profiles (masked by their circuit-breaker but silently dropping TLS impersonation) are restored to chrome_146. A new regression guard asserts every configured chrome_* profile exists in the installed wreq-js typings (tests/unit/tls-profiles-valid-5591.test.mjs). (2) [BUG] Codex Responses WebSocket bypasses configured Global Proxy in no-direct-egress Docker deployments #5611 — the upstream wreq-js.websocket() connect ignored the Proxy Registry, so a no-direct-egress Docker container failed with a DNS error; the prepare route now resolves the Global/provider proxy and threads it through to the WS connect. Regression guard in tests/unit/responses-ws-proxy.test.mjs. (#5591, #5611)

  • providers (GLM): GLM 5.1 / 5.2 now keep the system role instead of having the system prompt folded into the first user turn. roleNormalizer.ts matched every glm* id with a blanket startsWith("glm") / startsWith("glm-") prefix, so the next-generation models — which z.ai documents as supporting the system role (GLM > 5.0) — were normalized as if they rejected it, degrading instruction-following. The matcher is now version-aware: it strips the system role only for bare glm, the 4.x family, and the 5.0 generation, and preserves it for glm-5.1/glm-5.2 (and the Fireworks glm-5p1 point alias). The ZenMux vendor-prefixed z-ai/glm-* compressed-history rule and the ERNIE rule are unchanged. Regression guards in tests/unit/role-normalizer.test.ts. (#5610)

  • Security hardening follow-ups (v3.8.15): the auth_token cookie now sets an explicit 30-day maxAge so sessions persist as intended (Seg3); the management bootstrap warns at boot when INITIAL_PASSWORD is left at the insecure CHANGEME default (Seg2); VS Code path-token endpoints (/api/v1/vscode/raw/[token]) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via npm root -g instead of a hardcoded /app (Bug3); and auto-update mode detection segment-matches node_modules instead of substring-matching, eliminating false "global install" positives (Bug1).

  • fix(cli): rename the Node process title to omniroute so it shows correctly in ps/htop. (thanks @waguriagentic)

  • dashboard (model picker): guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. ModelSelectModal's custom-provider branch filtered modelAliases entries with a raw fullModel.startsWith(...), which threw a TypeError whenever an alias value was null/undefined (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new buildNodeAliasModels helper (mirroring the sibling passthrough-alias guard, Claude Code tool calls are broken and cannot edit or modify files #485) that requires typeof fullModel === "string" before calling .startsWith. Regression guard: tests/unit/model-select-null-alias-guard-2247.test.ts. (thanks @wahyuzero)

  • fix(translator): strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik)

  • fix(kiro): stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky)

  • fix(translator): prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv)

  • codex (agent goal streams): protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. (#5772 — thanks @nguyenxvotanminh3)

  • sse (zero-width markers): strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. (#5857 — thanks @DKotsyuba)

  • usage (om-usage endpoint): restore the om-usage HTTP endpoint. (#5859 — thanks @Witroch4)

  • sse (stream readiness): tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. (#5767 — thanks @nguyenxvotanminh3)

  • security (provider node URL): harden provider node URL validation. (#5760 — thanks @nguyenxvotanminh3)

  • cli (Windows doctor): correct rootDir resolution in doctor.mjs on Windows. (#5845 — thanks @arssnndr)

  • providers (Antigravity): fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of fix(antigravity): fix 429 hang on credit exhaustion and precise reset time lockout #5823. (#5846 — thanks @Chewji9875 / @diegosouzapw)

  • providers (qwen-web): unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. (#5855 — thanks @janeza2)

  • providers (kimi-web): migrate to the www.kimi.com Connect-RPC API after kimi.moonshot.cn was retired. (#5858 — thanks @janeza2)

  • dashboard (CSRF): unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. (#5856 — thanks @rdself)

  • db (health check interval): preserve healthCheckInterval=0 across connection create/update instead of coercing it to a default. (#5822 — thanks @atomlong)

  • sse (claude→codex streaming): stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (fix(claude): Streaming claude→codex: reasoning-summary leaks into content + duplicated deltas (3.8.41 & 3.8.42) #5786). (#5832 — thanks @diegosouzapw)

  • deps (runtime): add the missing runtime dependencies @toon-format/toon and safe-regex so the published package resolves them at runtime. (#5771 — thanks @chirag127)

  • system (Windows auto-update): route in-app auto-update npm calls through the win32 shell helper so updates run correctly on Windows (fix: in-app auto-update fails with 'spawn npm ENOENT' — use pnpm/npx fallback or document manual update path #5542). (#5797 — thanks @diegosouzapw)

  • dashboard (validation badge): show a neutral badge for unsupported validation and make OAuth error messages clickable links ([FIX] LMArena (Free) — cookie validation not supported + no free models found + wrong default name + oversized cookie field #5442, [FIX] GitLab Duo — OAuth setup instructions shown only in error; expose setup wizard in provider UI #5486). (#5795 — thanks @diegosouzapw)

  • providers (metadata): correct stale/broken provider metadata ([FIX] Qoder — OAuth disabled by default with no UI guidance; expose PAT option + setup steps #5487, [FIX] Scaleway provider — broken keyHelpUrl link (scaleway.com/en/ai/generative-apis/ returns 404) #5461, fix: Microsoft 365 Copilot (BizChat) — 'access_token + chathubPath' extraction steps missing; users can't find the token #5534, [FIX] Together AI — billing required for API keys; warn on provider form + hide free-models toggle #5470). (#5790 — thanks @diegosouzapw)

  • providers (local-catalog imports): import intentional local-catalog-only providers instead of surfacing a 502 ([FIX] Reka — model import fails with 'local catalog fallback not synced' + wrong default name + empty validation model ID #5460, [FIX] t3.chat — confusing cookie instructions; 'Cookie header' unexplained + no extraction steps #5465). (#5787 — thanks @diegosouzapw)

  • proxyfetch (failover): skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. (#5770 — thanks @Ardem2025)

  • batch (recovery): persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. (#5753 — thanks @ag-linden)

  • memory (Qdrant): enabling Qdrant now activates it as the retrieval engine (the auto default never selected it) and adds inline guidance (docs: Qdrant (Vector Store Tier 2) config has no inline guidance — what is it, when to use, what to fill in each field [reply] #5597). (#5741 — thanks @diegosouzapw)

  • chat (non-streaming aggregation): harden non-streaming SSE aggregation against malformed upstream event sequences. (#5746 — thanks @rdself)

  • sse (cooldown parsing): the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. (#5747 — thanks @diegosouzapw)

  • api (body size): raise the LLM API payload limit for the responses routes so larger requests aren't rejected. (#5652 — thanks @JxnLexn)

  • providers (HuggingChat): fix HuggingChat web-session routing (Fix HuggingChat web session routing #5592). (#5592 — thanks @backryun)

  • sse (heap pressure): bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load ([BUG] crashes and restarting after memory heap #5152). (#5425 — thanks @josevictorferreira)

  • providers (M365 Copilot): validate M365 Copilot web credentials. (#5432 — thanks @skyzea1)

  • providers (chatgpt-web): restore the dot-form Pro model ids. (#5549 — thanks @Thinkscape)

  • security (error stacks): avoid rendering error stacks in responses. (#5624 — thanks @KooshaPari)

  • security (linkify): restrict linkifyText hrefs to an explicit http(s) scheme allowlist. (#948d2d7 — thanks @diegosouzapw)

  • translator (doubled tool args): prevent doubled tool-call arguments in the OpenAI→Claude translation path. (#5828 — thanks @diegosouzapw)

  • translator (orphaned tool results): strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. (#5805 — thanks @diegosouzapw)

  • translator (Gemini/Claude hardening): re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. (#5706 — thanks @diegosouzapw)

  • kiro (tool-result turns): stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. (#5807 — thanks @diegosouzapw)

  • providers (Kiro catalog): add claude-sonnet-5 to the Kiro model catalog. (#5796 — thanks @diegosouzapw)

  • oauth (connection disambiguation): disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. (#5803 — thanks @diegosouzapw)

  • github (Copilot prefill): drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. (#5802 — thanks @diegosouzapw)

  • mitm (hosts cleanup): clean up privileged /etc/hosts entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. (#5808 — thanks @diegosouzapw)

  • dashboard (model picker): guard null modelAliases values in the model picker so a connection with no aliases no longer throws. (#5792 — thanks @diegosouzapw)

  • dashboard (error boundaries): add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. (#5788 — thanks @diegosouzapw)

  • cli (process title): rename the running process title to omniroute. (#5791 — thanks @diegosouzapw)

  • compression (context-editing telemetry): record Context Editing telemetry on the streaming path, not just the non-streaming path. (#5761 — thanks @diegosouzapw)

  • security (v3.8.15 hardening follow-ups): land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. (#5512 — thanks @diegosouzapw)

📝 Maintenance

  • docs (architecture): add docs/architecture/ROUTER_BACKENDS.md — an ADR pinning down how the routing engines (ts native, bifrost, cliproxy, 9router, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in #5603 (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via #5868. (#5891)

  • tests (autoCombo): stabilize the getTaskFitnessWithSource identifies fitness_table as source for known models unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model gpt-4o is a real models.dev catalog id, so the fitness resolution chain returned models_dev_tier instead of the expected static fitness_table source. The fixture now uses claude-sonnet (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact source and score assertions are preserved (0.95 = FITNESS_TABLE.coding["claude-sonnet"]). (#5890) — thanks @KooshaPari

  • oauth (dead-code removal): delete the superseded legacy OAuth service-class hierarchy under src/lib/oauth/services/. The live OAuth flow runs through src/lib/oauth/providers.ts + src/lib/oauth/providers/ (wired into the generic oauth/[provider]/[action] route); the old per-provider class *Service extends OAuthService implementations plus their barrel had zero production or test references. Removed oauth.ts (base class), openai.ts, github.ts, claude.ts, codex.ts, antigravity.ts, qwen.ts, qoder.ts, and the index.ts barrel (−1559 LOC). Kept the three still-live files that routes import directly by path: kiro.ts (Kiro import/exchange routes), cursor.ts (Cursor import route), and codexImport.ts (utility fns for the Codex bulk-import route). Proven safe by typecheck:core staying green (any live reference would fail the build) + a filesystem guard tests/unit/oauth-legacy-services-removed.test.ts pinning the removal against re-introduction. Salvage of the closed PR #5039. gaps v3.8.42 — T10 (5.7).

  • refactor (god-file decomposition): extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner (#5714, #5717, #5705, #5709, #5722, #5721); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag (#5824, #5794, #5736, #5734); usage families / callLogs / usageHistory / providerLimits (#5782, #5725, #5728, #5730); api provider-models discovery / unified-catalog (#5758, #5699); memory retrieval scoring (#5733); evals golden-set suites (#5740); modelsDevSync transform layer (#5743); resilience settings split (#5745); dashboard sidebarVisibility split (#5683); executor shared-utility dedup + tests (#5720 — thanks @pizzav-xyz). — thanks @diegosouzapw

  • chore (Bun script runner): adopt Bun 1.3.10 as a locked, allow-listed build/dev script runner for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. (#5615, #5617, #5612, #5643 — thanks @KooshaPari; docs #5703 — thanks @diegosouzapw)

  • docs (sync & housekeeping): i18n CHANGELOG mirror sync for the [3.8.43] section (#5789); MCP tool count synced to 95 + routing-strategy count (#5732); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes (#5713, #5738 — thanks @chirag127); security docs for banned-keyword/account-ban detection (#5756) and the full LOCAL_ONLY route set + GHSA advisory + audit path (#5748); relay backend-routing contract clarification (#5621 — thanks @KooshaPari); release-freeze scoped to /generate-release only (#5839); .editorconfig repository standards (#5879 — thanks @shiva24082). — thanks @diegosouzapw

  • test/ci (stabilization & ratchets): guard the tsx/esm→esbuild boot transform (#5773); align t3-web web-session metadata (#5835); repoint the sidebar quota-share placement scan (#5711); lightweight health probe for batch e2e (#5651 — thanks @KooshaPari); make release-green pre-flight gates visible + bounded (#5644); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) (#5682); close the QG v2 tail (#5681); normalize check route paths on Windows (#5613 — thanks @KooshaPari); pass sonar.projectVersion to the SonarQube scan (#5880); plus stryker tap.testFiles registration, compression-studio smoke re-anchoring, rtk_discover de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw

  • refactor (oauth): remove dead legacy OAuth service classes. (#5838 — thanks @diegosouzapw)

🙌 Contributors

Thanks to everyone whose work landed in v3.8.43:

Contributor PRs / Issues
@ag-linden #5753
@Ardem2025 #5770
@arssnndr #5845
@atomlong #5822
@backryun #5592
@baslr direct commit / report
@Chewji9875 #5563, #5579, #5846
@chirag127 #5738, #5771
@DKotsyuba #5857
@hartmark #5834
@ishatiwari21 #5799
@janeza2 #5855, #5858
@jetmiky direct commit / report
@josevictorferreira #5425
@JxnLexn #5652
@KooshaPari #5613, #5621, #5624, #5629, #5643, #5651, #5890
@KunN-21 direct commit / report
@manhdzzz direct commit / report
@nguyenxvotanminh3 #5760, #5767, #5772
@noir017 direct commit / report
@pizzav-xyz #5720
@rdself #5746, #5856
@shiva24082 #5879
@skyzea1 #5432, #5701
@Stazyu #5557
@Thinkscape #5549
@vishalrajv direct commit / report
@voravitl direct commit / report
@waguriagentic direct commit / report
@wahyuzero direct commit / report
@warelik direct commit / report
@Witroch4 #5731, #5859, #5863
@diegosouzapw maintainer — cycle reconciliation, release-close base-red fixes, god-file decomposition, compression/memory features


Evidence

lint:            0 error(s), 4199 warning(s) (ratchet rebaselined 4158→4199)
typecheck:core:  pass    ·  check:cycles: OK
check:docs-all:  PASS    ·  check:docs-sync: PASS (openapi 3.8.43, 42 i18n mirrors)
check:test-masking: OK   ·  check:file-size: OK
unit suite:      20403 tests / 20390 pass / 0 fail  (full globs, --test-force-exit)

Release-close base-red fixes (3 real regressions + alignments): opencode header fabrication (#5720), resolveEffectiveKey undefined (#5798), Responses→Claude terminal events (#5828), Kiro claude-sonnet-5 pricing (#5796). 8 test files aligned to intended #5834/#5805 behavior (no asserts weakened), golden snapshot regenerated.

⚠️ After merging: Phase 2 (Local VPS homologation) before tagging.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request bumps the version of OmniRoute from 3.8.42 to 3.8.43 across multiple package files, OpenAPI specifications, and localized changelogs. Feedback is provided regarding the placement of the new release block in the localized changelogs, which was inserted out of chronological order.

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.

Comment thread docs/i18n/ar/CHANGELOG.md Outdated
Comment on lines +9 to +17
## [3.8.43] — TBD

### ✨ New Features

### 🔧 Bug Fixes

### 📝 Maintenance

---

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The new release block ## [3.8.43] — TBD is inserted between ## [3.8.31] and ## [3.8.42]. This breaks the chronological/version order of the changelog (currently showing 3.8.31 -> 3.8.43 -> 3.8.42). Please verify the intended ordering (ascending vs. descending) and place the new block accordingly. This issue affects all localized changelog files under docs/i18n/.

@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown

CI Coverage Report

  • Coverage job: success
  • PR test policy: success

Coverage artifact was not available for this run.

KooshaPari and others added 26 commits June 30, 2026 10:40
Integrated into release/v3.8.43 (drift-shed: cherry-picked the real change onto the release tip; stale-base drift dropped).
Integrated into release/v3.8.43 (drift-shed: cherry-picked the real change onto the release tip; stale-base drift dropped).
Integrated into release/v3.8.43 (drift-shed: cherry-picked the real change onto the release tip; stale-base drift dropped).
…5557)

Integrated into release/v3.8.43 (drift-shed: cherry-picked the real change onto the release tip; stale-base drift dropped).
Integrated into release/v3.8.43 (drift-shed: cherry-picked the real change onto the release tip; stale-base drift dropped).
…nse 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).
…igravity

## 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.
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.
…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
…amBody (#5563)

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
)

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.
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).
… 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).
…i-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).
…ctions 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).
diegosouzapw and others added 15 commits July 2, 2026 01:16
…support (#5878)

* docs: add ai_features scope to GitLab Duo OAuth env setup instructions

* docs: add LIVE_WS_ALLOWED_HOSTS env var to example config for LAN/Tailscale setups

* feat: add web socket public URL for reverse proxy/Cloudflare Tunnel WebSocket setups

* fix(dashboard): resolve live WS public URL at runtime via handshake with scheme validation

- Read NEXT_PUBLIC_LIVE_WS_PUBLIC_URL lazily in /api/v1/ws (function, not
  module-level const) so runtime env changes are honored in prebuilt images.
- Only echo/consume publicUrl when it is a ws:// or wss:// URL (server and
  client guards); anything else is rejected to null.
- useLiveDashboard now fetches /api/v1/ws?handshake=1 before connecting and
  prefers: explicit wsUrl > build-time env > handshake publicUrl > default.
- Align GitLab Duo scopes line in .env.example with GITLAB_DUO_CONFIG.scope.
- Extend tests: lazy env read + scheme validation cases.
- CHANGELOG entry for 3.8.43.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

---------

Co-authored-by: Septianata Rizky Pratama <ian.rizkypratama@gmail.com>
…) (#5881)

* fix(dashboard): add Modal Token ID + Token Secret fields (#5446)

Modal authenticates with a Token ID (ak-…) + Token Secret (as-…) pair sent as
`Authorization: Bearer <TOKEN_ID>:<TOKEN_SECRET>`. The add-connection form only
exposed a single API-key field, so users could not enter both credentials.

Add a dedicated two-field form for the `modal` provider: the existing field is
relabeled "Token ID" and a new "Token Secret" field is rendered below it. Both
are combined into the single encrypted `apiKey` value via a new pure helper
`combineModalCredential(id, secret)` → `id:secret`, so the generic bearer
executor path emits `Bearer <id:secret>` with no registry/executor/DB changes.
An empty secret returns the id verbatim, preserving the ability to paste a
pre-combined `id:secret` into the single field. The field hint points to
https://modal.com/settings → API Tokens.

Registry (baseUrl/executor), DB schema, and the request-time header path are
untouched — Modal remains bring-your-own-deploy.

Tests: tests/unit/modal-credential-combine.test.ts (5, TDD).

* docs(changelog): add v3.8.43 bullet for Modal two-field auth (#5446)
* test(autoCombo): make fitness source test stable against model caps

* chore(ci): retrigger checks for PR 5890

* docs(changelog): add 3.8.43 bullet for the autoCombo fitness-source test stabilization (#5890)

---------

Co-authored-by: kooshapari <kooshapari@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
…5891)

* routing: add router backend registry

* docs(architecture): add Router Backends & Embedded Services ADR (#5603)

Document the two orthogonal axes that #5603 asked to clarify: an engine's
lifecycle (in-process / supervised / external / disabled) vs the relay routing
backend selection (ts / bifrost / auto). Anchors the ADR on the typed
`src/domain/routing/routerBackends.ts` registry as the single source of truth,
and captures the /api/services/* status-code contract (409/200/404/403/500 +
the LOCAL_ONLY loopback guard) so dashboard errors are interpretable.

Stacked on the router-backend-registry work so it documents a real contract.

* docs(architecture): reduce ADR PR to docs-only — registry lands via #5868; describe adoption as tracked, not current

* docs(changelog): add 3.8.43 bullet for the Router Backends ADR (#5891)

---------

Co-authored-by: KooshaPari <kooshapari@gmail.com>
diegosouzapw and others added 3 commits July 2, 2026 05:48
…st + 4 more base-reds (#5798) (#5896)

* fix(db): remove stale modelContextOverrides allowlist entry from check:db-rules (#5798)

* fix(ci): clear release/v3.8.43 fast-gates base-reds (env-docs, ADR refs, mutation-cov, ratchets) (#5798)

* fix(sse): type-safe resolveBaseUrl/resolveEffectiveKey coercions in BaseExecutor (typecheck:core base-red, #5798)

* chore(quality): freeze base.ts at post-typecheck-fix size (#5798)

* fix(docs): add required MDX frontmatter to ROUTER_BACKENDS ADR (build base-red, #5798)
* fix: preserve codex bare image model over combo shadowing

* docs(changelog): credit #5902 codex bare image alias fix

* docs(changelog): restore #5902 bullet after merge auto-resolve

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
…5842) (#5901)

* fix(providers): route OpenAI responses-only models to /v1/responses (#5842)

* docs(changelog): restore #5842 bullet after merge auto-resolve ate it

* docs(changelog): keep #5842 bullet additive over release tip
@pizzav-xyz

Copy link
Copy Markdown
Contributor

@diegosouzapw its really sad that you have made the decision to remove my crucial feature of the opencode header fabrication so the api key actually gets used instead of being classified as anomynous

@chirag127

chirag127 commented Jul 2, 2026 via email

Copy link
Copy Markdown
Contributor

@diegosouzapw diegosouzapw merged commit b729a8f into main Jul 2, 2026
63 checks passed
@sonarqubecloud

sonarqubecloud Bot commented Jul 2, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
0.0% Coverage on New Code (required ≥ 80%)
3.1% Duplication on New Code (required ≤ 3%)
D Security Rating on New Code (required ≥ A)
D Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

KooshaPari added a commit to KooshaPari/OmniRoute that referenced this pull request Jul 2, 2026
* 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 …
dimaslanjaka pushed a commit to dimaslanjaka/OmniRoute that referenced this pull request Jul 3, 2026
* 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

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.

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.

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).

`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).

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-identical (sed-extracted, verbatim
verified); the only host edits are the leaf import + one prettier collapse of
a pre-existing 2-line union type annotation to 1 line (token-identical,
typecheck-confirmed).

Characterize-first (operator-chosen): the existing db-migration-runner.test.ts
(26 tests) + no-migration-collisions/weak-rng-fixes/check-db-rules (11) prove
the reconciliation/dedup/already-applied BEHAVIOUR is unchanged; the new
tests/unit/db-migrationrunner-constants-split.test.ts (7 tests) PINS THE DATA
(counts + shape + spot-checks of every table) so a dropped/transposed row is
caught immediately.

Refs #3501.

* refactor(db): extract pure SQL-source builders from usageAnalytics.ts (#5722)

usageAnalytics.ts (924 lines, frozen-baselined) mixes two pure SQL-source
builders with ~20 getXxxRows() query functions. Extract the contiguous,
DB-free builder block verbatim into a leaf, leaving every query function in
the host:

  - usageAnalytics/sources.ts (208)  AnalyticsParams, BuildUnifiedSourceOptions,
    UnifiedSourceResult + buildUnifiedSource + buildPresetUnifiedSource (pure
    string builders; no DB, no imports)

Host usageAnalytics.ts: 924 -> 723. The query functions do not call the
builders (callers build the unified source then pass the string in), so the
host re-exports the 5 moved public symbols (2 fns + 3 types) and imports
AnalyticsParams as a type for its query signatures — the public API stays
IDENTICAL (39 symbols). Builder bodies moved byte-identical; the two orphaned
section-header banners that described the moved block were removed with it;
the retained query-function suffix is byte-identical to the original.

Behavior-preserving: 37 existing analytics consumer tests stay green
(usage-analytics 12, usage-endpoint-dimension 3, db-usage-analytics-3500 22);
new tests/unit/db-usageanalytics-split.test.ts (25 assertions) characterizes
buildUnifiedSource's needsAggregated branching (raw-only vs raw+daily_usage_summary)
+ guards the 39-symbol re-export barrel.

Refs #3501.

* docs(readme): refresh metrics, list 17 strategies, add Quota-Share + real provider logos

- Unify provider count to 236; MCP tools 87->94; cloud agents 3->4 (+Cursor); compression 9->10 engines (+relevance)

- Tests -> 21,000+ across 2,586 files; footer -> v3.8.43

- Raise lower bounds to real values: 90+ free, 80+ commands, 24+ CLIs

- Language flag grid 33->43 (15/14/14, all locales)

- List all 17 routing strategies; new Quota-Share section before Resilience

- Real provider logos (lobe-icons + local agentrouter) in providers grid and Free Forever

- Top Contributors: refreshed stats + add herjarsa; 280+ title; half-size avatars; contrib.rocks 100->200

- Acknowledgments: refreshed star counts; fix headroom repo rename

* docs(readme): update provider counts and add new badges

* feat(memory): T10/TV6 — opt-in typed memory decay (#5723)

Opt-in typed memory decay so the conversational memory store self-prunes stale episodic noise. access_count + last_accessed_at telemetry (migration 111) is always-on/non-destructive; the sweep is opt-in (MEMORY_TYPED_DECAY_ENABLED, default false). Only episodic decays by default (30d); factual/procedural/semantic immune; access_count>=3 earns immunity; deletions reuse deleteMemory (SQLite+vec+Qdrant in sync), fail-open. Regression guard: tests/unit/memory/typed-decay.test.ts (15). gaps v3.8.42 — T10/TV6.

* feat(dashboard): T06/T03 — drag-reorder compression pipeline editor + studio e2e (#5727)

T06: named-combos editor gains a @dnd-kit/sortable drag-to-reorder stacked pipeline backed by a pure model (compressionPipelineModel.ts: add/remove/move/update, engine->intensity invariant, never-empty). CompressionPipelineEditor.tsx replaces the inline fixed list in CompressionCombosPageClient; order persists via the existing combos endpoint (no API change). T03: adds tests/e2e/compression-studio.spec.ts (Tela A render + Play/Compare tab switch), the dedicated compression-studio e2e combo-live-studio.spec.ts did not cover. TDD: compression-pipeline-model.test.ts (11) + compression-pipeline-editor.test.tsx (4). gaps v3.8.42 — T06 + T03.

* fix(thinking): wire Thinking-Budget boot hydration into live instrumentation path (#5312) (#5729)

hydrateThinkingBudgetConfig was only called from the unused src/server-init.ts,
which never runs in production, so the dashboard Thinking-Budget mode silently
reverted to passthrough on every restart. Wire it into the real boot path
(src/instrumentation-node.ts), next to the Global System Prompt restore.

Surfaced by live Anthropic-OAuth validation on the VPS (fix A of #5312 was
non-functional even though its direct unit test passed). New guard
tests/unit/thinking-budget-boot-wiring-5312.test.ts asserts the production boot
module calls the hydration, closing the test gap that let this ship.

* refactor(usage): extract pure formatting helpers from callLogs.ts (#5725)

callLogs.ts (996 lines, frozen-baselined) mixes pure log-formatting /
sanitization helpers with DB CRUD, disk-artifact, and rotation logic. Extract
the ten pure, DB-free helpers verbatim into a leaf, leaving all stateful code
in the host:

  - callLogs/format.ts (129)  asRecord, toNumber, toStringOrNull, truncateText,
    parseInlineError, normalizeDetailState, sanitizeErrorForLog,
    toStoredErrorSummary, protectPipelinePayloads, buildRequestSummary

Host callLogs.ts: 996 -> 885. The stateful generateLogId (mutates logIdCounter)
stays in the host. These helpers were all module-internal, so the public API is
unchanged (10 exported functions). Bodies moved byte-identical; the host's now
unused 'sanitizePII' import (only referenced inside the moved bodies) moved to
the leaf; prettier wrapped buildRequestSummary's signature across lines once the
'export' prefix pushed it past 100 cols (token-identical).

Behavior-preserving: 46 existing call-log consumer tests stay green
(call-log-cap 14, pagination 4, file-rotation 5, log-retention 5, startup 1,
oom 2, trim-sql 2, db-settings-maintenance 13); new
tests/unit/calllogs-format-split.test.ts (26 assertions) characterizes the pure
helpers + guards the 10-function public API.

Refs #3501.

* refactor(usage): extract pure stat/coercer helpers from usageHistory.ts (#5728)

usageHistory.ts (987 lines, frozen-baselined) mixes pure DB-free helpers with
an in-memory pending-request state machine and DB CRUD. Extract the contiguous
pure block verbatim into a leaf, leaving all stateful code in the host:

  - usageHistory/helpers.ts (85)  asRecord, toStringOrNull, normalizeServiceTier,
    toNumber, percentile, stdDev, truncatePendingPreview (+ its MAX_PREVIEW_*
    bounds, co-located)

Host usageHistory.ts: 987 -> 916. The pending-request state machine (module
Maps + track/update/finalize/sweep) and DB CRUD stay in the host. These helpers
were all module-internal, so the public API is unchanged (21 direct exports +
the pre-existing getCompletedDetails re-export = 22). Bodies moved byte-identical
(leaf 0 non-verbatim lines); the host's local 'type JsonRecord' moved with the
bodies that used it (host no longer references it — typecheck-confirmed).

Behavior-preserving: 38 existing usage-history consumer tests stay green
(usage-history-db 5, api-key-usage-limits 6, log-retention 5,
usage-endpoint-dimension 3, provider-request-failure-pipeline 6,
database-settings-maintenance 13); new
tests/unit/usagehistory-helpers-split.test.ts (30 assertions) pins the
percentile/stdDev formulas + normalizeServiceTier + guards the public API.

Refs #3501.

* refactor(usage): extract pure quota-normalize helpers from providerLimits.ts (#5730)

providerLimits.ts (954 lines, frozen-baselined) is the heavily DB/network-coupled
provider quota sync module. Extract a small, fully SELF-CONTAINED leaf of pure
quota-key/quota-value normalization helpers (+ the isRecord type guard they
share), leaving all sync/DB/network code in the host:

  - providerLimits/quotaNormalize.ts (72)  isRecord, isUsageQuotaKeyAllowed,
    normalizeUsageQuotaKey, normalizeUsageQuotasForProvider,
    sanitizeUsageQuotasForProvider

Host providerLimits.ts: 954 -> 890. The leaf imports only the external
antigravity/agy model-alias helpers the moved bodies reference (moved from the
host's import block) — it does NOT import the host, so check:cycles stays clean
(no cycle). isRecord (used ~9x in the host) is co-extracted and imported back.
These five were all module-internal, so the public API is unchanged (13
exported functions). Bodies moved byte-identical.

Behavior-preserving: 18 existing provider-limits consumer tests stay green
(sanitize-scope 3, db-provider-limits 3, proxy-fail-closed 3,
rotating-expired-guard 7, codex-quota-sync 2); new
tests/unit/providerlimits-quotanormalize-split.test.ts (19 assertions) pins
isRecord + isUsageQuotaKeyAllowed + guards the 13-function public API.

Refs #3501.

* refactor(memory): extract pure scoring/conversion helpers from retrieval.ts (#5733)

retrieval.ts (1192 lines — ABOVE its 1171 frozen baseline) is the memory
retrieval engine (DB + vector + rerank network). Extract the pure, DB-free
scoring/conversion helpers (+ the MemoryRow row shape they share) verbatim into
a self-contained leaf, leaving all DB/vector/network code in the host:

  - retrieval/scoring.ts (104)  interface MemoryRow + estimateTokens,
    parseMetadata, rowToMemory, getRelevanceScore

Host retrieval.ts: 1192 -> 1072 — back UNDER the 1171 frozen baseline (the split
also repairs the pre-existing file-size drift). The leaf imports only ../types,
never the host, so check:cycles stays clean (no cycle). MemoryRow moved to the
leaf and imported back as a type by the host's DB row functions. The public
estimateTokens is re-exported from the leaf; the host also imports it for its
internal token-budget loops. The other three helpers were module-internal, so
the public API is unchanged (7 exports). Bodies moved byte-identical.

Behavior-preserving: 38 existing memory-retrieval consumer tests stay green
(rerank 5, hybrid 6, semantic 6, engine-status 9, stats-api 12); new
tests/unit/retrieval-scoring-split.test.ts (11 assertions) pins
estimateTokens (ceil(len/4)) + parseMetadata + rowToMemory mapping +
getRelevanceScore (+20 phrase / +3 token) and guards the public API.

Refs #3501.

* refactor(sse): extract reasoning-tag detection/extraction from responseSanitizer.ts (#5734)

responseSanitizer.ts (1133 lines, frozen-baselined) mixes reasoning-tag
detection/extraction with response/usage/streaming sanitization. Extract the
cohesive, ZERO-IMPORT reasoning block verbatim into a self-contained leaf:

  - responseSanitizer/reasoning.ts (143)  the reasoning regex consts +
    collapseExcessiveNewlines, cleanReasoningFragment,
    splitClosingOnlyReasoningPrefix, movePrefixBeforeContentTagToThinking,
    extractThinkingFromContent, normalizeReasoningRouteId,
    isAntigravityReasoningRoute, isTextualReasoningTagNativeRoute,
    shouldParseTextualReasoningTags

Host responseSanitizer.ts: 1133 -> 1003. The block's helpers only call each
other, so the leaf has ZERO imports — it cannot import the host (check:cycles
clean). The host imports back collapseExcessiveNewlines (6 call sites) +
extractThinkingFromContent, and re-exports the two public symbols
(extractThinkingFromContent, shouldParseTextualReasoningTags) — the public API
stays IDENTICAL (7 exports). Bodies moved byte-identical; two long declarations
(REASONING_TAG_FRAGMENT_REGEX, movePrefixBeforeContentTagToThinking signature)
were line-wrapped by prettier once the 'export' prefix pushed them past 100
cols (token-identical).

Behavior-preserving: 47 existing consumer tests stay green (response-sanitizer
36, strip-reasoning-header 8, textual-toolcall-false-positive 3); new
tests/unit/responsesanitizer-reasoning-split.test.ts (11 assertions)
characterizes extractThinkingFromContent + shouldParseTextualReasoningTags and
guards the public API.

Refs #3501.

* refactor(sse): extract rate-limit header parsing from rateLimitManager.ts (#5736)

rateLimitManager.ts (1034 lines, frozen-baselined) is the stateful rate-limiter
(Bottleneck limiters, watchdog timers, learned-limits Map). Extract the pure,
ZERO-IMPORT header-parsing block verbatim into a self-contained leaf, leaving
all stateful machinery in the host:

  - rateLimitManager/headers.ts (94)  STANDARD_HEADERS, ANTHROPIC_HEADERS,
    parseResetTime, toPlainHeaders

Host rateLimitManager.ts: 1034 -> 945. The four items are pure (no limiter
state, no external deps), so the leaf has ZERO imports — it cannot import the
host (check:cycles clean). The host imports all four back (used by
updateFromHeaders). They were module-internal, so the public API is unchanged
(17 exports). Bodies moved byte-identical.

Behavior-preserving: 21 existing rate-limit consumer tests stay green
(rate-limit-manager 7, limiter-lifecycle 4, queue-timeout-msg 2,
idle-eviction 6, body-lock 2); new
tests/unit/ratelimitmanager-headers-split.test.ts (7 assertions) pins
parseResetTime (durations / bare-number / nullish) + toPlainHeaders + guards
the 17-function public API (with a watchdog-timer teardown hook so the runner
exits cleanly).

Refs #3501.

* fix(config): back boot-hydrated proxy config singletons with globalThis (#5312) (#5742)

Next.js compiles instrumentation.ts as a separate webpack module graph from the
app-route/open-sse executors, so a module-local `let _config` is duplicated:
the boot-time hydration (applyRuntimeSettings / restore hooks) lands on the
instrumentation graph's copy, but the request path (base.ts) reads a different,
un-hydrated copy. Live VPS validation proved the Thinking-Budget hydrate ran to
completion at boot yet base.ts still read the passthrough default — why #5312
fix A stayed broken after the boot-wiring fix.

Back the singletons with globalThis (the pattern systemPrompt.ts already uses for
- thinkingBudget.ts        — dashboard Thinking-Budget mode reaches the executor
- backgroundTaskDetector.ts — opt-in background degradation actually fires
- systemTransforms.ts       — operator pipeline overrides reach the request path
payloadRules.ts was already safe (lazy per-request DB self-load, #2986).

Guards: thinking-budget-globalthis-5312 + runtime-config-globalthis-5312
(assert globalThis sharing; a module-local let fails them, RED->GREEN).

* refactor(evals): extract built-in golden-set suites from evalRunner.ts (#5740)

Move the 7 static built-in eval suites (golden-set, coding-proficiency,
reasoning-logic, multilingual, safety-guardrails, instruction-following,
codex-comparison) plus the builtInSuites aggregate into the pure-data leaf
src/lib/evals/evalRunner/builtinSuites.ts (zero imports, no side effects).

evalRunner.ts keeps all logic (register/get/list/evaluate/run/scorecard/reset)
and registers the leaf suites at module load, mirroring the original inline
calls. Public API is unchanged (7 exported functions; the suite consts were
already module-private). Host 960->301 LOC; leaf 676 LOC (< 800 cap); host
was frozen-satisfied (961), so this is debt reduction.

Suite data moved verbatim (652 data lines byte-identical). New split-guard
test characterizes the suite ids/case counts/key cases and proves the host
registers every leaf suite at load.

* refactor(models): extract pure transform layer from modelsDevSync.ts (#5743)

Move the models.dev data-model types, the provider-id mapping table
(MODELS_DEV_PROVIDER_MAP + mapProviderId), and the raw->OmniRoute transforms
(transformModelsDevToPricing, transformModelsDevToCapabilities) into the pure
leaf src/lib/modelsDevSync/transform.ts (zero imports, no DB, no module state).

modelsDevSync.ts keeps all sync orchestration, DB access, caches and the
periodic-sync timer; it imports the transforms for internal use and re-exports
mapProviderId/transformModelsDevToPricing/transformModelsDevToCapabilities plus
the ModelCapabilityEntry/CapabilitiesByProvider types, so the public API is
unchanged. Host 924->677 LOC; leaf 279 LOC (< 800 cap); host was
frozen-satisfied (934), so this is debt reduction.

238 moved lines are byte-identical. New split-guard test characterizes the
provider map + both transforms and proves the host re-exports them.

* refactor(resilience): split settings.ts into types + normalize leaves (#5745)

Decompose the (fully pure) resilience settings module into two sibling leaves:
- src/lib/resilience/settings/types.ts: the settings shape (11 public
  interfaces + JsonRecord/AuthCategory), zero imports.
- src/lib/resilience/settings/normalize.ts: the coercers (asRecord/toInteger/
  toBoolean/feature-flag resolvers) + the 11 per-section normalize* functions.

settings.ts keeps DEFAULT_RESILIENCE_SETTINGS, DEFAULT_REQUEST_QUEUE_MAX_WAIT_MS,
buildLegacyFallback, and the public orchestrators (resolveResilienceSettings,
mergeResilienceSettings, buildLegacyResilienceCompat); it imports the
coercers/normalizers for internal use and re-exports the 11 settings interfaces,
so the public API is unchanged. Host 840->363 LOC; leaves 182 + 359 LOC
(< 800 cap); host was frozen-satisfied (841), so this is debt reduction.

472 moved lines are byte-identical; no cycles (leaves never import the host).
New split-guard test characterizes the coercers/normalizers and the host
resolve/merge/compat orchestration.

* docs(readme): document faster/leaner install — skip native build, sql.js fallback (#5713)

Documents the optional better-sqlite3 + pure-JS fallback chain and OMNIROUTE_SKIP_POSTINSTALL/CI skip flags. Docs-only, claims verified. (#5550)

* feat(compression): T02 opt-in per-engine pipeline circuit-breaker (#5735)

Opt-in, default-off per-engine circuit-breaker for the stacked compression pipeline. Byte-identical to legacy when off. 9 regression tests.

* docs: sync MCP tool count to 95 + routing-strategy count (#5732)

Sync CLAUDE.md/README.md to canonical MCP tool count (95, 35 base) and routing strategies (17). Numbers fact-checked against getAllToolDefinitions()/ROUTING_STRATEGY_VALUES.

* feat(api): add first-class Ollama local provider card (#5712)

First-class ollama-local provider card (localhost:11434/v1, keyless, passthrough models) in LOCAL_PROVIDERS + SELF_HOSTED + default.ts executor case. Docs count 236→237, Local 11→12 (full README sweep). 4 tests. (#5578)

* feat(api): add opt-in API-key provider quota-policy bypass scope (#5731)

Adds an opt-in per-API-key scope (policy:bypass-provider-quota) that lets a key skip provider/account-side quota cutoffs during routing. Operator USD budgets/usage limits still enforced unconditionally (fail-closed, before the bypass). Default-off; UI toggle + badge in API Manager. Integrated into release/v3.8.43.

* feat(codex): opt-in auto-sync of Codex profiles after model discovery (#5737)

Auto-sync ~/.codex/*.config.toml profiles after a provider model sync, reusing the setup-codex generator. Opt-in, default OFF (OMNIROUTE_AUTO_SYNC_CODEX_PROFILES=true; also honors CLI_ALLOW_CONFIG_WRITES). Never touches the active Codex config. Gating test added.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* feat(providers): opt-in CLI profile auto-sync toggles + Claude Code auto-sync (#5755)

Providers-dashboard 'CLI profile auto-sync' card (Codex + Claude Code toggles), feature-flag backed (default off), + Claude Code auto-sync mirroring the Codex path. Follow-up to #5737.

* feat(compression): T08/H8 (2.3) — graduated CCR retrieval-feedback ramp (#5739)

Turns CCR retrieval feedback from a binary cliff into a graduated ramp: each prior retrieval raises a block's effective minChars linearly (effectiveMinChars); >= 3 retrievals still excluded (Infinity). retrievalRampFactor default 2 (config/env COMPRESSION_CCR_RETRIEVAL_RAMP_FACTOR); 1 = legacy binary. Regression guard: tests/unit/compression/ccr-retrieval-ramp.test.ts (12); 51 existing CCR tests green. gaps v3.8.42 — T08/H8 (2.3).

* feat(compression): T08/H5 (2.4) — usage-observed prefix freeze (opt-in) (#5744)

Evolves the cache-aware guard to also learn which system prompts recur: observed >= threshold → treated as a stable cacheable prefix and preserved even for providers the static check misses. Content-addressed by a hash of the system prompt (OpenAI/Claude/Gemini), in-memory, freeze=preserve (never mutates). Opt-in/default-off (COMPRESSION_PREFIX_FREEZE_ENABLED); respects the never preserve-mode. New prefixFreeze.ts wired into resolveCacheAwareConfig. Regression guard: prefix-freeze.test.ts (10); 44 cache-aware tests green. gaps v3.8.42 — T08/H5 (2.4).

* feat(compression): T08/H7 (2.5) — read-lifecycle engine (collapse superseded reads) (#5754)

New opt-in, default-off read-lifecycle engine: collapses stale/superseded file-Read tool results (same path re-read OR modified later) to a stub, keeping the current Read intact. Anthropic + OpenAI tool shapes; conservative (known tool names, exact path, strictly-later); fail-open. Lossy → opt-in. Regression guard: read-lifecycle.test.ts (10); 41 registry/pipeline suites green. gaps v3.8.42 — T08/H7 (2.5). Completes Onda 2.

* fix(sse): anti-thundering-herd guard tolerates numeric-epoch cooldowns (#5747)

markAccountUnavailable's dedupe guard used a raw `new Date()` on
rateLimitedUntil, which can hold a numeric-epoch string (e.g. the
Antigravity full-quota path via setConnectionRateLimitUntil). That
produced Invalid Date/NaN, so the guard never detected an already
cooling connection — a second concurrent failure on the same
connection overwrote a long quota-exhaustion cooldown with a much
shorter fresh backoff cooldown, making the account selectable again
far sooner than intended.

Reuses the existing cooldownUntilMs normalizer (#3954) instead of a
raw Date parse.

* fix(chat): harden non-streaming SSE aggregation (#5746)

* fix: repoint DashScope/Alibaba setup links to consoles (#5665) (#5762)

* fix: point Quick Start step 1 to API Keys page, not Endpoint (#5695) (#5763)

* fix: onboarding wizard saves providers with unsupported validation (#5692) (#5764)

* docs(security): document full LOCAL_ONLY route set + GHSA-fhh6-4qxv-rpqj + audit path (#5599) (#5748)

Expand ROUTE_GUARD_TIERS.md Tier 1 (LOCAL_ONLY):
- link the GHSA advisory and explain the attack class (RCE via a subprocess spawn
  reachable from non-loopback traffic)
- replace the 3-example prefix table with the full LOCAL_ONLY set, mirroring
  LOCAL_ONLY_API_PREFIXES / LOCAL_ONLY_API_PATTERNS in routeGuard.ts (the
  authoritative source; check-route-guard-membership enforces the code side)
- add an "Operator guidance & auditing" section for users behind
  nginx/Cloudflare/Tailscale: don't forge X-Forwarded-For loopback, keep the
  manage-scope bypass minimal, and how to audit non-loopback access

Docs-only; SECURITY.md already links here.

Closes #5599

* docs(security): document banned-keyword / account-ban detection (#5600) (#5756)

* docs(security): add BAN_DETECTION.md — banned-keyword / account-ban detection (#5600)

New docs/security/BAN_DETECTION.md documenting the previously-undocumented system:
- the 8 built-in ACCOUNT_DEACTIVATED_SIGNALS + custom keywords are additive
- detection flow (body substring match -> terminal `banned` state, skipped in
  account selection; `deactivated` on 401/403; autoDisableBannedAccounts)
- scope: global (all providers); the signal strings target OAuth/subscription scrapers
- custom keywords: add path, 200-char cap, hot-reload, and the false-positive
  warning (raw substring match -> prefer full ban sentences, not "quota"/"limit")
- recovery: terminal states never auto-recover -> re-test / re-auth / re-enable

Registered in security meta.json; cross-linked from RESILIENCE_GUIDE (terminal
states). Docs-only.

Closes #5600

* docs(security): clarify deactivated vs expired terminal-status split (#5600)

The same ACCOUNT_DEACTIVATED signal surfaces as two different terminal
statuses depending on the code path: chatCore.ts inline writes 'deactivated'
(401/403 via classifyProviderError), while markAccountUnavailable() ->
resolveTerminalConnectionStatus() writes 'expired'. Document both.

* fix: surface relay proxy-test errors instead of silent failure (#5716) (#5765)

* refactor(api): extract pure discovery leaves from provider-models route (#5758)

Split src/app/api/providers/[id]/models/route.ts (2511 -> 1818 LOC) by moving
the cohesive, DB-free discovery building blocks into four leaves under
discovery/:

- helpers.ts              record/string coercion, Azure + base-url helpers,
                          bearer/named-openai header builders
- normalizers.ts          Antigravity / DataRobot / OpenAI-like / SAP models
                          response normalizers
- providerModelsConfig.ts PROVIDER_MODELS_CONFIG + ProviderModelsConfigEntry
- providerSets.ts         NAMED_OPENAI_STYLE_PROVIDERS + isNamedOpenAIStyleProvider

The host keeps all request orchestration and imports the leaves back. The moved
symbols were module-private, so the route's public export set (GET) is unchanged
and no external importer needs updating. Bodies are byte-identical: the code-line
multiset of host + leaves equals the original route verbatim.

Tests:
- repoint the qwen-web source-guard in catalog-updates-v3829-kimi-qwen to the new
  config leaf (assertions unchanged)
- add provider-models-discovery-split as the split regression guard (leaf public
  surface + host wiring + the #5570 cablyai->aimlapi entry swap)

* fix(memory): enabling Qdrant activates it as the engine + inline guidance (#5597) (#5741)

* fix(memory): enabling Qdrant now activates it as the engine + inline guidance (#5597)

Enabling Qdrant in the Engine tab was inert: retrieval only routes to Qdrant when
memoryVectorStore === "qdrant" (the default "auto" never selects it), and the card
only wrote qdrantEnabled — nothing set the engine selector, and there is no UI for
it. So users configured Qdrant, saw "enabled", but it was never actually used.

- PUT /api/settings/qdrant now sets memoryVectorStore alongside the toggle:
  enable -> "qdrant", disable -> "auto". Editing other fields leaves it untouched.
- Add inline guidance to QdrantConfigCard: a Tier-1-vs-Tier-2 banner + per-field
  help (host, collection, embedding model). Note there is no "vector dimension" or
  "distance metric" field: dimension is auto-detected from the embedder, distance
  is always Cosine.
- Document the real behavior in MEMORY.md: engine gate, no back-fill of existing
  memories, dimension auto-detect, Cosine-only, API-key-only auth.

Tests: tests/integration/qdrant-routes.test.ts — enable->qdrant, disable->auto, and
field-edit-without-enabled leaves the engine untouched (TDD: red -> green).

Closes #5597

* fix(memory): invalidate memory-settings cache on Qdrant toggle (#5597)

The PUT handler wrote memoryVectorStore to the DB but retrieval reads through
getMemorySettings(), a module-level cache. Without busting it, the engine switch
did not take effect until a process restart (the DB said qdrant, retrieval kept
routing to sqlite-vec). Now calls invalidateMemorySettingsCache() after the write,
mirroring src/app/api/settings/memory/route.ts.

Regression test warms the cache, toggles via the route, and asserts
getMemorySettings().vectorStore flips to qdrant (fails without the invalidate call).

* fix(compression): record Context Editing telemetry on the streaming path (#5761)

Streaming SSE responses now preserve context_management from the final message_delta snapshot and fire the telemetry hook in onStreamComplete, so context-clear savings surface in compression analytics for streaming (not just non-streaming). Additive telemetry, Claude-only, opt-in-neutral. gaps v3.8.42 — T01 (5.1). Test: context-editing-streaming-telemetry.test.ts (3, failing->passing).

* Persist batch item checkpoints during recovery (#5753)

* fix(sse): checkpoint batch item recovery

* fix(db): renumber batch checkpoints migration 110→112 (collision with #5667)

110 was taken by 110_model_context_overrides.sql (#5667), which landed on the
release branch after this PR branched. migrationRunner throws a hard version-
collision error on startup when two files share a numeric prefix. 112 is the
next free slot (110/111 taken on the release tip).

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

---------

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* fix: resolve CCR MCP retrieve principal from api-key auth context (#5649) (#5768)

* feat(cli): show version in startup banner (integrates #5752) (#5769)

* feat(cli): show version in startup banner

Print dim 'v<version>' line below ASCII art logo in omniroute serve.
Uses readFileSync (same pattern as program.mjs) to read package.json.
Closes #5749.

* test(cli): guard startup-banner version line (#5752)

Source-inspection test (same pattern as cli-serve-port.test.ts) asserting
serve.mjs parses the version from package.json and prints v${_pkg.version}
in the startup banner — satisfies Hard Rule #8 for the bin/ change.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* docs(changelog): credit #5752 startup-banner version line (thanks @chirag127)

---------

Co-authored-by: Chirag Singhal <76880977+chirag127@users.noreply.github.com>

* fix(proxyfetch): skip fallback for non-replayable bodies (#5770)

* chore(release): open v3.8.42 cycle

Bump version to 3.8.42, add CHANGELOG placeholder, sync openapi/electron/open-sse + 42 i18n CHANGELOG mirrors.

* chore: remove unused qdrant schema aliases (#5404)

Integrated into release/v3.8.42

* chore: remove unused memory schema aliases (#5403)

Integrated into release/v3.8.42

* chore: remove unused quota schema types (#5402)

Integrated into release/v3.8.42

* chore: remove unused playground row type (#5401)

Integrated into release/v3.8.42

* chore: remove unused codegraph exports (#5400)

Integrated into release/v3.8.42

* chore: remove unused notion client type (#5399)

Integrated into release/v3.8.42

* chore: remove unused settings types (#5398)

Integrated into release/v3.8.42

* chore: remove unused combo types (#5396)

Integrated into release/v3.8.42

* chore: remove unused provider types (#5393)

Integrated into release/v3.8.42

* chore: remove unused skillssh skill type (#5392)

Integrated into release/v3.8.42

* chore: remove unused status hex key type (#5391)

Integrated into release/v3.8.42

* chore: remove unused batch provider type (#5390)

Integrated into release/v3.8.42

* chore: remove unused skills schema types (#5389)

Integrated into release/v3.8.42

* chore: remove unused codex auth input type (#5388)

Integrated into release/v3.8.42

* chore: remove unused memory schema types (#5387)

Integrated into release/v3.8.42

* chore: remove unused playground row type (#5386)

Integrated into release/v3.8.42

* chore: remove unused qdrant schema types (#5385)

Integrated into release/v3.8.42

* chore: remove unused kiro social schema (#5384)

Integrated into release/v3.8.42

* chore: remove unused memory schema types (#5383)

Integrated into release/v3.8.42

* chore: remove unused audit action type (#5382)

Integrated into release/v3.8.42

* chore: remove unused agent skills schema types (#5381)

Integrated into release/v3.8.42

* chore: remove unused shared logger default export (#5380)

Integrated into release/v3.8.42

* chore: remove unused sse logger helpers (#5378)

Integrated into release/v3.8.42

* chore: remove unused sse model legacy helpers (#5377)

Integrated into release/v3.8.42

* chore: remove unused v1 search response schema (#5376)

Integrated into release/v3.8.42

* chore: remove unused cloud agent result schemas (#5375)

Integrated into release/v3.8.42

* chore: remove unused a2a routing logger readers (#5374)

Integrated into release/v3.8.42

* chore: remove unused webhook delivery detail export (#5372)

Integrated into release/v3.8.42

* chore: remove unused api key type (#5395)

Integrated into release/v3.8.42

* chore: remove unused usage types (#5397)

Integrated into release/v3.8.42

* chore: remove unused cloud agent input types (#5373)

Integrated into release/v3.8.42

* deps: bump electron from 42.4.1 to 42.5.1 in /electron (#5413)

Integrated into release/v3.8.42

* deps: bump the production group with 11 updates (#5414)

Integrated into release/v3.8.42

* fix: frame non-streaming JSON responses (#5416)

Integrated into release/v3.8.42

* fix(services): runNpm shell on win32 + prefix via env for Node 24 EINVAL (#5379) (#5474)

Node 24 refuses execFile of npm.cmd without a shell (nodejs/node#52554),
so embedded-service install (9Router/CLIProxy) failed with spawn EINVAL on
Windows. runNpm now enables shell on win32 only; to stay Hard-Rule-#13 safe
under a shell, the install --prefix is passed via npm_config_prefix (env)
instead of an argv path (survives spaces), and the user-supplied version is
constrained by SERVICE_VERSION_PATTERN at the route boundary.

* fix(cli): restore dist/tls-options.mjs to npm tarball (#5452) (#5503)

Closes #5452

* fix(dashboard): render onboarding wizard on /providers/new (#5427) (#5505)

Closes #5427

* fix(db): EBUSY-safe database import on Windows (#5406) (#5507)

Closes #5406

* chore: remove unused gamification streak exports (#5463)

* chore: remove unused headroom log tail export (#5464)

* chore(dead-code): remove unused prompt cache control helper (#5466)

* chore(duplication): share vscode metadata helpers (#5471)

* chore(duplication): share auth zip extractors (#5475)

* chore(duplication): share vscode tokenized request helper (#5479)

* chore(duplication): share quota strategy ranking helpers (#5482)

* chore(duplication): share recharts donut card (#5484)

* chore(duplication): share provider specific validation (#5485)

* chore(duplication): share batch response formatter (#5488)

* chore(duplication): share redis runtime helpers (#5490)

* chore(duplication): share version manager request parsing (#5492)

* chore(duplication): share media generation route helpers (#5493)

* chore(duplication): share settings transform schemas (#5496)

* chore(duplication): share relay stream finalizer (#5497)

* chore(duplication): share machine id fallback (#5498)

* chore(duplication): share node sqlite adapter (#5500)

* fix: treat terminal stream cancels as complete (#5491)

* fix post-merge ci regressions (#5467)

* fix: gate claude adaptive thinking defaults (#5480)

Co-authored-by: KooshaPari <koosha@example.com>

* fix(fallback): normalize provider error rule headers (#5473)

Co-authored-by: KooshaPari <koosha@example.com>

* fix(rate-limit): normalize queue refresh settings (#5499)

Co-authored-by: KooshaPari <koosha@example.com>

* chore(ci): add npm fetch-retry + release-freeze protocol (Hard Rule #21) (#5506)

- .npmrc: bump fetch-retries 2->5 with backoff so transient registry ECONNRESET during npm ci (electron-release, v3.8.41) retries instead of failing the job; applies repo-wide.
- CLAUDE.md Hard Rule #21: release-freeze coordination marker (label release-freeze) that campaign workflows honor before merging into the active release branch, preventing the mid-release commit races that forced CHANGELOG re-reconciliation in v3.8.40/v3.8.41.

* chore(duplication): share service install helpers (#5495)

Share service install helpers; re-add SERVICE_VERSION_PATTERN regex to the shared schema (dropped in extraction, #5474) + tests rejecting malformed versions.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* chore(duplication): share proxy route handlers (#5472)

Share proxy route handlers; add resolveProxyLookupResponse regression test (3 branches + custom whereUsed param name).

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* chore(duplication): share combo builder model options (#5477)

Share combo builder model options; add regression test locking custom-model source classification (manual->custom, api-sync->imported).

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* chore(dead-code): ratchet dead code baseline (#5468)

Ratchet dead-code baseline to the true measured value (310 -> 225) after the v3.8.42 dead-code + duplication wave. Measured by check-dead-code.mjs on the tip.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* fix(dashboard): provider-add UX — i18n labels, surface import warning, default key name (#5511)

* fix(dashboard): provider-add UX — real i18n labels, surface import warning, default key name (#5421 #5428 #5429 #5431 #5435)

Three rough edges in the Add-API-Key / model-import flow, all from the
provider-catalog audit:

1. Validation Model + Account ID form fields shipped untranslated i18n
   stub copy ('Validation Model Id Label', etc.) that rendered verbatim.
   Replaced with real copy in en.json.
2. Model import silently fell back to the cached/local catalog — the route
   returns a 'warning' field the import hook never read. New pure helper
   extractImportWarning surfaces it as a log line.
3. Required connection-name field defaulted to '' (let browser autofill
   inject garbage like 'wiw'); now defaults to 'main'.

Regression guard: tests/unit/provider-add-ux-i18n-import-warning.test.ts.

* fix(dashboard): compress AddApiKeyModal comment to keep file under frozen size cap

* fix(providers): align Muse Spark (Meta AI) cookie copy to ecto_1_sess (#5449) (#5513)

* fix(providers): align Muse Spark (Meta AI) cookie copy to ecto_1_sess (#5449)

The default Meta AI session cookie migrated from the retired abra_sess to
ecto_1_sess (META_AI_DEFAULT_COOKIE), but the provider form hint and one
401 auth-failure message still named abra_sess, telling users to paste a
cookie that no longer exists. Both strings now name ecto_1_sess.

Regression guard: tests/unit/muse-spark-cookie-copy-5449.test.ts.

* chore: reconcile CHANGELOG with release (keep #5449 + #5511 bullets)

* fix(providers): correct FriendliAI (serverless) + Novita (/openai/v1) endpoints (#5430 #5455) (#5515)

* fix(providers): correct FriendliAI (serverless) and Novita (/openai/v1) endpoints (#5430 #5455)

Both rejected valid keys, verified live with real provider keys:
- FriendliAI baseUrl was /dedicated/v1/... which 403s a serverless flp_* token;
  switched to /serverless/v1/... + serverless modelsUrl.
- Novita baseUrl was the legacy /v3/... with a typo'd model id ai-ai/...
  (both 404); switched to OpenAI-compat /openai/v1/... + meta-llama/llama-3.1-8b-instruct.

Regression guard: tests/unit/provider-endpoints-friendliai-novita.test.ts.

* chore: reconcile CHANGELOG with release (keep #5430/#5455 + prior bullets)

* fix(providers): gate import for tool-only providers + sanitize Coze validation error (#5420 #5426) (#5522)

(web search / web fetch) via a capability check over resolved serviceKinds,
not just the -search suffix — firecrawl/jina-reader (webFetch) no longer
show an Import button that 400s. No LLM/media provider is affected.

({code,msg,logId,from}) into the UI; the Coze error becomes a friendly
message, scoped to provider === 'coze' so no other provider is affected.

Regression guards: tests/unit/model-listing-capability-5420.test.ts,
tests/unit/coze-validation-error-5426.test.ts.

* fix(providers): correct LongCat free tier — GA LongCat-2.0, one-time 10M (KYC) (#5508)

LongCat's preview ended and the Flash-* line was retired (2026-05-29);
the API now exposes only the GA LongCat-2.0 (1M context, 128K output).
The free tier is a ONE-TIME 10M-token grant unlocked after account
signup + KYC verification — NOT a recurring daily/monthly allowance.
The catalog still described the retired preview/Flash models and a
recurring 150M / 5M-per-day budget; this corrects every reference.

Config / code:
- registry/longcat: model LongCat-2.0-Preview -> LongCat-2.0, name +
  comment reflect one-time 10M (KYC) and pay-as-you-go beyond it.
- freeModelCatalog: longcat-2.0-preview (150M, recurring-daily) ->
  LongCat-2.0 (10M, freeType one-time-initial via creditTokens).
- freeTierCatalog: drop longcat from the recurring-monthly budget map
  (one-time credits are excluded by that catalog's own rule).
- regional.ts freeNote: one-time 10M after signup + KYC, not recurring.
- providerCostData: longcat-flash-lite -> longcat-2.0 (pay-as-you-go
  0.75/2.95 per 1M, 10M free quota).
- validation probe model longcat -> LongCat-2.0.

Tests:
- free-tier-catalog: longcat now absent from FREE_TIER_BUDGETS;
  providerCount 22->21 (clean 21->20); documented total ~1.39B.
- tierResolver: sample model flash-lite -> LongCat-2.0.

Docs:
- README, PROVIDERS-GUIDE, FREE-TIERS-GUIDE, FREE_TIERS: 50M/day
  Flash-Lite -> one-time 10M LongCat-2.0 (KYC); 'No auth' -> API key + KYC.
- Regenerated PROVIDER_REFERENCE.md (picks up the new freeNote).

typecheck:core clean; changed-file lint 0 errors; docs-sync PASS.

* fix(providers): Bytez OpenAI-compat base URL + auth-only key validation (#5422) (#5528)

Bytez IS OpenAI-compatible at .../models/v2/openai/v1, but the registry
stored the bare .../models/v2 base, so validation's chat-probe hit
.../models/v2/chat/completions -> 404 -> 'endpoint not supported'.

Part A: registry baseUrl -> full OpenAI-compat chat path.
Part B: a Bytez account only serves catalog-provisioned models, so chat-probe
validation 404s even for valid keys. validateBytezProvider instead probes the
auth-only GET .../models/v2/list/tasks (200=valid, 401/403=invalid).

Verified live with a real key: list/tasks -> 200 (valid) / 401 (invalid).
Regression guard: tests/unit/bytez-validation-5422.test.ts.

* fix(providers): remove dead Phind provider + dedupe HuggingChat catalog listing (#5530)

Integrated into release/v3.8.42 (round 3). Dead Phind removal + HuggingChat dedupe, verified complete.

* fix: protect dynamic dashboard tests with CSRF (#5405)

Integrated into release/v3.8.42 (round 3). Reworked CSRF (HMAC-signed synchronized token).

* docs: clarify bifrost relay backend envs (#5520)

Integrated into release/v3.8.42 (round 3). Doc-only: bifrost relay envs.

* test(quota): guard Claude-Code identity version lockstep (Phase 2) (#5514)

Integrated into release/v3.8.42 (round 3). Claude-Code identity version lockstep guard.

* feat(compression): T02 — honest default-on pipeline inflation guard (H1) (#5527)

Integrated into release/v3.8.42 (round 3). T02 pipeline inflation guard

* feat(compression): T05/C2 — caveman dedup + ultra packs for de, fr, ja (#5529)

Integrated into release/v3.8.42 (round 3). T…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

question: Modal — requires both Token ID and Token Secret, not a single API key