* fix(api): exempt test-model requests from Output Styles injection (#6240) (#6511) * fix(api): exempt test-model requests from Output Styles injection (#6240) Root cause: handleChatCore's Phase 4A Output Styles injection (chatCore.ts) was gated only by the operator's global compression.enabled switch, independent of the per-request x-omniroute-compression header. The dashboard 'Test model' action (modelTestRunner.ts) never sent that header, so a globally-enabled Output Style (e.g. 'Ultra terse') always leaked its system-prompt injection into a plain connection test. Fix: skip Output Styles injection when the request explicitly opts out via x-omniroute-compression: off, and always send that header from buildInternalChatRequest / buildInternalRerankRequest. Regression guard: tests/integration/test-model-compression-off-6240.test.ts, tests/unit/model-test-runner-compression-off-6240.test.ts * chore: sync CHANGELOG to release tip (#6511; bullet re-added at merge) * fix(api): return 400 for missing/invalid messages before model resolution (#6402) (#6515) fix(api): return 400 for missing/invalid messages before model resolution (#6402). Integrated into release/v3.8.47. (thanks @chirag127) * fix(providers): spawn Auggie CLI with shell:true on win32 (#6304) (#6510) * fix(providers): spawn Auggie CLI with shell:true on win32 (#6304) * chore: sync CHANGELOG to release tip (#6510; bullet re-added at merge) * fix(compression): honor UI-toggled engines in stackedPipeline dispatch + surface substitution (#6463) (#6534) fix(compression): honor UI-toggled engines in stackedPipeline dispatch + surface substitution (#6463). Integrated into release/v3.8.47. (thanks @chirag127) * fix(providers): fail fast on empty auto-combo pool instead of 15s timeout (#6458) (#6546) fix(providers): fail fast on empty auto-combo pool instead of 15s timeout (#6458). Integrated into release/v3.8.47. (thanks @chirag127) * fix(api): add explicit HEAD handler for /v1/models to prevent ~6s hang (#6400) (#6517) fix(api): add explicit HEAD handler for /v1/models to prevent ~6s hang (#6400) Integrated into release/v3.8.47. (thanks @chirag127) * fix(models): apply hidePaidModels to synced/custom/alias-backed/managed-fallback loops (#6328) (#6549) fix(models): apply hidePaidModels to synced/custom/alias-backed/managed-fallback loops (#6328) Integrated into release/v3.8.47. (thanks @chirag127) * fix(backup): exclude paid models from JSON export/backup when hidePaidModels=true (#6328) (#6551) fix(backup): exclude paid models from JSON export/backup when hidePaidModels=true (#6328) Integrated into release/v3.8.47. (thanks @chirag127) * fix(compression): surface fallback reasons in preview response (#6461) (#6519) fix(compression): surface fallback reasons in preview response (#6461). Integrated into release/v3.8.47. (thanks @chirag127) * fix(dashboard-api): apply hidePaidModels to /api/models + openrouter-catalog + test endpoints (#6328) (#6552) fix(dashboard-api): apply hidePaidModels to /api/models + openrouter-catalog + test endpoints (#6328). Integrated into release/v3.8.47. (thanks @chirag127) * fix(autoCombo): exclude paid models from fusion candidate pools when hidePaidModels=true (#6328) (#6550) fix(autoCombo): exclude paid-tier auto/* ids from the catalog when hidePaidModels=true (#6328). Integrated into release/v3.8.47. (thanks @chirag127) * fix(api): return 415 when /v1/chat/completions receives non-JSON Content-Type (#6414) (#6513) fix(api): return 415 on /v1/messages for non-JSON Content-Type via requireJsonContentType middleware (#6414) Integrated into release/v3.8.47. (thanks @chirag127) * fix(providers): honor fusion minPanel=1 and surface per-member failures in fusion 503 (#6454) (#6521) fix(providers): honor fusion minPanel=1 and surface per-member failures in fusion 503 (#6454) Integrated into release/v3.8.47. (thanks @chirag127) * fix(providers): reject image-only models on /v1/chat/completions with clear error (#6457) (#6525) fix(providers): reject image-only models on /v1/chat/completions with a clear error (#6457) Integrated into release/v3.8.47. (thanks @chirag127) * docs(changelog): add missing #6304 and #6240 bug-fix bullets to v3.8.47 (#6596) * fix(providers): stop cloudflare-ai from silently dropping image content parts (#6390) (#6597) * fix(providers): include custom models in Free Provider Rankings filters (#6368) (#6598) * fix(logger): tolerate write to removed DATA_DIR so tests don't crash on teardown (#6360) (#6599) * fix(dashboard): size the web-session cookie modal to fit on 1080p (#6265) (#6601) * fix(resilience): thread connection snapshot into headroom Codex quota fetch (#6379) (#6600) orderTargetsByHeadroom already loaded the per-connection DB snapshot (with decrypted credentials) via expandTargetsByQuotaAwareConnections, but discarded it before calling getSaturation. For Codex, fetchCodexSaturation forwards straight to fetchCodexQuota(connectionId, connection), which needs the connection object (or a prior registerCodexConnection() call that never happens before headroom ranking runs) to read accessToken. Without it, fetchCodexQuota returned null for every candidate, saturation failed open to 0 across the board, and headroom ranking fell back to the original combo order regardless of actual free quota. getSaturation() and the headroom SaturationFetcher seam now accept and thread the loaded connection snapshot through to fetchCodexQuota. Regression guard: tests/unit/headroom-codex-quota-snapshot-6379.test.ts (seeds two real Codex connections in a throwaway SQLite DB with a fake upstream fetch, confirms RED on unfixed code, GREEN after the fix). * fix(oauth): persist and reuse rotated Codex OAuth refresh token (#6352) (#6602) * fix(test): replace tautology in playground-api-tab + make test-masking catch it (#6404) (#6603) playground-api-tab.test.tsx's SSE test always took the disabled-button branch (the fetch mock returned an empty model list) and asserted a tautology instead of exercising the SSE path it claims to verify. The test now selects a real model to enable Send, asserts it is actually enabled, and asserts the streamed SSE content reached the response editor. check-test-masking.mjs's tautology subcheck only compares base-vs-HEAD counts within a PR's own diff and no-ops entirely outside PR context (no GITHUB_BASE_SHA/REF) -- so a tautology merged once, or checked with a bare local run, stayed invisible forever after. Added an always-on absolute-floor scan (scanBareTautologies/countBareTautologies) over every tracked test file, scoped to the bare expect(true).toBe(true)/assert.equal(1,1) patterns that have zero legitimate uses in this codebase -- deliberately excluding assert.ok(true), which has ~15 pre-existing verified-legitimate try/catch-fallback uses and stays on the lenient diff-only path. * fix(providers): keep image/diffusion models out of the chat models catalog (#6457) (#6606) * fix(providers): honor fusion config.judgeModel for final synthesis (#6455) (#6607) The fusion single-survivor degrade path (added for #6454) returned the lone panel answer directly whenever only one panelist succeeded, ignoring an explicitly configured judgeModel. With default minPanel=2 and a 2-model panel, any single flaky panelist forced this path every request, so the configured judge never ran and the response .model reflected a panel member. The judge is now still invoked to synthesize a lone surviving answer when judgeModel is explicitly configured; the direct-answer shortcut is kept only for the implicit case (no judgeModel, judge defaults to panel[0]). * fix(api): serialize tool-call args correctly through /anthropic translation (#6459) (#6609) appendToolCallArgumentDelta() treated any non-string incoming fragment as empty, silently dropping tool-call arguments delivered as an already-parsed JSON object/array (a non-conformant shape some upstreams emit for tool_calls[].function.arguments) instead of JSON-encoding them. This left tool_use.input empty on the /anthropic streaming path and opened the door to downstream [object Object] string coercion once buffers were concatenated. Now JSON.stringify()s the non-string fragment instead of discarding it. * fix(providers): backfill #6454 CHANGELOG bullet + 11-member fusion regression guard (#6614) The fusion quorum-clamp/failure-detail root cause reported in #6454 was already fixed and merged via #6521 (open-sse/services/fusion.ts already carries Math.max(1, cfg.minPanel) + per-member failure reasons on this branch). That merge never landed a CHANGELOG bullet for #6454 itself. Backfills the missing bullet and adds a regression test at the exact repro scale (11-member fusion-free-style panel, 2 cooling / 9 healthy) to lock in that a cooling minority no longer sinks a healthy majority, while a genuinely all-failed panel still returns the documented 503. * fix(compression): add adaptive-ladder rankings for non-default catalog engines (#6533) (#6615) * fix(resilience): fall back on a 200 masking in-body credit exhaustion (#6427) (#6616) `validateResponseQuality()` only inspected a response's top-level `error` field when `choices` was also missing/empty (the narrower #3424 case), so a masked HTTP 200 that echoed a non-empty stub `choices` alongside a structured error object — or a known exhaustion phrase like "insufficient credits" / "quota exceeded" in the error envelope — slipped through as valid, and a `priority` combo kept hammering the exhausted target instead of failing over. The check now inspects the error envelope (top-level `error` object, or a bounded exhaustion-phrase match against error.message/code/type and top-level message/detail) unconditionally, before any shape-specific branch — never against `choices[].message.content`, so legitimate completions that merely mention "quota" in prose are not misclassified. Regression guard: tests/unit/masked-200-exhaustion-fallback-6427.test.ts * fix(startup): generate AgentBridge MITM certs for all 4 antigravity hosts (#6494) (#6617) generateCert() hard-coded a single SAN entry (daily-cloudcode-pa.googleapis.com) while server.cjs terminates TLS locally for all 4 antigravity/cloudcode-pa hosts, so 3 of the 4 hosts served a cert whose CN/SAN didn't match and MITM interception failed for them. Source the host list from the existing authoritative ANTIGRAVITY_TARGET.hosts registry instead of a second hard-coded copy. * fix(providers): send a Cloudflare-accepted Content-Type on Worker upload (#6416) (#6618) * fix(startup): resolve AgentBridge MITM router key from existing OmniRoute key (#6403) (#6619) AgentBridge's start/restart actions only ever checked an explicit apiKey request field (never sent by the UI) and the ROUTER_API_KEY process env var (unset unless manually exported), so startMitm() always spawned server.cjs with an empty ROUTER_API_KEY and it hard-exited with "no API key was provided". resolveRouterApiKey() now falls back to pickApiKeyForInternalUse(), the same DB-backed selector already used by the combo-health-check / cloud-sync-verify internal probes. * chore(cli): harden empty catches in completion.mjs with env-gated error logging (#6257) chore(cli): harden empty catches in completion.mjs with env-gated error logging (#6257). Reconstructed cleanly onto release/v3.8.47; env var documented. Integrated into release/v3.8.47. * chore(open-sse): remove vestigial @ts-nocheck from usageTracking.ts (#6173) chore(open-sse): remove vestigial @ts-nocheck from usageTracking.ts (#6173). Restores type-checking on the token-usage hot path under typecheck:core. Integrated into release/v3.8.47. * fix(auth): enforce API-key model/combo policy on the Codex Responses WebSocket bridge (#6564) (#6621) The Codex Responses-over-WebSocket bridge authenticated the API key but never called enforceApiKeyPolicy(), so a key restricted via allowedModels/allowedCombos could still reach a direct Codex model (e.g. gpt-5.5) through this transport, bypassing what the HTTP /v1/responses path already enforces. prepare() now builds an equivalent Request carrying an explicit Authorization: Bearer <apiKey> header (the WS bridge's token normally arrives via a query param) and calls enforceApiKeyPolicy() against the client-requested model before any Codex-specific remapping or credential selection. * fix(startup): normalize non-Error throws + tolerate closed DB in instrumentation bootstrap (#6560) (#6622) An update/restart could crash the whole server at boot with TypeError: Cannot create property 'message' on string 'Database closed', masking the real failure. driverFactory.ts's preInitSqlJs() cached its sql.js WASM adapter per file path but never checked whether it had since been closed by a racing gracefulShutdown/resetDbInstance; reusing the dead handle made the next query throw sql.js's own raw string "Database closed" straight out of instrumentation-node.ts's previously-unguarded ensureDbInitialized() call. Next.js's registerInstrumentation() wrapper unconditionally does err.message = ... on whatever register() rejects with, and assigning .message on a primitive string throws in strict mode -- that secondary TypeError is what actually crashed the process. Fixed in two parts: preInitSqlJs() now evicts a closed cached adapter instead of returning it, and a new ensureDbReadyForBoot() normalizes any non-Error throw and retries once for a transient "database closed" message before re-throwing anything else as a real Error. * fix(api): stop POST /api/keys hanging on the fire-and-forget Cloud sync (#6570) (#6624) cloudEnabled defaults to true in settings.ts::getSettings() for any install with no persisted settings row (every fresh install), so the create-key handler's unconditional `await syncKeysToCloudIfEnabled()` always attempted a real outbound fetch() to CLOUD_URL via syncToCloud(). When that endpoint is unset/unreachable/slow, the HTTP response blocked until the request settled or timed out (20-90s+), unlike sibling routes (regenerate, /api/combos) that never touch this side effect. syncKeysToCloudIfEnabled() is now dispatched fire-and-forget instead of awaited; its internal try/catch already logs failures, so cloud sync still runs in the background without blocking the response. * fix(api): accept valid Codex connection edits instead of rejecting as Invalid request (#6562) (#6626) * fix(fusion): judge replayed a panel answer via idempotency-key collision (#6558) Merged — thank you, @developerjillur! Namespaces the idempotency key by target provider/model + a messages digest so fusion panel/judge sub-requests can't collide on a shared client Idempotency-Key. Existing chatCore extracted-module tests were aligned to the composed-key contract. Integrated into release/v3.8.47. * fix(security): loopback-gate /api/middleware/* (arbitrary JS via vm.Script) (#6541) Merged — thank you, @developerjillur! Loopback-gates /api/middleware/* (arbitrary JS via vm.Script) for RCE parity with /api/plugins/*. Integrated into release/v3.8.47. * fix(security): SSRF-guard provider validation probes (block cloud metadata) (#6542) Merged — thank you, @developerjillur! SSRF-guards the provider-validation probes (block-metadata + no redirect) so a caller-controllable baseUrl can't relay to cloud metadata. Integrated into release/v3.8.47. * fix(security): fail-closed CORS for cloud-agent management routes (#6543) Merged — thank you, @developerjillur! Fail-closed CORS for the cookie/session-authed cloud-agent management routes (allowlist echo, credentials only for an explicitly allowlisted origin). Integrated into release/v3.8.47. * feat(combo): sanitized diagnostic trace on auto-combo terminal failure (#6545) Merged — thank you, @developerjillur! Sanitized diagnostic trace on an auto-combo terminal failure (ids/reason-codes only, capped), plus an actionable reasoning-budget-exhausted message. Integrated into release/v3.8.47. * perf(health): short-TTL cache for GET /api/monitoring/health (#6553) Merged — thank you, @developerjillur! Short-TTL (1s) cache for the frequently-polled GET /api/monitoring/health, invalidated on DELETE (circuit-breaker reset). Integrated into release/v3.8.47. * fix(playground): accept a dashboard session for presets under REQUIRE_API_KEY (#6554) Merged — thank you, @developerjillur! Accept a valid dashboard session for /api/playground/presets under REQUIRE_API_KEY (the Playground page authenticates via cookie, not an API key). Integrated into release/v3.8.47. * feat(compression): omniglyph engine (context-as-image, Fable 5 direct) — stack + single mode (#6556) * feat(compression): dependência omniglyph (file:) + smoke de import * feat(compression): engine omniglyph — contexto-como-imagem com gates fail-closed * fix(compression): omniglyph adapter fail-open no transform (try/catch) * feat(compression): registra omniglyph no registry e catálogo (single mode, stackPriority 90) * feat(compression): modo único omniglyph (async), selecionar o modo é o enable * feat(compression): plumbing supportsVision + providerTransport até os engines * feat(compression): estimador de tokens image-aware — modo stacked mantém a saída do omniglyph * docs(compression): corrige comentário do prefixo base64 no decode PNG (64 chars) * feat(compression): registra omniglyph nas listas de modo/engine (db, combo, deriveDefaultPlan, mcp) * feat(dashboard): dedicated OmniGlyph engine screen (context-as-image) Adds a per-engine detail page at /dashboard/context/omniglyph, alongside the other compression engines in the sidebar. Four sections: the economics (measured savings), a REAL before→after (dense text vs the rendered PNG page, not a mockup), the fail-closed gate flow, and the enable control wired to /api/settings/compression (preview engine, off by default). Sidebar entry + i18n label across all locales. * chore(compression): consume published omniglyph@^1.0.0 from the npm registry Replaces the local file: dependency used during the preview phase — npm ci now resolves omniglyph from the registry with integrity, unblocking CI. * fix(compression): satisfy v3.8.47 quality gates for the omniglyph engine - dependency-allowlist: approve omniglyph (own package, published from diegosouzapw/OmniGlyph; supply-chain review done by the maintainer) - ladder maps (#6533 guard): rank omniglyph 80 (stackPriority 90, runs after every text engine) with expectedReductionFactor 0.35 (measured 0.23-0.33) - drop the two explicit any casts in omniglyph tests (no-explicit-any is error-level in tests since #6218) * chore(compression): rebaseline strategySelector for the omniglyph mode dispatch +18 lines of cohesive dispatch/type wiring at the existing mode chokepoints (sync no-op + async single-mode branch + providerTransport on the options types) — not extractable without hiding the dispatch boundary, mirroring the prior compression rebaselines. Also drops an unused eslint-disable directive in image-aware-tokens.test.ts (warning-level red under --max-warnings 0). * chore(quality): register inherited base tests in stryker tap.testFiles masked-200-exhaustion-fallback-6427 and headroom-codex-quota-snapshot-6379 arrived via the base merge without their stryker registration — check:mutation-test-coverage --strict requires covering tests to be listed. * refactor(compression): keep omniglyph wiring under the complexity gate - extract the async single-mode resolution to engines/omniglyphSingleMode.ts (runCompressionAsync was at complexity 17 after the mode branch; back <=15) - split OmniglyphContextPageClient into section components (was 161 lines in one function; every function now under the 80-line cap) - complexity baseline 2052->2053: the +1 is inherited base drift (the ratchet does not run on fast-path merges — same pattern as the v3.8.44/46 rebaselines); this PR's own code is measured complexity-net-zero * chore(quality): register 3 more inherited base tests in stryker tap.testFiles route-guard-middleware-local-only, combo-diagnostics-trace and idempotency-fusion-collision arrived via the latest base merge without their stryker registration (fast-path merges skip check:mutation-test-coverage). * chore(quality): cognitive-complexity baseline 883->884 (inherited base drift) check:cognitive-complexity measures 884 identically on the pristine origin/release/v3.8.47 tip and on this HEAD — the PR itself is cognitive-net-zero (single-mode resolution extracted to its own module, page client split into section components). Same inherited-drift pattern as the v3.8.4x release rebaselines. --------- Co-authored-by: diegosouzapw <diegosouzapw@devbox.local> * chore(deps): bump omniglyph to ^1.0.2 (security: ReDoS fixes) (#6661) The lockfile pinned omniglyph@1.0.0, which carries the polynomial-ReDoS regex paths fixed in 1.0.1/1.0.2 (all upstream CodeQL alerts resolved). Bump the range to ^1.0.2 and refresh the lock so `npm ci` installs 1.0.2. No change to the omniglyph engine behavior — 1.0.1/1.0.2 touched only regex hot paths and docs; the dependency tree is unchanged (gpt-tokenizer ^3.4.0). Co-authored-by: diegosouzapw <souzamiriamrodrigues790@gmail.com> * docs(claude): atualiza nomes da família de skills review/triage/implement (Hard Rule #21) (#6663) * fix(mimocode): handle 400 with cooldown + account rotation (#6648) * fix(electron): bump electron 42→43 + build better-sqlite3 from source (ABI 148) (#6605) fix(electron): bump electron 42→43 + rebuild better-sqlite3 from source against the Electron ABI (148). Electron 43 raises NODE_MODULE_VERSION to 148; better-sqlite3@12.11.1 has no electron-v148 prebuild, so the packaged app died with 'Nenhum driver SQLite disponível'. prepare-electron-standalone now compiles better-sqlite3 from source against the electron headers into build/Release (where 'bindings' resolves it). Validated by Electron Package Smoke (green) + local (node_register_module_v148). Supersedes #6378. (--admin: the only reds are SonarQube/SonarCloud failing on a coverage-report artifact digest-mismatch — a GitHub Actions infra flake, not this diff; Sonar is green on main and the diff touches only the electron build.) * deps: bump the development group across 1 directory with 6 updates (#6588) deps: bump the development group (6 updates). Rebased onto current main; all checks green after the electron-smoke fix (#6605). * fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump (#6620) fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump. undici 8.6+ changed ProxyAgent to forward plain-HTTP via request-proxy instead of CONNECT, breaking OAuth refresh through a connection proxy (501). proxyDispatcher now passes proxyTunnel:true. Validated: Unit Tests 3/8 (the OAuth-proxy test) green, new regression test green (fails without the fix on undici 8.7), SonarQube green. Supersedes #6380. (--admin: the only red is Electron Package Smoke failing on a next-build artifact 'digest-mismatch' — a GitHub Actions infra flake corrupting the asar ('file data stream has unexpected number of bytes'); the better-sqlite3 rebuild itself succeeded (gyp ok) and the electron path is unchanged from #6605 which passed the smoke. Not this diff.) * fix(mimocode): handle 400 with cooldown + account rotation Treat HTTP 400 responses the same as 429: mark the account on cooldown and continue to the next fingerprint/proxy. Previously, 400 fell through to markSuccess and returned immediately, so only 1 of N accounts was ever tried per request. Refs: #5925 * chore(mimocode): drop unrelated dependency/electron drift from PR #6648's stale fork package.json/package-lock.json (bun/eslint-config-next/cyclonedx bumps), electron/package.json, electron/package-lock.json, open-sse/utils/proxyDispatcher.ts, prepare-electron-standalone.mjs and tests/unit/proxy-dispatcher-family.test.ts were already present in the contributor's single commit but are unrelated to the mimocode 400-handling fix — restored to release/v3.8.47's versions so the PR stays scoped to open-sse/executors/mimocode.ts. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(mimocode): classify 400 body before rotating — rate-limit-text 400s rotate, malformed 400s fail fast (#2101/#4976 guard) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(mimocode): extract auth-retry + 429/400 gating helpers — keep execute() under the cognitive-complexity gate Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: pizzav-xyz <pizzav-xyz@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> * feat: add setting for provider/model-specific parameters (#6649) * fix(electron): bump electron 42→43 + build better-sqlite3 from source (ABI 148) (#6605) fix(electron): bump electron 42→43 + rebuild better-sqlite3 from source against the Electron ABI (148). Electron 43 raises NODE_MODULE_VERSION to 148; better-sqlite3@12.11.1 has no electron-v148 prebuild, so the packaged app died with 'Nenhum driver SQLite disponível'. prepare-electron-standalone now compiles better-sqlite3 from source against the electron headers into build/Release (where 'bindings' resolves it). Validated by Electron Package Smoke (green) + local (node_register_module_v148). Supersedes #6378. (--admin: the only reds are SonarQube/SonarCloud failing on a coverage-report artifact digest-mismatch — a GitHub Actions infra flake, not this diff; Sonar is green on main and the diff touches only the electron build.) * deps: bump the development group across 1 directory with 6 updates (#6588) deps: bump the development group (6 updates). Rebased onto current main; all checks green after the electron-smoke fix (#6605). * fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump (#6620) fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump. undici 8.6+ changed ProxyAgent to forward plain-HTTP via request-proxy instead of CONNECT, breaking OAuth refresh through a connection proxy (501). proxyDispatcher now passes proxyTunnel:true. Validated: Unit Tests 3/8 (the OAuth-proxy test) green, new regression test green (fails without the fix on undici 8.7), SonarQube green. Supersedes #6380. (--admin: the only red is Electron Package Smoke failing on a next-build artifact 'digest-mismatch' — a GitHub Actions infra flake corrupting the asar ('file data stream has unexpected number of bytes'); the better-sqlite3 rebuild itself succeeded (gyp ok) and the electron path is unchanged from #6605 which passed the smoke. Not this diff.) * feat(db): add provider param filter config store (key_value namespace) Add paramFilters.ts module for CRUD against provider_param_filters namespace in the key_value table, with in-memory cache + generation counter invalidation. Supports denylist/allowlist per provider and per model, plus auto-learn flag. Migration 118 documents the namespace (no schema change). Issue: #6625 * feat(proxy): add detectUnsupportedParam regex for auto-learning Add UNSUPPORTED_PARAM_RE and detectUnsupportedParam() to extract the offending parameter name from upstream 400 error messages like 'Unsupported parameter(s): thinking'. Issue: #6625 * feat(proxy): extend stripUnsupportedParams with config-driven denylist/allowlist Add applyConfigFilters() called after hardcoded STRIP_RULES in stripUnsupportedParams(). Config-driven rules (DB-backed via paramFilters.ts) support provider-level and model-level: 1. Provider denylist (delete body[key]) 2. Model denylist (delete body[key]) 3. Provider allowlist (restore from pre-strip snapshot) 4. Model allowlist (restore from pre-strip snapshot) Allowlist only restores keys the client actually sent — never introduces new params. Issue: #6625 * feat(proxy): wire auto-learn of unsupported params into 400-downgrade loop When a provider returns 400 with 'Unsupported parameter: X' and the provider config has autoLearn enabled, auto-detect the param name via detectUnsupportedParam(), persist it to the provider's block list via addParamToBlocklist(), then strip and retry. Issue: #6625 * test: add tests for provider param filter denylist/allowlist/auto-learn Three new test files: - param-filters-apply.test.ts — hardcoded rules regression + direct applyConfigFilters tests (no DB dependency) - param-filters-db.test.ts — CRUD against key_value, cache invalidation, full filter pipeline (DB-backed config → stripUnsupportedParams), 16 tests in isolated temp DB - param-filters-auto-learn.test.ts — UNSUPPORTED_PARAM_RE regex matching and detectUnsupportedParam edge cases All existing tests unchanged and passing. Issue: #6625 * feat(proxy): add global auto-learn flag for unsupported params Add isAutoLearnGloballyEnabled() and setGlobalAutoLearnEnabled() to paramFilters.ts. The global flag (stored as key __global__ in the provider_param_filters namespace) acts as a master switch: when enabled, ALL providers auto-learn unsupported params from 400 errors. In base.ts, the auto-learn check now evaluates: shouldAutoLearn = isAutoLearnGloballyEnabled() || perProviderConfig?.autoLearn Global flag defaults to false (opt-in). Tests cover enable/disable/ default/no-interference-with-per-provider-config. Issue: #6625 * fix: apply PR#6649 review feedback — model-scoped auto-learn and precedence order Fixes from gemini-code-assist[bot] review: - HIGH: Auto-learn now scoped to the specific model that triggered the 400 (addParamToBlocklist(this.provider, autoLearned, model)) instead of adding to the provider-level blocklist globally - HIGH: Reordered applyConfigFilters so model-level operations run AFTER provider-level operations (model denylist → model allowlist override provider allowlist → provider denylist) - MEDIUM: Include model name in auto-learn log message Adds regression test verifying model-level denylist beats provider-level allowlist. Issue: #6625 PR: #6649 * feat(ui): add provider-level param filter section to detail page Add ProviderParamFilterSection component rendered on each provider detail page, backed by GET|PUT|DELETE /api/providers/[id]/param-filters. UI allows operators to configure: - Blocked params (comma-separated, stripped from outgoing requests) - Allowed params (comma-separated, re-added after denylist stripping) - Auto-learn toggle (per-provider, enables auto-learning from 400 errors) Wired into ProviderDetailPageClient.tsx between the Playground panel and the Modals section. Issue: #6625 PR: #6649 * feat(ui): add model-level param filter fields in compat popover Extend ModelCompatPopover with Blocked params and Allowed params text inputs for model-level denylist/allowlist overrides. Model-specific block/allow data is persisted via the param-filters API endpoint (PUT /api/providers/:id/param-filters) with the model scope under the models key. Both ModelRow and PassthroughModelRow now pass providerId and modelId to the popover. Issue: #6625 PR: #6649 * chore: gitignore .claude-flow/ * fix(param-filters): review follow-ups — auth gate, error sanitization, Zod body validation, typecheck, file-size, i18n keys Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(param-filters): drop unrelated main-drift from the fork branch (deps/electron/proxy files belong to #6620/#6605/#6588, not this PR) * refactor(param-filters): split oversized functions — keep complexity gate at baseline Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(param-filters): decompose config parser helpers — keep cognitive-complexity gate at baseline Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(changelog): restore sibling #6648 bullet eaten by merge auto-resolve + re-insert #6649 entry Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> * fix(cli): compression REST fallback uses canonical defaultMode + JSON object cells (#6571) (#6682) * fix(cli): compression REST fallback uses canonical defaultMode + JSON object cells (#6571) * test(cli): align existing compression-command tests to the #6571 canonical field contract (engine→strategy/defaultMode) * ci(quality): route the 3 heavy fast-path jobs to the self-hosted VPS pool when USE_VPS_RUNNER is on (#6691) Extends the same dynamic-runner gate ci.yml already uses (build/test-unit/test-vitest) to quality.yml's fast-gates/fast-vitest/fast-unit — the ~9min-on-ubuntu jobs that run on every PR→release/**. Inert until USE_VPS_RUNNER flips to true (falls back to ubuntu-latest when the var is unset/false OR the PR is a fork — own-origin branches only, never the LAN runner for fork code). lint-guard/merge-integrity stay on ubuntu-latest (trivial; keeps VPS concurrency low). No behavior change today. * ci(vps): honor VPS_ALWAYS_ON — release teardown is a no-op on the dedicated 24/7 host (#6693) The .113 VM is now a dedicated, always-on CI host so day-to-day quality.yml PRs (PR→release/**) use the 32-core VPS, not just release CI. release-runner-down.sh must not flip USE_VPS_RUNNER=false / shut the VM down when VPS_ALWAYS_ON=true, or every PR after a release would fall back to ubuntu-latest. Legacy on-demand teardown still applies when the var is unset/false. * docs(changelog): add v3.8.47 Contributors section (32 contributors) * chore(vscode): update search exclude patterns and add documentation Add several directories to the search exclude list to improve search performance and add a comment explaining why certain directories are not being hidden from the file explorer. * docs(readme): update star badges and star history chart links * fix(providers): remove obsolete providers (glhf, kluster, cablyai, inclusionai) (#6675) Drop dead catalog/registry entries, keep Synthetic as the GLHF replacement path, regenerate provider reference/docs counts, and lock APIKEY family-split + file-size gates so CI stays green. Ignore prettier on freeModelCatalog.data.ts so dense one-line budget rows are not expanded past the 800-line new-file cap. * fix: move tier-flow SVG images to public directory (#6538) Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> * fix(cli): per-agent DNS, startup guards, and batched Windows hosts writes (#6338) DNS toggle in AgentBridge was broken for 8 of 9 agents: addDNSEntry/ removeDNSEntry always resolved the legacy Antigravity default hosts regardless of which agent's dns_enabled flag was flipped. Both now accept an optional agentId and resolve hosts via ALL_TARGETS; the [id]/dns route passes id through and returns 404 for an unknown agent instead of silently falling back to the defaults. startMitmInternal() now wraps generateCert(), the provisionDnsEntries() call, and the PID-file write in try/catch so a mid-startup failure can't orphan the already-spawned MITM child process. On Windows, addDNSEntries/removeDNSEntries batch every missing/present entry into a single elevated PowerShell invocation instead of one UAC prompt per host line. Scope note: this PR originally bundled an unrelated SkillOpt feature (DB migration, 6 API routes, dashboard UI) and a checks-free CI build workflow alongside this DNS/startup fix. Both were dropped here as out-of-scope per review-group-prs analysis (2-implementing plan); only the DNS/startup-guard delta (dnsConfig.ts, manager.ts, the [id]/dns route, and their tests) is applied. Co-authored-by: hamsa0x7 <hamsa0x7@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(providers): web-cookie fallback validation reports unsupported instead of a false valid (#6309) validateWebCookieProvider() previously required a providerRegistry.ts entry and returned "Provider not found in registry" for web-cookie-only providers like lmarena, gemini-business, poe-web, venice-web and v0-vercel-web. A fallback to WEB_COOKIE_PROVIDERS[provider].website was proposed, but live verification showed probing `${website}/models` does not reliably signal session validity for these (redirects/SPA 200s regardless of cookie validity) — it would report an expired or garbage cookie as valid, which is worse than an honest "not supported". Until each provider has a verified, side-effect-free auth probe against its real API host, the fallback now returns `unsupported: true` with no network call. Also reverts the probe transport from validationRead back to directHttpsRequest, which fixes a globalThis.fetch mock/patch-timing mismatch that made the pre-existing tests/unit/provider-validation-web-cookie-auth007.test.ts hit the live network in CI, and adds the missing Cookie header to the probe request. Regression guard: tests/unit/web-cookie-validation-fallback.test.ts. Co-authored-by: oyi77 <oyi77@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * docs(claude): fix p2c casing to match ROUTING_STRATEGY_VALUES (#6643) * fix(electron): bump electron 42→43 + build better-sqlite3 from source (ABI 148) (#6605) fix(electron): bump electron 42→43 + rebuild better-sqlite3 from source against the Electron ABI (148). Electron 43 raises NODE_MODULE_VERSION to 148; better-sqlite3@12.11.1 has no electron-v148 prebuild, so the packaged app died with 'Nenhum driver SQLite disponível'. prepare-electron-standalone now compiles better-sqlite3 from source against the electron headers into build/Release (where 'bindings' resolves it). Validated by Electron Package Smoke (green) + local (node_register_module_v148). Supersedes #6378. (--admin: the only reds are SonarQube/SonarCloud failing on a coverage-report artifact digest-mismatch — a GitHub Actions infra flake, not this diff; Sonar is green on main and the diff touches only the electron build.) * deps: bump the development group across 1 directory with 6 updates (#6588) deps: bump the development group (6 updates). Rebased onto current main; all checks green after the electron-smoke fix (#6605). * fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump (#6620) fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump. undici 8.6+ changed ProxyAgent to forward plain-HTTP via request-proxy instead of CONNECT, breaking OAuth refresh through a connection proxy (501). proxyDispatcher now passes proxyTunnel:true. Validated: Unit Tests 3/8 (the OAuth-proxy test) green, new regression test green (fails without the fix on undici 8.7), SonarQube green. Supersedes #6380. (--admin: the only red is Electron Package Smoke failing on a next-build artifact 'digest-mismatch' — a GitHub Actions infra flake corrupting the asar ('file data stream has unexpected number of bytes'); the better-sqlite3 rebuild itself succeeded (gyp ok) and the electron path is unchanged from #6605 which passed the smoke. Not this diff.) * docs(claude): fix p2c casing to match ROUTING_STRATEGY_VALUES * chore(merge): drop unrelated main-drift from PR fork (deps/electron/proxy files belong to #6620, not this PR) Restores electron/package-lock.json, electron/package.json, package-lock.json, package.json, open-sse/utils/proxyDispatcher.ts, scripts/build/prepare-electron-standalone.mjs and tests/unit/proxy-dispatcher-family.test.ts to origin/release/v3.8.47's content. The PR fork branched from a state of main that already includes #6620 (proxy CONNECT tunnel fix + deps bump), which is not yet synced into release/v3.8.47 — the 3-way merge would otherwise silently carry that unrelated content into this doc-only PR. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Chirag Singhal <chirag127@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> * docs: sync routing-strategy count to 18 across README + AGENTS.md (#6644) * fix(electron): bump electron 42→43 + build better-sqlite3 from source (ABI 148) (#6605) fix(electron): bump electron 42→43 + rebuild better-sqlite3 from source against the Electron ABI (148). Electron 43 raises NODE_MODULE_VERSION to 148; better-sqlite3@12.11.1 has no electron-v148 prebuild, so the packaged app died with 'Nenhum driver SQLite disponível'. prepare-electron-standalone now compiles better-sqlite3 from source against the electron headers into build/Release (where 'bindings' resolves it). Validated by Electron Package Smoke (green) + local (node_register_module_v148). Supersedes #6378. (--admin: the only reds are SonarQube/SonarCloud failing on a coverage-report artifact digest-mismatch — a GitHub Actions infra flake, not this diff; Sonar is green on main and the diff touches only the electron build.) * deps: bump the development group across 1 directory with 6 updates (#6588) deps: bump the development group (6 updates). Rebased onto current main; all checks green after the electron-smoke fix (#6605). * fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump (#6620) fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump. undici 8.6+ changed ProxyAgent to forward plain-HTTP via request-proxy instead of CONNECT, breaking OAuth refresh through a connection proxy (501). proxyDispatcher now passes proxyTunnel:true. Validated: Unit Tests 3/8 (the OAuth-proxy test) green, new regression test green (fails without the fix on undici 8.7), SonarQube green. Supersedes #6380. (--admin: the only red is Electron Package Smoke failing on a next-build artifact 'digest-mismatch' — a GitHub Actions infra flake corrupting the asar ('file data stream has unexpected number of bytes'); the better-sqlite3 rebuild itself succeeded (gyp ok) and the electron path is unchanged from #6605 which passed the smoke. Not this diff.) * docs: sync routing-strategy count to 18 across README + AGENTS.md * chore(merge): drop unrelated main-drift from PR fork (deps/electron/proxy files belong to #6620, not this PR) Restores electron/package-lock.json, electron/package.json, package-lock.json, package.json, open-sse/utils/proxyDispatcher.ts, scripts/build/prepare-electron-standalone.mjs and tests/unit/proxy-dispatcher-family.test.ts to origin/release/v3.8.47's content. The PR fork branched from a state of main that already includes #6620 (proxy CONNECT tunnel fix + deps bump), which is not yet synced into release/v3.8.47 — the 3-way merge would otherwise silently carry that unrelated content into this doc-only PR. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Chirag Singhal <chirag127@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> * docs(routing): reconcile 17 vs 18 public-strategy count in AUTO-COMBO (#6646) * fix(electron): bump electron 42→43 + build better-sqlite3 from source (ABI 148) (#6605) fix(electron): bump electron 42→43 + rebuild better-sqlite3 from source against the Electron ABI (148). Electron 43 raises NODE_MODULE_VERSION to 148; better-sqlite3@12.11.1 has no electron-v148 prebuild, so the packaged app died with 'Nenhum driver SQLite disponível'. prepare-electron-standalone now compiles better-sqlite3 from source against the electron headers into build/Release (where 'bindings' resolves it). Validated by Electron Package Smoke (green) + local (node_register_module_v148). Supersedes #6378. (--admin: the only reds are SonarQube/SonarCloud failing on a coverage-report artifact digest-mismatch — a GitHub Actions infra flake, not this diff; Sonar is green on main and the diff touches only the electron build.) * deps: bump the development group across 1 directory with 6 updates (#6588) deps: bump the development group (6 updates). Rebased onto current main; all checks green after the electron-smoke fix (#6605). * fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump (#6620) fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump. undici 8.6+ changed ProxyAgent to forward plain-HTTP via request-proxy instead of CONNECT, breaking OAuth refresh through a connection proxy (501). proxyDispatcher now passes proxyTunnel:true. Validated: Unit Tests 3/8 (the OAuth-proxy test) green, new regression test green (fails without the fix on undici 8.7), SonarQube green. Supersedes #6380. (--admin: the only red is Electron Package Smoke failing on a next-build artifact 'digest-mismatch' — a GitHub Actions infra flake corrupting the asar ('file data stream has unexpected number of bytes'); the better-sqlite3 rebuild itself succeeded (gyp ok) and the electron path is unchanged from #6605 which passed the smoke. Not this diff.) * docs(routing): reconcile 17 vs 18 public-strategy count in AUTO-COMBO * chore(merge): drop unrelated main-drift from PR fork (deps/electron/proxy files belong to #6620, not this PR) Restores electron/package-lock.json, electron/package.json, package-lock.json, package.json, open-sse/utils/proxyDispatcher.ts, scripts/build/prepare-electron-standalone.mjs and tests/unit/proxy-dispatcher-family.test.ts to origin/release/v3.8.47's content. The PR fork branched from a state of main that already includes #6620 (proxy CONNECT tunnel fix + deps bump), which is not yet synced into release/v3.8.47 — the 3-way merge would otherwise silently carry that unrelated content into this doc-only PR. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Chirag Singhal <chirag127@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> * fix(cli): detect WinGet Claude Code on Windows (#6647) * fix(electron): bump electron 42→43 + build better-sqlite3 from source (ABI 148) (#6605) fix(electron): bump electron 42→43 + rebuild better-sqlite3 from source against the Electron ABI (148). Electron 43 raises NODE_MODULE_VERSION to 148; better-sqlite3@12.11.1 has no electron-v148 prebuild, so the packaged app died with 'Nenhum driver SQLite disponível'. prepare-electron-standalone now compiles better-sqlite3 from source against the electron headers into build/Release (where 'bindings' resolves it). Validated by Electron Package Smoke (green) + local (node_register_module_v148). Supersedes #6378. (--admin: the only reds are SonarQube/SonarCloud failing on a coverage-report artifact digest-mismatch — a GitHub Actions infra flake, not this diff; Sonar is green on main and the diff touches only the electron build.) * deps: bump the development group across 1 directory with 6 updates (#6588) deps: bump the development group (6 updates). Rebased onto current main; all checks green after the electron-smoke fix (#6605). * fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump (#6620) fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump. undici 8.6+ changed ProxyAgent to forward plain-HTTP via request-proxy instead of CONNECT, breaking OAuth refresh through a connection proxy (501). proxyDispatcher now passes proxyTunnel:true. Validated: Unit Tests 3/8 (the OAuth-proxy test) green, new regression test green (fails without the fix on undici 8.7), SonarQube green. Supersedes #6380. (--admin: the only red is Electron Package Smoke failing on a next-build artifact 'digest-mismatch' — a GitHub Actions infra flake corrupting the asar ('file data stream has unexpected number of bytes'); the better-sqlite3 rebuild itself succeeded (gyp ok) and the electron path is unchanged from #6605 which passed the smoke. Not this diff.) * fix(cli): detect WinGet Claude Code on Windows * chore(merge): drop unrelated main-drift from PR fork (deps/electron/proxy files belong to #6620, not this PR) Restores electron/package-lock.json, electron/package.json, package-lock.json, package.json, open-sse/utils/proxyDispatcher.ts, scripts/build/prepare-electron-standalone.mjs and tests/unit/proxy-dispatcher-family.test.ts to origin/release/v3.8.47's content. The PR fork branched from a state of main that already includes #6620 (proxy CONNECT tunnel fix + deps bump), which is not yet synced into release/v3.8.47 — the 3-way merge would otherwise silently carry that unrelated content into this doc-only PR. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(quality): rebaseline cliRuntime.ts file-size freeze for #6647 (1100->1110) The file was already exactly at the frozen 1100-line cap on release/v3.8.47. PR #6647's WinGet Claude Code detection path adds 10 lines (irreducible — the 62-char package folder name forces Prettier's 100-char width to break the path.join call across the same multi-line form used by every other long path in this function), tripping the Fast Quality Gates check:file-size job. Bumping the frozen cap to the file's real new size per the documented allowlist-with-justification policy (this is a pass/fail policy gate, not the ratchet metrics system). Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: quanturbo <faralechko@gmail.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> * feat(sandbox): native Apple Container, WSL, OrbStack, Podman runtime support (#6611) * feat(sandbox): native Apple Container, WSL, OrbStack, Podman runtime support * fix(skills): align sandbox fallback kill container-name convention sandbox.ts's docker-fallback kill path (used only when cachedProvider is unexpectedly null) still targeted the pre-PR omniroute-sandbox-${id} container name, while containerProvider.ts's SANDBOX_NAME now produces omniroute-${id}. Align the fallback naming so it matches the provider convention, with a regression test covering kill()/killAll() before a provider has ever been resolved. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(docs): document SKILLS_SANDBOX_RUNTIME and drop unrelated env leftovers Two fixes surfaced by CI's env/docs contract gate: - Add the SKILLS_SANDBOX_RUNTIME row to docs/reference/ENVIRONMENT.md so the new container-runtime override introduced by this PR is documented, matching .env.example. - Remove the Substrate/Bifrost/OTEL .env.example blocks that leaked in from this branch's stale main-based history during the release-branch sync merge — none of that belongs to this PR (native container runtimes for the skill sandbox) and none of it exists on release/v3.8.47 yet. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * Expose per-combo reasoning token buffer toggle (#6702) * fix(combos): default reasoning token buffer off * feat(combos): expose reasoning token buffer toggle * fix(combos): keep reasoning-token buffer default enabled, opt-out toggle #6702 shipped bundled with #6536's own commit (identical SHA37ac38f17f), flipping the combo-wide reasoningTokenBufferEnabled default from true to false. #6536 was subsequently closed by the author in favor of #6714, which explicitly keeps the existing default-enabled buffer behavior and instead clamps the buffer to the model's known output cap. Reconciled #6702 with that resolution: dropped the default-flip changes across comboConfig.ts, combo.ts, comboSetup.ts, ComboDefaultsTab.tsx, and the combo-defaults settings route (plus their test assertions), and inverted the new per-combo ReasoningTokenBufferToggle to opt-out semantics (`!== false`) so an existing combo's behavior is unchanged unless the operator explicitly unchecks it. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(sse): preserve server-tool literal names in message history and tool_choice (#6586) * fix(sse): preserve server-tool literal names in message history and tool_choice The v3.8.36 guard (isAnthropicServerToolType, #2943) protects Anthropic server tools (web_search_20250305, bash_20250124, ...) from the tool-name cloak only in the tools[] array. The same reserved literal names were still rewritten in message-history tool_use blocks and in tool_choice, and remapToolNamesInRequest had no guard at all (bash -> Bash). The resulting asymmetry — tools[] keeps 'web_search' while the history reference becomes 'WebSearch' — makes Anthropic reject every follow-up turn of a native web-search conversation: [400] Tool 'WebSearch' not found in provided tools Collect the declared server-tool names once per request and skip them in every rewrite path of both remapToolNamesInRequest and cloakThirdPartyToolNames (tools[], message history, tool_choice). Plain custom tools with the same names (no server type) remain remapped/cloaked exactly as before, symmetrically in all sections. Surfaced on Claude Code 2.1.x native WebSearch; same class as CLIProxyAPI #1094/#1179. TDD: 5 failing repro tests -> guard -> 7/7 green (92/92 across the remapper suite), typecheck:core clean. * fix(sse): skip null entries in tools[] before server-tool type check Review follow-up (gemini-code-assist): a null element in tools[] made the new isAnthropicServerToolType(tool.type) check throw. The crash path is pre-existing (String(tool.name) on the next line threw identically), but the guard is cheap and mirrors the null checks already used in cloakThirdPartyToolNames. Adds a regression test (8/8 green). * fix(sse): count gate/combo-rejected requests in per-api-key usage (#6698) Requests rejected before handleChatCore — a pipeline-gate rejection (provider circuit breaker OPEN / model cooldown) or a combo whose targets were all exhausted — short-circuited in chat.ts and only wrote a call_logs row (dashboard/logs). They never reached persistFailureUsage, so no usage_history row was created and the per-api-key usage counter (getApiKeyUsageRows reads usage_history) never incremented. An API key whose traffic was entirely gate/breaker-rejected showed zero requests despite real usage. Route both rejection paths through recordRejectedRequestUsage(), which writes the call_logs row (unchanged visibility) AND a usage_history row attributed to the api key with success:false, mirroring persistFailureUsage. Regression guard: tests/unit/rejected-request-usage.test.ts. * fix(vision-bridge): auto-reroute non-vision models to fastest vision model when images detected (#6640) * fix(electron): bump electron 42→43 + build better-sqlite3 from source (ABI 148) (#6605) fix(electron): bump electron 42→43 + rebuild better-sqlite3 from source against the Electron ABI (148). Electron 43 raises NODE_MODULE_VERSION to 148; better-sqlite3@12.11.1 has no electron-v148 prebuild, so the packaged app died with 'Nenhum driver SQLite disponível'. prepare-electron-standalone now compiles better-sqlite3 from source against the electron headers into build/Release (where 'bindings' resolves it). Validated by Electron Package Smoke (green) + local (node_register_module_v148). Supersedes #6378. (--admin: the only reds are SonarQube/SonarCloud failing on a coverage-report artifact digest-mismatch — a GitHub Actions infra flake, not this diff; Sonar is green on main and the diff touches only the electron build.) * deps: bump the development group across 1 directory with 6 updates (#6588) deps: bump the development group (6 updates). Rebased onto current main; all checks green after the electron-smoke fix (#6605). * fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump (#6620) fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump. undici 8.6+ changed ProxyAgent to forward plain-HTTP via request-proxy instead of CONNECT, breaking OAuth refresh through a connection proxy (501). proxyDispatcher now passes proxyTunnel:true. Validated: Unit Tests 3/8 (the OAuth-proxy test) green, new regression test green (fails without the fix on undici 8.7), SonarQube green. Supersedes #6380. (--admin: the only red is Electron Package Smoke failing on a next-build artifact 'digest-mismatch' — a GitHub Actions infra flake corrupting the asar ('file data stream has unexpected number of bytes'); the better-sqlite3 rebuild itself succeeded (gyp ok) and the electron path is unchanged from #6605 which passed the smoke. Not this diff.) * fix(vision-bridge): auto-reroute non-vision models to fastest vision model when images detected The VisionBridgeGuardrail was describing images as text via a vision model and sending text to the original (non-vision) model. This defeated the purpose when the final target was already vision-capable (auto/vision, combos with vision targets) and never actually rerouted requests to a vision model. Changes: - Individual non-vision models + images → reroute to the fastest available vision-capable model (via getBestVisionModel), keeping images intact - Auto/ prefix models (auto/vision, auto) → skip guardrail entirely, letting the auto-combo resolver handle vision-capable model selection - Combo mappings with non-vision targets → keep existing describe behavior (fallback path via checkModelHasComboMapping) - chat.ts: sync modelStr from body.model after guardrail execution so downstream routing uses the rerouted model * fix(vision-bridge): use getBestVisionModel auto-routing instead of fixed model Address Gemini review feedback: getBestVisionConfig({}) with empty object bypassed auto-routing by always defaulting to a fixed model. Auto-select the best vision model from available providers instead. * fix: compact modelStr sync to stay under file-size cap (1632) * fix: remove debug log, orphaned brace to keep file under cap * chore: trigger CI re-run with file-size fix and PR evidence * chore: rebaseline chat.ts frozen cap to 1754 (PR #6640 +3 lines) * fix(auto-combo): respect hidden models from dashboard toggle getHiddenModelsByProvider() only queried modelCompatOverrides and customModels namespaces, missing the hiddenModels namespace used by the dashboard hide/unhide toggle. Auto-combo candidates now filter out models the user explicitly hid. * fix(changelog): restore CHANGELOG bullets eaten by release sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: herjarsa <herjarsa@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> * fix(api): sanitize catch-block error.message in middleware/hooks routes (#6645) * fix(api): sanitize catch-block error.message in middleware/hooks routes POST /api/middleware/hooks and PUT /api/middleware/hooks/[name] returned the raw error?.message in their 500 response bodies (Hard Rule #12), which could leak internal SQLite error text/paths on a DB failure. Both now route through sanitizeErrorMessage() from open-sse/utils/error.ts, matching the pattern already used elsewhere in the codebase. Regression guard: tests/unit/middleware-hooks-error-sanitization.test.ts Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * test(mutation): register middleware-hooks-error-sanitization in stryker tap.testFiles The mutation test-coverage gate (check:mutation-test-coverage --strict) flagged tests/unit/middleware-hooks-error-sanitization.test.ts as covering open-sse/utils/error.ts but missing from stryker.conf.json's tap.testFiles list. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(changelog): restore CHANGELOG bullets eaten by release sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(changelog): re-restore CHANGELOG bullet after further release sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Chirag Singhal <chirag127@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> * fix(chatgpt-web): render citations as markdown links (#6635) * fix(providers): render ChatGPT-web citation markers as Markdown links ChatGPT Web responses leaked raw chatgpt.com UI citation markup (private-use marker tokens like `citeturn0search0`, `entity[...]`) instead of real Markdown links, since these are normally resolved client-side by chatgpt.com's own JS using `message.metadata.content_references`. cleanChatGptText() now resolves content_references (grouped webpages, footnote sources, inline webpage/url mentions) into `[label](url)` Markdown links for the streaming and non-streaming response builders and the GPT-5.5 Pro stream_handoff polled-answer path, falling back to stripping any marker with no resolvable source. The citation parsing/rendering logic was extracted into a new pure sibling module (open-sse/executors/chatgpt-web/citations.ts), decomposed into small per-reference-type helpers, to keep the executor under the frozen file-size cap and the complexity/cognitive-complexity ratchets. Regression tests moved to a dedicated tests/unit/chatgpt-web-citations.test.ts for the same reason. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(changelog): restore CHANGELOG bullets eaten by release sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(changelog): re-restore CHANGELOG bullet after further release sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(changelog): re-restore CHANGELOG bullet after further release sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: Thinkscape <thinkscape@users.noreply.github.com> * feat(settings): 9router-style Routing Strategy card + sticky parity (#6678) * feat(dashboard): 9router-parity Routing Strategy card + provider/combo sticky override (#6678) Add a Routing Strategy settings card (Settings -> Routing) surfacing account round-robin/sticky-limit knobs plus a new combo-level sticky round-robin (comboStickyRoundRobinLimit), and a per-provider account-routing override (providerStrategies) wired into getProviderCredentials() ahead of the global fallback strategy. Rebased onto release/v3.8.47 (credit-preserving reconstruction: unrelated package.json/electron/proxyDispatcher drift from the PR's stale base was dropped, only the author's own 12 files were re-applied). Split ProviderAccountRoutingCard/RoutingStrategyCard into smaller hook+subcomponent pieces to stay under the frozen complexity/file-size gates; rebaselined ProviderDetailPageClient.tsx/auth.ts's frozen file-size caps for the small additive growth. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(quality): register combo-rr-sticky-9router.test.ts in stryker tap.testFiles (#6678) CI's Fast Quality Gates -> check:mutation-test-coverage --strict flagged the new test as missing from stryker.conf.json's tap.testFiles (it covers the mutated module open-sse/services/combo/rrState.ts). Adds the single entry, alphabetized next to the existing combo-rr-fallback-advance-948.test.ts. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(changelog): restore CHANGELOG bullets eaten by release sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(changelog): re-restore CHANGELOG bullet after further release sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(changelog): re-restore CHANGELOG bullet after further release sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: SeaXen <SeaXen@users.noreply.github.com> * fix(cloudflare-relay): use Service Worker syntax with body_part metadata (#6416) (#6496) * fix(providers): Cloudflare relay Worker uses Service Worker syntax + body_part CONTEXT: #6416/#6618 fixed the multipart Content-Type but the emitted worker source still used ES-module syntax (`export default { fetch }`) with `main_module` metadata. Cloudflare's Workers upload API parses a plain `application/javascript` script part as Service Worker syntax regardless of `main_module`, and `main_module` requires the script to actually be an ES module — so the upload was still rejected. CHANGE: buildCloudflareWorkerScript() now emits Service Worker syntax (`addEventListener("fetch", ...)`, no top-level `export`) and the upload metadata uses `body_part` instead of `main_module`. Also restores the SSRF-guard bracket-stripping regex for bracketed IPv6 hosts (`[::1]`, `[fd00::1]`) that an earlier revision of this change accidentally double-escaped, with regression coverage added to tests/unit/relay-deploy-5128.test.ts. Updates the sibling tests/unit/proxy-pool-cloudflare-workers-deployer.test.ts assertion that still expected the old ES-module contract. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(changelog): restore CHANGELOG bullets eaten by release sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(changelog): re-restore CHANGELOG bullet after further release sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(changelog): re-restore CHANGELOG bullet after further release sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(changelog): re-restore CHANGELOG bullet after further release sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(changelog): correct CHANGELOG restoration (previous attempt had a script-path bug) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: SeaXen <SeaXen@users.noreply.github.com> * fix: update Dockerfile with --allow-scripts for better-sqlite3 compil… (#6700) * fix(docker): compile better-sqlite3 via direct node-gyp rebuild in the Dockerfile The `builder` stage installs dependencies with `npm ci --ignore-scripts` (deliberate supply-chain hardening) and then re-enables the native build for the one package that needs it. `npm rebuild better-sqlite3` re-runs that indirectly through the package's own install script, which under npm 11 depends on npm's script-allowlist machinery correctly re-enabling it — some self-hosted build environments (e.g. Dokploy) hit a broken/mismatched native binding through that indirection. Invoke `node-gyp rebuild` directly inside `node_modules/better-sqlite3` instead, bypassing npm's script-running layer entirely, so the compile step is deterministic regardless of npm version or ignore-scripts allowlist behavior. Rebased onto the current release/v3.8.47 tip: dropped this branch's stale electron/package.json + package-lock.json diff (would have reverted the electron 42->43 ABI-148 fix from #6605) and the unconsumed root `allowScripts` package.json field (npm does not read that key; has zero effect). Regression guard: tests/unit/dockerfile-better-sqlite3-node-gyp-6700.test.ts. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(changelog): restore CHANGELOG bullets eaten by release sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(changelog): re-restore CHANGELOG bullet after further release sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(changelog): re-restore CHANGELOG bullet after further release sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(changelog): re-restore CHANGELOG bullet after further release sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(changelog): correct CHANGELOG restoration (previous attempt had a script-path bug) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(changelog): re-restore #6700 bullet after #6496 release sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(changelog): re-restore CHANGELOG bullet after further release sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: nowhats-br <nowhats-br@users.noreply.github.com> * Continue fix bugs and upgrade skill_collector (#6294) * fix(skills): gate skill-collector CLI detection behind management auth + loopback PR #6294 fork-main bundled genuinely new skill-collector CLI-detection routes (GET /api/skills/collect/detect, POST /api/skills/collect/install) on top of content already shipped via #6186. This reconstructs the PR against the current release tip, keeping only the new detect/install routes and their SKILL.md, and drops the 3 already-merged commits so two post-merge quality fixes on /api/github-skills (Zod validation + sanitizeErrorMessage) are not reverted. - GET /api/skills/collect/detect spawned a child process per CLI_TOOL_IDS entry via getCliRuntimeStatus(), unauthenticated and reachable over any tunnel. All 3 routes (github-skills GET/POST, skills/collect/detect, skills/collect/install) now require requireManagementAuth(), matching every sibling /api/skills/* route. - Classified /api/skills/collect/ in LOCAL_ONLY_API_PREFIXES and SPAWN_CAPABLE_PREFIXES (routeGuard.ts / spawnCapablePrefixes.ts) and added src/app/api/skills/collect to SPAWN_CAPABLE_ROUTE_ROOTS in check-route-guard-membership.ts so the automated gate actually scans it (Hard Rules #15 + #17). - omniroute_github_skills_install MCP tool now reports the honest action: "planned" instead of "installed", matching the REST route. - Dropped docker-compose.drive-d.yml, start.sh, and the unrelated @types/node/settings.ts changes (personal dev-machine / out-of-scope). - Added route-level tests for all 3 routes + the 3 MCP tools (auth-required and no-stack-trace-leak assertions) and a route-guard regression test. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(quality): register new routeGuard covering test in stryker.conf.json check:mutation-test-coverage --strict (Fast Quality Gates) flagged tests/unit/authz/route-guard-skills-collect.test.ts as a covering unit test for src/server/authz/routeGuard.ts that was missing from tap.testFiles, so its mutant kills would silently not count toward the mutation-test baseline. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore: resync CHANGELOG after merging release/v3.8.47 Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: Moseyuh333 <Moseyuh333@users.noreply.github.com> * fix(providers): update web model discovery (#6308) Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> * feat(providers): ClinePass OAuth login — dual-auth on top of #5942 (reconciles #5924) (#6126) * feat(providers): rebase ClinePass dual-auth (OAuth + BYOK) onto release/v3.8.47 ClinePass now offers both sign-in methods on its dashboard page: OAuth (reusing the Cline WorkOS flow, primary "Connect" button) or a pasted BYOK API key ("Manual API key"), instead of only the API-key-only provider shipped in #5942. - Registry: authType oauth + oauth urls, alias aligned to "cp" (matches the OAUTH_PROVIDERS catalog alias so <alias>/<modelId> routing resolves); keeps the #6165 forceStream:true fix (streaming-only API). - Executor: new buildClinepassHeaders() (src/shared/utils/clineAuth.ts) picks buildClineHeaders() for an OAuth accessToken or a plain Bearer + Cline identification headers for a BYOK key — extracted to a leaf module to avoid growing the frozen open-sse/executors/default.ts. - Refresh: dispatch clinepass to the shared refreshClineToken() (was falling through to the generic refresh and failing silently). - Catalog: admit the BYOK path through a dedicated DUAL_AUTH_APIKEY_PROVIDER_IDS gate (src/lib/providers/catalog.ts) so POST /api/providers accepts an apikey connection without flipping isOAuth off (which would break the primary Connect->OAuth routing). - Dashboard: render both "Connect" + "Manual API key" buttons for clinepass (ConnectionsHeaderToolbar.tsx, EmptyConnectionsPlaceholder.tsx). - Dedup: removed the now-redundant API-key-only APIKEY_PROVIDERS_GATEWAYS entry so ClinePass is listed once (OAuth-primary). - oauth.ts: added the clinepass catalog entry (was reverted by staleness during rebase); src/lib/oauth/providers/index.ts: clinepass -> cline. This branch was ~167 commits / weeks behind release/v3.8.47; a real merge surfaced 61 conflicting files, several of which are already-shipped fixes (forceStream #6165, zed-hosted, requesty, agentrouter CC-wire-image, NVIDIA/Mistral/kimi executor fixes, chatCore hardening) that a naive resolution would have silently reverted. Reconstructed clean on top of current release/v3.8.47, isolating and re-applying only the clinepass dual-auth feature and preserving every already-shipped fix untouched. tokenRefresh.ts's frozen-file cap raised by the irreducible 1-line `case "clinepass":` switch label (config/quality/file-size-baseline.json, justified inline); open-sse/executors/default.ts stays under its cap via the buildClinepassHeaders() extraction. Regression guard: tests/unit/clinepass-provider.test.ts (15/15, extended with the dual-auth admission-gate and alias-consistency guards). Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * test(providers): update APIKEY_PROVIDERS spread-merge count 171->170 The ClinePass dual-auth rebase (this PR) removed the now-redundant API-key-only APIKEY_PROVIDERS_GATEWAYS.clinepass entry (dedup — clinepass is OAuth-primary now, with its BYOK path admitted through the DUAL_AUTH_APIKEY_PROVIDER_IDS gate instead of a second catalog entry), which drops the total APIKEY_PROVIDERS spread-merge count by one. tests/unit/providers-constants-split.test.ts hardcoded the prior count (171); updated to 170 to match, confirmed via CI (Unit Tests fast-path 1/2 and 2/2 both failed on the stale count). Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(oauth): register clinepass in PROVIDERS enum to fix Unknown provider error Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(test): rebaseline oauth-providers-config.test.ts frozen size for clinepass entries Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(changelog): re-restore #6126 bullet after release sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: hajilok <hajilok@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> * feat(kiro): support enterprise External IdP (Your organization) logins (#6363) * feat(kiro): support enterprise External IdP ("Your organization") logins Kiro's enterprise "Your organization" sign-in federates through the org's own identity provider (e.g. Microsoft Entra ID) and produces an `external_idp` token that is fundamentally different from AWS Builder ID / IAM Identity Center (AWS SSO-OIDC, refresh token starts with `aorAAAAAG`) and the Google/GitHub social flow. Its `~/.aws/sso/cache/kiro-auth-token.json` carries an org-IdP JWT access token, an IdP refresh token, a per-tenant `tokenEndpoint`, a public `clientId` (no secret) and `scopes` (`codewhisperer:conversations …`). Before this change every import path rejected these tokens (the `aorAAAAAG` format gate + no client secret), and the runtime/quota calls would have failed even if imported, so organization accounts could not be used. This adds full external_idp support: - New `open-sse/services/kiroExternalIdp.ts`: public-client refresh_token grant builder (`buildExternalIdpRefreshParams`), a token-endpoint SSRF allowlist (`validateExternalIdpTokenEndpoint` — Microsoft/Okta/Auth0/OneLogin/Ping/ Google/Cognito, https only), scope normalization, JWT identity extraction (`preferred_username`/`upn`/`email`), and the `TokenType: EXTERNAL_IDP` header constants. - Runtime executor (`open-sse/executors/kiro.ts`): send `TokenType: EXTERNAL_IDP` for external_idp accounts. CodeWhisperer only binds the org-IdP bearer to the Amazon Q Developer profile with this header; without it every call returns `ValidationException: Invalid ARN <clientId>`. - Runtime + import token refresh (`open-sse/services/tokenRefresh.ts`, `src/lib/oauth/services/kiro.ts`): refresh external_idp tokens with a form-encoded public-client `refresh_token` grant against the org IdP's `tokenEndpoint` instead of AWS OIDC / the Kiro social endpoint. - Quota (`open-sse/services/usage/kiro.ts`): send the same header on `GetUsageLimits` so organization quota resolves. - Import routes: `POST /api/oauth/kiro/import` gains an external_idp branch (skips the `aorAAAAAG` gate, refreshes via the org IdP, stores clientId/tokenEndpoint/scope/region/profileArn); `GET /auto-import` now recognizes external_idp tokens in `~/.aws/sso/cache`, reads the profile ARN from the Kiro IDE `profile.json` (org tokens can't enumerate it via `ListAvailableProfiles`), and persists the connection. The profile.json reader is factored into a shared `readKiroIdeProfileArn()` helper. - Validation schema (`kiroImportSchema`): accept `tokenEndpoint` + `scopes`. Tests: new `tests/unit/kiro-external-idp.test.ts` (endpoint allowlist, scope normalization, identity extraction, public-client refresh body, the org IdP refresh path, and the `TokenType: EXTERNAL_IDP` header gating). Also hardens `kiro-windows-auto-import-3363.test.ts` to isolate `USERPROFILE` (Windows `os.homedir()` reads it, not `HOME`) so the probe never reads a real on-host Kiro login. * fix(changelog): restore #6363 bullet after release resync (CHANGELOG-eat guard) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(changelog): re-restore #6363 bullet after release sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(merge): restore #6126 clinepass files reverted by release auto-resolve + rebaseline own tokenRefresh growth The release sync's merge auto-resolve silently reverted sibling PR #6126's clinepass work (registry entry, catalog, oauth constants, clineAuth.ts, the clinepass token-refresh case, and its tests) — all outside this PR's Kiro external-IdP scope. Restored every affected file to the release version; the remaining diff is Kiro-IdP-only. Rebaselined tokenRefresh.ts 2182->2249 (+67, this PR's own external_idp refresh branch) with justification, and restored the #6126 CHANGELOG bullet (re-inserting only this PR's own). Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: artickc <artickc@users.noreply.github.com> * feat: add Kiro API key authentication (#6587) * feat(oauth): add Kiro long-lived API key auth (#6587) New /api/oauth/kiro/api-key route + KiroService.validateApiKey let a Kiro account be linked with a long-lived AWS CodeWhisperer/Kiro API key instead of the interactive OAuth device flow, with live per-account model discovery (ListAvailableModels, 5-minute cache) layered over the existing static registry fallback. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(changelog): re-restore #6587 bullet after release sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(merge): restore #6126 clinepass files reverted by release auto-resolve + baseline re-merge The release sync's auto-resolve reverted sibling PR #6126's clinepass work (registry, catalog, oauth constants, clineAuth.ts, token-refresh case, tests) and the file-size baseline — all outside this PR's scope. Restored to the release versions, re-applied only this PR's own baseline entries, restored the #6126 CHANGELOG bullet (re-inserting only this PR's own). Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(quality): freeze public-creds FP — AWS region default in validateApiKey signature Same class as the existing minimax fn-param FPs: CRED_KEY_RE matches the apiKey: param annotation and captures the region default "us-east-1", which is not a credential. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(kiro): keep hard-failure reject semantics + kill public-creds fn-param FP at the source - getKiroUsage: exhausted non-auth attempts now REJECT with the last HTTP-status failure in the pre-#6587 format (usage-service-hardening relies on it); auth failures keep the soft social-auth message. - validateApiKey: region default moved out of the parameter list (the check-public-creds CRED_KEY_RE matches the apiKey: annotation and flags any literal in the signature); drops the brittle line-keyed allowlist entry. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: strangersp <strangersp@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> * fix: Stabilize live dashboard WebSocket routing (#6335) * fix(dashboard): allow anonymous WS handshake + public /api/health/ping The live-dashboard WebSocket descriptor handshake (GET /api/v1/ws?handshake=1) and the lightweight GET /api/health/ping liveness probe both 401'd for unauthenticated callers, even though both are metadata-only reads intended to be public. clientApiPolicy required a bearer/dashboard-session before the WS route handler could even return its own wsAuth/protocol descriptor, and /api/health/ping was never added to PUBLIC_READONLY_API_ROUTE_PREFIXES despite its own docstring documenting it as "No auth required". clientApiPolicy.evaluate() now allows an anonymous {kind:"anonymous", id:"ws-handshake"} subject for GET/HEAD/OPTIONS on /api/v1/ws?handshake=1 — the route handler still performs its own real wsAuth/dashboard/API-key decision before opening the socket — and /api/health/ping is now in PUBLIC_READONLY_API_ROUTE_PREFIXES. Re-scoped from the original PR per review-group-prs analysis: the overlapping hardcoded /live-ws path-derivation change (useLiveDashboard.ts, ws/route.ts) is dropped here since it conflicts with #6072's different (dynamic, env-derived) approach to the same problem; only the non-overlapping auth-policy win ships in this PR. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(changelog): resync CHANGELOG.md after merging release/v3.8.47 Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: JxnLexn <JxnLexn@users.noreply.github.com> * feat(icons): prioritize local SVG icons over LobeHub npm for faster rendering (#6317) * feat(icons): prioritize local SVG icons over LobeHub npm for faster rendering * docs(changelog): add #6317 local-icons New Features bullet --------- Co-authored-by: hamsa0x7 <hamsa0x7@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> * feat(chaos): big update - optimize, fix bugs, add features, enhance UX (#6728) * feat(chaos): add Chaos Mode — multi-model parallel/collaborative execution - New DB column chaos_mode_enabled on api_keys table - API key create/PATCH routes support chaosModeEnabled toggle - Core library src/lib/chaos/chaosConfig.ts for persistent config - API routes: GET/PUT/DELETE /api/chaos/config - Chaos execution POST /api/skills/collect/chaos with key auth - Dashboard page at /dashboard/chaos with full config UI - Sidebar entry in Agentic Features section - Chaos mode toggle in API Key editor permissions panel - i18n keys for chaos config (en.json) * feat(chaos): big update — optimize, fix bugs, add features === Changes === 1. NEW: src/lib/chaos/chaosExecutor.ts — shared execution engine - Removed ~150 lines of duplicate dispatch logic between two API routes - Single executeChaosRun() function used by both endpoints - Added concurrency limit (max 10 parallel requests) - Added proper TypeScript interfaces (ChaosRunInput, ChaosRunResult) - Added error logging throughout 2. FIX: src/app/api/skills/collect/chaos/route.ts - Was MISSING logger import (log.error was undefined at runtime) - Reduced from 388 lines → 142 lines by delegating to shared executor - Added maxTokens support in schema validation 3. REFACTOR: src/app/api/chaos/run/route.ts - Simplified to thin wrapper: auth + validate + delegate to executor - Added maxTokens support 4. ENHANCE: src/lib/chaos/chaosConfig.ts - Added maxTokens config field (256-128k, default 4096) - Persisted per-instance via settings table 5. ENHANCE: UI — ChaosConfigPageClient.tsx - Loads available providers from /api/models for dropdown autocomplete - Added datalist-based provider selector in overrides section - Added Max Tokens configuration input - Added expandable provider list showing all detected providers - Fixed duplicate override detection * fix(chaos): fetch providers from /api/providers instead of /api/keys * fix(chaos): remove dead code isOverrideDuplicate, fix maxTokens fallback to include global config * fix(chaos): resetConfig now shows error on HTTP failure (was silent) * feat(dashboard): Chaos Mode — multi-model parallel/collaborative execution Splits the PR down to only the genuinely new Chaos Mode feature (drops the duplicate Skill Collector/GitHub-discovery portion already shipped via #6186). Replaces the loopback fetch() dispatch (hardcoded to the wrong port) with the established in-process synthetic-Request/route-handler pattern used by src/lib/batches/dispatch.ts, moves settings persistence off raw SQL, and adds unit test coverage for chaosConfig, chaosExecutor and the 3 chaos API routes. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(chaos): fix external Bearer-auth bypass and stale config cache in tests validateApiKey() returns a plain boolean for both the deployment-time env key and a DB-backed key, so branching on `keyInfo === true` in verifyChaosKey() (src/app/api/skills/collect/chaos/route.ts) treated every valid API key as having full env-key access, silently skipping the chaosModeEnabled permission check entirely. Now always resolves through getApiKeyMetadata() and only bypasses the per-key check for the synthesized env-key record (id: "env-key"). Also exports invalidateChaosConfigCache() from chaosConfig.ts and wires it into the route tests' resetStorage() — the in-process config cache was surviving DB resets between tests, causing state to leak across cases. Fixes CHANGELOG-eat from the release merge (re-inserted the Chaos Mode bullet against the base CHANGELOG.md, verified additive via check-changelog-integrity.mjs) and re-syncs against release/v3.8.47 tip. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * docs(changelog): Chaos Mode overhaul bullet referencing #6728 after release sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(merge): restore #6126 clinepass files reverted by release auto-resolve + baseline re-merge The release sync's auto-resolve reverted sibling PR #6126's clinepass work (registry, catalog, oauth constants, clineAuth.ts, token-refresh case, tests) and the file-size baseline — all outside this PR's scope. Restored to the release versions, re-applied only this PR's own baseline entries, restored the #6126 CHANGELOG bullet (re-inserting only this PR's own). Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(dashboard): chaos client hook must not import the server Pino logger useChaosConfigData ("use client") pulled @/sse/utils/logger → shared Pino → logRotation/dataPaths → node:fs into the browser bundle, breaking next build (Turbopack: Can't resolve 'fs') — caught by the DAST smoke's isolated build. console.error matches every other dashboard client component. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * test(api-manager): align switch-count invariant with the extracted toggle components The Self-service block now renders 4 inline switches; the #5731 quota-bypass and #6728 chaos-access toggles were extracted into dedicated components. The type="button" invariant is preserved AND extended: the test now also asserts each extracted component's switches declare type="button". Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(sync): merge release tip + restore own CHANGELOG bullet Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Moseyuh333 <Moseyuh333@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> * feat(cli): add CLI tools for pi, omp, letta, codewhale and jcode (#6318) * feat(cli): add CLI tools for pi, omp, letta, codewhale and jcode * fix(build): resolve CI build and lint errors * fix(cli): resolve merge conflicts, add tests, align error handling for cli-additions Resolve duplicate codewhale key from base merge, add unit/integration tests for omp/letta settings routes and the omp DB module, and align omp-settings/letta-settings error handling with sanitizeErrorMessage() + the pattern used by sibling jcode/pi/codewhale routes in this PR. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(quality): correct cliRuntime.ts file-size baseline to actual post-merge line count Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(changelog): re-restore #6318 bullet after release sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(merge): restore #6126 clinepass files reverted by release auto-resolve + baseline re-merge The release sync's auto-resolve reverted sibling PR #6126's clinepass work (registry, catalog, oauth constants, clineAuth.ts, token-refresh case, tests) and the file-size baseline — all outside this PR's scope. Restored to the release versions, re-applied only this PR's own baseline entries, restored the #6126 CHANGELOG bullet (re-inserting only this PR's own). Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(db): re-export db/omp from localDb (check:db-rules #2) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(db): keep localDb.ts at the 800-line cap after the omp re-export Folded the MemoryVecMeta type re-export into the memoryVec named-export block (inline 'type' specifier) so adding the db/omp line stays within the new-file cap. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(cli): reduce #6318 scope to omp + letta (pi/codewhale/jcode already shipped) pi, codewhale, and jcode landed via a separate PR before this one was reconciled — re-adding parallel versions of their catalog entries, routes, dashboard card, and i18n strings would have been a straight regression (duplicate "pi" key silently shadowing the release's own entry, orphaned JcodeToolCard/BaseUrlSelect/ApiKeySelect/cliEndpointMatch UI files with no release-side wiring, and unrelated formatting/refactor drift in codewhale-settings/pi-settings/config-generator/routeGuard picked up along the way). This PR now ships only the two tools that are genuinely new: omp (Oh My Pi) and letta. Both settings routes shell out to `which omp`/`which letta` to detect the local install, so they're loopback-gated in LOCAL_ONLY_API_PREFIXES (Hard Rules #15/#17) in addition to the shared requireCliToolsAuth() guard every cli-tools route requires (tests/unit/cli-tools-auth-hardening.test.ts) — neither route had the guard wired in yet. cli-catalog-counts.test.ts is updated to the real cardinality (8 agent entries / 32 total, since omp+letta are both category "agent"; pi/codewhale/jcode were always category "code" and are unaffected). The integration tests for omp/letta now pass a Request object to GET/DELETE and assert the 401-when-auth-required path, matching the pattern already used by the codewhale/jcode sibling routes. complexity-baseline.json is back to the release's 2053 (the #6318 rebaseline note is gone — dropping the duplicate JcodeToolCard.tsx/BaseUrlSelect.tsx removed the violations it was covering); file-size-baseline.json's cliTools.ts entry shrank 955->915 to match the smaller real file. CHANGELOG bullet rewritten to describe only omp+letta, with a note on why pi/codewhale/jcode aren't part of this PR; also restores the Kiro External IdP bullet that a prior merge auto-resolve had dropped from the living section. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * test(cli-tools): align cli-tools-schema registry count with omp+letta (30→32) Second exact-count guard missed in the scope-reduction pass; same legitimate alignment as cli-catalog-counts. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(cli-tools): omp entry needs docsUrl (CliCatalogEntrySchema requires it) https://github.com/can1357/oh-my-pi — verified official repo. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(quality): cliTools.ts frozen 915→916 (+1 omp docsUrl line, own growth) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(changelog): restore base + re-insert #6318 bullet after release sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(sync): merge release tip + restore own CHANGELOG bullet Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: hamsa0x7 <hamsa0x7@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * feat(release): changelog.d/ fragments — eliminate the CHANGELOG merge-storm cascade (#6783) * feat(release): changelog.d/ fragments — kill the CHANGELOG-eat merge-storm cascade Every PR used to edit the same top lines of CHANGELOG.md (its bullet), so in a merge-storm each merge conflicted every sibling (CHANGELOG-eat / DIRTY cascade), forcing a re-sync push + full CI re-run per PR per merge — O(N^2) CI runs. A PR now adds ONE new file under changelog.d/{features|fixes|maintenance}/ with its bullet; two PRs never touch the same file. scripts/release/aggregate-changelog.mjs (npm run changelog:aggregate) folds fragments into the living section and deletes them at release reconciliation. check:changelog-integrity (already wired in the merge-integrity CI job — zero workflow change) now also validates fragment well-formedness. This PR dogfoods the convention: its own entry is a fragment. * chore(changelog): fragment filename matches PR number (#6783) * ci(quality): shard unit fast-path 2→4 — halves the heaviest job's wall time (#6781) * ci(quality): TIA impacted-run splits dashboard tests onto the tsx loader (closes #6787) (#6788) The impacted branch ran every selected file under --import tsx/esm; the canonical test:unit:ci:shard runs tests/unit/dashboard/** under --import tsx (CJS transform, required for @lobehub/icons/es/* deep imports). Any PR whose impact map reached a dashboard component false-redded with 'Unexpected token export' (reproduced on unrelated PRs #6317 and #6335 the same evening). The selection is now split by segment with loader parity. * chore(release): merge-train — batch-validate queued PRs once, --admin with evidence (#6784) Merges every queued PR into a throwaway detached worktree cut from origin/<base>, runs the fast-gates parity suite ONCE on the final train tip, and prints the evidence line that authorizes gh pr merge --squash --admin per member (merge-gates.md §7). Conflicting PRs are ejected and reported, the train continues. Never pushes, never merges PRs, never stashes. * fix(providers): register openrouter rerank provider (#6574) (#6681) * fix(providers): register openrouter rerank provider (#6574) * fix(changelog): restore CHANGELOG bullets eaten by release sync * fix(changelog): re-restore CHANGELOG bullet after further release sync * fix(changelog): re-restore CHANGELOG bullet after further release sync * fix(changelog): re-restore CHANGELOG bullet after further release sync * fix(changelog): correct CHANGELOG restoration (previous attempt had a script-path bug) * fix(changelog): re-restore CHANGELOG bullet after further release sync * fix(changelog): re-restore #6681 bullet after #6700 release sync * chore(sync): merge release tip + restore own CHANGELOG bullet Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(sync): merge release tip + restore own CHANGELOG bullet * fix(api): close HEAD requests immediately instead of hanging (#6400) (#6608) * fix(api): close HEAD requests immediately instead of hanging (#6400) Next.js 16's App Router route-handler pipeline (send-response.js) already skips piping a Response body for HEAD, but its page-rendering pipeline (pipe-readable.js -> pipeToNodeResponse, used for every app-router page/layout render, including the not-found boundary any unmatched path falls through to) has no such check and always streams the full rendered body regardless of method. Combined with Node's default keep-alive framing, this left some clients unsure whether the (implicitly bodyless) HEAD response had actually finished. Add scripts/dev/head-response-guard.cjs, wired into both the dev/start custom server (run-next.mjs) and the packaged standalone server (standalone-server-ws.mjs) at the same tier as the existing http-method-guard.cjs/peer-stamp.mjs wrappers: for every inbound HEAD request it discards any body bytes the inner handler writes and forces Connection: close once .end() is called, independent of route existence or auth state. Regression guard: tests/unit/head-request-closes-6400.test.ts * chore(changelog): restore #6400 bullet before re-sync * chore(sync): merge release tip + restore #6608 bullet * chore(sync): merge release tip + restore #6400 bullet * chore(changelog): re-sync after release merge — preserve #6574 rerank bullet Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * feat(oauth): accept 9router camelCase Codex export in bulk import (#6665) (#6697) * feat(oauth): accept 9router camelCase Codex export in bulk import (#6665) * fix(changelog): restore CHANGELOG bullets eaten by release sync * fix(changelog): re-restore CHANGELOG bullet after further release sync * fix(changelog): re-restore CHANGELOG bullet after further release sync * fix(changelog): re-restore #6697 bullet after release sync (#6678 landed) * fix(changelog): re-restore CHANGELOG bullet after further release sync * fix(changelog): re-restore #6697 bullet after #6700 release sync * fix(changelog): re-restore #6697 bullet after release sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(changelog): restore #6126 bullet eaten by ancestry merge; re-insert only #6697's own * chore(sync): merge release tip + restore own CHANGELOG bullet Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(sync): merge release tip + restore own CHANGELOG bullet * chore(changelog): re-sync after release merge — preserve sibling bullets Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(resilience): release combo session-stickiness pin on a terminal/quality-rejected account (#6692) (#6733) applySessionStickiness() gated the sticky pin only on 5h/weekly usage headroom, which is orthogonal to account availability, so a credits_exhausted/banned/ expired/rate-limited connection (or a quality-validation-rejected 200) kept being re-promoted forever, defeating failover for that conversation. * fix(i18n): translate provider visibility/free-paid filter labels across 15 locales (#6694) (#6719) * feat(fusion): let judge use its own knowledge and override the panel (#6804) The judge prompt said to write an answer 'grounded in that analysis', implicitly capping output at the panel's union. When all panel members miss or are collectively wrong on something, the judge should apply its own reasoning as a full participant and override consensus, while keeping an honesty guard against fabrication. Adds a regression test. Co-authored-by: Chirag Singhal <chirag127@users.noreply.github.com> * fix(api): raise provider apiKey cap for cookie-based web providers (#6715) (#6759) * fix(cli): fall back to settings.json when Claude Code binary is unresolvable (#6701) (#6734) getCliRuntimeStatus() only ever answered `installed` from binary resolution (known install paths + where/which PATH search), so a stale PATH, moved binary, or uncatalogued install method reported "not found" even when ~/.claude/settings.json proved the CLI was installed and used before — regressing behind upstream 9router's checkClaudeInstalled(), which already falls back to the settings file when where/which fails. withSettingsFallback() (new src/shared/services/cliInstallFallback.ts, kept out of the frozen cliRuntime.ts to respect its file-size ceiling) restores that parity: only when the binary lookup's own reason is "not_found" (never for deliberate security rejections like unsafe/relative env overrides or symlink escapes) and the tool's settings file exists on disk. * fix(providers): honor explicit thinking.budget_tokens 0 in openai->gemini transform (#6813) (#6821) The transform forwarded the Claude-style thinking.budget_tokens into generationConfig.thinkingConfig.thinkingBudget, but the presence check was truthy (&& thinking.budget_tokens). An explicit budget_tokens: 0 — the natural way to disable thinking — is falsy, so it was dropped and the request fell through to the default thinkingConfig injection, making the model think despite an explicit request for zero. Use an explicit numeric check so 0 is honored as thinkingBudget 0; includeThoughts is only set for a non-zero budget. * fix(compression): reconcile outer vs per-engine token counts (#6488) (#6741) * fix(compression): reconcile outer vs per-engine token counts on degenerate output (#6488) Outer originalTokens/compressedTokens (real tiktoken counter over extracted message text) diverged from engineBreakdown[0]'s counts (a crude JSON.stringify(requestBody).length/4 estimate), worst on small/degenerate inputs where JSON structural overhead dominates. A single-engine breakdown entry represents the exact same before/after transformation as the overall response, so reconcileSingleEngineTokens() now overwrites that one entry's counts with the outer, more accurate figures; multi-step pipeline breakdowns are left untouched. * chore(6741): resolve release sync — CHANGELOG.md restored to release tip, entry moved to changelog.d fragment (fragments-first) * fix(api): accept enableRenderers in RTK compression config schema (#6703) (#6757) * fix(db): break probe-failed/restore loop on large storage.sqlite (#6632) Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR); keeps only the author's changes. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * feat(cursor): add Opus 4.8, Fable 5, and Sonnet 5 model families (#6779) Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR); keeps only the author's cursor registry + test changes. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(translator): read PDF/video file attachments for Gemini/Antigravity and Claude (#6790) Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR); keeps only the author's translator + test changes. Co-authored-by: Wital <witalorocha216@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(codex): strip include from compact responses requests (#6805) * fix(codex): strip include from compact responses requests Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR); keeps only the author's changes. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(6805): move include-strip assertion to standalone test file to keep executor-codex.test.ts under frozen size cap Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(i18n): translate hardcoded Portuguese dashboard strings to English (#6769) Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR); keeps only the author's changes. Co-authored-by: Chirag Singhal <chirag127@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(bootstrap): filter empty process.env values to prevent Docker env crash loop (#6828) Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR) and the direct CHANGELOG.md edit (fragments-first); keeps only the author's bootstrap change. Co-authored-by: Andrian B. <andrewbalanesq@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(providers): update SenseNova Token Plan support (#6330) Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR); the author's constants/registry/snapshot deltas were re-applied cleanly onto the release tip. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(providers): classify 404 as MODEL_NOT_FOUND to stop retry storm (#6829) Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR) and the direct CHANGELOG.md edit (fragments-first); the author's chatCore/errorClassifier deltas were re-applied cleanly onto the release tip. Co-authored-by: Andrian B. <andrewbalanesq@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(api): accept all catalog engines on compression PUT schema (#6792) Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR). Resolved the release's OmniGlyph engine addition additively (types.ts/compression.ts kept both 'relevance' and 'omniglyph') and extended stackedPipelineStepSchema + STACKED_PIPELINE_ENGINE_INTENSITIES with the omniglyph branch so the ENGINE_CATALOG-parity test passes. Co-authored-by: Pitchfork-and-Torch <Pitchfork-and-Torch@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(api): point CLI health command at /api/monitoring/health (#6677) (#6717) * fix(api): point CLI health command at /api/monitoring/health (#6677) bin/cli/commands/health.mjs called GET /api/health, a route that was moved to /api/monitoring/health without updating the CLI; the top-level /api/health handler never existed on disk (only degradation/ and ping/ sub-routes). Point runHealthCommand()/runHealthComponentsCommand() at /api/monitoring/health and read its real payload shape (activeConnections, circuitBreakers: {open,halfOpen,closed}, memoryUsage) instead of the old nonexistent requests/breakers/cache/memory fields. * chore(6717): re-sync onto release tip; move CHANGELOG entry to changelog.d fragment (fragments-first) * chore(cursor): add Grok 4.5 effort/fast model IDs (#6774) Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR); keeps only the author's changes. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(providers): ensure DeepSeek Web SSE emits [DONE] after FINISHED (#6791) * fix(providers): ensure DeepSeek Web SSE emits [DONE] after FINISHED Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR); keeps only the author's changes. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(deepseek): extract done-terminator helper to keep frozen file under cap Extracts the FINISHED-drain scheduler and finish-once guard added for the [DONE] terminator fix (#6777) into a new deepseek-web-done-terminator.ts module, so deepseek-web.ts stays under its frozen line cap (1148). Behavior is unchanged. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Pitchfork-and-Torch <Pitchfork-and-Torch@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * feat(models): add capability override UI (#6727) * feat(models): add capability override UI Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR); renumbered the migration 118 -> 119 to resolve the collision with 118_provider_param_filters.sql already on release/v3.8.47; the author's i18n/localDb deltas were re-applied cleanly onto the release tip. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(6727): import model-capability-overrides DB fns directly (not via localDb barrel) to keep localDb under file-size cap; aligns with anti-barrel convention * chore(db): satisfy known-symbols contract for modelCapabilityOverrides Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> * fix(cursor): use Agent CLI build id for x-cursor-client-version (#6795) * fix(cursor): use Agent CLI build id for x-cursor-client-version Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR); the author's .env.example/docs deltas were re-applied cleanly onto the release tip. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(changelog): re-sync CHANGELOG.md to release tip (restore #6701 bullet) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(startup): rename reasoningControls.ts to avoid webpack casing collision (#6584) (#6718) * fix(startup): rename reasoningControls.ts to avoid webpack casing collision (#6584) * chore(6718): re-sync onto release tip; CHANGELOG entry → changelog.d fragment (fragments-first) * fix(build): suppress Turbopack over-bundling warning from agentSkills generator (#6582) (#6720) * fix(build): suppress Turbopack over-bundling warning from agentSkills generator (#6582) generator.ts builds outputBase from a non-literal outputDir parameter, so Turbopack's file-tracing analyzer can't narrow it and emits an "Overly broad patterns" warning per entry point that imports the module (603 warnings on v3.8.46, up from 379). The fs access is legitimate and bounded, so next.config.mjs now suppresses this specific diagnostic via turbopack.ignoreIssue, mirroring the existing webpack.ignoreWarnings precedent in the same file. * chore(6720): re-sync onto release tip; CHANGELOG entry → changelog.d fragment (fragments-first) * fix(providers): drop image_generation for Codex Spark models regardless of plan (#6651) (#6721) * fix(providers): drop image_generation for Codex Spark models regardless of plan (#6651) * chore(6721): re-sync onto release tip; CHANGELOG entry → changelog.d fragment (fragments-first) * fix(providers): stop quota card re-sorting Codex/GLM bars by remaining % (#6687) (#6722) * fix(providers): stop quota card re-sorting Codex/GLM bars by remaining % (#6687) QuotaCardExpanded.tsx unconditionally re-sorted quotas by remaining percentage via sortQuotasByRemaining(), discarding the deterministic CODEX_QUOTA_ORDER/GLM_QUOTA_ORDER window order quotaParsing.ts's sortCodexOrder()/sortGlmOrder() had already established. A new hasFixedQuotaOrder() + resolveQuotaDisplayOrder() skip the re-sort for providers with a fixed window order (codex, glm family), threading providerId from QuotaCard.tsx through to the display layer. Regression guard: tests/unit/quota-card-expanded-fixed-order-6687.test.ts * chore(6722): re-sync onto release tip; CHANGELOG entry → changelog.d fragment (fragments-first) * fix(startup): lazy-import ioredis in rateLimiter to fix MCP ERR_MODULE_NOT_FOUND (#6559) (#6725) * fix(startup): lazy-import ioredis in rateLimiter to fix MCP ERR_MODULE_NOT_FOUND (#6559) * chore(6725): re-sync onto release tip; CHANGELOG entry → changelog.d fragment (fragments-first) * fix(resilience): resolve fp-pinned combo account back to real connection id (#6696) (#6732) * fix(resilience): resolve fp-pinned combo account back to real connection id (#6696) * chore(6732): re-sync onto release tip; CHANGELOG entry → changelog.d fragment (fragments-first) * fix(api): Responses passthrough emits event-only SSE frames after filtering commentary output (#6561) (#6735) * fix(api): Responses passthrough emits event-only SSE frames after filtering commentary output (#6561) The #6199 commentary-drop `continue;` branches in stream.ts skipped the data: line for a dropped commentary event but never cleared the already-buffered event: line for the same frame, so the next blank line flushed the stale event: line alone -- an event-only SSE frame that crashes the OpenAI Python SDK's json.loads(). Both drop sites now call clearPendingPassthroughEvent() before continue. The commentary-drop decision was extracted into a new responsesCommentaryDrop.ts module so the fix does not grow the frozen stream.ts. * chore(6735): re-sync onto release tip; CHANGELOG entry → changelog.d fragment (fragments-first) * fix(api): emit reasoning_content on claude-web + v0-vercel-web SSE (#6662) (#6743) * fix(api): emit reasoning_content on claude-web + v0-vercel-web /v1/chat/completions SSE (#6662) * chore(6743): re-sync onto release tip; CHANGELOG entry → changelog.d fragment (fragments-first) * fix(sse): unwrap bare {function:{…}} tools in openai→claude translation (#6704) * fix(sse): unwrap bare {function:{…}} tools in openai→claude translation Some OpenAI-shape clients send a tool as a bare `{ function: {...} }` object, omitting the spec-required `type: "function"` parent wrapper. The tools-mapping in openai-to-claude.ts (~line 366) only unwrapped `tool.function` when `tool.type === "function"` was ALSO true, so a bare-function tool fell through to `toolData = tool` (the wrapper itself, with no `.name`), producing an empty `originalName` and silently dropping the tool from the translated request — worse than a 400, since the caller has no signal the tool never made it upstream. Unwrap `tool.function` whenever present, independent of the parent `type` field. Regression guard: tests/unit/openai-to-claude-bare-tool.test.ts. Co-authored-by: Samir Abis <me@samirabis.com> Inspired-by: https://github.com/decolua/9router/pull/2473 * chore(6704): re-sync onto release tip; CHANGELOG → changelog.d fragment (fragments-first) --------- Co-authored-by: Samir Abis <me@samirabis.com> * fix(oauth): avoid bare-email dedup of Codex OAuth logins (#6706) * fix(oauth): avoid bare-email dedup of Codex OAuth logins When an incoming Codex OAuth connection has no verifiable workspace/account id, do not merge it into an existing row on email match alone — that silently overwrote the other account's token pair. Require a matching chatgptUserId (a stable per-account JWT id) before merging; otherwise insert a distinct connection row. Co-authored-by: lucasjustinudin <34107354+lucasjustinudin@users.noreply.github.com> Inspired-by: https://github.com/decolua/9router/pull/2477 * chore(6706): re-sync onto release tip; CHANGELOG → changelog.d fragment (fragments-first) --------- Co-authored-by: lucasjustinudin <34107354+lucasjustinudin@users.noreply.github.com> * fix(sse): skip thinkingConfig for gemma models in openai→gemini translation (#6708) open-sse/translator/request/claude-to-gemini.ts already guards against sending thinkingConfig for gemma-4-* models (Gemma doesn't support it — Vertex returns 400: "Thinking budget is not supported for this model"), but the OpenAI-shape path (openai-to-gemini.ts) lacked the same guard, so OpenAI-shape clients hitting a vertex gemma-4-* model still got a 400. Mirrors the existing claude-to-gemini.ts guard: wrap the reasoning_effort and Claude-shape thinking.budget_tokens branches with a model.startsWith ("gemma-4") check. Branch 3 (default includeThoughts for modern Gemini models) already excludes non-"gemini" model ids and needed no change. Inspired-by: https://github.com/decolua/9router/pull/2480 Co-authored-by: chy1211 <31048289+chy1211@users.noreply.github.com> * fix(codex): surface capacity errors embedded in 200-OK SSE streams (#6710) * fix(codex): surface capacity errors embedded in 200-OK SSE streams Codex sometimes answers with HTTP 200 and a text/event-stream body whose payload carries a transient error mid-stream (e.g. "Selected model is at capacity...", server_is_overloaded, service_unavailable_error). Because the outer HTTP status was 200, this looked like a successful response to every caller — no retry, no circuit breaker, and no combo/account fallback ever engaged, so a healthy account sat idle while the request silently failed or truncated. Add peekCodexSseTransientError() to open-sse/executors/codex.ts: it peeks the first bytes of a text/event-stream Codex response, pattern-matches the known transient-error signatures, and converts a match into a real 503 Response via errorResponse() (Hard Rule #12 — sanitized, never raw upstream text). A 503 is already a recognized provider-failure status in accountFallback.ts, so combo routing and connection cooldown pick it up automatically. When no error signature is found, the peeked prefix is prepended back onto the remaining upstream body so the passthrough stays byte-identical to the unmodified response. Regression guard: tests/unit/codex-sse-capacity-fallback.test.ts — a model-at-capacity payload and a server_is_overloaded/service_unavailable_error payload both convert to 503; a normal single-chunk SSE stream and one split across multiple network chunks both reassemble byte-for-byte unchanged. Inspired-by: https://github.com/decolua/9router/pull/2452 (sub-bug #3 only — OmniRoute already covers PR #2452's other two sub-bugs: service_tier "fast" normalization and reasoning_effort "max" normalization). Co-authored-by: ryanngit <74137224+ryanngit@users.noreply.github.com> * chore(6710): re-sync onto release tip; CHANGELOG → changelog.d fragment (fragments-first) --------- Co-authored-by: ryanngit <74137224+ryanngit@users.noreply.github.com> * fix(volcengine): clamp Kimi max_tokens to Ark endpoint cap (#6712) * fix(volcengine): clamp Kimi max_tokens to Ark endpoint cap VolcEngine Ark's Kimi coding-plan endpoint (ark.cn-beijing.volces.com) enforces max_tokens <= 32768 server-side and returns 400 "integer above maximum value, expected a value <= 32768" for anything over that ceiling. OmniRoute's StripRule only supported dropping params outright, with no numeric clamp mechanism, so a client sending a larger max_tokens (common default, e.g. 65536) 400s outright against volcengine's kimi-k2-5-260127. The 32768 cap is independently confirmed against two live-endpoint bug reports hitting this exact Ark endpoint for both kimi-k2.5 and kimi-k2.7-code (NousResearch/hermes-agent#51773, MoonshotAI/kimi-cli#1124), not just upstream's own value — same cap upstream 9router#2460 uses. StripRule gains two optional fields: `clampToModelMaxOutput` (clamp to the model's own catalog maxOutputTokens ceiling, when set) and `maxOutputCap` (a fixed endpoint-imposed ceiling); when both apply, the lower wins. The new rule is scoped to the literal id `kimi-k2-5-260127` (OmniRoute's real volcengine Kimi model, not upstream's `Kimi-K2.7-Code`), not a broad /kimi/i regex, so it can never clamp an unrelated future Kimi listing whose Ark cap may differ. glm-4-7-251222 (the other volcengine model) is unaffected. Inspired-by: https://github.com/decolua/9router/pull/2460 Co-authored-by: whale9820 <whale9820@users.noreply.github.com> * chore(6712): re-sync onto release tip; CHANGELOG → changelog.d fragment (fragments-first) --------- Co-authored-by: whale9820 <whale9820@users.noreply.github.com> * fix(antigravity): surface aborted Gemini tool calls off end_turn (#6713) * fix(antigravity): surface aborted Gemini tool calls off end_turn Gemini/Antigravity aborts a turn with finishReason MALFORMED_FUNCTION_CALL (or a sibling like UNEXPECTED_TOOL_CALL) instead of completing cleanly. Both Claude-facing translators collapsed these to a clean end_turn, hiding the aborted tool call as a successful completion: - the OpenAI hub path (openai-to-claude.ts convertFinishReason default), and - the DIRECT Gemini->Claude path (gemini-to-claude.ts), which is the one Claude Code actually hits through an antigravity/Gemini-routed model. Add isAbortFinishReason() to finishReason.ts and map these reasons to tool_use on both paths; genuinely unknown reasons still fall back to end_turn. Co-authored-by: anhdiepmmk <n08ni.dieppn@gmail.com> Inspired-by: https://github.com/decolua/9router/pull/2462 * chore(6713): re-sync onto release tip; CHANGELOG → changelog.d fragment (fragments-first) --------- Co-authored-by: anhdiepmmk <n08ni.dieppn@gmail.com> * fix(translator): strip empty cloud_base_branch from Cursor Subagent tool call (#6729) * fix(translator): strip empty cloud_base_branch from Cursor Subagent tool call (port from 9router#2446) The Responses->Chat tool-arg cleanup (stripEmptyOptionalToolArgs) only stripped empty-string/empty-array optional args for Claude Code's Read tool. Cursor's local Subagent tool call therefore passed through with the cloud-only field cloud_base_branch: "", which Cursor rejects ("cloud_base_branch may only be specified when environment equals cloud") before starting the subagent. Extend the cleanup to an allowlist of Read + Subagent; arbitrary tools stay untouched. Reported-by: like3213934360-lab (https://github.com/decolua/9router/issues/2446) * chore(6729): re-sync onto release tip; CHANGELOG → changelog.d fragment (fragments-first) * fix(translator): defer content_block_start until GLM streams the tool name (#6730) * fix(translator): defer content_block_start until GLM streams the tool name (port from 9router#2077) GLM 5.2 (and similar OpenAI-compatible upstreams) stream a tool call's id and function.name across separate SSE delta chunks. The openai-to-claude streaming translator emitted content_block_start immediately on the id-only chunk with an empty name; the Claude SSE protocol cannot patch a block after emission, so the later name-only chunk was dropped and Claude Code rejected the tool_use with an empty tool name / "No such tool available:". Defer content_block_start until the name arrives (start on args if they arrive first), and emit a start for any orphaned id-only tool call at finish so content_block_stop is never orphaned. Reported-by: itiwant (https://github.com/decolua/9router/issues/2077) * chore(6730): re-sync onto release tip; CHANGELOG → changelog.d fragment (fragments-first) * feat(dashboard): add search to Playground model picker dropdown (#4086) (#6811) * feat(dashboard): add search to Playground model picker dropdown (#4086) The shared ModelSelectModal (combo builder + CLI-code cards) already had search, but the Playground's raw model <select> in StudioConfigPane stayed a flat unsearchable list - unusable once a provider like OpenRouter contributed 50+ models. Adds a search input above the dropdown that filters options via filterModelsByQuery() (Turkish-safe accent/case-insensitive match, reusing matchesSearch()). The currently selected model always stays pinned in the list even when it doesn't match the query, so typing never silently swaps the active selection. Reuses the existing common.search i18n key already translated in all 42 locales - no new key needed. * chore(6811): re-sync onto release tip; CHANGELOG → changelog.d fragment (fragments-first) * feat: request count log per provider, per date (#4009) (#6812) * feat(dashboard): request count log per provider, per date (#4009) Some providers bill by request rather than by token, so operators need a plain per-provider, per-date request count breakdown, not just token aggregates. Adds a new getProviderDailyUsageRows() aggregation query (src/lib/db/usageAnalytics.ts), a dedicated GET /api/usage/requests-by-provider-date route (kept separate from the frozen /api/usage/analytics route to respect the file-size baseline), and a sortable, single-date-filterable table on Dashboard -> Analytics. Closes #4009 * chore(6812): re-sync onto release tip; CHANGELOG → changelog.d fragment (fragments-first) * feat(xai): route xAI clients to Grok native /v1/responses endpoint (#6709) * feat(xai): route xAI clients to Grok native /v1/responses endpoint xAI ships a native /v1/responses endpoint (https://api.x.ai/v1/responses) alongside /v1/chat/completions, but XaiExecutor extended BaseExecutor without overriding buildUrl(), so every request always resolved to the static chat-completions baseUrl regardless of target format — the last genuinely-missing slice of decolua/9router#2439 (grok-build-0.1, the reasoning-effort suffix routing, and bare grok-* routing were already ported in prior cycles). Add responsesBaseUrl to the xai registry entry and tag grok-4.20-multi-agent-0309 (upstream's own Responses-only id) with targetFormat: "openai-responses", mirroring the existing model-tag-driven routing pattern already used by the gh executor (9router#102) and the "openai" -pro heuristic in open-sse/executors/default.ts — the per-model registry tag is the single source of truth that also drives chatCore's body translation, so URL and body stay in lockstep. XaiExecutor.buildUrl now checks getModelTargetFormat("xai", model) and resolves to the native Responses endpoint only for tagged models, leaving every other grok-* model on the existing chat-completions bridge. TDD: tests/unit/executor-xai.test.ts adds a RED-then-GREEN case asserting grok-4.20-multi-agent-0309 resolves to https://api.x.ai/v1/responses and a control case asserting grok-4.3 still resolves to https://api.x.ai/v1/chat/completions. Co-authored-by: ryanngit <74137224+ryanngit@users.noreply.github.com> Inspired-by: https://github.com/decolua/9router/pull/2439 * chore(6709): re-sync onto release tip; CHANGELOG → changelog.d fragment (fragments-first) --------- Co-authored-by: ryanngit <74137224+ryanngit@users.noreply.github.com> * fix(resilience): route remaining credential-selection call sites through quota preflight (#6686) (#6742) * fix(resilience): route remaining credential-selection call sites through quota preflight (#6686) * chore(6742): re-sync onto release tip; CHANGELOG entry → changelog.d fragment (fragments-first) * fix(resilience): apikey-provider 429s honor explicit quota-exhausted text (#6638) (#6731) * fix(resilience): apikey-provider 429s honor explicit quota-exhausted text (#6638) Ollama Cloud (and any other apikey-category provider) 429s skipped body-text quota classification entirely; a genuine multi-day quota exhaustion was misclassified as a plain rate_limit_exceeded with a few seconds of cooldown, so combo routing retried the account immediately. shouldPreserveQuotaSignals() now lets an explicit quota-exhausted signal (looksLikeQuotaExhausted) override the apikey-category default, and parseDayGranularityResetMs() adds day- granularity reset-hint parsing ("...reset in 3 days.") alongside the existing Xh/Ym/Zs parsing. Regression guard: tests/unit/issue-6638-ollama-quota.test.ts (RED before the fix, GREEN after). Aligned two tests/unit/account-fallback-service.test.ts cases that had codified the old buggy behavior for apikey-provider quota text. * chore(6731): re-sync onto release tip; CHANGELOG entry → changelog.d fragment (fragments-first) * feat(resilience): weekly-429 cooldown for fetcher-less providers (#3709) (#6817) * feat(resilience): weekly-429 cooldown for fetcher-less providers (#3709) Ollama Cloud free-tier accounts have a hard WEEKLY request cap. On cap the upstream returns 429 "you (<account>) have reached your weekly usage limit", but ollama-cloud is an apikey-category provider, so the existing oauth-only shouldUseQuotaSignal gate in checkFallbackError skips the subscription-quota-text classifier (Issue #2321) for its 429s -- the account fell through to the generic exponential backoff (~1s, capped at 2min) and got retried every few minutes for the rest of the week (one account took 285x429 in 48h). Adds a new, ungated weekly-usage-limit text classifier that applies a 24h QUOTA_EXHAUSTED cooldown regardless of provider category. Extracted the new classifier -- together with the existing #2321 subscription-quota logic -- into a new open-sse/services/quotaTextCooldowns.ts module so the frozen accountFallback.ts (file-size-baseline cap) didn't have to grow; net effect shrinks accountFallback.ts by 20 lines. This is Phase A of the plan (open-sse/services/accountFallback.ts:1038-1045 "weekly-429 cooldown"); Phase B (generic local request-counter preflight for manual provider_plans dimensions) is a separate, larger follow-up per the plan's own phasing. * chore(6817): re-sync onto release tip; CHANGELOG entry → changelog.d fragment (fragments-first) * fix(providers): Kiro adaptive-thinking allowlist excludes sonnet-4.5/haiku-4.5 (#6576) (#6726) * fix(providers): Kiro adaptive-thinking allowlist excludes sonnet-4.5/haiku-4.5 (#6576) * chore(6726): re-sync onto release tip; CHANGELOG entry → changelog.d fragment (fragments-first) * test(kiro): migrate selector-strip test to claude-sonnet-5 (only Kiro adaptive-thinking model, #6576) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(quality): rebaseline complexity 2053->2054 (merge-burst drift, v3.8.47) Inherited drift from today's /implement-prs merge burst (~36 PRs). check:complexity does not run on the PR->release fast-path, so the branch accrued +1 unmeasured. No orphan/feature PR introduces a NEW violation (complexity-net-zero); the only flagged function is the pre-existing getResolvedModelCapabilities. Owner-approved rebaseline to unblock the FQG of ~7 green-except-complexity orphans. * chore(stryker): register ollama-quota covering tests (merge-burst drift, v3.8.47) The 3 covering unit tests from #6731/#6817/#6742 (issue-6638-ollama-quota, ollama-cloud-weekly-quota-cooldown-3709, issue-6686-quota-preflight-coverage) exist on release but were never added to tap.testFiles when those PRs merged. Completes the registration so mutant kills count; unblocks every PR touching a mutated module. Part of the owner-approved merge-burst drift cleanup. * fix: auto-start WS server in-process and change default port to 20132 (#6072) * feat: change default LIVE_WS_PORT from 20129 to 20132 Update the default WebSocket port for the live dashboard server from 20129 to 20132 across all configuration files, documentation, code comments, and tests. Also consolidate OMNIROUTE_DISABLE_LIVE_WS and OMNIROUTE_ENABLE_LIVE_WS into a single OMNIROUTE_ENABLE_LIVE_WS flag. Wire the live WebSocket server to start in-process via instrumentation-node.ts. * feat: clarify NEXT_PUBLIC_LIVE_WS_PUBLIC_URL path usage and derive upgrade path from URL Update .env.example and ENVIRONMENT.md to document that the pathname portion of NEXT_PUBLIC_LIVE_WS_PUBLIC_URL (e.g. /live-ws) is used as the WebSocket upgrade path by the dev proxy, handshake response, and client connection logic. Extract deriveLiveWsPath() into shared/utils/wsPath.ts and wire it through: - src/app/api/v1/ws/route.ts — handshake response path field - src/hooks/useLiveDashboard.ts — build * fix: use the standard URL API to safely parse and update the effectiveWsUrl * build(docker): expose live WebSocket server port and configure CORS origins Add LIVE_WS_PORT (20132), LIVE_WS_HOST (0.0.0.0), and LIVE_WS_ALLOWED_ORIGINS environment variables to all Docker Compose profiles and expose the WebSocket port mapping. Prevent infinite self-loop in standalone-server-ws.mjs by skipping proxy when the server itself is running on the LiveWS port. * docs(env): fix comment formatting for HOST and HOSTNAME variables --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(logs): prevent stale detail refresh reopening modal (#6323) * fix(logs): prevent stale detail refresh reopening modal * chore(stryker): register ollama-quota covering tests (release drift from merge burst) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * \ feat: operator-configurable account rotation\ (#6763) * feat(resilience): operator-configurable account rotation Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR); the author's accountFallback/.env deltas were re-applied cleanly onto the release tip. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * docs(env): document configurable account-rotation env vars in ENVIRONMENT.md Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(rotation): extract rotation gate/context helpers to keep accountFallback.ts under frozen cap Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(changelog): re-sync CHANGELOG.md to release tip (restore lost base bullet) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * test(stryker): register rotation-config test in tap.testFiles for mutation coverage Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * test(stryker): register ollama-quota covering tests (drift from #6731/#6817/#6742) + re-sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> * fix(lmarena): modernize Arena web provider + static Direct-chat catalog (#6280) * fix(lmarena): modernize Arena web provider + static Direct-chat catalog Update the lmarena provider for arena.ai (product rebranded from LMArena): - Route chat via arena.ai create-evaluation with Chrome TLS impersonation (tls-client-node) and optional browser-minted recaptchaV3Token. - Seed Text+Search (48) into the chat registry; seed Image (27) only into IMAGE_PROVIDERS. Disable live HTML model discovery; resolve public names to Arena UUIDs from the static TypeScript allowlist (no scrape JSON in-repo). - Soft-exclude 404/502 model ids; slow/stop bulk test-all probes for this provider. - Do not fold IMAGE_PROVIDERS/video specialty into the chat provider catalog when a chat registry already exists (lmarena/openai/xai). - Display name Arena (Free); keep wire id `lmarena` / alias `lma` for back-compat. - Theme-aware provider icons: arena-light.svg / arena-dark.svg. - Preserve split Supabase SSR cookie reconstruction for arena-auth-prod-v1.*. * fix(providers): align provider-models-route test fixture + regen provider reference Fold the topaz image-only catalog entry's apiFormat/supportedEndpoints into the local-catalog test fixture (route now tags media-only providers per the lmarena PR's staticModels.ts change), regenerate PROVIDER_REFERENCE.md against the merged release providers.ts, and add the changelog fragment for #6280. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * test: align web-cookie fallback suite — lmarena now has a registry entry (probe path) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * docs(changelog): reconcile 3-day merge burst — 16 fragments, 4 promised credits, contributors hall 32→63 - changelog.d fragments for the 20 merged PRs that landed without a bullet (#6072 #6308 #6323 #6538 #6556 #6586 #6611 #6647 #6675 #6698 #6757 #6759 #6804 #6821 + ci rollup #6781/#6691/#6693 + docs rollup #6643/#6644/#6646/#6663; omniglyph bump #6661 folded into the #6556 bullet) - deliver the 4 credits promised in close comments but never written: @alltomatos (#6819 dup of #6721), @samimozcan (#6762/#6753 subsumed by #6790), @chirag127 (#6756 dup of #6757), @Squawk7777 (#6565 dup of #6564 — appended to the existing #6564 bullet; changelog-integrity flags that edit as a removal, intentional: ALLOW_CHANGELOG_REMOVALS justification) - rebuild the v3.8.47 Contributors hall from merged-PR authors + thanks credits + prior hall: 32 → 63 contributors * Clamp reasoning token buffer to model output cap (#6714) * fix(combo): clamp reasoning buffer to model output cap * fix(routing): preserve near-cap reasoning max tokens * fix(routing): getExplicitModelOutputCap falls through to registry cap on non-numeric synced limit_output getExplicitModelOutputCap short-circuited to null whenever a synced capability row existed, even if that row's limit_output was not a number (models.dev commonly omits it). That silently disabled the reasoning-token buffer clamp for any model with a synced row lacking an output limit. Now only return the synced value when it IS a number; otherwise fall through to registryModel.maxOutputTokens / spec.maxOutputTokens, matching the ??-chain precedence already used by getResolvedModelCapabilities(). Adds a standalone regression test (proves the fallthrough returns the real registry cap, not null) and hardens the #6274 fixture id so its no-output-cap case does not prefix-match the real glm-5.2 static spec. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(stryker): register ollama-quota covering tests (release drift from merge burst) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * feat(i18n): add Traditional Chinese (zh-TW) localization for frontend and CLI (#6320) * feat(i18n): add Traditional Chinese (zh-TW) localization for frontend and CLI - Add src/i18n/messages/zh-TW.json translating frontend web UI - Add bin/cli/locales/zh-TW.json translating CLI commands and descriptors - Register zh-TW in config/i18n.json and docs/guides/I18N.md - Update scripts/i18n/generate-multilang.mjs matching the new locale setup * fix: update i18n locale count from 42 to 43 after adding zh-TW The docs strict checker (check-docs-counts-sync.mjs) validates that README.md and I18N.md reflect the real locale count. Adding zh-TW bumped the count from 42 → 43. * fix(i18n): translate providers free-filter labels in zh-TW (#6694 guard) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: lunkerchen <lunkerchen@users.noreply.github.com> * feat(proxy): implement latency-optimized proxy rotation strategy (#6798) * feat(proxy): implement latency-optimized proxy rotation strategy Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR) and the direct CHANGELOG.md edit (fragments-first); the author's env/docs/i18n deltas were re-applied cleanly onto the release tip. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(proxy): add latency-rotation env var to .env.example PROXY_LATENCY_WINDOW_HOURS was referenced in src/lib/db/proxies.ts and documented in docs/reference/ENVIRONMENT.md, but missing from .env.example, tripping the env/docs sync gate. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(changelog): re-sync CHANGELOG.md to release tip Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(proxy): extract latency-strategy helpers to keep frozen files under cap Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * test(db-rules): expect 35 audited modules (proxyLatency joins INTENTIONALLY_INTERNAL) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * docs(readme): fix stale strategy/tool/scoring counts (#6853) README still claimed 17 routing strategies (the table was missing pipeline), 95 MCP tools, and 9-factor Auto-Combo scoring. Align with the source (ROUTING_STRATEGY_VALUES has 18 entries) and the canonical docs (MCP-SERVER.md: 94 tools; AUTO-COMBO.md: 12-factor). * fix(antigravity): sanitize Cloud Code safety settings (#6839) Co-authored-by: kfiramar <83420275+kfiramar@users.noreply.github.com> * fix(kiro): probe IdC region during profileArn discovery, cross-region (recovers #6099) (#6840) * fix(kiro): route Amazon Q runtime by profileArn region for cross-region IdC Enterprise AWS IAM Identity Center accounts whose IdC instance lives outside the two Amazon Q Developer profile regions (us-east-1 / eu-central-1) - e.g. eu-north-1 (Stockholm), start URL https://d-XXXX.awsapps.com/start - showed no limits and returned 502 on every request. Root cause: the backend used the IdC/OIDC token region (providerSpecificData.region, e.g. eu-north-1) for every CodeWhisperer runtime call, hitting q.eu-north-1.amazonaws.com - a host that does not exist as a Q Developer runtime endpoint. Per AWS docs ("Supported Regions for the Q Developer console and Q Developer profile"), the Q Developer *profile* (which produces the profileArn and hosts generateAssistantResponse / GetUsageLimits / ListAvailableModels / ListAvailableProfiles) is only hosted in us-east-1 and eu-central-1, regardless of the IdC region; "data is stored in the Region where you create the Amazon Q Developer profile." Fix (new open-sse/services/kiroRegion.ts) decouples the two regions: - providerSpecificData.region stays the IdC/OIDC region, used ONLY for oidc.{region}.amazonaws.com token mint/refresh. - The runtime region is derived from the profileArn (resolveKiroRuntimeRegion): profileArn region -> a valid stored profile region -> us-east-1. A stored IdC region that is not a Q profile region (eu-north-1) is ignored for runtime. - Profile discovery (discoverKiroProfileArnAcrossRegions) probes the Q profile regions (EU IdC -> eu-central-1 first) with the cross-region SSO token instead of q.{idcRegion}. Wired into: executors/kiro.ts (generateAssistantResponse targets the profile region), services/usage/kiro.ts (getKiroUsage multi-region discovery + profileArn runtime region so Limits resolves), services/kiroModels.ts (ListAvailableModels), and src/lib/oauth/providers/kiro.ts (login-time postExchange profile discovery). Adds tests/unit/kiro-idc-cross-region.test.ts (15 cases). All Kiro suites pass (60 tests). * fix(kiro): probe the IdC region too during profileArn discovery (any IdC region) Make profile discovery general for an IdC in ANY of the ~30 IdC-supported AWS regions (us-west-2, ap-southeast-2, me-central-1, af-south-1, ...), not just eu-north-1. buildKiroProfileDiscoveryRegions now probes the two documented Q Developer profile regions FIRST (us-east-1 / eu-central-1, EU-first for EMEA IdC regions to cut latency), then appends the IdC/stored region itself as a forward-compatible fallback: if AWS ever co-locates the profile with the IdC or expands the profile-region list, a same-region probe still finds it. Probing a region with no profile simply returns nothing and we fall through. The profileArn's own region remains authoritative for every runtime call (resolveKiroRuntimeRegion), so a newly-issued ARN in any region is honored automatically. Adds ap-southeast-2 (APAC) cross-region coverage and updates the discovery-order tests. --------- Co-authored-by: artickc <artur1992123@mail.ru> * feat(providers): manual context-window override for custom models (#4125) (#6822) Add a manual per-model "Context Window Override" so an operator can correct a provider's misreported context length (e.g. reports 1M when the real limit is 128K) instead of the model getting silently dropped from combo routing once the wrong value lands in the catalog. Reuses the existing Feature-5004 model_context_overrides table (source="manual") — already the priority-0 source getModelContextLimit() (the function combo's context-window filter calls) reads ahead of the models.dev/registry/static catalog — so no new resolver logic was needed, only the missing write path: - PUT /api/provider-models now accepts an optional contextWindowOverride (number to set, null to clear), persisted via setModelContextOverride/ removeModelContextOverride. - GET /api/provider-models surfaces the current override value + source back on each custom-model row. - CustomModelsSection.tsx: edit form gained a Context Window Override input + a badge on the model row when an override is set. Regression guard: tests/unit/provider-models-context-window-override-4125.test.ts (manual override wins over a misreported catalog value, GET round-trip, clearing via null, default-unchanged behavior). * feat(dashboard): improve Provider Quota page horizontal density (#3520) (#6815) QuotaCardGrid stacked every provider group vertically in a single flex flex-col container, and each group's own card grid didn't go multi-column until the md breakpoint. Provider groups now flow into a 2-column CSS multi-column layout on very wide (2xl) screens instead of an unconditional vertical stack, and each group's card grid starts at 2 columns immediately, filling horizontal whitespace sooner on narrower-but-not-mobile viewports. Regression guard: tests/unit/quota-card-grid-horizontal-layout.test.ts * refactor(usage): type saveRequestUsage with UsageEntry interface + any-budget ratchet (#3512) (#6809) Replace saveRequestUsage(entry: any) with a typed UsageEntry interface mirroring the usage_history columns 1:1. Fields stay optional/nullable since different writers (chatCore success/failure, rejected-request accounting, Codex Responses WS) populate the row incrementally; tokens stays unknown since callers pass either raw provider-shaped usage or the normalized {input,output,cacheRead,...} shape. Also cleaned the file's other any usages (getUsageHistory filter, getUsageDb next-cursor cast, appendRequestLog tokens param, getRecentLogs catch) so it now sits at zero any and can be added to the check:any-budget:t11 zero-any allowlist. Documents the DB-entity <-> TS-interface convention in docs/architecture/CODEBASE_DOCUMENTATION.md Sec 11. * feat(combo): strict budget-cap fallback policy for auto/* combos (#3470) (#6816) Auto-combo transparency + budget controls: the engine's budgetCap enforcement always degraded to the globally cheapest candidate when every candidate exceeded the cap - silently overspending instead of respecting the cap. - engine.ts: budgetFallback "cheapest" (default, legacy) | "strict" (BudgetExceededError when no candidate fits budgetCap) - requestControls.ts: X-OmniRoute-Budget-Fallback header + resolveRequestAutoControls() consolidating mode/budget/fallback parsing - resolveAutoStrategy.ts / autoConfig.ts: thread combo-level config.budgetFallback and catch BudgetExceededError into an HTTP 402 - chat.ts: switch to the consolidated resolveRequestAutoControls() helper (net line reduction, stays under the frozen file-size baseline) Regression guard: tests/unit/auto-combo-budget-fallback-3470.test.ts * fix(usage): honor xAI provider-reported exact cost (#6711) OmniRoute's calculateCost() always estimated request cost from token counts x static pricing, discarding xAI's exact provider-reported cost when present. xAI's chat-completions usage object reports the precise billed cost via cost_in_usd_ticks (docs.x.ai/developers/cost-tracking and the API reference's usage schema: "TICKS_IN_USD_CENT: i64 = 100_000_000" => 1e10 ticks/USD, e.g. 37756000 ticks ~= $0.0038). calculateCost()/computeCostFromPricing() now short-circuit to this exact figure when present -- before any pricing DB lookup, so it also works for models without a local pricing row -- and still fall back to the token-based estimate when it is absent. The field is threaded through both the streaming (extractUsage/normalizeUsage) and non-streaming (extractUsageFromResponse) usage-extraction paths. Corrected divisor vs upstream: the upstream PR used /1e12 (a 100x under-report, e.g. reporting $0.00123 as the doc's $0.123 example); this port uses the doc-verified /1e10 instead, confirmed against both the cost-tracking guide and the API reference's usage-object schema. Inspired-by: https://github.com/decolua/9router/pull/2453 Co-authored-by: ryanngit <74137224+ryanngit@users.noreply.github.com> * docs: rename /implement-prs → /merge-prs in Hard Rule #21 (skill renamed 2026-07-11) (#6847) * docs: refresh stale llm.txt facts + relocate design.md to docs/architecture/DESIGN_SYSTEM.md (#6849) * docs: refresh stale llm.txt facts + move design.md to docs/architecture/DESIGN_SYSTEM.md llm.txt was frozen at the v3.8.8 era (177 providers, 37 MCP tools, 14 strategies, 9-factor scoring, 75% coverage gate). Update every factual claim to the current state (248 providers, 94 tools / 30 scopes, 18 strategies, 12-factor scoring, ratchet + 60% floor, TS 6, current docs/ layout) and re-sync the 42 exact-copy i18n mirrors. design.md at the root was a standardization plan whose phases 1-6 all shipped; rewrite its header as a permanent reference and relocate it to docs/architecture/DESIGN_SYSTEM.md per the root-hygiene policy (root = configs + canonical docs only). * docs: add MDX frontmatter to DESIGN_SYSTEM.md (in-app docs pipeline requires it) * feat: per-model web-search interception rule (#3384) (#6814) * feat(routing): per-model web-search interception rule (#3384) Adds a per-provider/per-model interceptSearch rule (src/lib/db/interceptionRules.ts, key_value namespace interception_rules) that overrides the existing native web-search bypass defaults (Codex/Gemini/Claude->Claude passthrough) in webSearchFallback.ts. Wired at the existing prepareWebSearchFallbackBody() call site in chatCore.ts. Resolution precedence: per-model rule > provider-level rule > existing native-bypass defaults. This lands Phase 1-2 of the plan (rule store + search interception). Web-fetch interception and the dashboard UI toggle are tracked as follow-up phases. * fix(db): register interceptionRules in localDb re-export layer (db-rules gate) * fix(db): renumber interception_rules migration 119→120 (collision with model_capability_overrides) * feat: sidebar search/filter input (#4013) (#6810) * feat(dashboard): add search/filter input to the dashboard sidebar (#4013) Adds a search box at the top of the expanded sidebar that filters nav sections/groups/items client-side by label, so users don't have to hunt through the growing nav tree. Reuses the existing common.search / common.noResults i18n keys (no new locale edits needed) and the shared Input icon="search" pattern. Matching sections auto-expand while searching and the accordion/pin state is restored once the query is cleared. Filtering logic is extracted into a pure filterSidebarSectionsByQuery() helper (src/shared/utils/sidebarSearch.ts) so it is trivially unit testable independent of React/next-intl/next-navigation. * fix(test): move Sidebar.search test to a runner-collected path (test-discovery gate) * fix(i18n): backfill 194 missing pt-BR keys (#6695) (#6723) * fix(i18n): backfill 194 missing pt-BR keys and add key-parity regression test (#6695) * Merge branch 'release/v3.8.47' into fix/6695-i18n-drift Resolve i18n key-parity and CHANGELOG-fragment conflicts: - Convert the #6695 CHANGELOG.md bullet to a changelog.d/ fragment (the fragment convention landed on release/v3.8.47 after this PR branched, per changelog.d/README.md). - Backfill 61 additional pt-BR keys that entered en.json on release/v3.8.47 after this PR's original 194-key backfill, so the PR's own key-parity regression test (tests/unit/i18n-pt-br.test.ts) stays green against the moving release baseline. * Discover live Codex models (#6776) * Add live model discovery for provider catalog * Fix model discovery request headers * fix(codex): sync live model limits with local catalog * test(codex): split live model discovery coverage into dedicated route tests * fix(codex): use chatgpt account id for live model sync * Add GitHub-backed Codex model discovery fallback * fix(providers): tighten oauth config tests and provider model display comments * test: align client version expectations with release default * fix(codex): keep discovery complexity within baseline * fix: rebase live Codex model discovery onto release/v3.8.47, preserving kimi-web buildHeaders (#6308) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * feat(codex): echo requested effort-suffixed model id in Responses payloads (#3697) (#6820) * feat(codex): echo requested effort-suffixed model id in Responses payloads (#3697) Codex CLI compatibility shim: the Responses API response.created/ response.in_progress/response.completed payloads now carry a `model` field (previously absent), and for Codex-CLI-originated requests it echoes the client-requested effort-suffixed model id (e.g. gpt-5.5-xhigh) instead of the bare upstream id (gpt-5.5), so the Codex CLI status line/model button shows the active reasoning effort. - openai-responses.ts translator threads the upstream model into the Responses event objects (additive, omitted when unknown). - New isCodexOriginatedHeaders() (codexIdentity.ts) reuses PR #3481's originator/User-Agent detection, header-based so it still fires when a combo routes codex/gpt-5.5-xhigh to a non-codex upstream. - chatCore's existing opt-in #1311 echoModel pipeline now also fires automatically for Codex clients on the Responses API, regardless of the echoRequestedModelName setting. - responseModelEcho.ts now also rewrites the nested response.model field the Responses API uses (previously only top-level model). - /v1/models keeps returning models: [] for Codex (unchanged, #3481). Regression guard: tests/unit/codex-effort-model-echo-3697.test.ts. Closes #3697 * chore(merge): re-sync with release/v3.8.47 (restore CHANGELOG, keep own bullet) * chore(merge): re-sync with release/v3.8.47; move changelog bullet to changelog.d fragment (merge-storm proof) * feat(usage): surface Antigravity weekly quota alongside the 5-hour window (#4017) (#6818) * feat(usage): surface Antigravity weekly quota alongside the 5-hour window (#4017) Antigravity enforces both a 5-hour and a weekly usage limit, but the agy/antigravity quota widget only exposed the 5-hour window. The weekly limit isn't in the per-model retrieveUserQuota response already fetched — it lives in a separate, undocumented retrieveUserQuotaSummary RPC that groups models into families (Gemini Models, Claude and GPT models) with one weekly bucket per family. Adds a self-contained usage/antigravityWeeklyQuota.ts leaf: a cached, best-effort fetch of that RPC + a pure parser that extracts the weekly-labeled bucket per group (window inferred from bucketId/displayName text, matching the reverse-engineered shape documented by third-party Antigravity clients) into gemini_weekly/ claude_gpt_weekly quota entries, merged into the existing quotas map the widget already renders generically. A failed/unavailable RPC never affects the existing per-model quotas. Live VPS validation attempt (192.168.0.15, real antigravity account): both retrieveUserQuota and retrieveUserQuotaSummary currently return 429 RESOURCE_EXHAUSTED for that account, so the live response shape could not be captured directly. The parser was instead validated via TDD against the bucket shape documented by CodexBar (steipete/CodexBar), a third-party Antigravity client that reverse-engineered the same RPC, and is defensive against both response envelopes it has observed (top-level groups[] and nested quotaSummary.groups[]). * chore(merge): re-sync with release/v3.8.47 (restore CHANGELOG, keep own bullet) * chore(merge): re-sync with release/v3.8.47; move changelog bullet to changelog.d fragment (merge-storm proof) * feat: add Z.ai Web free web-cookie provider (#4056) (#6823) * feat(providers): add Z.ai Web free web-cookie provider (#4056) New zai-web web-session provider drives the free chat.z.ai consumer chat UI via a pasted browser cookie, distinct from the existing API-key zai/glm/glm-cn/glmt providers (api.z.ai). ZaiWebExecutor posts to chat.z.ai/api/chat/completions with the cookie forwarded both as Cookie and Authorization: Bearer <token>, and normalizes both z.ai's internal delta_content/phase SSE envelope and a pass-through OpenAI-shaped choices[].delta frame into standard chat-completion chunks. Registered in WEB_COOKIE_PROVIDERS, WEB_SESSION_CREDENTIAL_REQUIREMENTS, the provider registry (GLM-4.6/4.5/4.5V models), the executor factory, and tokenExtractionConfig.ts for in-app cookie capture. * fix(providers): regenerate translate-path golden for zai-web + reduce cognitive complexity * fix(providers): rename ZaiWebExecutor.buildHeaders to avoid incompatible BaseExecutor override * chore(merge): re-sync with release/v3.8.47; move changelog bullet to changelog.d fragment (merge-storm proof) * fix(codex): bump default client version to 0.144.0 (#6780) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(usage): extract per-group parsing in antigravityWeeklyQuota (cognitive-complexity gate 886→885, release-level drift from #6818 merge) * ci(quality): cut PR gate wall time without dropping protection (#6716) Collapse duplicate CI spend while keeping each gate's existence reason: - quality.yml: TIA __RUN_ALL__ defers full unit to fast-unit 4-shard (#6781); path filters via classify-pr-changes; docs-gates split; draft skip - ci.yml: wire docs/i18n/code path filters; ESLint JSON artifact for quality-gate; drop advisory typecheck:noimplicit; float actions/cache@v6 - TIA parity: memory/usage/combo/serial; **/*.test.mjs any depth; electron/bin no longer force unit __RUN_ALL__ - check:complexity-ratchets: one ESLint walk, ruleId-isolated baselines + cache - check:api-docs-refs + lib/apiRoutes: shared API route inventory - husky pre-push: intentionally light (gates live in pre-commit); CLAUDE.md + QUALITY_GATES.md docs synced - collect-metrics / lint:json: path.resolve cache path; Windows-safe eslint bin - env-doc allowlist for ESLINT_RESULTS_JSON / COMPLEXITY_ESLINT_REPORT - release-green --full-ci expects check:api-docs-refs (not docs-symbols alone) Tests: select-impacted, classify-pr-changes, api-routes lib, complexity-rule-count, validate-release-green. Reconciled after #6781 (fast-unit 2→4 shards) per maintainer request on #6716. Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> * fix(docs): document Turbopack build memory tradeoff for RAM-constrained machines (#6409) (#6885) * fix(routing): recognize Kimi token-limit 400 as context overflow for combo fallback (#6637) (#6893) combo.ts's isContextOverflow400() guard required the literal word 'context' in the 400 error body before letting a combo fall through to the next target. Kimi's exact wording ('Your request exceeded model token limit: 262144 (requested: 308458)') never says 'context', so the guard misclassified it as a body-specific error and halted the whole combo instead of trying the next (larger-context) target. accountFallback.ts's CONTEXT_OVERFLOW_PATTERNS already recognized this wording one layer below (via checkFallbackError -> shouldFallback), so the two independently-maintained classifiers disagreed and the stricter one won. Export CONTEXT_OVERFLOW_PATTERNS from accountFallback.ts and reuse it inside combo.ts's isContextOverflow400() so both layers share a single source of truth. Regression test: tests/unit/repro-6637-kimi-token-limit.test.ts (RED on unfixed code -> GREEN after the fix). Existing #4519 guard tests (tests/unit/combo-param-validation-fallback-4519.test.ts) still pass, including the negative case that a genuinely body-specific 400 is NOT misclassified as overflow. * fix(providers): honor a provider-level proxy assigned to no-auth providers (#6272) (#6895) No-auth providers (mimocode, opencode, ...) are always dispatched with a single hardcoded connectionId ("noauth" — SYNTHETIC_NOAUTH_CONNECTION_ID in src/sse/services/auth.ts). No provider_connections row ever has id="noauth", so resolveProxyForConnection() in src/lib/db/settings.ts could never populate connectionRecord for them, and its provider-level proxy lookup (Steps 6/8) only runs when connectionRecord is present. A proxy assigned via Settings -> Providers -> mimocode was therefore silently ignored, reproducing the reporter's "same thing happen when i set the proxy directly in the provider menu" symptom. Adds a best-effort fallback (src/lib/db/settings/noAuthProxyFallback.ts): when connectionRecord could not be resolved, scan the known no-auth provider ids for a configured provider-level proxy (registry first, then legacy) before falling through to the global/direct steps. Regression test: tests/unit/proxy-noauth-provider-6272.test.ts (RED on unfixed code — resolved to level=direct/proxy=null; GREEN after the fix). * fix(dashboard): surface Claude extraUsage credits in quota card (#6806) (#6896) Enterprise-tier Claude accounts (default_raven_enterprise) don't get five_hour/seven_day utilization windows from Anthropic's OAuth usage endpoint — only an extra_usage credit-billing block. parseClaude() only read data.quotas, so quotas stayed {} and the dashboard showed "No quota data" even when extraUsage showed the account 100% exhausted. parseClaude() now folds an enabled extraUsage block into a credits-style quota row (mirroring parseCodex's bankedResetCredits pattern), both when quotas is empty and when it's already populated. * fix(db): share sql.js preinit across callers, fix named-param bind (#6628, #6802) (#6899) - preInitSqlJs() now memoizes an in-flight Promise (not just the resolved adapter) per filePath, so concurrent BATCH/STARTUP/HealthCheck/ ProviderLimitsSync callers at boot share one full-file read+WASM decode instead of each independently reloading the whole database — the thundering-herd amplifier of the OOM condition #6632 already partly fixed, left un-implemented by the reporter's own proposed fix (#6628). - sqljsAdapter's run/get/all now unwrap a lone named-parameter object (e.g. .all({ isActive: 1 }) for "WHERE is_active = @isActive", the same call shape getProviderConnections() already uses against better-sqlite3) before calling sql.js's stmt.bind(), expanding it to the @/:/$ sigil variants sql.js's own named-bind path requires. Previously the object was wrapped into an array and sql.js took the positional-bind path, throwing "Wrong API use : tried to bind a value of an unknown type ([object Object])." whenever the sql.js WASM fallback driver was active — exactly the error #6802 reported (misattributed to better-sqlite3). Regression tests added to tests/unit/db-adapters/driverFactory.test.ts and tests/unit/db-adapters/sqljsAdapter.test.ts, both proven RED against the prior code and GREEN after the fix. * fix(plugin): split OC-gate provider id from OmniRoute-facing routing id (#6859) (#6900) resolveOmniRoutePluginOptions() auto-prefixes providerId with "opencode-" (commit75b52e286) so OpenCode 1.17.8+'s native-adapter gate accepts it as a registered provider id. That prefixed value was being reused for the OmniRoute-server-facing identifiers too: mapRawModelToModelV2's id/providerID, mapComboToModelV2's providerID, and the dynamic provider hook's combo catalog keys. OmniRoute's server has no "opencode-<x>" provider alias, so every dispatched model failed credential lookup with "No credentials for opencode-omniroute" / "No active credentials for provider: opencode-omniroute". Add a second, unprefixed omnirouteProviderId field and thread it through the four dynamic-hook call sites that emit server-facing identifiers, while leaving the OC-gate-prefixed providerId in place for AuthHook.provider, provider registration (hook.id), and the static-catalog path (which OC strips before dispatch, per the existing static-block comments). * fix(compression): surface silently-dropped stacked-pipeline steps and fix inflation-guard no-op misfire (#6479, #6480, #6491) (#6901) Two related root causes in the stacked compression pipeline: - #6479/#6491: a dispatched step whose engine legitimately finds nothing eligible (session-dedup with no repeated blocks, ccr below its min-chars threshold) returns `{ stats: null }`. `mergeStackStep()` silently dropped that step from `engineBreakdown` with zero trace — no warning, no error. Now records a `"<engine>: skipped (no eligible content)"` validation warning for any null-stats step, covering every engine that follows this convention (session-dedup, ccr, headroom, relevance, llm, llmlingua, ionizer, readLifecycle), not just the two reported. - #6480: `finalizeStackedResult` ran the aggregate `guardPipelineInflation` check unconditionally, even when the loop-level `compressed` flag stayed false (no step ever advanced `currentBody`). Since tokens are trivially equal when nothing ran, the guard mislabeled a genuine no-op as `fallbackApplied: true` with a misleading "reverted to original" warning. Extracted the guard into `applyStackedInflationGuard()` in `pipelineGuards.ts` (keeps `strategySelector.ts` under its frozen line budget) and gated it on `compressed === true`. Also fixes `compression-pipeline-inflation-guard.test.ts`'s wire test, which passed a bare engine-id string to the pipeline; `normalizePipelineStep()` only recognizes a fixed set of built-in string aliases and silently downgrades any other string to `{ engine: "caveman" }`, so the test's custom inflating engine was never actually exercised. Passing a step object restores the test's original intent. New regression tests: tests/unit/compression/repro-6479-6491-null-stats-silent-drop.test.ts, tests/unit/compression/repro-6480-noop-guard-misfire.test.ts. * fix(mcp): de-duplicate TOTAL_MCP_TOOL_COUNT by tool name (#6854) (#6902) TOTAL_MCP_TOOL_COUNT in open-sse/mcp-server/server.ts summed collection sizes additively, double-counting tools registered in more than one collection. The agent-skills trio (omniroute_agent_skills_list/get/coverage) is intentionally defined in both MCP_TOOLS (schemas/tools.ts) and agentSkillTools (tools/agentSkillTools.ts), inflating the reported count from 96 unique tools to 99. Replace the additive sum with countUniqueMcpTools() (new open-sse/mcp-server/toolCount.ts), which unions all collection tool names into a Set before counting, so any future overlap self-corrects instead of double-counting. Regression test: tests/unit/mcp-tool-count-dedup-6854.test.ts * fix(sse): apply cliproxyapiModelMapping at CLIProxyAPI dispatch time (#6876) (#6903) * fix(providers): honor max_token capability override in reasoning buffer clamp (#6524) (#6904) getExplicitModelOutputCap() (the clamp ceiling used by resolveReasoningBufferedMaxTokens) only ever read the unvalidated synced limit_output / registry / static-spec chain — it ignored the operator-settable max_token capability override that getResolvedModelCapabilities() already consulted. When a provider's synced catalog row reports a wrong limit_output (e.g. ollama-cloud/deepseek-v4-flash: limit_output=1048576, same as limit_context, while the real upstream cap is 65536), the reasoning-buffer clamp trusted the bad number and inflated max_tokens 64000 -> 96000, which upstream rejected with "exceeds model's maximum output tokens (65536)". The override table (model_capability_overrides, "max_token" key, /api/model-capability-overrides) is the existing, already-shipped remediation path for exactly this class of bad catalog data, but reasoningTokenBuffer.ts had no way to benefit from it. Extracted the override lookup into a shared getMaxTokenCapabilityOverride() helper and made getExplicitModelOutputCap() consult it first, so both read paths now agree. * fix(api): merge id-only tool_call continuation deltas in stream summary (#6276) (#6905) * fix(dashboard): logs detail modal no longer reopens on first close (#6830) LogsPage recomputed initialId from window.location on every render, but the App Router syncs window.location only after the navigation commits. Closing the detail modal re-rendered the page while the URL still carried ?id=X, so initialSelectedId flipped null -> X and the child's one-shot deep-link effect (guard still unarmed after the open-click render, where location was stale in the other direction) reopened the modal. Only the second close worked. Read the id once via lazy useState so the prop stays stable for the page's lifetime; deep links still open the modal on mount. Regression test reproduces the App Router ordering with a router.replace mock that re-renders the page before committing the URL. * fix(fusion): select judge from a surviving panel member when no explicit judge (#6869) When no explicit judgeModel is configured, the judge defaulted to panel[0] before fan-out and was never reassigned. If panel[0] failed fan-out (timeout / rate-limit / dropped straggler → it lands in `failures`, not `answers`), the multi-answer synthesis path still dispatched the judge to that dead panel[0], erroring the whole fusion request even though a quorum of other panel members succeeded — exactly the failure fusion exists to tolerate. Resolve the effective synthesis judge from a survivor when no explicit judge is set: prefer panel[0] only when it survived, otherwise the first surviving answer. An explicitly configured judge is still honored unchanged (operator intent), and the answers.length===0 (503) and single-survivor branches keep their existing semantics. Co-authored-by: Chirag Singhal <chirag127@users.noreply.github.com> * fix(api): return 400 (not 500) on malformed JSON body (#6871) Co-authored-by: Chirag Singhal <chirag127@users.noreply.github.com> * chore(ci): fix shared base-reds blocking PR queue (stryker registration + codex 0.144 test) - register tests/unit/cliproxyapi-model-mapping-dispatch.test.ts in stryker.conf.json tap.testFiles (gap from #6903) - update provider-models-route-codex.test.ts client_version 0.142.0 -> 0.144.0 (stale test from #6780 prod bump) * chore(quality): rebaseline cognitiveComplexity 885->890 (v3.8.47 merge-train burst) Owner-approved merge-burst reconciliation. cognitive-complexity does not run on PR->release fast-gates, so incidental growth across the 23-PR merge-ready batch accrued unmeasured (measured 890 on the combined merge-train tip vs 885 pristine). * chore(deps): bump github/codeql-action/analyze from 4.36.3 to 4.37.0 (#6831) * fix(electron): bump electron 42→43 + build better-sqlite3 from source (ABI 148) (#6605) fix(electron): bump electron 42→43 + rebuild better-sqlite3 from source against the Electron ABI (148). Electron 43 raises NODE_MODULE_VERSION to 148; better-sqlite3@12.11.1 has no electron-v148 prebuild, so the packaged app died with 'Nenhum driver SQLite disponível'. prepare-electron-standalone now compiles better-sqlite3 from source against the electron headers into build/Release (where 'bindings' resolves it). Validated by Electron Package Smoke (green) + local (node_register_module_v148). Supersedes #6378. (--admin: the only reds are SonarQube/SonarCloud failing on a coverage-report artifact digest-mismatch — a GitHub Actions infra flake, not this diff; Sonar is green on main and the diff touches only the electron build.) * deps: bump the development group across 1 directory with 6 updates (#6588) deps: bump the development group (6 updates). Rebased onto current main; all checks green after the electron-smoke fix (#6605). * fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump (#6620) fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump. undici 8.6+ changed ProxyAgent to forward plain-HTTP via request-proxy instead of CONNECT, breaking OAuth refresh through a connection proxy (501). proxyDispatcher now passes proxyTunnel:true. Validated: Unit Tests 3/8 (the OAuth-proxy test) green, new regression test green (fails without the fix on undici 8.7), SonarQube green. Supersedes #6380. (--admin: the only red is Electron Package Smoke failing on a next-build artifact 'digest-mismatch' — a GitHub Actions infra flake corrupting the asar ('file data stream has unexpected number of bytes'); the better-sqlite3 rebuild itself succeeded (gyp ok) and the electron path is unchanged from #6605 which passed the smoke. Not this diff.) * chore(deps): bump github/codeql-action/analyze from 4.36.3 to 4.37.0 Bumps [github/codeql-action/analyze](https://github.com/github/codeql-action) from 4.36.3 to 4.37.0. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](54f647b7e1...99df26d4f1) --- updated-dependencies: - dependency-name: github/codeql-action/analyze dependency-version: 4.37.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump github/codeql-action/init from 4.36.3 to 4.37.0 (#6832) * fix(electron): bump electron 42→43 + build better-sqlite3 from source (ABI 148) (#6605) fix(electron): bump electron 42→43 + rebuild better-sqlite3 from source against the Electron ABI (148). Electron 43 raises NODE_MODULE_VERSION to 148; better-sqlite3@12.11.1 has no electron-v148 prebuild, so the packaged app died with 'Nenhum driver SQLite disponível'. prepare-electron-standalone now compiles better-sqlite3 from source against the electron headers into build/Release (where 'bindings' resolves it). Validated by Electron Package Smoke (green) + local (node_register_module_v148). Supersedes #6378. (--admin: the only reds are SonarQube/SonarCloud failing on a coverage-report artifact digest-mismatch — a GitHub Actions infra flake, not this diff; Sonar is green on main and the diff touches only the electron build.) * deps: bump the development group across 1 directory with 6 updates (#6588) deps: bump the development group (6 updates). Rebased onto current main; all checks green after the electron-smoke fix (#6605). * fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump (#6620) fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump. undici 8.6+ changed ProxyAgent to forward plain-HTTP via request-proxy instead of CONNECT, breaking OAuth refresh through a connection proxy (501). proxyDispatcher now passes proxyTunnel:true. Validated: Unit Tests 3/8 (the OAuth-proxy test) green, new regression test green (fails without the fix on undici 8.7), SonarQube green. Supersedes #6380. (--admin: the only red is Electron Package Smoke failing on a next-build artifact 'digest-mismatch' — a GitHub Actions infra flake corrupting the asar ('file data stream has unexpected number of bytes'); the better-sqlite3 rebuild itself succeeded (gyp ok) and the electron path is unchanged from #6605 which passed the smoke. Not this diff.) * chore(deps): bump github/codeql-action/init from 4.36.3 to 4.37.0 Bumps [github/codeql-action/init](https://github.com/github/codeql-action) from 4.36.3 to 4.37.0. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](54f647b7e1...99df26d4f1) --- updated-dependencies: - dependency-name: github/codeql-action/init dependency-version: 4.37.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * feat(provider): add OpenVecta AI inference gateway (#6833) * fix(electron): bump electron 42→43 + build better-sqlite3 from source (ABI 148) (#6605) fix(electron): bump electron 42→43 + rebuild better-sqlite3 from source against the Electron ABI (148). Electron 43 raises NODE_MODULE_VERSION to 148; better-sqlite3@12.11.1 has no electron-v148 prebuild, so the packaged app died with 'Nenhum driver SQLite disponível'. prepare-electron-standalone now compiles better-sqlite3 from source against the electron headers into build/Release (where 'bindings' resolves it). Validated by Electron Package Smoke (green) + local (node_register_module_v148). Supersedes #6378. (--admin: the only reds are SonarQube/SonarCloud failing on a coverage-report artifact digest-mismatch — a GitHub Actions infra flake, not this diff; Sonar is green on main and the diff touches only the electron build.) * deps: bump the development group across 1 directory with 6 updates (#6588) deps: bump the development group (6 updates). Rebased onto current main; all checks green after the electron-smoke fix (#6605). * fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump (#6620) fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump. undici 8.6+ changed ProxyAgent to forward plain-HTTP via request-proxy instead of CONNECT, breaking OAuth refresh through a connection proxy (501). proxyDispatcher now passes proxyTunnel:true. Validated: Unit Tests 3/8 (the OAuth-proxy test) green, new regression test green (fails without the fix on undici 8.7), SonarQube green. Supersedes #6380. (--admin: the only red is Electron Package Smoke failing on a next-build artifact 'digest-mismatch' — a GitHub Actions infra flake corrupting the asar ('file data stream has unexpected number of bytes'); the better-sqlite3 rebuild itself succeeded (gyp ok) and the electron path is unchanged from #6605 which passed the smoke. Not this diff.) * feat(provider): add OpenVecta AI inference gateway OpenVecta (https://openvecta.com/) is an OpenAI-compatible AI inference gateway hosting LLMs (GLM, Claude, DeepSeek, GPT OSS, Llama, Kimi, Nemotron...) plus text-embedding-* models behind a single Bearer key. Wiring (7 integration points): - src/shared/constants/providers/apikey/inference-hosts.ts: catalog entry - open-sse/config/providers/registry/openvecta/index.ts: registry w/ 9 seed LLMs - open-sse/config/providers/index.ts: wire into REGISTRY - src/app/api/providers/[id]/models/discovery/providerModelsConfig.ts: live /v1/models URL - src/app/api/providers/[id]/models/discovery/providerSets.ts: NAMED_OPENAI_STYLE_PROVIDERS - public/providers/openvecta.svg: brand icon - tests/unit/openvecta-provider-registration.test.ts: regression guard (6 tests, all pass) No executor needed — buildOpenAiCompatibleRegistryEntry wires format=openai / executor=default / authType=apikey / authHeader=bearer. Live catalog discovery uses the existing NAMED_OPENAI_STYLE_PROVIDERS path (live /v1/models fetch + registry seed as offline fallback). Validation: - npm run typecheck:core clean - npm run typecheck:noimplicit:core 4 errors in unchanged files (combo.ts, cliRuntime.ts); 0 in new code - npm run lint clean - node --import tsx/esm --test tests/unit/openvecta-provider-registration.test.ts 6/6 pass - sibling tests/unit/openai-style-providers-4239-4155-3841.test.ts 18/18 pass (no regression) * chore(merge): drop unrelated main-drift from PR fork + fix count/golden drift The fork branch predated main's electron 42→43 bump (#6605) and several other package.json/lockfile churn; those files are unrelated to the OpenVecta provider addition and were reintroducing an older/stale state (version 3.8.46, electron 42, older bun/eslint-config-next) that broke the Electron Package Smoke check. Restored package.json, package-lock.json, electron/package.json, electron/package-lock.json, and scripts/build/prepare-electron-standalone.mjs to match origin/release/v3.8.47. Also updates the two provider-count assertions (166->167) and regenerates the translate-path golden snapshot to account for the new openvecta entry. Co-authored-by: hajilok <120608486+hajilok@users.noreply.github.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> * fix(sse): combo model lockout honors parsed upstream quota reset (#6863) (#6866) * fix(sse): combo model lockout honors parsed upstream quota reset (#6863) The combo failure path recorded model lockouts from checkFallbackError's cooldownMs only, discarding quotaResetHintMs — the ungated channel that carries a parsed upstream quota reset (e.g. Antigravity 429 "Resets in 92h27m28s"). With OAuth profiles defaulting useUpstreamRetryHints=false, the lockout fell back to the base cooldown (seconds), so quota-dead accounts were re-walked serially by every combo request for days (measured 122s per request, 494s worst case in #6863). Thread max(cooldownMs, quotaResetHintMs) into selectLockoutCooldownMs at both combo lockout sites, mirroring the single-model path pattern in src/sse/services/auth.ts (v3.8.43). All three resilience fences are preserved: useUpstreamRetryHints still gates connection cooldowns, the hint only affects model-scope lockouts, and combo 429s remain non-persistent. TDD: tests/unit/combo-lockout-quota-reset-6863.test.ts fails on base (lockout 5000ms) and passes with the fix (~92.5h). * test(sse): tighten #6863 lockout assertion to parsed-reset bounds; prettier pass Assert remainingMs falls within (parsedResetMs - 5s, parsedResetMs] so a hardcoded long cooldown cannot satisfy the regression test. Also apply Prettier to both changed files (includes one pre-existing formatting fix in handleRoundRobinCombo picked up by --write). * fix(sse): align combo lockout hint selection with single-model path; register test in mutation gate Adopt review feedback: replace Math.max(cooldownMs, quotaResetHintMs) with the auth.ts pattern (usedUpstreamRetryHint ? cooldownMs : quotaResetHintMs) so a parsed reset SHORTER than the fallback cooldown wins too — e.g. the subscription-quota branch returns a 1h fallback while the body says "resets in 45m"; max() would over-lock by 15 minutes. Add a regression test for the short-reset case (fails against the max() variant) and register the new test file in stryker.conf.json tap.testFiles to satisfy check:mutation-test-coverage --strict. * chore(quality): register cliproxyapi dispatch test in mutation gate tests/unit/cliproxyapi-model-mapping-dispatch.test.ts landed on release/v3.8.47 via #6903 without a tap.testFiles entry, so check:mutation-test-coverage --strict fails on the branch tip and on every PR merge ref. Register it so the gate is green again. --------- Co-authored-by: judy459 <JUDYZHU459@outlook.com> Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> * feat(proxy): shorthand proxy formats + protocol header mode for bulk import (#6867) * fix(electron): bump electron 42→43 + build better-sqlite3 from source (ABI 148) (#6605) fix(electron): bump electron 42→43 + rebuild better-sqlite3 from source against the Electron ABI (148). Electron 43 raises NODE_MODULE_VERSION to 148; better-sqlite3@12.11.1 has no electron-v148 prebuild, so the packaged app died with 'Nenhum driver SQLite disponível'. prepare-electron-standalone now compiles better-sqlite3 from source against the electron headers into build/Release (where 'bindings' resolves it). Validated by Electron Package Smoke (green) + local (node_register_module_v148). Supersedes #6378. (--admin: the only reds are SonarQube/SonarCloud failing on a coverage-report artifact digest-mismatch — a GitHub Actions infra flake, not this diff; Sonar is green on main and the diff touches only the electron build.) * deps: bump the development group across 1 directory with 6 updates (#6588) deps: bump the development group (6 updates). Rebased onto current main; all checks green after the electron-smoke fix (#6605). * fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump (#6620) fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump. undici 8.6+ changed ProxyAgent to forward plain-HTTP via request-proxy instead of CONNECT, breaking OAuth refresh through a connection proxy (501). proxyDispatcher now passes proxyTunnel:true. Validated: Unit Tests 3/8 (the OAuth-proxy test) green, new regression test green (fails without the fix on undici 8.7), SonarQube green. Supersedes #6380. (--admin: the only red is Electron Package Smoke failing on a next-build artifact 'digest-mismatch' — a GitHub Actions infra flake corrupting the asar ('file data stream has unexpected number of bytes'); the better-sqlite3 rebuild itself succeeded (gyp ok) and the electron path is unchanged from #6605 which passed the smoke. Not this diff.) * feat(proxy): add shorthand formats + protocol header mode for bulk import Supports 6 new shorthand formats alongside the existing pipe-delimited parser: - ip:port - ip:port:user:pass - user:pass@ip:port - user:pass:ip:port - protocol://ip:port - protocol://user:pass@ip:port Protocol header mode: a bare protocol name (http/https/socks5) on its own line sets the default type for subsequent protocol-less shorthand lines. Explicit protocol:// prefix always takes precedence over the header default. Changes: - Rewrite parseBulkProxyImport.ts with parseShorthandLine helper - Use Record<string, true> for static lookup tables (VALID_PROXY_TYPES, VALID_PROXY_STATUSES) per project convention - Add looksLikeHost() heuristic to disambiguate 4-colon format (ip:port:user:pass vs user:pass:ip:port) - Update BULK_IMPORT_TEMPLATE in ProxyRegistryManager.tsx with full documentation and examples for all formats - Update en.json bulkImportDescription to list all supported formats - Add 30 unit tests covering every format, edge cases, and regressions for the existing pipe-delimited path --------- Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> * fix(sse): stop combo path tripping whole-provider breaker on plain 429 (#6868) * fix(sse): stop combo path tripping whole-provider breaker on plain 429 The combo path recorded a whole-provider circuit-breaker failure for a plain rate-limit 429, opening the breaker after N consecutive 429s and blocking every account+model on that provider. This contradicts the single-model path and the documented RESILIENCE_GUIDE policy. - Single-model path uses PROVIDER_BREAKER_FAILURE_STATUSES = Set([408, 500, 502, 503, 504]) (src/sse/handlers/chat.ts:206) — 429 excluded. - Combo path gated shouldRecordProviderBreakerFailure on isProviderFailureCode (accountFallback.ts), whose PROVIDER_FAILURE_ERROR_CODES INCLUDES 429 for connection-cooldown scope — so a plain 429 wrongly tripped the whole-provider breaker. Fix scopes tightly: comboPredicates now tests a local PROVIDER_BREAKER_FAILURE_STATUSES set mirroring the single-model constant (429 excluded), instead of isProviderFailureCode. The shared isProviderFailureCode / PROVIDER_FAILURE_ERROR_CODES are deliberately left untouched — they drive connection-cooldown / model-lockout logic where 429 must still count. A genuine quota/token-limit terminal 429 is handled elsewhere; only the whole-provider breaker-recording gate changes. Adds tests/unit/combo-breaker-429.test.ts covering the 429 exclusion, the 408/5xx inclusion, and the sameProviderNext / skipProviderBreaker suppression paths. * test(quality): register combo-breaker-429.test.ts in stryker tap.testFiles Fast Quality Gates' mutation-coverage drift check flagged this PR's new covering test for comboPredicates.ts as unregistered. Co-authored-by: Chirag Singhal <chirag127@users.noreply.github.com> --------- Co-authored-by: Chirag Singhal <chirag127@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> * fix(oauth): embed Trae OAuth client_id via resolvePublicCred (Hard Rule #11) (#6870) * fix(oauth): embed Trae OAuth client_id via resolvePublicCred (Hard Rule #11) * fix(quality): register tests/unit/trae-publiccred.test.ts in stryker tap.testFiles The mutation test-coverage gate (check:mutation-test-coverage --strict) flags new covering unit tests that mutate open-sse/utils/publicCreds.ts but aren't listed in stryker.conf.json's tap.testFiles, so their mutant kills wouldn't count. Register the new test file alphabetically. Co-authored-by: Chirag Singhal <chirag127@users.noreply.github.com> --------- Co-authored-by: Chirag Singhal <chirag127@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> * fix(ci): publish electron-updater latest.yml manifests in release assets (#6766) (#6881) * fix(ci): publish electron-updater latest.yml manifests in release assets (#6766) * fix(ci): register new mutation-covering test + realign stale codex-cli version fixture - stryker.conf.json: add tests/unit/cliproxyapi-model-mapping-dispatch.test.ts to tap.testFiles so it counts toward mutation coverage for the newly-added comboContextCache.ts coverage (check:mutation-test-coverage --strict was failing). - tests/unit/provider-models-route-codex.test.ts: DEFAULT_CODEX_CLIENT_VERSION was bumped to 0.144.0 on release/v3.8.47 after this test's fixtures were written; realign the hardcoded 0.142.0 expectations to the current constant. * fix(ci): exclude check-test-masking.test.ts fixtures from self-referential tautology gate (#6634) (#6884) * fix(ci): exclude check-test-masking.test.ts fixtures from self-referential tautology gate (#6634) * fix(ci): extend test-masking self-fixture exclusion to sibling gate regression files (#6634) The #6634 fix added isSelfTestFixtureFile()/scanBareTautologies() exclusions that only matched check-test-masking.test.ts exactly. Its own new regression file check-test-masking-selfref-6634.test.ts also embeds tautology-pattern literals as fixtures/documentation, so the absolute-floor scanBareTautologies gate self-tripped a HARD failure on the PR's own file. Generalize the exclusion to the whole check-test-masking* self-test family and lock it with two regression tests. * fix(api): route error responses through sanitizeErrorMessage (Hard Rule #12) (#6886) * fix(api): route error responses through sanitizeErrorMessage (Hard Rule #12) 9 API routes returned raw String(error)/error.message directly in HTTP 500 bodies, leaking SQLite paths, SQL text and internal messages. Route all through sanitizeErrorMessage() per Hard Rule #12: - settings/compression (GET+PUT), settings/compression/mcp-accessibility (GET+PUT) - cache/entries (GET+POST), db/health (GET+POST), db-backups/exportAll - assess, combos/test, settings/notion, settings/obsidian Test: tests/unit/rule12-error-sanitization-sweep.test.ts asserts sanitized 500 bodies contain no absolute paths / stack tails. * test(stryker): register rule12 error-sanitization sweep in tap.testFiles The new tests/unit/rule12-error-sanitization-sweep.test.ts covers a mutated module, so it must be listed in stryker.conf.json tap.testFiles for the mutation-coverage gate (check-mutation-test-coverage.mjs --strict) to pass. Co-authored-by: Chirag Singhal <chirag127@users.noreply.github.com> --------- Co-authored-by: Chirag Singhal <chirag127@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> * fix(routing): honor no-auth provider connection isActive in auto-combo pool (#6557) (#6889) * fix(providers): strip redundant node prefix on connId-addressed custom models (#6772) (#6890) * fix(providers): give v0-vercel-web its own alias so credentials are detected (#6343) (#6891) * fix(providers): give v0-vercel-web its own alias so credentials are detected (#6343) * test(6343): type casts to satisfy no-explicit-any gate * test(6343): register v0-web + cliproxyapi tests in stryker tap.testFiles The two unit tests added on this branch cover mutated modules (src/sse/services/auth.ts, comboContextCache.ts) but were missing from stryker.conf.json tap.testFiles, tripping check:mutation-test-coverage --strict. Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> * fix(cli): waitForServer must not report ready on bare TCP accept (#6800) (#6892) waitForServer() polled /api/monitoring/health but fell back to declaring the server ready once the port had merely accepted TCP connections for >= 3s, even if no HTTP response was ever received. On CPU-bound warmup (e.g. small VPS running Next.js standalone), the OS-level listener accepts TCP almost immediately while the request pipeline is still compiling, so the fallback fired within ~3-7s and the CLI printed 'OmniRoute is running!' 30-60s before any route actually answered. Classify each health poll into ready / fast-reject / hanging / not-listening: only a fast HTTP rejection (fetch error that is not a timeout, e.g. ECONNRESET before the route mounts) grants the original #2460 Windows-cold-start grace window. A request that times out with zero response (the reported #6800 symptom) resets the grace window instead of accumulating toward it. Regression tests: tests/unit/waitForServer-tcp-fallback-6800.test.mjs (new RED-then-GREEN probe from the bug analysis) and tests/unit/cli-waitForServer.test.mjs (existing suite realigned to the corrected contract, plus a new case for the hanging-socket scenario). * fix(providers): wire devin cloud-agent into provider validation and static models (#6142) (#6894) * fix(sse): de-flake timing-sensitive combo cooldown/breaker tests (#6803) (#6897) * fix(sse): de-flake timing-sensitive combo cooldown/breaker tests (#6803) Extracts 3 wall-clock-sensitive assertions (combo-quota-share cooldown ceiling x2, circuit-breaker HALF_OPEN race) into tests/unit/serial/ (--test-concurrency=1, the repo's established remedy for this class of test) and widens their margins, since a starved CI-runner event loop can blow even a serialized test's timing window. Also adds an explicit 30s vitest timeout to the MCP audit shutdown test, which had no override and inherited vitest's 5000ms default. Regression proof: reproduced RED locally under real devbox CPU contention (2644ms/1796ms elapsed vs the old 1500ms ceiling, exactly the reported failure mode); confirmed GREEN after the fix under the same contention. * fix(quality): register new serial timing tests + prune stale any-suppression count - stryker.conf.json: add tests/unit/serial/combo-quota-share-cooldown-wait-timing.test.ts and tests/unit/serial/combo-strategy-fallbacks-half-open-timing.test.ts to tap.testFiles so their mutant kills count for accountFallback.ts and circuitBreaker.ts (PR #6897 added these files but didn't register them). - eslint-suppressions.json: combo-strategy-fallbacks.test.ts's no-explicit-any suppression count was stale (35) after this PR trimmed 2 any-usages out of the file when extracting the half-open timing test; corrected to 33. Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com> * fix(db): stop legacy log-archive migration from deleting the live app-logger directory and crashing startup on a stat/stream race (#6401) (#6898) archiveLegacyRequestLogs() swept the entire DATA_DIR/logs directory as a single "legacy" target and recursively deleted it after zipping. Since PR #6234 moved the default app-log path to DATA_DIR/logs/application, the migration was deleting the live file logger's own directory on every boot until its marker file existed (#6799). Separately, yazl's addFile() does an internal stat-then-stream read; if a target file grows between those two steps (e.g. an actively-written log), yazl emits "error" directly on the ZipFile instance. That event had no listener, so Node re-threw it as an uncaughtException that crashed the whole process at startup (#6401) — misdiagnosed upstream as Turbopack/Windows chunk corruption because the stack trace pointed into a bundled chunk. Fix: - listArchiveTargets() now enumerates DATA_DIR/logs entries individually and skips the live app-logger directory (resolved via logEnv.getAppLogFilePath()), so the shared parent directory is never deleted wholesale. - createLegacyArchive() wires a zipFile.on("error", ...) handler so a stat/stream race rejects the promise (caught by the existing try/catch) instead of escaping as an uncaughtException. Regression test: tests/unit/usage-migrations-legacy-archive-safety.test.ts (RED on unfixed code, GREEN after fix). Updated tests/unit/request-log-migration.test.ts to the corrected contract — DATA_DIR/logs itself now survives the archive sweep. Gates run clean: file-size, complexity, cognitive-complexity, changelog-integrity, typecheck:core, eslint (suppressions), and the existing usage-migrations/request-log-migration unit suites. * fix(cli): ship head-response-guard.cjs in the standalone bundle (#6908) * fix(cli): ship head-response-guard.cjs in the standalone bundle server-ws.mjs imports ./head-response-guard.cjs, but assembleStandalone had no EXTRA_MODULE_ENTRIES entry for it, so every build:release bundle crashed at boot with ERR_MODULE_NOT_FOUND (found deployingd1d75fdbfto the VPS on 2026-07-11). Adds the missing entry plus a regression test that derives the required sidecar list from server-ws.mjs's own relative imports, guarding the whole class of missing-sidecar bugs. * docs(changelog): fragment for #6908 * fix(responses): escape literal control chars in tool call JSON; emit … (#6786) * fix(responses): escape literal control chars in tool call JSON; emit status=failed on upstream error #6785 Two bugfixes in the Responses API translator: 1. escapeJsonStringValues() sanitizes tool call arguments containing literal 0x0A/0x0D/0x09 bytes (emitted by Gemma4 models) into valid JSON \n/\r/\t escapes, preventing SSE framing corruption. Only escapes inside JSON string contexts — already-escaped sequences and structural JSON pass through unchanged. 2. sendCompleted() checks state.upstreamError and emits status="failed" with error.code + error.message instead of silently hardcoding status="completed" + error=null, so mid-stream errors (e.g. Gemini 503 after partial content) are properly surfaced to the client. 3. stream.ts: calls translateResponse(null,...) before controller.error() so the translator can emit close events (reasoning item done, response.completed) before the stream is terminated. * test(boundary): fix ESLint no-explicit-any warnings and quality gates Green the PR against release/v3.8.47 quality gates without weakening tests: - Replace @typescript-eslint/no-explicit-any in the new boundary/gemma4 tests with proper interfaces (ResponseBody, ToolDef, ToolArgs, SseEvent item accessors) — fixes the "No new ESLint warnings" gate. - Split tests/unit/translator-resp-openai-responses.test.ts (1079 LOC) by extracting the round-trip suite into a sibling file so both stay under the 800-line test cap — fixes check:file-size. - Rename the 5 live boundary tests to *.live.test.ts, gate them behind RUN_BOUNDARY_LIVE=1, add a test:boundary:live npm script and register the glob in check-test-discovery COLLECTORS — fixes check:test-discovery (they hit a live remote and must never run unopted in CI). Co-authored-by: Markus Hartung <mail@hartmark.se> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> * fix(providers): route AgentRouter key validation through CC wire image (#6377) (#6882) * fix(providers): route AgentRouter key validation through CC wire image (#6377) * test(6377): type fetch mock to satisfy no-explicit-any gate * fix(api): recognize OpenRouter reasoning/reasoning_details in non-streaming OpenAI-to-Claude conversion (#6623) (#6887) * fix(providers): scope nvidia NIM 404s to the single failing model (#6773) (#6888) * fix(providers): scope nvidia NIM 404s to the single failing model (#6773) The nvidia registry entry multiplexes 17 models from 9 different upstream vendors (z-ai/, minimaxai/, deepseek-ai/, qwen/, mistralai/, stepfun-ai/, moonshotai/, openai/, nvidia/) behind one connection, but was missing passthroughModels: true — unlike 34 other multi-model registries (modelscope, synthetic, kilo-gateway, etc). Without it, hasPerModelQuota returns false for nvidia, so a 404 on a single stale/renamed model falls through checkFallbackError's generic catch-all as a connection-wide cooldown instead of being scoped to just that model, poisoning all 17 nvidia models for the cooldown window. Add passthroughModels: true to the nvidia registry entry so 404/429s on one model lock out only that model. Regression test: tests/unit/nvidia-passthrough-models-6773.test.ts * fix(quality): register nvidia passthrough test in stryker tap.testFiles check-mutation-test-coverage.mjs --strict flagged tests/unit/nvidia-passthrough-models-6773.test.ts as an unregistered covering test for accountFallback.ts (Fast Quality Gates). Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com> * fix(usage): strict validation for xAI exact provider-reported cost (#6856) extractUsageFromResponse() and normalizeUsage() used Number(x) coercion for cost_in_usd_ticks, which silently turned null/"" into 0 -- accepted downstream as a valid $0 exact cost instead of falling back to the token-based estimate. Both call sites now require typeof === "number" && Number.isFinite && >= 0. Rebased onto current release/v3.8.47 tip (already carries #6711) and trimmed to just the incremental validation fix + 2 regression tests, replacing the stale-base diff that re-added the whole already-merged feature. Co-authored-by: KooshaPari <koosha@phenotype.io> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(sse): treat compression no-op as zero-savings, not inflation/silent-drop (#6883) A structural engine (ccr / session-dedup) that finds nothing to compress returns the body unchanged. That no-op was mishandled three ways — the code-level root cause of the #6465–#6493 "0% savings, no reason" symptom class: A. Inflation guard mislabelled a no-op as inflation. guardPipelineInflation used `compressedTokens >= originalTokens`, so an unchanged body (compressedTokens === originalTokens) tripped the guard, setting fallbackApplied=true and emitting a misleading "did not shrink; reverted to original" warning. Changed to strict `>` — only a strictly larger output is inflation; equality is a no-op. Genuine inflation still reverts. B. Disabled-engine skip was silent and asymmetric with the breaker skip. Both stacked loops (sync + async) skipped a registry-disabled engine with a bare `continue`, recording no validationWarning — while the sibling breaker-open branch does. Both loops now add `${engine}: skipped (engine disabled in registry)`, mirroring the breaker branch. C. No-op engine lost its identity in engineBreakdown. mergeStackStep early-returned on null stats, pushing no breakdown entry, so ensureEngineBreakdown synthesized a generic "stacked" 0% node. It now records a zero-savings entry keyed on the engine that actually ran, preserving identity. Tests: tests/unit/compression-noop-guard.test.ts covers A (equal-token no-op not inflated; strictly-larger still reverts), B (disabled skip surfaces a "disabled" warning), and C (no-op engine keeps its own id in the breakdown). Updated the existing inflation-guard test whose net-zero case encoded the old buggy behaviour, and switched its wire test to object-form pipeline steps so the intended engine runs. Co-authored-by: Chirag Singhal <chirag127@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> * chore(quality): freeze file-size for #6909 (localDb 805) + #6807 (translator test 1195) Owner-approved /merge-prs tail freeze. localDb.ts is re-export-only (Hard Rule #2); translator test grew from #6807's regression suite. Both frozen (shrink-only). * fix(sse): default reasoning summary for effort-only Responses requests (#6807) A Chat-Completions client can only express reasoning via the top-level reasoning_effort hint and has no way to request a reasoning summary. When that hint is promoted to the Responses API's reasoning.effort, the upstream returns an empty summary and downstream chat clients see no thinking stream (encrypted reasoning only). Default reasoning.summary "auto" plus include ["reasoning.encrypted_content"] on the effort-only path so the summary actually streams back to the chat client, mirroring the Codex executor's ensureCodexReasoningSummary. An explicit reasoning object from a Responses-shaped client is preserved untouched, and reasoning_effort "none" is left without a summary. Adds regression tests for the effort-only default, the none case, and keeps the existing explicit-reasoning-object behavior unchanged. Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> * fix(proxy): relay repair + free-pool UX + relay awareness (#6909) * feat(proxy): relay repair + free-pool UX + relay awareness * fix(proxy): preserve existing notes fields on relay repair; fix cpu limit /1000 across all 5 container providers * feat(proxy): extract bulk-import and pool-modal hooks (#6625) * fix: prevent relay type normalization to http on PATCH Bug: updateProxyRegistrySchema inherited .default("http") from the base schema, causing PATCH to silently overwrite relay types (vercel/deno/cloudflare) with "http" when the client didn't send a type field. - Move .default('http') from proxyRegistryFieldsSchema to createProxyRegistrySchema (only applies to new proxies) - Strip undefined keys from validated changes before passing to updateProxy — .partial() leaves absent fields as undefined, which the spread merge in updateProxyRow would propagate to the DB - Add console.warn in extractRelayAuth when decrypt fails on a known-encrypted relayAuthEnc blob Closes #6905 * feat: replace Load More with page-number pagination in FreePoolTab - Adds page-number pagination controls with prev/next buttons - Shows per-page summary with total counts - Resets to page 1 on filter change via wrapper setters * fix(proxy): restore free-proxy sync-error tracking reverted by pagination commit commit2c8e79f13(page-number pagination for FreePoolTab) accidentally reverted the recordFreeProxySyncErrors/clearFreeProxySyncErrors/ getFreeProxySyncErrors functions and the search/sortBy list options that an earlier commit (6f9ce75f3) in this same PR had added to src/lib/db/freeProxies.ts and src/app/api/settings/free-proxies/route.ts. localDb.ts still re-exported the three sync-error functions, so every module that transitively imports it (which is most of the unit test suite, plus the Next.js build used by dast-smoke) failed at load time with "The requested module './db/freeProxies' does not provide an export named 'clearFreeProxySyncErrors'". Restores the reverted implementation (verbatim) and fixes the two new "requires management auth" tests, which asserted 401 for an unauthenticated request without configuring INITIAL_PASSWORD + requireLogin first — on a fresh DB with no password configured, isAuthRequired() treats a loopback request as the pre-setup bootstrap path and allows it through, so the assertion needs the same password/requireLogin setup already used by tests/unit/api/settings-audit.test.ts. Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> * fix(proxy): trim unrelated scope from relay-repair/free-pool PR Remove two subsystems that are unrelated to relay repair and the free-pool UX and were never wired into the app: - open-sse/services/combo/capabilityRequirements.ts and CapabilityRequirementsEditor.tsx: a combo capability-filtering feature never imported by combo.ts or combos/page.tsx, with no test coverage. - src/lib/skills/containerProvider.ts CPU-limit rescale: an untested change to sandbox resource limits, unrelated to the proxy/relay subsystem this PR targets. Restored to the release baseline. Also renumber the free_proxy_sync_errors migration 121 -> 122 to avoid colliding with #6855, which independently claims 121 on the same release branch. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: oyi77 <oyi77@users.noreply.github.com> Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(security): scrub hardcoded live-instance creds from boundary tests (#6786) The 3 tests/boundary/*.live.test.ts files merged via #6786 hardcoded a real Bearer API key, an auth_token JWT cookie, and a live instance URL. Replaced with env reads (OMNIROUTE_TEST_BASE/BEARER/COOKIE), preserving the RUN_BOUNDARY_LIVE gate. The leaked key/cookie must still be revoked/rotated on the affected instance and purged from history separately (operator action). * feat(compression): update vendored GCF (Headroom) codec to spec v3.2 — nested flattening (#6838) * feat(compression): update vendored GCF (Headroom) codec to spec v3.2 (nested flattening) Homogeneous arrays whose rows carry nested objects/arrays now tabularize via GCF v3.2 `>`-path flattening instead of a low-yield per-row fallback, so nested MCP tool-result rows (meta:{...}, tags:[...]) compact like flat rows. Round-trip stays lossless (order-insensitive deepEqual). Re-vendored from current gcf-typescript into the Headroom generic-profile codec (open-sse/services/compression/engines/headroom/gcf/); still zero runtime deps, MIT, SPDX-marked, generic-profile only. Also folds in two upstream round-trip-safety fixes: the [N]: inline-array quoting fix and canonical decimal formatting. Regression guard: tests/unit/compression/headroom-smartcrusher.test.ts gains a deep-nested case (two-level object + array-of-objects) asserting the v3.2 flatten paths and order-insensitive round-trip. Vendored-code baseline bumps (complexity 2053->2055, cognitive 885->888, decode_generic no-explicit-any 18->22) each carry an inline _rebaseline_2026_07_10_gcf_v3_2 justification noting the growth is the vendored surface, not new project code. * chore(changelog): add fragment for headroom GCF v3.2 nested flattening (#6838) * docs(readme): note Headroom handles nested arrays (GCF v3.2) in the engine-stack table * docs(readme): note Headroom handles nested arrays (GCF v3.2) in the engine-stack table * fix(compression): harden vendored GCF decoder against prototype pollution The v3.2 flatten/unflatten paths (and the pre-existing inline-object parser) built decoded objects with bracket assignment and `key in obj` membership, so a hostile or unusual payload could pollute Object.prototype via a `__proto__` path segment, and any key shadowing an Object.prototype member (`toString`, `constructor`) was misparsed or wrongly flagged duplicate. - Encoder (`analyzeFlattenable`): builds the shape map with `Object.create(null)` and refuses to flatten objects carrying `__proto__`/`constructor`/`prototype` keys (they round-trip whole instead). - Decoder: `unflattenPaths` drops any path with an unsafe segment; a shared `safeAssign` writes a literal `__proto__` key as an own data property (JSON.parse semantics) instead of reassigning the prototype, used at every object-build site; `checkDup` and orphan-merge use `hasOwnProperty` so built-in-named keys are not spuriously treated as duplicates. Also a losslessness fix: objects with keys named `toString`/`constructor`/ `valueOf` now round-trip. Regression guard: prototype-pollution + built-in-key cases in tests/unit/compression/headroom-smartcrusher.test.ts. Prototype pollution is JS/TS-specific; the Go/Python/Rust/Swift/Kotlin SDKs use native maps and are unaffected. * fix(compression): apply GCF decoder review hardening (hasOwnProperty sweep, unflatten null-guard, strict count) Addresses the second-round review on the vendored codec: - Replace every `key in obj` membership test with `Object.prototype.hasOwnProperty.call(...)` across generic.ts (flatten shape analysis, key-chain resolution, inline-schema/shared-array helpers, row encode) so inherited names (`toString`/`constructor`) never match the prototype chain, and remove a redundant `obj` re-declaration in the ">" field attachment loop. - `unflattenPaths` guards each intermediate segment: a missing OR non-object slot is replaced with a fresh object before traversal, so malformed/hostile input can no longer dereference a primitive and crash. - Use the strict `parseCount` helper (not `parseInt`) for the shared-schema count so malformed counts fail the mismatch check instead of coercing. The decoder grew past the 800-line file-size cap; frozen at 880 in file-size-baseline.json with a justification (vendored file kept faithful to upstream gcf-typescript for clean re-vendoring). Verified: prototype-pollution + hostile-input + built-in-key round-trip probes, 54/54 compression tests, typecheck, lint, cyclomatic/cognitive baselines unchanged, compression-budget. * fix(compression): do not flatten a nested object that is null in any row (losslessness) analyzeFlattenable skipped null values during shape analysis, so a field that was an object in some rows and null in others was still flattened. On decode, the null row's leaves resolved as absent ("~") and unflattened to a missing key instead of null, silently dropping the value (e.g. {meta:{owner:null}} decoded to {}). analyzeFlattenable now bails (returns null) when the field is null in any row, routing it through the lossless whole-object attachment path. Applies at every nesting depth via the existing recursion. Regression guard: null nested-object cases in tests/unit/compression/headroom-smartcrusher.test.ts. * fix(compression): narrow the null-nested flatten bail to intermediate nulls only The previous fix bailed flattening whenever a nested field was null in any row. That is correct but over-broad: a top-level null round-trips losslessly through flattening (it emits "-" and reconstructs via the all-null rule). Only a null at an intermediate nesting level loses data (its leaves encode as absent "~" and unflatten to a missing key). Bail only when parentPath is non-empty, so top-level nulls keep flattening (compression preserved) while intermediate nulls fall back to the lossless attachment path. Matches GCF conformance fixtures 004/013. --------- Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> * feat(providers): add GPT-5.6 model family (#6862) * feat(providers): add GPT-5.6 model family * fix(chatgpt-web): resume temporary chat handoffs * fix(codex): auto-merge discovery, filter denylist, revalidate on lifecycle Restore live/GitHub auto-merge for Codex catalogs, drop models via explicit denylist (GPT-5.4 family), and run scrub+live re-sync once on first-start, app upgrade, or setup completion. Success log: kill deprecated models complete. * fix(codex): preserve live catalog reconciliation Expose remote-only Codex models without dropping user custom entries, and complete lifecycle revalidation only after a successful internal sync. Keep credentialed self-fetches pinned to the active dashboard listener. --------- Co-authored-by: backryun <backryun@daonlab.local> * fix(release): read changelog.d fragments in list-uncovered-commits (#6857) (#6878) * fix(release): count changelog.d fragment refs in list-uncovered-commits (#6857) Since fragments-first (#6783), a merged PR's changelog entry usually lives in changelog.d/{features,fixes,maintenance}/<PR>-<slug>.md and is only folded into CHANGELOG.md at release time. list-uncovered-commits.mjs scanned only CHANGELOG.md, so every fragment-covered commit was reported as an uncovered gap (some fragments — e.g. 6708, 6709 — carry no #N in the body, only in the filename). Add fragment-aware ref collection: fragmentFilenameRef() reads the leading <N>- of a fragment filename, fragmentRefs() unions filename PR numbers with every #N in the body, and collectChangelogRefs() unions the CHANGELOG scan window with the fragment refs. main() now reads changelog.d via readChangelogFragments() and feeds it into the union. On release/v3.8.47 tip this moves 44 commits from uncovered to covered (215/341 vs the prior 171/341) without changing the covered/uncovered classification logic. * chore(release): add changelog.d fragment for #6878 Housekeeping item requested in review: the PR fixing changelog-fragment coverage tracking (#6857) did not itself have a changelog.d fragment. --------- Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> * [trim] feat(combo): add context requirements config for target filtering (#6907) * feat(combo): add context requirements config for target filtering Add contextRequirements config field to combo runtime config: - minContextWindow: filter models below threshold (0-10M tokens) - preferLargeContext: sort targets by context size descending - contextFilterMode: 'strict' excludes unknown limits, 'lenient' includes them Implementation: - Added Zod schema validation in combo.ts - Created contextRequirements.ts module with applyContextRequirements() - Integrated filtering after filterTargetsByRequestCompatibility() - Full test coverage with unit + integration tests Tests: 17/17 pass (combo-context-requirements.test.ts + integration) * feat(combo): add ContextRequirementsEditor UI component Add standalone React component for editing context requirements config: - Slider for minContextWindow (0 to 1M tokens) with presets - Toggle for preferLargeContext sorting - Radio group for contextFilterMode (strict/lenient) - Tooltips explaining each option - Active filters summary display Component features: - Shadcn UI components (Card, Slider, Switch, RadioGroup) - Preset buttons for common context sizes (8K, 32K, 128K, 1M) - Conditional display of filter mode when minContextWindow > 0 - Clear visual feedback of active filters Integration: Import and use in combo config form where other config fields like fusionTuning and judgeModel are edited. Pass combo.config.contextRequirements as value prop and update on onChange. Example usage: <ContextRequirementsEditor value={config.contextRequirements} onChange={(val) => updateConfig({ contextRequirements: val })} /> UI matches existing combo config editor patterns. * feat(combo): wire ContextRequirementsEditor into combo config form Adds context requirements section to combo edit page (strategy section), matching existing ResponseValidation pattern. Placed after response validation block, before agent features. * docs(combo): add context requirements feature documentation Covers: config schema, behavior, use cases, UI integration, troubleshooting, and test instructions. * fix(combo): pass provider+modelStr to getModelContextLimit for accurate context resolution Per gemini-code-assist review feedback: model names are not globally unique across providers. Passing both provider and modelStr ensures correct context limit resolution in applyContextRequirements(). * fix(combo): repair broken doc links and restore test:unit:fast flag - Point docs/combo-context-requirements.md 'Related' links at real docs (routing/AUTO-COMBO.md, architecture/RESILIENCE_GUIDE.md) — the three placeholder links (strategies.md/model-capabilities.md/fusion-tuning.md) did not exist and failed check:doc-links (Docs Gates fast-path). - Revert an out-of-scope package.json change to test:unit:fast that dropped --test-isolation=none; restore to match release/v3.8.47. Co-authored-by: oyi77 <oyi77@users.noreply.github.com> * test(combo): validate context requirements against the real Zod schema Point tests/unit/combo-context-requirements.test.ts at the real comboRuntimeConfigSchema export (src/shared/validation/schemas/combo.ts) instead of hand-duplicating the Zod schema inline, so the test catches schema drift. Also declare contextRequirements on DEFAULT_COMBO_CONFIG so resolveComboSetupConfig's inferred return type includes the key — combo.ts reads config.contextRequirements but the property was missing from the object typecheck:core infers types from, causing a build error. Co-authored-by: oyi77 <oyi77@users.noreply.github.com> * fix(combos): remove dead ContextRequirementsEditor scaffolding (broken ui/card+label imports) The editor imported @/components/ui/card and @/components/ui/label which do not exist in the repo, breaking the Turbopack build. Removed the editor + its page.tsx usage + doc mention; the real fix (comboConfig contextRequirements default + test) is preserved. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: oyi77 <oyi77@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> * feat: add icons for 46 missing provider images (#6926) * feat: add icons for 46 missing provider images - Add SVG icons for 46 providers missing brand images - Add 3 LOBE aliases (bai, clinepass, copilot-m365-web) - Register all new SVGs in KNOWN_SVGS lookup New SVG icons cover: api-airforce, auggie, bluesminds, byteplus, bytez, charm-hyper, chipotle, chutes, crof, dgrid, digitalocean, dit, duckduckgo-web, factory, freeaiapikey, freemodel-dev, galadriel, gitlawb, gitlawb-gmi, hackclub, haiper, hcnsec, ideogram, kenari, leonardo, llm7, modelscope, nube, openadapter, orcarouter, pioneer, publicai, qiniu, requesty, sumopod, t3-web, theoldllm, tokenrouter, uncloseai, veoaifree-web, wafer, x5lab, yuanbao-web, zed-hosted, zenmux, zenmux-free * fix(icons): restore accidentally-deleted cohere alias in LOBE_PROVIDER_ALIASES The 46-icon addition dropped the existing `cohere: "Cohere"` entry; restore it alphabetically between codex-cloud and comfyui. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: oyi77 <oyi77@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(sse): wire shared quota-fetch throttle into all provider fetchers (#6911) (#6963) OMNIROUTE_QUOTA_FETCH_MIN_INTERVAL_MS / quotaFetchThrottle.ts documents itself as used by 'the provider quota fetchers' (plural), but only codexQuotaFetcher.ts ever called throttleQuotaFetch(). N accounts on one IP for DeepSeek, Bailian, OpenCode, or Crof still burst simultaneously. Wire throttleQuotaFetch() into fetchDeepseekQuota, fetchBailianQuota (both the primary and China-region retry fetch sites), fetchOpencodeQuota, and fetchCrofUsage, placed after the existing cache short-circuit so cache hits stay unaffected (mirrors the codexQuotaFetcher.ts pattern). PROVIDER_LIMITS_SYNC_SPACING_MS / providerLimits.ts's OAuth vs non-OAuth split is left unchanged — that split is intentional by design and already regression-guarded by tests/unit/provider-limits-oauth-sequential-sync.test.ts. The generic usage.ts::getUsageForProvider dispatch path (github, glm, minimax, nanogpt, xai, etc.) is intentionally out of scope for this fix to avoid scope creep; ENVIRONMENT.md now documents the actual post-fix coverage instead of the prior overclaim. * fix(sse): rename max_completion_tokens to max_tokens for volcengine/DeepSeek (#6912) (#6964) * fix(sse): defer response.completed until trailing usage-only chunk (#6906) (#6965) Real OpenAI-compatible upstreams with stream_options.include_usage=true send finish_reason in one chunk (usage: null) and the actual token counts in a separate, trailing usage-only chunk (choices: [], usage: {...}). Both the live translator (openai-responses.ts) and the legacy transformer (responsesTransformer.ts) fired response.completed as soon as they saw finish_reason, so the trailing usage chunk's token counts were captured into state but never emitted -- Codex CLI and other /v1/responses consumers saw response.completed with no usage field (permanent 0% context-used). Both translators now defer response.completed via an awaitingTrailingUsage state flag when finish_reason arrives without usage already captured, and complete on the next usage-only chunk (or at stream end via the existing flush fallback) instead. Extracted the duplicated events/emit boilerplate into a new openai-responses/eventEmitter.ts leaf to keep the frozen openai-responses.ts file under its file-size baseline. Fixes 3 existing tests that encoded the old chunk ordering and adds a permanent regression test (tests/unit/responses-usage-trailing-6906.ts) covering both translators. * fix(sse): omit removed attachments field from Muse Spark Web request (#6935) (#6960) * fix(dashboard): label audio/embeddings/image compatible providers by kind on ProviderCard (#6936) (#6961) ProviderCard's compatibility badge used a binary apiType ternary (responses vs everything-else -> "Chat"), so audio-transcriptions, audio-speech, images-generations and embeddings compatible providers (e.g. a locally-hosted speaches TTS/STT server) were mislabeled as "Chat". Reuse the existing KIND_LABEL map (stt/tts/image/embedding) instead of adding new i18n keys. * fix(sse): classify LAN embeddings providers as no-auth (#6925) (#6962) Private/LAN embeddings provider_nodes (10.0.0.0/8, 192.168.0.0/16, 100.64.0.0/10 CGNAT) were excluded by a hand-rolled hostname filter that only matched localhost/127.0.0.1/172.16-31, forcing them through the apikey/bearer credential fallback and returning 401 for keyless local providers like a LAN Ollama instance. Reuse the shared isPrivateHost()/isCloudMetadataHost() classification from outboundUrlGuard.ts in both the dynamic-provider filter and the provider_node fallback branch, so any private host resolves to authType 'none' while cloud-metadata endpoints stay blocked. * fix(api): use local-first SSRF guard for LAN model-list discovery (#6939) (#6966) * fix(sse): set includeServerSideToolInvocations on Antigravity tool cloak decoys (#6914) (#6959) cloakAntigravityToolPayload() injects decoy functionDeclarations (search_web, browser_subagent, read_url_content, generate_image) that mimic Antigravity's built-in server-side agent tools whenever any real tool is declared, but never set the companion toolConfig.includeServerSideToolInvocations flag a genuine Antigravity client sends alongside them. Google's Cloud Code backend (Gemini 3+) requires this opt-in whenever server-side built-in tool categories are combined with custom function declarations, causing every Antigravity tool-calling request to fail upstream. Set toolConfig.includeServerSideToolInvocations = true whenever decoy tools are injected. * fix(sse): escape backslash in ChatGPT-web citation link text (#6569) (#6944) * fix(sse): escape backslash in ChatGPT-web citation link text (#6569) markdownLinkText() escaped [ and ] but not the backslash itself, so a citation label ending in (or containing) a backslash produced a broken Markdown link — e.g. [Path C:\](url), where the trailing \ escapes the closing bracket and consumes the link. Escape the backslash first, then the brackets. Clears the CodeQL js/incomplete-sanitization alerts at open-sse/executors/chatgpt-web/citations.ts:52 (2 of the 9 new alerts on the v3.8.47 release PR). Regression guard: tests/unit/chatgpt-web-citations-escape.test.ts (trailing backslash, backslash-before-bracket, bracket-only, plain). * chore(changelog): add changelog.d fragment for #6944 Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: brick30llc-ctrl <admin@brick30.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(sse): flatten structured (array) content in Qwen Web executor (#6927) * fix(sse): flatten structured (array) content in Qwen Web executor foldMessages did String(m.content), turning OpenAI-style content-part arrays into the literal "[object Object]" prompt. Add contentToText() to extract the text parts. Reported on the support mesh. TDD: red->green regression test tests/unit/qwen-web-content-array-serialization.test.ts * docs(changelog): add fragment for #6927 * fix(stryker): register qwen-web content-array test in tap.testFiles Fast Quality Gates flagged the new coverage for open-sse/executors/qwen-web.ts as missing from stryker.conf.json's tap.testFiles list. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(db): add authType filter support to getProviderConnections (#6946) getProviderConnections ignored the authType query param, causing callers like tokenHealthCheck.ts and /api/token-health to fetch and decrypt every connection instead of only the OAuth ones they asked for. Add the missing auth_type WHERE clause and a regression test. Rebased to drop the unrelated 46-icon commit (duplicate of #6926) and the accidentally-committed tests/unit/authz/__stub_apiKeys.mjs runtime artifact; replaced with a real unit test asserting the authType filter excludes non-matching connections. Co-authored-by: oyi77 <oyi77@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(tokenHealthCheck): case-sensitive provider comparisons break rotating/gh checks (#6947) ROTATING_REFRESH_PROVIDERS.has(conn.provider) fails for 'OpenAI' or 'Github' - the set is all lowercase. Same issue for the GitHub Copilot sub-token refresh guard. Both now normalize to lowercase before comparison, matching the established pattern from getHealthCheckSkipProviders() (line 201) and isGitHubAccessTokenOnlyConnection() (line 94). Replaces the whole-file regex assertion in oauth-providers-error-handling (which passed even on unfixed code) with two statement-scoped regression tests that fail against origin/release/v3.8.47's unfixed source and pass only once both call sites are normalized. Co-authored-by: oyi77 <oyi77@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * perf: thread pre-fetched token to checkRateLimit avoiding re-query (#6930) * perf: thread pre-fetched token to checkRateLimit avoiding re-query getRelayTokenByHash already fetches the full RelayToken row. A few lines later checkRateLimit(token.id) does a second SELECT * FROM relay_tokens on a different predicate (id instead of token_hash). Change: - checkRateLimit accepts an optional existingToken parameter; when provided, skips the re-query entirely. - Both relay routes (chat completions + bifrost) pass the already- fetched token. - The function now uses RelayToken (camelCase) instead of RelayTokenRow (snake_case) when the token is passed in. PR-URL: fix-relay-thread-token * test(db): add regression coverage for checkRateLimit existingToken fast-path Adds node:test coverage for src/lib/db/relayProxies.ts::checkRateLimit proving the existingToken fast-path (pre-fetched RelayToken threaded in, no re-query) agrees with the legacy re-query path (no token passed), and that the per-minute cap is still enforced through the fast-path. Also adds a changelog.d fragment for the perf fix in9d4cd90e7. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: oyi77 <oyi77@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix: eliminate redundant getApiKeyMetadata call in embeddings route (#6929) enforceApiKeyPolicy() already fetches the API key metadata and returns it as policy.apiKeyInfo. The old code at line 72 called getApiKeyMetadata a third time per request (third hash+DB query after isValidApiKey and enforceApiKeyPolicy's internal fetch). Change: use policy.apiKeyInfo directly instead of re-querying. Also removes the now-unused getApiKeyMetadata import. Adds a regression test exercising the dashboard-playground-key path (no bearer token, only enforceApiKeyPolicy's resolvePlaygroundTestKey fallback resolves the key) — the old apiKeyRaw-gated call always produced a null apiKeyMeta on that path, while policy.apiKeyInfo correctly carries it through to the downstream call log. Split out of the original PR: dropped the unrelated 46-provider-icon commit that had been bundled onto the same branch. Co-authored-by: oyi77 <oyi77@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(sse): normalize assistant input_text to output_text in Codex Responses input (#6932) codex-cli replays assistant history with content parts typed as `input_text`, but the Responses API only accepts `output_text` (or `refusal`) on assistant turns — `input_text` is user-only. `normalizeCodexMessageContentPart` previously only rewrote parts literally typed `text`, leaving explicit `input_text` on assistant turns untouched, which the Codex/OpenAI backend rejects with a 400. Rewrite explicit `input_text` (and `text`) to `output_text` on assistant-role parts, dropping the assistant-only `annotations`, `logprobs`, and `obfuscation` fields. Mode-agnostic, applies to all Codex models. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(6813): fix thinking budget zero drop and default thinkingConfig injection (#6943) * fix(electron): bump electron 42→43 + build better-sqlite3 from source (ABI 148) (#6605) fix(electron): bump electron 42→43 + rebuild better-sqlite3 from source against the Electron ABI (148). Electron 43 raises NODE_MODULE_VERSION to 148; better-sqlite3@12.11.1 has no electron-v148 prebuild, so the packaged app died with 'Nenhum driver SQLite disponível'. prepare-electron-standalone now compiles better-sqlite3 from source against the electron headers into build/Release (where 'bindings' resolves it). Validated by Electron Package Smoke (green) + local (node_register_module_v148). Supersedes #6378. (--admin: the only reds are SonarQube/SonarCloud failing on a coverage-report artifact digest-mismatch — a GitHub Actions infra flake, not this diff; Sonar is green on main and the diff touches only the electron build.) * deps: bump the development group across 1 directory with 6 updates (#6588) deps: bump the development group (6 updates). Rebased onto current main; all checks green after the electron-smoke fix (#6605). * fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump (#6620) fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump. undici 8.6+ changed ProxyAgent to forward plain-HTTP via request-proxy instead of CONNECT, breaking OAuth refresh through a connection proxy (501). proxyDispatcher now passes proxyTunnel:true. Validated: Unit Tests 3/8 (the OAuth-proxy test) green, new regression test green (fails without the fix on undici 8.7), SonarQube green. Supersedes #6380. (--admin: the only red is Electron Package Smoke failing on a next-build artifact 'digest-mismatch' — a GitHub Actions infra flake corrupting the asar ('file data stream has unexpected number of bytes'); the better-sqlite3 rebuild itself succeeded (gyp ok) and the electron path is unchanged from #6605 which passed the smoke. Not this diff.) * fix(6813): fix thinking budget zero drop and default thinkingConfig injection - Fix truthy check for budget_tokens to allow 0 - Stop injecting default thinkingConfig when no knobs present - Add tests covering all scenarios Related: #6813 --------- Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * feat(sse): add connection backpressure for chat handler (#6590) Add checkConnectionCapacity guard with 429 + Retry-After in handleChat(). Introduce OMNI_MAX_CONCURRENT_CONNECTIONS env-bound cap, disabled (0) by default so existing deployments are unaffected until an operator opts in. Reconstructed from PR #6590, isolating only the backpressure change — the original branch also carried unrelated headroom/docker/perf work from the author's separate #6572 branch. Co-authored-by: oyi77 <oyi77@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(base): fix 2 mechanical release-tip base-reds (relayProbeStats re-export + OMNI_MAX_CONCURRENT_CONNECTIONS docs) * fix(sse): apply commentary-phase drop filter in TRANSLATE mode (#6952) (#6990) The #6199/#6561 commentary-phase filter (shouldDropResponsesCommentaryEvent) was wired only into createSSEStream's PASSTHROUGH branch. The TRANSLATE-mode loop (openai-responses upstream -> another client format, e.g. codex routes streaming into Claude Code) called translateResponse() on every raw chunk without checking phase, so internal commentary-phase scratchpad text leaked into the client-visible content channel as duplicate prose and narrated tool-call arguments. Extends the same stateful filter into TRANSLATE mode via a small factory (createTranslateCommentaryFilter) that owns its own item/index Sets, keeping the wiring in stream.ts (a frozen file) to a single guarded line. Fail->pass evidence: - tests/unit/repro-6952-commentary.test.ts against origin/HEAD (pre-fix): FAILED - "commentary-phase prose must not reach the translated client stream" - Same test against the fix: PASSED (2/2) * fix(combos): show embedding/rerank models and disambiguate duplicate names in builder options (#6975, #6957) (#6991) Removes the leftover chat-only isChatCapable gate from addModelOption() (#6975) and adds a name-disambiguation pass at the end of buildModelOptions() so distinct model ids sharing the same upstream display name fall back to their id (#6957). Both proven with TDD repro tests (RED->GREEN). * fix(sse): schema-aware optional tool-arg normalization for Codex routes (#6951) (#6992) stripEmptyOptionalToolArgs was allowlist-only (Read/Subagent) and only stripped empty-string/empty-array values, so Responses API strict mode (every property forced into `required`) could forward a forced non-empty value (e.g. Agent.isolation) or a schema-declared default value verbatim to the client. Add schema-aware drop-if-default and generalized drop-if-empty (any tool, gated on schema.required), and thread each tool's JSON Schema from the request's tools[] into the two streaming call sites (response.output_item.done handling). Closes #6951 * chore(base): fix release-tip base-reds — eslint severity revert (#6786 regression), migration gap 121, file-size freeze bumps * fix(test): align emergency fallback budget-exhaustion test with #6912 max_tokens normalization (#6967) The test asserted both max_tokens and max_completion_tokens=4096 on the nvidia/openai/gpt-oss-120b emergency fallback request. Commita34fb6b3e(#6912, merged into this release tip) added a symmetric normalization in chatCore.ts that renames/deletes the redundant max_completion_tokens field whenever the target provider supportsMaxTokens() (nvidia does), so only max_tokens reaches the upstream request. The old dual-field assertion is an outdated contract, not a regression. Align the test to the new intentional behavior while keeping the max_tokens=4096 cap assertion as the fallback-cap guard. * fix(test): deterministic openadapter live-catalog import repro (#6967) * chore(base): backfill #6909 i18n keys (en+pt-BR) and align gemini defaults test with #6943 (unit-full pre-flight) * chore(release): v3.8.47 pre-flight fixes — orphan test relocation (#6943), eslint suppression match, file-size/zizmor rebaselines * fix(quality): restore zizmorFindings ratchet object shape (value 169 + justification) * chore(quality): v3.8.47 cycle-close pct rebaselines (openapiCoverage 39.3->38, i18nUiCoverage 76.8->75.5) with justification * docs(changelog): v3.8.47 reconciliation — aggregate 125 fragments, backfill 41 missing bullets, contributors hall (55), date header * docs(release): v3.8.47 feature documentation sync (What's New + doc updates + provider reference regen) * chore(quality): allowlist verified assert reductions from #6897 de-flake and #6862 stronger deepEqual (test-masking) * chore(quality): allowlist remaining verified assert migrations (#6862 GPT-5.6 matrix, #6675 provider removals) * fix(skills): remove uncataloged skills/cli-skill-collector orphan (added by #6294 without a catalog entry — skills/ is generated from the catalog) * fix(combo,ws): comboStickyRoundRobinLimit inherits instead of shadowing batched default; LiveWS standalone script boots again (#6678/#6072 follow-ups caught by release CI) * fix(security): linear-time Basic-auth regex in the dev webdav handler (CodeQL js/polynomial-redos #708) * fix(dashboard): restore poolLoaded/poolSaving state deleted by #6909 refactor (settings page runtime crash, caught by release E2E) * fix(dashboard): restore the 8 bulk-import state declarations deleted by #6625 hook extraction (ReferenceError: bulkImportOpen — release E2E) --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Chirag Singhal <76880977+chirag127@users.noreply.github.com> Co-authored-by: KooshaPari <42529354+KooshaPari@users.noreply.github.com> Co-authored-by: Jillur Rahman <developerjillur@gmail.com> Co-authored-by: diegosouzapw <diegosouzapw@devbox.local> Co-authored-by: diegosouzapw <souzamiriamrodrigues790@gmail.com> Co-authored-by: PizzaV <103120356+pizzav-xyz@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: pizzav-xyz <pizzav-xyz@users.noreply.github.com> Co-authored-by: ThongAccount <206392198+ThongAccount@users.noreply.github.com> Co-authored-by: backryun <bakryun0718@proton.me> Co-authored-by: Septianata Rizky Pratama <19322988+ianriizky@users.noreply.github.com> Co-authored-by: Hamsa_M <116961508+hamsa0x7@users.noreply.github.com> Co-authored-by: hamsa0x7 <hamsa0x7@users.noreply.github.com> Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com> Co-authored-by: oyi77 <oyi77@users.noreply.github.com> Co-authored-by: Chirag Singhal <chirag127@users.noreply.github.com> Co-authored-by: enjoyer-hub <miseylorenach@gmail.com> Co-authored-by: quanturbo <faralechko@gmail.com> Co-authored-by: Xiangzhe <32761048+xz-dev@users.noreply.github.com> Co-authored-by: MikeTuev <ra9ftm@gmail.com> Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com> Co-authored-by: herjarsa <herjarsa@users.noreply.github.com> Co-authored-by: Arthur Bodera <abodera@gmail.com> Co-authored-by: Thinkscape <thinkscape@users.noreply.github.com> Co-authored-by: SeaXen <71036788+SeaXen@users.noreply.github.com> Co-authored-by: SeaXen <SeaXen@users.noreply.github.com> Co-authored-by: nowhats-br <brazziltec@gmail.com> Co-authored-by: nowhats-br <nowhats-br@users.noreply.github.com> Co-authored-by: Moseyuh333 <148680980+Moseyuh333@users.noreply.github.com> Co-authored-by: Moseyuh333 <Moseyuh333@users.noreply.github.com> Co-authored-by: janeza2 <49841619+janeza2@users.noreply.github.com> Co-authored-by: Imam Wahyu Widodo <120608486+hajilok@users.noreply.github.com> Co-authored-by: hajilok <hajilok@users.noreply.github.com> Co-authored-by: NOXX - Commiter <artur1992123@mail.ru> Co-authored-by: artickc <artickc@users.noreply.github.com> Co-authored-by: Thiago Reis <strangersp@outlook.com> Co-authored-by: strangersp <strangersp@users.noreply.github.com> Co-authored-by: Jan Leon <Jan.gaschler@gmail.com> Co-authored-by: JxnLexn <JxnLexn@users.noreply.github.com> Co-authored-by: Ronaldo Davi <ronaldodavi@gmail.com> Co-authored-by: Andrew Munsell <andrew@wizardapps.net> Co-authored-by: WITALO ROCHA <witalo_rocha@hotmail.com> Co-authored-by: Wital <witalorocha216@gmail.com> Co-authored-by: Aoxiong Yin <i@yinaoxiong.cn> Co-authored-by: Andrew B. <37745667+AndrianBalanescu@users.noreply.github.com> Co-authored-by: Andrian B. <andrewbalanesq@gmail.com> Co-authored-by: Jon Bailey <297513015+Pitchfork-and-Torch@users.noreply.github.com> Co-authored-by: Pitchfork-and-Torch <Pitchfork-and-Torch@users.noreply.github.com> Co-authored-by: Samir Abis <me@samirabis.com> Co-authored-by: lucasjustinudin <34107354+lucasjustinudin@users.noreply.github.com> Co-authored-by: chy1211 <31048289+chy1211@users.noreply.github.com> Co-authored-by: ryanngit <74137224+ryanngit@users.noreply.github.com> Co-authored-by: whale9820 <whale9820@users.noreply.github.com> Co-authored-by: anhdiepmmk <n08ni.dieppn@gmail.com> Co-authored-by: lunkerchen <labanchen@gmail.com> Co-authored-by: lunkerchen <lunkerchen@users.noreply.github.com> Co-authored-by: Ray Doan <raydoan.contact@gmail.com> Co-authored-by: kfiramar <83420275+kfiramar@users.noreply.github.com> Co-authored-by: Someres <168349709+quanturbo@users.noreply.github.com> Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: AgentKiller45 <jamalzzj45@gmail.com> Co-authored-by: judy459 <JUDYZHU459@outlook.com> Co-authored-by: growab <nekron@icloud.com> Co-authored-by: Markus Hartung <mail@hartmark.se> Co-authored-by: KooshaPari <koosha@phenotype.io> Co-authored-by: Jade Guo <jade.gly@gmail.com> Co-authored-by: Dayna Blackwell <dayna@blackwell-systems.com> Co-authored-by: backryun <backryun@daonlab.local> Co-authored-by: brick30llc-ctrl <brick30llc@gmail.com> Co-authored-by: brick30llc-ctrl <admin@brick30.com> Co-authored-by: Saren <saren@dumstruck.com> Co-authored-by: Rafael Dias Zendron <mmmarckos@gmail.com>
67 KiB
🚀 OmniRoute — 免费 AI 网关
🌐 语言: 🇺🇸 English · 🇸🇦 ar · 🇧🇬 bg · 🇧🇩 bn · 🇨🇿 cs · 🇩🇰 da · 🇩🇪 de · 🇪🇸 es · 🇮🇷 fa · 🇫🇮 fi · 🇫🇷 fr · 🇮🇳 gu · 🇮🇱 he · 🇮🇳 hi · 🇭🇺 hu · 🇮🇩 id · 🇮🇹 it · 🇯🇵 ja · 🇰🇷 ko · 🇮🇳 mr · 🇲🇾 ms · 🇳🇱 nl · 🇳🇴 no · 🇵🇭 phi · 🇵🇱 pl · 🇵🇹 pt · 🇧🇷 pt-BR · 🇷🇴 ro · 🇷🇺 ru · 🇸🇰 sk · 🇸🇪 sv · 🇰🇪 sw · 🇮🇳 ta · 🇮🇳 te · 🇹🇭 th · 🇹🇷 tr · 🇺🇦 uk-UA · 🇵🇰 ur · 🇻🇳 vi · 🇨🇳 zh-CN · 🇹🇼 zh-TW
🚀 OmniRoute — 免费 AI 网关
编码,永无止境。通过一个端点,让所有 AI 工具直连 236 家服务商 — 50+ 家免费。
将 Claude Code、Codex、Cursor、Cline、Copilot 和 Antigravity 接入免费的 Claude / GPT / Gemini。自动容灾,无感切换。
RTK + Caveman 压缩引擎,Token 节省 15–95%。从此告别用量限制。
约 1.6B 可统计免费 Token / 月 — 计入注册奖励后,首月最高可达 ~2.1B — 聚合各家免费层配额,外加一众永久免费、不限量的服务商;再叠加上述压缩引擎,每一枚 Token 都物超所值。(统计方法 →)
💬 加入社区
疑难解答、服务商攻略、路线图与支持 → Discord · Telegram · WhatsApp 🌍 全球 / 🇧🇷 巴西
🚀 快速开始 • 🎯 Combo • 🌐 服务商 • 🔌 CLI 与 MCP • 🗜️ 压缩 • 🌍 官网
💥 我们的承诺 • 🤔 为什么选择 OmniRoute • 🏆 核心优势 • 🤖 兼容的编程工具 • 🖥️ 运行平台 • 🔒 隐私优先 • 🎬 实机演示 • 📚 探索更多 • 📧 支持
| 🇺🇸 | 🇧🇷 | 🇪🇸 | 🇫🇷 | 🇮🇹 | 🇷🇺 | 🇨🇳 | 🇹🇼 | 🇩🇪 | 🇯🇵 | 🇰🇷 |
| 🇹🇭 | 🇻🇳 | 🇮🇩 | 🇲🇾 | 🇵🇭 | 🇸🇦 | 🇮🇱 | 🇦🇿 | 🇺🇦 | 🇵🇱 | 🇨🇿 |
| 🇳🇱 | 🇧🇬 | 🇩🇰 | 🇫🇮 | 🇳🇴 | 🇸🇪 | 🇭🇺 | 🇷🇴 | 🇸🇰 | 🇵🇹 |
💰 约 1.6B 免费 Token / 月
手动凑各家免费额度有多痛苦 — 数十套 SDK、数十个速率限制,根本搞不清到底还剩多少。OmniRoute 将 40+ 服务商池 / 500+ 模型的可核实免费层聚合为一个真实的统一数字,并在控制台实时展示 (
/dashboard/free-tiers)。
- 约 1.6B 免费 Token / 月(稳定值) — 注册奖励加持下,首月最高约 2.1B。
- 去重统计,诚实透明 — 每个共享免费池只计一次,标题数字不被速率上限注水。若以全天候速率上限累算会得出 ~10B 的虚假数据,我们从不发布此类数字。
- 外加不可计数的部分 — 永久免费、无 Token 上限的服务商(SiliconFlow、Z.AI GLM-Flash、Kilo、OpenCode Zen…)以及 $10 的 OpenRouter 充值可解锁 +24M/月,二者独立列示,绝不混入标题数字。
- 逐模型明细、当月已用 / 剩余实时显示,以及每家服务商的透明条款标注。
示例预览 — 待
/dashboard/free-tiers页面验证后替换为真实截图。完整统计方法(池去重、额度层级、服务商条款):docs/reference/FREE_TIERS.md。
💥 我们的承诺
一个端点。236 家服务商。 编码不止步 — 让 OmniRoute 帮你选出最便宜且可用的那个。
| 🚫 永不触达限制 横跨 236 家服务商的毫秒级自动切换。配额耗尽?下一家即刻接管 — 零停机。 |
💸 Token 节省高达 95% RTK + Caveman 级联压缩可削减 15–95% 的可压缩 Token(工具密集型会话平均约 89%)。 |
🆓 零元起步 50+ 家服务商提供免费层,其中 11 家永久免费(Kiro、Qoder、Pollinations、LongCat…)。无需绑卡。 |
| 🔌 所有工具一网打尽 16+ 款编程助手 — Claude Code、Codex、Cursor、Cline、Copilot、Antigravity — 一套配置全搞定。 |
🧩 一个端点通吃 OpenAI ↔ Claude ↔ Gemini ↔ Responses API 无缝翻译。任意工具指向 /v1 即开即用。 |
🛡️ 生产级品质 熔断器、TLS 指纹伪装、MCP(87 工具)、A2A、记忆系统、安全护栏、评估框架。14,965 项测试。 |
🤔 为什么选择 OmniRoute?
告别在十个控制台之间疲于奔命、处理失效的 API 密钥和天降账单的日子。
| ❌ 日常痛点 | ✅ OmniRoute 如何解决 |
|---|---|
| 📉 每月订阅配额用不完就浪费 | 压榨订阅价值 — 追踪配额,在重置前用尽每一枚 Token |
| 🛑 写到一半被限速打断 | 四层自动切换 — 订阅 → API Key → 廉价 → 免费,毫秒级接续 |
🔥 工具输出(git diff、grep、日志)狂烧 Token |
RTK + Caveman 压缩 — 每次请求可省 15–95% 可压缩 Token |
| 💸 昂贵的 API(每服务商 $20–50/月) | 成本优先路由 — 自动导向性价比最高的可用模型 |
| 🧰 每款 AI 工具各有一套繁琐配置 | 一个端点、一套配置、一个控制台 |
| 🌍 所在国家/地区封锁 AI | 三级代理 + TLS 指纹伪装 — 无论身在何方,AI 任你用 |
┌──────────────────────────────────────────────────────────┐
│ 你的 IDE / CLI (Claude Code, Cursor, Cline…) │
└─────────────────────────┬──────────────────────────────────┘
│ http://localhost:20128/v1
▼
┌──────────────────────────────────────────────────────────┐
│ OmniRoute — 智能路由中枢 │
│ RTK + Caveman 压缩 · 17 种路由策略 │
│ 熔断器 · TLS 指纹伪装 · MCP · A2A · 安全护栏 │
└─────────────────────────┬──────────────────────────────────┘
┌─────────────┬────┴────────┬─────────────┐
▼ 第一梯队 ▼ 第二梯队 ▼ 第三梯队 ▼ 第四梯队
订阅 API Key 廉价 免费
Claude Code, DeepSeek, GLM $0.5, Kiro, Qoder,
Codex, Copilot Groq, xAI MiniMax $0.2 Pollinations
配额耗尽? ───▶ 预算触顶? ─▶ 预算触顶? ─▶ 永久在线
🎯 Combo — 招牌功能
Combo 是 OmniRoute 自动路由的模型接力链路。配额耗尽、服务商宕机或成本飙升 — Combo 自动滑向下一个模型,无声无息。正是它让 OmniRoute 坚不可摧。 🛡️
⚡ 零配置 — 只需设为 auto
无需预先配置 Combo。将模型 ID 设为 auto(或其变体),OmniRoute 会基于你已连接的服务商实时评分,自动构建虚拟 Combo:
| 模型 ID | 优化目标 |
|---|---|
auto |
🎯 均衡默认(LKGP — 沿用上次表现最好的服务商) |
auto/coding |
🧑💻 代码质量优先 |
auto/fast |
⚡ 最低延迟优先 |
auto/cheap |
💰 单位 Token 成本最低优先 |
auto/offline |
🔋 配额 / 限速余量最充裕优先 |
auto/smart |
🔭 质量优先 + 10% 探索度以发现更优模型 |
🔀 或亲手定制 — 17 种路由策略
| 目标 | 对应策略 / 组合 |
|---|---|
| 🥇 榨干订阅额度再用付费 | priority / fill-first |
| ⚖️ 跨账号均衡负载 | round-robin · weighted · p2c · least-used |
| 💸 永远选最便宜的可行模型 | cost-optimized · auto/cheap |
| 🧠 模型间接力传递长上下文 | context-relay · context-optimized |
| 🎲 随机 / 隐私路由 | random · strict-random |
| 🧬 多模型并行 + 裁判裁决 | fusion |
| 📊 按剩余配额余量路由 | reset-window · headroom |
| 🤖 智能自动 | auto(9 维度评分)· lkgp · reset-aware |
Auto-Combo 引擎基于 9 个维度(健康度、配额、成本、延迟、成功率、新鲜度…)逐候选打分 — 详见 docs/routing/AUTO-COMBO.md。
🧱 内置三层容灾
| 层级 | 作用范围 | 机制 |
|---|---|---|
| 🔌 熔断器 | 整家服务商 | 停止向上游持续失败的服务商发送请求;自动探测恢复 |
| 💤 连接冷却 | 单个账号 / 密钥 | 跳过快触达速率上限的密钥,其余密钥继续服务 |
| 🎯 模型隔离 | 服务商 + 模型 | 仅隔离单一配额耗尽的模型,不影响该服务商的其他连接 |
Combo: "always-on" 策略: priority
1. cc/claude-opus-4-7 ← 订阅(先用满)
2. cx/gpt-5.5 ← 第二订阅
3. glm/glm-5.1 ← 廉价备选 ($0.5/1M)
4. kr/claude-sonnet-4.5 ← 免费、无限(永不断线)
结论: 四层容灾 = 零停机
📖 Auto-Combo 引擎 · 容灾指南
🏆 OmniRoute 何以脱颖而出
| 功能 | OmniRoute | 其他路由方案 |
|---|---|---|
| 🌐 服务商数量 | 231 | 20–100 |
| 🆓 免费服务商 | 50+ (其中 11 家永久免费) | 1–5 |
| 🔀 路由策略 | 17 种(优先级、加权、成本优先、上下文中继、融合…) | 1–3 |
| 🗜️ Token 压缩 | RTK + Caveman 级联(15–95%) | 无 / 20–40% |
| 🧰 内置 MCP 服务器 | 87 个工具、3 种传输、30 个权限域 | 少见 |
| 🤝 A2A 代理协议 | 6 项技能、JSON-RPC 2.0 | 无 |
| 🧠 记忆系统(FTS5 + 向量) | 原生支持 | 少见 |
| 🛡️ 安全护栏(PII、注入、视觉) | 原生支持 | 少见 |
| ☁️ 云代理 | Codex、Devin、Jules | 无 |
| 🥷 TLS 指纹伪装 | JA3/JA4 基于 wreq-js | 无 |
| 🖥️ 多平台 | Web · 桌面 · Termux · PWA | 仅 Web |
| 🌍 国际化 | 42 种语言 | 0–4 |
📊 与 LiteLLM、OpenRouter、Portkey 的详细对比 → docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md
✨ 近期更新
v3.8.20 → v3.8.41 重点更新。完整日志见
CHANGELOG.md。
- ⚖️ Quota-Share 路由 — 专用 Combo 策略,按可用配额跨账号分配负载:Deficit-Round-Robin 调度、每连接
max_concurrent配合冷却等待队列、多时间窗口用量桶(5 小时 / 7 天 / 每模型)、每 (密钥, 模型) 用量上限、会话粘性保障 Prompt 缓存完整性,以及基于上游 Token 用量头的主动饱和检测。→ 容灾指南 - 🤖 一键 CLI/Agent 配置 — 专用
setup-*命令为各编程工具一键配置 OmniRoute 路由(Claude Code、Codex、Cline、Continue、Cursor、Roo Code、Kilo Code、Crush、Goose、Qwen Code、Aider、OpenCode);omniroute launch/omniroute launch-codex为零配置启动器。→ CLI 集成 - 🛰️ 远程模式 — 通过授权范围 Token 从任意机器操控远程 OmniRoute(
omniroute connect/omniroute contexts/omniroute tokens);另附omniroute login antigravity辅助命令,在你的本机运行 Google "native/desktop" OAuth 后将凭证 blob 粘贴至远程/VPS 安装实例(因远程环境无法接收 loopback 回调)。→ 远程模式 - 🧭 更智能的自动路由 — OpenRouter 风格的
auto/<category>:<tier>Combo(如auto/coding:fast、auto/reasoning:pro)、Fusion 策略(并行分发至多模型面板后由裁判合成最优结果)、任务感知路由(按任务类型匹配最佳连接)、每请求X-Route-Model覆盖、实时 Arena-ELO + models.dev 模型智能评分、每步骤账号白名单、服务商通配符策略步骤、嵌套Combo引用执行、粘性加权选择以及web_search感知路由。→ Auto-Combo - 🗜️ 可插拔压缩体系 — 9 大可组合引擎的异步流水线,含 Compression Studios、LLMLingua-2 ONNX 引擎和启发式/SLM 双层 Ultra、RTK、委托式 Anthropic 上下文编辑、输出风格(输出轴调控:简洁文章 / 少代码 / 简洁文言)、自适应上下文预算旋钮(仅推进到刚好适应上下文窗口的程度)、每请求
x-omniroute-compression控制、可选离线评估套件、控制台一键 Headroom 代理生命周期管理(支持 Docker 边车)、合成压缩演练场(Play 通道 + A/B 对比,附 USD 上限保真度判定)、可选每步保真度门控(在有损引擎降低 Prompt 质量前将其拦截)、Best-of-N 候选编码器(GCF vs TOON — 取更短者,Studio 中附 A/B 字节/Token 对照表)、CCR 范围/grep/统计检索(直接拉取储存块的精确字节/行切片或摘要而无需全量展开),以及统一面板含命名配置文件 + 活动配置文件选择器。→ 压缩 - 🕵️ 透明 MITM 解密(TPROXY) — 捕获并翻译忽略代理环境变量的 CLI 流量,含每 SNI 证书颁发机构和信任存储安装器。→ MITM/TPROXY
- 💸 全方位成本遥测 — 每个端点上的
X-OmniRoute-*成本/用量响应头(含媒体端点)、非 Token 成本引擎、缓存命中X-OmniRoute-Cost-Saved响应头,以及每密钥美元消费配额。→ API 参考 - 🧠 完全可控的记忆系统 — 可选 int8 向量量化(Qdrant + sqlite-vec)、默认关闭记忆、每请求
x-omniroute-no-memory响应头。→ 记忆系统 - 🛡️ 安全 — 所有 LLM 路由的提示注入防护(后台有红队测试套件),外加免费的 DuckDuckGo 兜底网页搜索。→ 安全护栏
- 🤝 更多服务商与代理 — Cursor Cloud Agent(第四云代理)、CodeBuddy CN(
copilot.tencent.com)、Google Flow 视频生成服务商、新网关 DGrid 和 Pioneer AI(Fastino Labs)、入站 xAI Grok 翻译器加 Grok Build (xAI)(含 OAuth 导入 Token 流程)、GitHub Copilot 服务商的 GPT-4 / GPT-4o-mini、多模型 Factory Droid、ZenMux Free(会话 Cookie 免费层)、阿里云 DashScope 文生视频(wan2.7-t2v)、刷新至 236 家服务商的目录(OrcaRouter、Wafer AI、OpenAdapter、dit.ai、TokenRouter…)、Vertex AI 媒体生成(语音/转录/音乐/视频),以及一键从 CLIProxyAPI 导入账号(~/.cli-proxy-api/)。→ 服务商 - ⚡ 本地性能与基础设施 — 一键本地 Redis 启动器(
omniroute redis up,含控制台 Redis 面板)、一键 Cloudflare Workers 和 Deno Deploy 中继部署器(接入代理池),以及可选 Bifrost Go 边车将最热中继路径卸载至 Go 侧(BIFROST_BASE_URL,超时自动回退 TypeScript 路径)— 现支持中继后端选择器(OMNIROUTE_RELAY_BACKEND=ts|bifrost|auto),/v1/relay端点保持对外稳定接口的同时内部自动择取最快后端。→ 环境配置
🤖 兼容的 CLI 与编程助手
一个配置 —
http://localhost:20128/v1— 所有 AI IDE 或 CLI 都能跑在免费与低成本模型上。
Claude Code |
Codex CLI |
![]() Cursor |
![]() Copilot |
![]() Continue |
|
OpenCode |
Kilo Code |
Droid |
![]() OpenClaw |
Kiro |
Command |
📖 16+ 款工具的逐项配置指南 → docs/reference/CLI-TOOLS.md · 🧩 OpenCode 插件 → @omniroute/opencode-provider
🌐 231 家 AI 服务商 — 50+ 家免费
开源路由方案中最完整的服务商目录:236 家服务商、50+ 家含免费层、11 家永久免费。
🆓 永久免费 — 零元,无需绑卡
GPT-5、Claude、Gemini $100 免费额度 |
Kimi-K2、DeepSeek-R1 无限免费 |
GPT-5、Claude、Llama 4 无需密钥 |
LongCat-2.0 一次性 10M Token (需 KYC) 🔑 |
50+ 模型 10K 神经元/天 |
129 个模型 ~40 RPM 免费 |
Qwen3 235B 1M Token/天 |
📖 完整机器可读目录 → docs/reference/PROVIDER_REFERENCE.md
🖥️ OmniRoute 运行平台 — 无处不在
同一套应用,你的机器,你的规则。从全局
npm install到你的手机(通过 Termux),无所不跑。
| 平台 | 安装方式 | 亮点 |
|---|---|---|
| 📦 npm(全局) | npm install -g omniroute |
一行命令,任意 OS |
| 🐳 Docker | docker run … diegosouzapw/omniroute |
多架构 AMD64 + ARM64 |
| 🖥️ 桌面(Electron) | npm run electron:build |
原生窗口 + 系统托盘 — Windows / macOS / Linux |
| 💪 ARM | 原生 arm64 |
树莓派、ARM 服务器、Apple Silicon |
| 📱 Android(Termux) | pkg install nodejs && npx -y omniroute |
在手机上 7×24 运行,无需 Root |
| 📲 PWA | "添加到主屏幕" | 全屏、离线、可从浏览器安装 |
| 🧩 OpenCode 插件 | @omniroute/opencode-provider |
原生 OpenCode 集成 |
| 🛠️ 源码构建 | npm install && npm run dev |
动手改造,贡献代码 |
📖 Docker 指南 · 桌面端 · Termux · PWA · OpenCode
🔒 隐私优先,数据本地
你的密钥、你的机器、你的数据。OmniRoute 是本地代理 — 绝不会向外回传。
- 🏠 100% 运行在本地硬件上 — npm、Docker、桌面端或你的手机。请求链路中不存在任何 OmniRoute 云端节点。
- 🔐 凭据静态加密 — API 密钥与 OAuth 令牌以 AES-256-GCM 封存。
- 🚫 默认零遥测 — 你的提示只发送给你选定的服务商,别无他处。
- 🛡️ 网关加固 — API 密钥权限域、IP 过滤、速率限制、提示注入防护、仅限 loopback 的进程路由。
- 📜 MIT 协议、完全开源 — 逐行可审计,永久可自托管。
🔌 完整 CLI + A2A 与 MCP
OmniRoute 不只是一台服务器 — 它是拥有 60+ 命令的全功能命令行驾驶舱,外加开放的代理协议,让 AI 代理自主操控 OmniRoute。
⌨️ 真正的 CLI(不止 start)
omniroute # 启动网关 + 控制台(端口 20128)
omniroute chat # 交互式 TUI 聊天客户端(斜杠命令:/model /combo /skill /memory)
omniroute setup # 引导式首次设置向导
omniroute doctor # 诊断服务商、端口、原生依赖
🛰️ 远程模式 — CLI 在本地,OmniRoute 在远端的 VPS
OmniRoute 跑在服务器上?用同一套 CLI 从笔记本远程操控。登录一次,绑定授权范围 Token;后续所有命令自动指向远端。
omniroute connect 192.168.0.15 # 密码 → 范围 Token,保存为上下文
omniroute models list # ← 在远端服务器上执行
omniroute configure codex # ← 选择远端模型,写入本地 Codex 配置文件
omniroute tokens create --name ci --scope read # 为其他机器签发更窄范围的 Token
omniroute contexts use default # ← 切回本机服务器
Token 权限域为 read / write / admin;涉及进程启动的路由仅限 loopback 执行。
📖 远程模式
providers · oauth · keys · combo · nodes · models · cache · compression · cost · usage · quota · health · resilience · telemetry · logs · audit · mcp · a2a · cloud · memory · skills · eval · tunnel · backup · sync · webhooks · policy · pricing · translator · simulate …
🤝 接入 AI 代理 — 让代理自主操控 OmniRoute
通过 MCP 或 A2A 协议暴露 OmniRoute,任何智能代理都能获得网关的完整控制权 — 路由、服务商、Combo、缓存、压缩、记忆 — 全自主运行。
| 协议 | 端点 | 用途 |
|---|---|---|
| 🧰 MCP(stdio) | omniroute --mcp |
接入 Claude Desktop、Cursor 等各种 MCP 客户端 |
| 🌊 MCP(HTTP) | http://localhost:20128/api/mcp/stream |
远程 MCP — 87 个工具、30 个权限域、完整审计追踪 |
| 📡 MCP(SSE) | http://localhost:20128/api/mcp/sse |
流式 MCP 传输 |
| 🤝 A2A | http://localhost:20128/.well-known/agent.json |
代理间通信,JSON-RPC 2.0 + SSE,6 项技能 |
# 通过 MCP 将 OmniRoute 完整工具集赋予 Claude Code:
claude mcp add-server omniroute --type http --url http://localhost:20128/api/mcp/stream
🗜️ 自动节省 15–95% Token
Token 够用就好,何必铺张浪费? 每个请求透明地通过 OmniRoute 压缩流水线 — 客户端无需任何改动。现已升级为 9 大可组合引擎的级联体系,按 Combo 自由排列组合 — 凝聚了 RTK、Caveman(⭐ 51K+)、LLMLingua-2 和 Troglodita(PT-BR)的技术精华。
🧱 九引擎级联体系
引擎按流水线顺序执行;每个引擎均可独立启停,按 Combo 粒度配置:
| # | 引擎 | 作用 |
|---|---|---|
| 1 | Session-Dedup | 剔除跨轮次重复的内容(基于内容寻址,跨轮次比对) |
| 2 | CCR | 将大文本块归档到检索标记后,按需拉取 |
| 3 | RTK | 智能工具输出过滤、去重与截断(理解命令语义) |
| 4 | Headroom | 同构 JSON 数组的无损表格式压缩(~30%+) |
| 5 | Caveman | 基于规则的叙述性文本压缩(输出端约 65–75%) |
| 6 | LLMLingua-2 | 基于 MobileBERT ONNX 的 ML 语义剪枝 — 代码安全、异步 |
| 7 | Lite | 空白符 + 图片 URL 精简(低延迟基线) |
| 8 | Aggressive | 摘要浓缩 + 老旧轮次渐进式老化 |
| 9 | Ultra | 启发式 Token 剪枝 + 可选小模型(SLM)层 |
代码块、URL 和结构化数据永远逐字节原样保留。一键预设快速组合引擎:
| 模式 | 节省比例 | 最佳场景 |
|---|---|---|
| 🪶 Lite | ~15% | 常驻开启的安全默认 |
| 🪨 标准(Caveman) | ~30% | 日常编码 |
| ⚡ Aggressive | ~50% | 长时间工具密集型会话 |
| 🔥 Ultra | ~75% | 最大化节省 |
| 🧰 RTK | 60–90% | Shell/测试/构建/Git 输出 |
| 🔗 级联(RTK → Caveman) | 78–95% | 混合提示 + 工具日志 |
真实案例 — 标准模式:
压缩前(69 Token): "The reason your React component is re-rendering is likely because you're creating a new object reference on each render cycle. When you pass an inline object as a prop, React's shallow comparison sees it as a different object every time, which triggers a re-render. I would recommend using useMemo to memoize the object."
压缩后(19 Token): "New object ref each render. Inline object prop = new ref = re-render. Wrap in useMemo."
同样的回答。节省 72% Token。精度毫无损失。 ✅
PT-BR 案例 — Troglodita 模式:
压缩前(42 Token): "O problema é que o componente está re-renderizando porque uma nova referência de objeto está sendo criada em cada ciclo de renderização. Eu recomendaria usar useMemo."
压缩后(12 Token): "Re-render: ref nova cada ciclo (objeto inline recriado). Usar
useMemo."同样的回答。约 70% 更少 Token。技术精度完好无损。 ✅
📖 工作原理 — 流水线、架构与节省量计算
Client (10,000 tok) ──▶ OmniRoute Compression (9 engines) ──▶ Provider (~1,080 tok, 节省高达 95%)
默认级联组合为 RTK → Caveman。当二者作用于同一工具/上下文负载时,节省效果叠加:
组合节省率 = 1 − (1 − RTK) × (1 − Caveman_input)
平均值 = 1 − (1 − 0.80) × (1 − 0.46) = 89.2%
区间 = 78.4 – 94.6%
代码块、URL、JSON 和结构化数据始终受到保护引擎的保全。
🎚️ 引擎之外 — 输出风格、自适应旋钮与逐请求控制
上述 9 大引擎负责压缩输入端。还有三个额外层面,分别控制如何压、何时压以及输出端的效果:
- 🪄 输出风格 (输出轴调控) — 注入确定性强、缓存友好的响应结构指令;可组合使用,每项提供
lite/full/ultra三个强度档。添加风格只需一行注册代码:- 简明文章 — 剔除填充词/冠词/暧昧语;技术实质精确传达。
- 少即是多 — "经验丰富的高级开发" YAGNI 风格:最小化可用改动,不主动添加脚手架。
- 文言简雅 — 仿文言文的极致简洁风格(区域锁定至
zh)。
- 🎯 自适应上下文预算 (调节旋钮) — 取代简单的开/关阈值,改为渐次递进:从最轻量、最无损的引擎开始,仅推进到刚好适配目标模型上下文窗口的程度。策略:
reserve-output(默认,模型感知)·percentage·absolute。模式:floor(确保适配)·replace-autotrigger(你的显式选择优先)·off(传统阈值模式)。 - 🎛️ 压缩决策的优先链路 (从高到低) — 逐请求
x-omniroute-compression头 › Combo 覆写 › 活动命名配置 › 自适应/自动触发 › 面板默认 › 关闭。最终采用的压缩方案会通过X-OmniRoute-Compression: <mode>; source=<source>响应头回显。
可依阈值自动触发、旋钮自适应调节、固定命名配置文件、逐请求一次性压缩,或为每条Combo 专属分配流水线 — 工作负载千差万别,总有一种适配。可选离线评估套件(npm run eval:compression)在固定语料集上量化评分,助你在推广变更前验证保真度与节省效果。
📖 COMPRESSION_GUIDE.md · RTK_COMPRESSION.md · COMPRESSION_ENGINES.md
⚡ 快速开始
1) 安装并运行
npm install -g omniroute
omniroute
控制台:http://localhost:20128 · API:http://localhost:20128/v1
2) 连接免费服务商(无需注册)
控制台 → Providers → 连接 Kiro AI(免费 Claude,约 50 积分/月/账号)或 OpenCode Free(无需认证)→ 完成。
3) 配置你的编程工具
Base URL: http://localhost:20128/v1
API Key: [从 控制台 → Endpoints 复制]
Model: auto (零配置智能路由 — 也可指定任意服务商/模型)
4) 验证链路
curl http://localhost:20128/v1/models -H "Authorization: Bearer YOUR_KEY"
你应该能看到已连接模型的列表。🎉 至此大功告成 — 开始编码,OmniRoute 自动路由、自动容灾。
如果你的客户端无法发送自定义请求头,OmniRoute 也提供 Token 化兼容别名:
OpenAI 模型目录: http://localhost:20128/vscode/YOUR_KEY/
OpenAI 模型列表: http://localhost:20128/vscode/YOUR_KEY/models
OpenAI 聊天: http://localhost:20128/vscode/YOUR_KEY/chat/completions
OpenAI 响应: http://localhost:20128/vscode/YOUR_KEY/responses
Ollama 聊天: http://localhost:20128/vscode/YOUR_KEY/api/chat
Ollama 标签: http://localhost:20128/vscode/YOUR_KEY/api/tags
仅限无法附带 Authorization: Bearer ... 头的客户端使用。标准请求头认证始终是推荐方式。
📦 更多安装方式 — Docker、源码、pnpm、Arch
🐳 Docker
docker run -d --name omniroute --restart unless-stopped --stop-timeout 40 \
-p 20128:20128 -v omniroute-data:/app/data diegosouzapw/omniroute:latest
🛠️ 源码构建
cp .env.example .env && npm install
PORT=20128 npm run dev
📦 pnpm
pnpm add -g omniroute@latest --allow-build=better-sqlite3 --allow-build=@swc/core && omniroute
🐧 Arch Linux(AUR)
yay -S omniroute-bin && systemctl --user enable --now omniroute.service
🔧 Nix(Flake)
# 使用 Nix flakes
nix develop
npm run dev
# 或使用 devbox
devbox run npm run dev
📖 Docker 指南 — Compose 配置、Caddy HTTPS、Cloudflare 隧道。
🦭 Podman
# 1. 构建镜像
podman build --target runner-base -t omniroute:base .
# 2. 修复无 Root 权限 Podman 的数据目录权限
mkdir -p data && podman unshare chown 1000:1000 ./data
# 3. 在 .env 中设置运行时,然后运行(参见 contrib/podman/ 中的 Quadlet)
echo "CONTAINER_HOST=podman" >> .env
podman compose --profile base up -d
📖 Podman 指南 — Quadlet 设置、podman-compose、Quadlet。
🎬 实机演示
🎬 制作了关于 OmniRoute 的视频? 通过链接创建 issue 或 discussion — 我们将在本节予以展示。
📚 探索更多
💰 费用一览与零元免费栈(11 家服务商)
| 层次 | 举例 | 成本 |
|---|---|---|
| 💳 订阅制 | Claude Code Pro / Codex / Copilot | $10–200/月 |
| 🔑 API Key(含免费层) | NVIDIA NIM、Cerebras、Groq | 免费 |
| 💰 廉价 | GLM-5 $0.5/1M · MiniMax M2.5 $0.3/1M | 几分钱 |
| 🆓 永久免费 | Kiro、Qoder、Qwen、Pollinations、LongCat | $0 |
零元免费栈 — 合并为一条坚不可摧的 Combo:
| 服务商 | 前缀 | 免费模型 | 配额 |
|---|---|---|---|
| Kiro | kr/ |
Claude Sonnet 4.5、Haiku 4.5、Opus 4.6 | 50 积分/月 |
| Qoder | if/ |
kimi-k2-thinking、qwen3-coder-plus、deepseek-r1 | ♾️ 无限 |
| Qwen | qw/ |
qwen3-coder-plus/flash/next | ♾️ 无限 |
| Pollinations | pol/ |
GPT-5、Claude、Gemini、DeepSeek、Llama 4 | 无需密钥 |
| LongCat | lc/ |
LongCat-2.0 | 一次性 10M (需 KYC) |
| Cloudflare AI | cf/ |
50+ 模型 | 10K 神经元/天 |
| NVIDIA NIM | nvidia/ |
129 个模型 | ~40 RPM |
| Cerebras | cerebras/ |
Qwen3 235B、GPT-OSS 120B | 1M Token/天 |
💡 控制台上的"费用"是节省追踪器,而非账单 — OmniRoute 从不向你收费。显示"$290 总费用"意味着你使用免费模型省下了 $290。
📖 完整免费服务商目录 → docs/reference/FREE_TIERS.md — 25+ 家服务商、配额、Base URL。
🎯 实用场景 — 即拿即用的 Combo 配方
永久零元:
1. kr/claude-sonnet-4.5 (Kiro — ~50 积分/月/账号)
2. if/kimi-k2-thinking (Qoder — 无限)
3. pol/gpt-5 (Pollinations — 无需密钥)
4. lc/LongCat-2.0 (一次性 10M 备用,需 KYC)
压缩方案: aggressive (~50%) → 免费额度翻倍 · 成本: $0/月
7×24 无中断: 串联 2 个订阅 → 廉价 → 免费,五层容灾。
地理封锁区: 免费服务商 + 全局/按服务商代理 → 从任何国家访问 AI。
最大化节省: 订阅 + 廉价备用 + ultra 压缩(~75%)→ 重度用户每月节省约 $150–300。
🌍 绕过地理封锁 — 三级代理 + 隐身
🇷🇺 🇨🇳 🇮🇷 🇨🇺 🇹🇷 身处受限地区?OmniRoute 的三级代理体系(全局 / 按服务商 / 按连接)代理 API 请求、OAuth 流程、连通性测试、Token 刷新和模型同步。
- 协议: HTTP/HTTPS、SOCKS5、需认证代理
- 🆓 1proxy 市场 — 数百个免费验证代理、质量评分、自动轮换
- 反检测 — TLS 指纹伪装(
wreq-js)、CLI 指纹匹配、代理 IP 保持
✨ 完整功能清单 — 30+ 核心能力(记忆、评估、可观测性)
路由: 15 种策略 · 任务感知智能路由 · 思考预算控制 · 通配符路由 · 系统提示注入。
兼容性: OpenAI ↔ Claude ↔ Gemini ↔ Responses API · 自动 OAuth 刷新(PKCE,8 家服务商)· 多账号轮询 · Batch + Files API · 实时 OpenAPI 3.0。
协议: MCP(87 工具、3 种传输、30 个权限域)· A2A(JSON-RPC 2.0、SSE、6 项技能)· ACP · 云代理(Codex、Devin、Jules)。
插件: 自定义插件市场(系统配置的注册 URL,带 SSRF 防护拉取)· 安装/启用/禁用 · Notion + Obsidian 知识库集成(WebDAV 文件服务器、仓库搜索、笔记 CRUD)。
嵌入式服务: 一键安装与生命周期管理本地边车服务(CLIProxy、NineRouter)。
质量与运维: 内置 Evals 评估框架(黄金标准集:精确匹配/包含/正则/自定义)· 安全护栏(PII 脱敏、注入防护、视觉桥接)· 健康监控面板 · p50/p95/p99 遥测 · Webhooks · 合规审计。
AI Agent 技能: 即插即用的 Markdown 技能清单 — 将任意代理指向 skills/*/SKILL.md 清单。43 项可用技能。
📖 环境变量、设置与常见问题
| 环境变量 | 默认值 | 用途 |
|---|---|---|
PORT |
20128 |
API + 控制台端口 |
REQUIRE_API_KEY |
false |
是否要求所有请求携带 API Key |
DATA_DIR |
~/.omniroute |
数据库与配置存储路径 |
OmniRoute 会向我收费吗? 不会 — 它是运行在你本机的免费开源软件。你只直接向付费服务商付款。OmniRoute 不含任何计费系统。 免费服务商真的无限使用吗? 绝大多数是 — Qoder、Pollinations、LongCat 和 Cloudflare 免费且无单账号额度上限。Kiro 也是免费,但每月每账号约 50 积分封顶。在 Combo 中叠加多家免费服务商,自动容灾确保零元持续可用。 压缩会影响输出质量吗? 不会 — 它仅压缩输入端;代码、URL、JSON 永远保留不损。 AI 服务被封锁的地区能用吗? 能 — 三级代理 + 1proxy 市场可覆盖全部 236 家服务商。
🐛 故障排除
| 问题 | 快速解决方案 |
|---|---|
| "Language model did not provide messages" | 服务商配额耗尽 → 使用 Combo 自动切换 |
| 速率限制(429) | 设置容灾链路:cc/claude → glm/glm-4.7 → if/kimi-k2-thinking |
| OAuth Token 过期 | 自动刷新;若卡住,在 Providers 页面删除后重新认证 |
unsupported_country_region_territory |
在设置 → 代理中配置代理 |
| Docker SQLite 锁定 | 使用 --stop-timeout 40 确保干净的 WAL 检查点 |
| Node 运行时错误 | 使用 Node >=22.0.0 <23 或 >=24.0.0 <27 |
🐛 报告 Bug? 运行 npm run system-info 并附上生成的 system-info.txt。📖 docs/guides/TROUBLESHOOTING.md
📧 支持与社区
💬 与社区交流 — Discord、Telegram 和 WhatsApp(🌍 / 🇧🇷)链接详见 本 README 顶部。
- 🌍 官网:omniroute.online
- 🐙 GitHub:github.com/diegosouzapw/OmniRoute
- 🐛 Issues:报告 Bug(请附上
npm run system-info的输出结果) - 🤝 贡献:参见 CONTRIBUTING.md 或选取
good first issue
🛠️ 技术栈
- 运行时:Node.js 22.x 或 24.x LTS(推荐 24 LTS)—
>=22.0.0 <23 || >=24.0.0 <27 - 语言:TypeScript 6.0 — 跨
src/和open-sse/100% TypeScript(核心模块自 v2.0 起零any) - 框架:Next.js 16 + React 19 + Tailwind CSS 4
- 数据库:better-sqlite3 (SQLite) + LowDB(JSON 兼容)— 域状态、代理日志、MCP 审计、路由决策、记忆、技能
- 模式校验:Zod(MCP 工具 I/O 校验、API 合约)
- 协议:MCP(stdio/HTTP)+ A2A v0.3(JSON-RPC 2.0 + SSE)
- 流式传输:服务器推送事件(SSE)+ WebSocket 桥接(
/v1/ws) - 认证:OAuth 2.0(PKCE)+ JWT + API Key + MCP 权限域授权
- 测试:Node.js 原生测试运行器 + Vitest(14,965 个测试用例,覆盖 517 个文件 — 单元、集成、E2E、安全、生态)
- 平台:桌面端(Electron)、Android(Termux)、PWA(任意浏览器)
- CI/CD:GitHub Actions(Release 时自动发布至 npm + Docker Hub)
- 官网:omniroute.online
- npm 包:npmjs.com/package/omniroute
- Docker:hub.docker.com/r/diegosouzapw/omniroute
- 容灾:熔断器、指数退避、防惊群效应、TLS 伪装、Auto-Combo 自愈
📖 文档
📘 入门指南
| 文档 | 说明 |
|---|---|
| 用户指南 | 服务商、Combo、CLI 集成、部署 |
| 设置指南 | 全安装方法、CLI 工具配置、协议设置、超时调优 |
| CLI 工具指南 | Claude Code、Codex、Cursor、Cline、OpenClaw、Kilo、Copilot 逐工具配置 |
| 远程模式 | 通过授权范围 Token 从笔记本 CLI 操控远端 OmniRoute(VPS) |
| Claude Code 配置 | 使用 launch + 按模型配置文件将 Claude Code 指向 OmniRoute(本地/远程) |
| 快速开始 | 三步搞定:安装 → 连接 → 配置 |
🔧 运维与部署
| 文档 | 说明 |
|---|---|
| Docker 指南 | Docker 运行、Compose 配置、Caddy HTTPS、隧道、镜像标签 |
| Podman 指南 | Quadlet systemd 集成、podman-compose、SELinux |
| 虚拟机部署 | 完整指南:VM + nginx + Cloudflare 配置 |
| Fly.io 部署 | 部署至 Fly.io,含持久化存储 |
| Termux 指南 | 通过 Termux 在 Android 上运行 OmniRoute |
| PWA 指南 | 渐进式 Web 应用安装、缓存、架构 |
| 卸载指南 | 所有安装方式的干净移除 |
| 环境配置 | 完整 .env 变量与参考 |
🧠 功能与架构
| 文档 | 说明 |
|---|---|
| 架构 | 系统架构、数据流与内部机制 |
| 压缩指南 | 七级选项流水线:off / lite / standard / aggressive / ultra / RTK / stacked |
| RTK 压缩 | 命令输出压缩、过滤器、信任、验证、原始输出恢复 |
| 压缩引擎 | Caveman、RTK、级联流水线、控制台/API/MCP 操作界面 |
| 压缩规则格式 | Caveman 和 RTK 过滤器的 JSON 规则包 Schema |
| 压缩语言包 | 语言检测与 Caveman 规则包编写 |
| 容灾指南 | 熔断器、冷却、队列、防惊群效应、TLS 伪装 |
| Auto-Combo 引擎 | 九维度评分、模式包、自愈 |
| 代理指南 | 三级代理体系、1proxy 市场、注册 CRUD |
| 免费服务商 | 25+ 家免费 API 服务商统一目录 |
| 功能画廊 | 带截图的控制台视觉导览 |
| 代码库文档 | 新手友好的代码库导览 |
🤖 协议与 API
| 文档 | 说明 |
|---|---|
| API 参考 | 全端点含示例 |
| OpenAPI 规范 | OpenAPI 3.0 规格 |
| MCP 服务器 | 87 个 MCP 工具、IDE 配置、Python/TS/Go 客户端 |
| MCP 服务器指南 | MCP 安装、传输与工具参考 |
| A2A 服务器 | JSON-RPC 2.0 协议、技能、流式传输、任务管理 |
| A2A 服务器指南 | A2A Agent Card、任务、技能与流式传输 |
📋 项目与质量
| 文档 | 说明 |
|---|---|
| 贡献指南 | 开发环境设置与规范 |
| 更新日志 | 完整按版本发布历史 |
| 安全策略 | 漏洞报告与安全实践 |
| i18n 指南 | 40+ 语言支持、翻译流程、RTL |
| 发布检查清单 | 发布前验证步骤 |
| 测试覆盖计划 | 测试覆盖策略与 14,965 测试套件 |
⭐ 核心贡献者
OmniRoute 由充满热情的开源社区共同塑造。以下同仁做出了卓越贡献,直接影响着项目的质量、稳定性与影响力。衷心感谢。
![]() oyi77 🥇 190 次提交 · +72K 行 分析引擎、SQL 聚合、 代理市场、测试覆盖 |
![]() Chris Staley 🥈 72 次提交 · +5.7K 行 SSE 流加固、Responses API、 Gemini 分页、回归修复 |
![]() zenobit 🥉 62 次提交 · +24K 行 CI/CD 流水线、33 种语言 i18n、 Void Linux 包、跨平台修复 |
![]() R.D. & Randi 🏅 107 次提交 · +28K 行 Endpoints 页面、隧道集成、 Docker 工作流、A2A 状态、压缩 UI |
![]() benzntech 🏅 20 次提交 · +7.5K 行 Electron 桌面应用、自动更新、 发布构建工作流、跨平台 CI |
🙏 这些贡献者的功能、Bug 修复和基础设施改进,是 OmniRoute 可靠且功能丰富的核心支柱。每一个 Pull Request、每一个测试用例、每一个 i18n 翻译文件都意义重大。开源正是由他们这样的人建造的。
👥 贡献者
如何贡献
- Fork 本仓库
- 创建功能分支(
git checkout -b feature/amazing-feature) - 提交更改(
git commit -m 'Add amazing feature') - 推送分支(
git push origin feature/amazing-feature) - 创建 Pull Request
详见 CONTRIBUTING.md 获取完整开发指南。
发布新版本
# 创建 Release — npm 发布将自动触发
gh release create v3.8.2 --title "v3.8.2" --generate-notes
🙏 致谢
OmniRoute 是站在巨人肩膀上的作品。它始于 9router 的一个 Fork 以及 Go 项目 CLIProxyAPI 的 TypeScript 移植 — 自此,以下每个子系统均受惠于先行者的开源成果。每一个项目都在 OmniRoute 中留下了具体印记。这是我们对所有项目的由衷感谢。🙏
⭐ 星标数为 2026 年 6 月数据 — 请给这些项目点颗星。
🧬 渊源与网关
| 项目 | ⭐ | 对 OmniRoute 的启发 |
|---|---|---|
| 9router · decolua | 17.9k | 此 Fork 所基于的原型项目 — 此处扩展了多模态 API 并完成了全面 TypeScript 重写。 |
| CLIProxyAPI · router-for-me | 37.8k | 启发本 JavaScript/TypeScript 移植版的 Go 语言实现。 |
| LiteLLM · BerriAI | 50.8k | AI 网关,其公开定价数据集为我们提供成本同步数据,其服务商规范化模型启发了我们的路由体系。 |
🗜️ 上下文与 Token 压缩 — 引擎
| 项目 | ⭐ | 对 OmniRoute 的启发 |
|---|---|---|
| Caveman · JuliusBrussee | 74.5k | "Token 够用就好"爆款项目 — 其原始人风格哲学驱动着我们的标准压缩模式及 30+ 条填充词/凝练规则。 |
| RTK – Rust Token Killer · rtk-ai | 63.6k | 高性能命令输出压缩 — 启发了我们的 RTK 引擎、JSON 过滤器 DSL、原始输出恢复及 RTK → Caveman 级联流水线。 |
| headroom · chopratejas | 33.6k | 可逆上下文压缩(SmartCrusher)— 启发了我们的 headroom 引擎及 ccr 检索标记模式。 |
| LLMLingua · Microsoft | 6.3k | 提示压缩研究(LLMLingua / LLMLingua-2)— 启发了我们的异步、代码安全、Fail-Open 的 llmlingua 引擎。 |
| llmlingua-2-js · atjsh | 27 | JS/ONNX 移植(MobileBERT / XLM-RoBERTa),用作我们 LLMLingua 引擎的 Worker Thread 后端。 |
| Troglodita · Lenine Júnior | 15 | PT-BR Token 压缩 — 驱动我们的 pt-BR 语言包:针对巴西葡萄牙语语法调优的赘语消减与填充词移除。 |
| ponytail · DietrichGebert | 51.4k | "经验丰富的高级开发" YAGNI 编码技能 — 启发了我们的少即是多输出风格:最小化可用改动引导,减少生成代码量。 |
🧩 紧凑格式、Token 研究与代码感知工具
| 项目 | ⭐ | 对 OmniRoute 的启发 |
|---|---|---|
| TOON · toon-format | 24.6k | Token 导向对象表示法 — 其列式、表头加行的数据模型塑造了我们的表格式压缩阶段。 |
| GCF – Graph Compact Format · Blackwell Systems | 11 | 模式感知的"LLM 专用 JSON"表示法 — 共同启发了我们带 [N rows] 标记的无损同构数组压缩。 |
| token-optimizer-mcp · ooples | 409 | Brotli/SQLite 缓存 + 按会话上下文增量 — 启发了我们的 session-dedup 引擎。 |
| token-savior · Mibayy | 993 | Bash 输出压缩 + MCP 配置文件 — 启发了我们的压缩安全回退机制及 MCP 工具清单简化。 |
| token-saver · ppgranger | 103 | 内容感知、按文件类型输出压缩及故障感知回退 — 验证了我们的按类型分发和最低收益跳过策略。 |
| token-optimizer · alexgreensh | 1.4k | "发现隐藏 Token" — 其卸载+可恢复句柄模式启发了我们的 CCR 卸载思路。 |
| TokenMizer · Shweta-Mishra-ai | 1 | 会话图 + 跨轮次行去重蓝图,启发了我们的 session-dedup 设计。 |
| OmniCompress · jessefreitas | 2 | Rust 列式 JSON + 内容寻址检索 + 跨消息去重 — 验证了我们 headroom/ccr/session-dedup 引擎设计及"压缩形态位置无关"的缓存稳定不变量。 |
| mcp-compressor · Atlassian Labs | 80 | MCP 工具 Schema/描述压缩 — 启发了我们的 MCP 工具清单基数缩减。 |
| RepoMapper · pdavis68 | 182 | Aider 风格仓库地图排序 — 启发了我们的仓库地图/检索排序探索。 |
| quiet-shell-mcp · mrsimpson | 4 | 基于 MCP 的声明式 Shell 输出缩减 — 验证了我们的声明式 Bash 输出压缩。 |
| ts-morph · David Sherret | 6.1k | TypeScript 编译器 API 工具包 — 启发了我们基于解析器的注释移除,完整保留字符串、模板和正则字面量。 |
🧠 记忆与 RAG
| 项目 | ⭐ | 对 OmniRoute 的启发 |
|---|---|---|
| Mem0 · mem0ai | 58.9k | 通用记忆层 — 其代理即写入/读取边界模型塑造了我们的记忆架构。 |
| Letta (MemGPT) · letta-ai | 23.4k | 具备分层记忆的有状态代理 — 启发了我们的上下文控制与恢复(CCR)分层模型。 |
| WFGY · onestardao | 1.8k | 16 种常见 RAG/LLM 失效模式的 ProblemMap 分类法 — 构成了我们故障排除指南的共享词汇。 |
🛰️ 流量检查、MITM 与透明代理
| 项目 | ⭐ | 对 OmniRoute 的启发 |
|---|---|---|
| llm-interceptor · chouzz | 46 | 编码助手 ↔ LLM 流量 MITM 拦截/分析 — 我们的流量检查器移植了其 SSE 合并、对话归一化、主机透传及密钥掩码方案。 |
| ProxyBridge · InterceptSuite | 5.1k | 透明每进程代理路由 — 启发了我们崩溃安全的 MITM 拆卸、Socket 空闲超时、/proc 进程归因及 TPROXY 捕获。 |
📚 模型数据、可观测性与 UI
| 项目 | ⭐ | 对 OmniRoute 的启发 |
|---|---|---|
| models.dev · SST / OpenCode | 5.1k | AI 模型规格、定价与能力的开放数据库 — 原生同步至我们的模型目录。 |
| React Flow / xyflow · xyflow | 37.1k | 驱动我们实时 Compression Studio 及 Combo/Routing Studio 的基于节点的图形库。 |
| LangGraph · LangChain | 35.1k | LangGraph Studio 的实时工作流图形可视化启发了我们 Studios 的实时级联视图。 |
| Langfuse · Langfuse | 29.3k | 其 trace → span → generation 可观测性模型塑造了我们的 Compression Studio 瀑布图。 |
| Kiali · Kiali | 3.6k | Istio 服务网格可观测性 — 启发了我们 Routing/Combo Studio 中的熔断器徽章和错误边界可视化。 |
| lobe-icons · LobeHub | 2.1k | AI/LLM 品牌图标,渲染控制台中各服务商标识。 |
🛡️ 安全
| 项目 | ⭐ | 对 OmniRoute 的启发 |
|---|---|---|
| awesome-secure-defaults · tldrsec | 708 | 一份精选的安全默认库清单,指导我们的安全技术选型(Helmet.js、DOMPurify、ssrf-req-filter、safe-regex、Google Tink)。 |
❤️ 支持
OmniRoute 是免费开源项目,在公开环境中持续构建与维护。如果它帮你节省了时间或金钱,请考虑以以下方式支持开发:
- ⭐ 为本仓库加颗 Star — 这确确实实能帮我们提升可见度
- 💖 GitHub Sponsors — 资助持续维护和新服务商接入
- 🐛 在 Discussions 中反馈 Bug 和分享意见
📄 许可证
MIT 协议 — 详见 LICENSE。
⬆ 返回顶部 · 用 ❤️ 为开源 AI 社区构建。
OmniRoute v3.8.24 · Node ≥22.0.0 · MIT License · omniroute.online



















