fix(i18n): restore brand proper nouns and unify terminology in zh-CN and zh-TW (#8355)

* chore(ci): add .mergify.yml to main — Mergify only reads config from the default branch (#7168)

* fix(ci): add the auto-enqueue pull_request_rule to the Mergify config (queue_conditions alone are eligibility-only) (#7179)

* fix(ci): migrate Mergify auto-enqueue to merge_protections_settings.auto_merge_conditions (rules-based path is EOL 2026-07-16) (#7216)

* fix(ci): drop Mergify batch settings (batching is a paid-tier feature; free plan queue is serial) (#7220)

* fix(ci): merge queue tolerates the advisory dast-smoke failure (its GH-hosted build hang dequeued every attempt) (#7225)

* test(ci): make the #6634 selfref guard hermetic — main's copy hard-fails every PR (#7341)

main's copy of this test still does git I/O inside a unit test:

    const baseSrc = git(['show', 'origin/main:' + FILE]);

Runners check out a shallow single ref, so origin/main does not resolve and the
test dies with 'fatal: invalid object name origin/main'. Every PR into main
fails Unit Tests (7/8) on it — today that is #7313, #7315, #7316, #7334, #7336
and #7337, six PRs red on a defect none of them introduced. #7313 has no other
red at all.

release/v3.8.49 already carries a fix (2e42b8efc, #7174: try/catch, fetch
origin/main on demand, t.skip() when unreachable), but it only reaches main at
release time — so main stays broken for the whole cycle. Cherry-picking it would
also import a new problem: PR Test Policy classifies t.skip() as a silenced
assertion, which we watched it correctly catch on #7300 today.

This is the hermetic version instead (ported from #7327, which does the same for
the release branch): read the file straight off disk, compare against an empty
base so baseTaut/baseExtTaut are 0 — the strictest possible comparison point —
and call evaluateMasking() directly. No git ref, no fetch, no skip, nothing the
runner's checkout depth can break.

The #6634 regression stays covered: the guard's logic lives in
SELF_TEST_FIXTURE_RE (check-test-masking.mjs:337), not in the test. Proven both
ways on main before committing — neutralise SELF_TEST_FIXTURE_RE to /$^/ and
the test FAILS; restore it and it passes 2/2, with check-test-masking.mjs left
byte-identical.

Co-authored-by: growab <nekron@icloud.com>

* chore(quality): tighten main's coverage baseline to the CI's real numbers (#7347)

main's ratchet had been failing --require-tighten on every PR: 11 metrics
improved but the baseline was never tightened. Same class as the #6634
selfref guard — an infra fix that lands only on the release branch leaves
main red for the whole cycle, and every PR into main pays for it.

Values are the merged-coverage numbers from a run on main itself (a local
run measures ~68% vs CI's ~80%; the baseline's own note warns about that
gap). Only the 11 coverage values change — gitleaks and semgrepFindings
keep main's own state.

No changelog fragment: #7326 carries it on release/v3.8.49, and a second
one here would double the entry at release time.

* Add cliproxy provider exposure controls and manifest injection (#7329)

* 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-"
(commit 75b52e286) 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 …

* test(ci): static body in codex e2e mock route bridge (CodeQL #737) (#7559)

CodeQL js/stack-trace-exposure flags ANY error-derived value returned in
the mock route bridge's 500 path, not just error.stack — swapping .stack
for error.message (in #7354, alert #736) left sibling alert #737 open on
the same line. Replace the body with a static string; the test only
asserts status===200, so the 500 body is never inspected. Clears the last
open CodeQL alert repo-wide, unblocking the Quality Ratchet on every PR.

Companion to the release/v3.8.49 PR (merge-gates §8 — gate/CI-touching fix
lands on main in the same session).

* fix(security): bump adm-zip >=0.6.0 + exact host matching in mitm DNS test (#7733)

* chore(deps): resolve 7 open Dependabot alerts via npm overrides (#8067)

- fast-uri ^3.1.3 (root + electron) — host confusion via IDN (#131/#126, high)
- hono ^4.12.27 — JSX ctx isolation / cx() XSS / v1 adapter req drop (#128/#129/#130, medium)
- @hono/node-server ^2.0.5 — serve-static path traversal (#127, medium); MCP uses only getRequestListener, not serve-static
- body-parser ^2.3.0 — DoS on invalid limit (#125, low)

Resolved: fast-uri 3.1.4, hono 4.12.31, @hono/node-server 2.0.11, body-parser 2.3.0. All clear in npm audit; lockfile-lint OK.

* chore(deps): resolve 3 Dependabot alerts (dompurify, fast-xml-parser, sharp) (#8070)

- dompurify ^3.4.12 (#132, low), fast-xml-parser ^5.10.1 (#133, high DOCTYPE), sharp ^0.35.0 (#134, high libvips)
Resolved: dompurify 3.4.12, fast-xml-parser 5.10.1, sharp 0.35.3. All clear in npm audit; lockfile-lint OK.

* docs(readme): use local SVG flags + convert doc-link tables to lists (#8317)

Flag emoji (regional-indicator) do not render on Windows and several
Safari/WebKit configs, breaking the 'In 42+ languages' table there.
Replace each emoji with a local SVG <img> from flag-icons (MIT), served
from docs/assets/flags/ so the language selector renders everywhere.

Also convert the 5 'Document | Description' reference tables to definition
lists — full-width and scroll-free on GitHub (tables are capped at
width:max-content and scroll horizontally on narrow viewports).

* fix(i18n): restore brand proper nouns and unify terminology in zh-CN and zh-TW

- Restore untranslated brand/model proper nouns (Claude, OpenAI, Anthropic, Gemini, MiniMax, etc.) in zh-CN and zh-TW
- Replace Chinese phonetic/translation forms (克劳德->Claude, 打开Ai->OpenAI, 人择->Anthropic, 双子座->Gemini, etc.)
- Unify 'provider' translation to '供应商'/'供應商' (was inconsistent: 提供者/服务商)
- Apply zh-TW localized terminology (網路/設定/檔案/新增/啟用/搜尋/儲存)
- Fix 'providers' value bug in zh-CN

* docs: sync env-var contract (chaos panel, notion TLS, grok auth path) + repair glued VNC line in .env.example (#8362)

* Add comparison and zero-config installation diagrams in SVG format

- Created a comparison table SVG illustrating the capabilities of OmniRoute versus competitors (9router, OpenRouter, CLIProxyAPI, LiteLLM) across 13 features.
- Added a zero-config installation SVG demonstrating the ease of setting up OmniRoute with three simple steps: installation, pointing to the tool, and receiving instant replies.

* fix(runtime): isolate unique 8177 repairs (#8298)

* perf(api): singleflight version lookups (#8278) (#8301)

Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>

* fix(providers): adapt Kimi nonstream requests internally (#8302)

Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>

* fix(mcp): keep POST SSE responses uncompressed (#8303)

Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>

* fix(cpa): isolate credential-pool failures (#8308)

* fix(cpa): isolate credential pool failures

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(cpa): forward transport through the chatCore key-health wrapper

The local recordKeyHealthStatus wrapper in handleChatCore only declared
(status, creds), so the transport argument added for CPA credential-pool
isolation was silently dropped at the call site (TS2554 "Expected 2
arguments, but got 3" once chatCore.ts is typechecked with tsc directly —
this file is not in tsconfig.typecheck-core.json's file list, so `npm run
typecheck:core` did not surface it). The CPA isolation guard in
keyHealth.ts never received `transport`, so it never fired.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* fix(sse): suppress </think> by default on Chat Completions (#8245) (#8309)

Claude→OpenAI translation was emitting a literal </think> into
delta.content for ordinary Chat Completions clients. Reasoning already
ships as reasoning_content, so default to suppress and keep
x-omniroute-thinking-marker: on as the #4633 opt-in.

* fix(sse): preserve Responses combo payloads (#8310)

* fix(providers): classify HTTP 400 model-unavailable as MODEL_NOT_FOUND so Antigravity Pro fallback locks out the deprecated model (#8319)

* fix(providers): carve cookie-auth providers out of terminal 401 'expired' classification so one 401 cooldowns instead of killing the connection (#8321)

* fix(api): fold namespace into the flattened Chat tool name so cross-namespace leaves do not collide (#8322)

* fix(providers): classify per-model-quota 403 and DEGRADED 400 as model-unhealthy in checkFallbackError (#8247, #8248) (#8323)

* fix(providers): route noauth opencode-zen connections through their assigned proxy (#8324)

* fix(sse): re-export PROVIDER_BREAKER_FAILURE_STATUSES for the orphaned all-rate-limited breaker path (#8390)

Root cause: #8013 extracted shouldTripProviderBreakerForResult() from
src/sse/handlers/chat.ts into the new src/sse/handlers/chatPredicates.ts,
taking the (non-exported) const PROVIDER_BREAKER_FAILURE_STATUSES with it.
A second, independent use of that const survived in chat.ts's
handleSingleModelChat(), in the "all credentials rate-limited" block
(~line 1340) — that reference was left orphaned by the extraction.

Production impact: any request where every credential for a
provider+model is simultaneously rate-limited throws
`ReferenceError: PROVIDER_BREAKER_FAILURE_STATUSES is not defined` at
runtime in that code path. Concretely this meant:
- breaker._onFailure() was unreachable on the all-rate-limited path, so
  the provider circuit breaker could not trip from it
- the ReferenceError propagated up and got mapped to a generic 502,
  masking the real 503 upstream-unavailable status in combo responses
- the issue-agent route surfaced a generic 400 instead of the actual
  429 provider-rate-limited response

Fix: export PROVIDER_BREAKER_FAILURE_STATUSES from chatPredicates.ts and
add it to chat.ts's existing import block from that module. No behavior
change — the classification set ([408, 500, 502, 503, 504]) is
unchanged, this only repairs the broken reference.

Also re-points tests/unit/nvidia-quota-phase1.test.ts's regex-based
declaration check at chatPredicates.ts, where the const now actually
lives (it previously read chat.ts via fs+regex and silently failed to
find the declaration). The regex and the classification assertions
themselves are unchanged — this test still proves 429 is excluded from
the whole-provider breaker.

Refs #8013

* fix(sse): gate reasoning-placeholder strip to chunks that contain the sentinel (#8382)

Regression: #8162 (port of #8081) added an unconditional `.trim()` to
stripInternalReasoningPlaceholder(), applied to every streaming
delta.content chunk across 3 call-sites (openai-to-claude.ts,
openai-responses.ts, responsesTransformer.ts). Leading/trailing
whitespace at a chunk boundary is a real word boundary between
streaming fragments; trimming it glues adjacent chunks together on
the client ("Hello, " + "world." + " Bye." -> "Hello,world.Bye.").

Fix: early-return via .includes() before the replaceAll+trim, so the
function is a true no-op when the sentinel is absent from the chunk.
Behavior when the sentinel IS present is unchanged.

Validation:
- tests/unit/streaming-reasoning-dedup-5786.test.ts: the "(A-guard)"
  test was RED on the base branch ('Hello,world.Bye.' vs
  'Hello, world. Bye.'); GREEN after the fix (4/4 passing).
- tests/unit/translator-resp-openai-to-claude.test.ts: added a new
  multi-chunk boundary-whitespace regression test, proven RED against
  the pre-fix code (12/13), GREEN after (13/13).
- No regressions in responses-transformer.test.ts (17/17),
  responses-transformer-dense-output.test.ts (3/3), or the other
  suites exercising the shared placeholder utility (160/160 total
  across all consumers).

Refs #8162
Refs #8081

* fix(resilience): terminal-skip spares the recoverable GitHub Copilot no_refresh_token state (#8389)

Cause: #8182's terminal-connection guard in checkConnection() returns
early for any testStatus in {credits_exhausted, banned, expired} to
stop the sweep from wasting CPU/network probing connections that can
never self-heal. But testStatus="expired" + errorCode="no_refresh_token"
is exactly the state the pre-existing GitHub Copilot self-heal targets
(isGitHubAccessTokenOnlyConnection + canClearGitHubNoRefreshTokenState,
~line 83-97 / 413-492): a Copilot connection with no OAuth refresh
token but a still-valid copilotToken, which the sweep is supposed to
flip back to "active". With the new guard placed ahead of that block
unconditionally, the self-heal became unreachable for exactly the
state it exists to clear.

Impact: healthy GitHub Copilot connections that once lost their OAuth
refresh token got stuck at testStatus="expired" in the dashboard
forever, even though their Copilot sub-token kept working and the
sweep would have cleared the stale status back to "active" every
cycle before #8182.

Fix: carve out the exact recoverable shape from the terminal-skip
guard — testStatus==="expired" && errorCode==="no_refresh_token" &&
isGitHubAccessTokenOnlyConnection(conn) — so the guard still skips
every other terminal case (credits_exhausted, banned, and "expired"
for any other reason) untouched, matching #8182's original intent.

Validation: tests/unit/token-health-no-refresh-token-expired-5326.test.ts
was red (1 fail / 4 pass) before the fix — "checkConnection clears
stale no_refresh_token state for usable GitHub Copilot connections"
asserted testStatus flips back to "active" but got "expired". Green
after the fix (6/6, including a new boundary test proving a GitHub
Copilot connection expired for any OTHER reason, e.g. errorCode
"invalid_grant", is still skipped untouched). Also reran the adjacent
checkConnection/tokenHealthCheck suites (token-health-check.test.ts,
token-health-check-circuit-breaker.test.ts,
apikey-connection-health-check.test.ts, token-health-check-sweep.test.ts,
token-health-check-tickms-defined.test.ts, tokenHealthCheck-batchSize.test.ts,
codex-oauth-refresh-persist-6352.test.ts, oauth-providers-error-handling.test.ts)
— all green, confirming #8182's terminal-skip behavior is otherwise
unchanged. npm run typecheck:core clean.

Refs #8182
Refs #8286
Refs #5326

* fix(sse): cap exact cooldowns only when synthetic — verified upstream resets pass uncapped (#8393)

Contract vs cap: #6863 requires a model lockout to honor a VERIFIED
upstream quota reset exactly (e.g. Antigravity "Resets in 92h27m28s",
shipped in v3.8.47). #7940 requires SYNTHETIC exact-cooldown estimates
(the quota_exhausted until-midnight heuristic) to respect the
operator's maxCooldownMs so they cannot balloon unbounded. Both are
legitimate, non-conflicting contracts — they apply to different kinds
of values.

Root cause: #7980 (fixing #7940) changed recordModelLockoutFailure()
in open-sse/services/accountFallback.ts to unconditionally clamp
every exactCooldownMs against maxCooldownMs, with no way to
distinguish a verified upstream reset from a synthetic estimate. A
real ~92h reset got clamped to the operator's ~30min cap, and the
router went on hammering 429 against quota that was known not to
recover for days — regressing #6863's contract by omission, not by
new policy (the "honor it exactly" docstrings on
selectLockoutCooldownMs() and its call sites were left untouched and
now describe dead code).

Fix: add an opt-in `exactCooldownVerified` flag to
recordModelLockoutFailure()'s options. When true, exactCooldownMs
bypasses the maxCooldownMs clamp entirely; when false/omitted
(the default), behavior is byte-identical to before this change.
Set the flag only at the 4 call sites that already carry upstream
provenance for the value they pass — usedUpstreamRetryHint /
quotaResetHintMs from checkFallbackError():
  - open-sse/services/combo.ts (2 sites): exactCooldownVerified
    mirrors lockoutHintMs > 0, which is only ever nonzero when it
    traces back to a genuine upstream signal.
  - src/sse/services/auth.ts (2 sites): exactCooldownVerified mirrors
    the same usedUpstreamRetryHint / quotaResetHintMs check already
    used to derive exactCooldownMs at each site.
The quota_exhausted → until-midnight synthetic default and plain
exponential backoff are untouched and stay capped, per #7940. The
two other recordModelLockoutFailure call sites (combo.ts quality
failure, auth.ts local-404/grok-web-403) never carry a verified hint
and were left unmodified.

Validation (TDD): tests/unit/combo-lockout-quota-reset-6863.test.ts
red→green with its assertions unchanged (was clamping ~332,848,000ms
to ~1,799,995ms; now honors the parsed reset). Added a boundary pair
to tests/unit/model-lockout-exact-cooldown-cap.test.ts proving the
same magnitude resolves differently by provenance: synthetic stays
capped, verified passes through whole. Full existing suite in that
file plus combo-model-lockout-honors-reset-1308.test.ts stay green
unmodified. Swept 45 lockout/cooldown-adjacent test files (502/505
passing); the 3 failures reproduce byte-identical on a pristine
origin/release/v3.8.49 checkout (PROVIDER_BREAKER_FAILURE_STATUSES
ReferenceError in untouched chat.ts, and a documented timing-sensitive
serial test) — confirmed pre-existing, out of this fix's scope.
npm run typecheck:core and npm run lint are clean.

Refs #6863
Refs #7940
Refs #7980

* fix(sse): family auto-combos include any backend that serves the family (no-auth allowlist scoped to tier pools) (#8391)

Context: #8183 introduced AUTO_COMBO_NOAUTH_ALLOWLIST (opencode, felo-web) to
gate no-auth providers out of every auto/* candidate pool, motivated by public
HTTP egress reliability on the reference VPS (.15) — several no-auth backends
(duckduckgo-web, theoldllm, chipotle, aihorde) were flaky there. Its own tests
(noauth-autocombo-allowlist.test.ts, virtual-auto-combo.test.ts) never
exercised the auto/<family> path, so the gate silently applied there too.

auto/<family> combos (#6453, e.g. auto/glm, auto/zai) are a different axis:
an identity selector ("route to whatever genuinely serves GLM"), not a
reliability-curated pool. auggie (local CLI subprocess, zero HTTP egress —
the reliability concern #8183 targets doesn't even apply to it) advertises a
literal glm-5.2 model and had an explicit design-test seat in auto/glm since
#7032, but the #8183 allowlist silently excluded it from that pool.

Operator decision (2026-07-24): the no-auth allowlist gate keeps applying to
category/tier and flat-variant auto/* pools (auto/best-free, auto/coding:fast,
...), but auto/<family> pools bypass it — any no-auth backend that genuinely
serves the family is admitted.

Fix: thread a `bypassAllowlist` flag through isChatAutoComboNoAuthProvider()
and getNoAuthCandidates(), set to `Boolean(spec?.family)` at the single call
site in createVirtualAutoCombo(). Family narrowing (buildFamilyCandidateFilter)
still runs afterward, so a bypassed no-auth candidate only survives if its
model actually belongs to the requested family. Category/tier and flat-variant
pools (spec.family unset) keep the gate fully intact.

Validation:
- tests/unit/autoCombo/provider-family-combos.test.ts:136 was red (expected
  ["auggie","glm","zai"], got ["glm","zai"]) — now green (11/11 passing).
- tests/unit/noauth-autocombo-allowlist.test.ts (3/3) and
  tests/unit/virtual-auto-combo.test.ts (10/10, including "restricts the
  no-auth pool to the allowlist") stay green — the #8183 gate is untouched for
  spec-less/category/tier pools.
- Full tests/unit/autoCombo/ vitest sweep: 5 files, 36/36 passing.
- npm run typecheck:core clean.
- 4 unrelated failures pre-exist on origin/release/v3.8.49 (verified via `git
  show HEAD:<path>` swap, no stash) in
  tests/unit/auto-combo-credentialed-model-pool.test.ts (antigravity/gemini-3.5
  credentialed-pool logic, untouched by this change).

Refs #8183, Refs #6453, Refs #7032

* fix(compression): rank codex-responses in adaptive-ladder maps (#8381)

#8010 registered the codex-responses engine in the compression catalog
(engineCatalog.ts, stackPriority 12 between rtk's 10 and headroom's 15)
but never added it to adaptiveCompression/ladder.ts's AGGRESSIVENESS and
REDUCTION_FACTOR maps. Those maps' own header documents that they must
cover every real catalog/registry engine, not just the 7 in
DEFAULT_LADDER, so an operator adding codex-responses via ladderOverride
silently fell back to aggressivenessOf() === 0 (same as "off") and
expectedReductionFactor() === 0.9 (the generic default), breaking
floor-mode escalation ranking for any ladder that includes it.

Add "codex-responses" to both maps between rtk and ionizer, matching
its stackPriority (12) sitting between rtk's (10) and ionizer's (13):
- AGGRESSIVENESS: 22 (between rtk's 20 and ionizer's 25)
- REDUCTION_FACTOR: 0.84 (between rtk's 0.85 and ionizer's 0.83),
  reflecting its "lossless-first, bounded diagnostic" guidance in
  engineCatalog.ts

Validation: tests/unit/ladder-engine-maps-6533.test.ts red -> green
(2 of 3 tests were failing on the missing engine; all 3 pass after the
fix). Sanity-checked neighbors compression-exclusions.test.ts and
compression/adaptive-resolve-plan.test.ts still pass.

Refs #8010

* fix(i18n): restore #8219 CacheSettingsTab key sync + synthetic fixture for zh-TW repro test (#8387)

Root cause (two independent causes):
1. PR #8219 (commit 2a865aaaa7) added CacheSettingsTab.tsx with 12
   t("settings.*") calls whose keys were never created anywhere, not even
   in en.json (the source of truth). The same PR added only 3 sidebar/header
   keys (settingsCache, settingsCacheSubtitle, settingsCacheDescription) to
   en+es, without running `npm run i18n:sync-ui` to propagate to the other
   41 locales.
2. tests/unit/i18n-missing-placeholder-fallback.test.ts had a "#7258 repro"
   test asserting the real zh-TW.json still carried raw __MISSING__:
   placeholders — a premise invalidated by #8024, which completed the
   Traditional Chinese translation to 100%.

What changed:
- Added the 12 missing settings.* keys to en.json, mirroring the sibling
  requestBodyLimit* family (placeholders {min}/{max}/{value} match the
  component exactly).
- Added real, natural translations for all 15 CacheSettingsTab-related keys
  (12 settings.* + 3 sidebar/header) to pt-BR.json, vi.json and es.json (es
  already had the 3 sidebar/header keys).
- Ran `npm run i18n:sync-ui` (official tool, no locale hand-edited) to stub
  the remaining 39 locales with __MISSING__:<english>. This also discovered
  17 pre-existing unrelated missing keys (compression-exclusions settings,
  8 new-provider onboarding descriptions) never synced since #8031, and
  pruned 3 dead orphaned zh-TW-only keys (codexSessionAffinity{Title,Desc,
  Ttl}, superseded by the generic sessionAffinity* keys since #7274,
  confirmed unused anywhere in src/) — verified programmatically as
  +32/-0/~0 changed per stub locale, +32/-3/~0 changed for zh-TW.
- Rewrote the "#7258 repro" test to use a synthetic fixture (same style as
  the sibling deepMergeFallback fixtures in the same file) instead of
  depending on zh-TW.json's real, evolving translation-completeness state.
  Proves the same behavior: collectPlaceholderLeaves() detects a raw
  __MISSING__: leaf before deepMergeFallback (the fix) is exercised.

Validation: all 4 previously-red files green (23/23 assertions). Broader
sweep of 271 i18n-adjacent unit tests unaffected. i18n:check-ui-coverage
(42/42 locales >=80%, 99.7-100%) and i18n:check-glossary both pass.
typecheck:core clean.

Refs #8219
Refs #8024

* chore(quality): drop stale muse-spark-web allowlist entry + sync sidebar order snapshots (#8383)

Two independent "code is right, bookkeeping lagged" base-reds:

1. #8233 made open-sse/executors/muse-spark-web.ts import
   sanitizeErrorMessage from utils/error.ts (a real Rule #12 fix), but
   left its KNOWN_MISSING_ERROR_HELPER allowlist entry in
   scripts/check/check-error-helper.mjs in place. The gate's own
   stale-allowlist enforcement (assertNoStale) correctly flagged the now
   -obsolete entry: `npm run check:error-helper` failed with "1 entrada(s)
   obsoleta(s)", and tests/unit/check-error-helper.test.ts's "the shipped
   allowlist freezes exactly the known current violators" test expected
   an empty Set. Removed the entry (kept the assertNoStale machinery and
   the general scope-header comments untouched).

2. #8064 added the "compression-exclusions" sidebar item right after
   "compression-studio" in COMPRESSION_CONTEXT_GROUP (deliberate,
   complete feature) but didn't update two order-snapshot tests written
   before that item existed:
   - tests/unit/sidebar-visibility.test.ts expected the "omni-proxy"
     section's flattened id list to end the compression block at
     "compression-studio".
   - tests/unit/ui/sidebar-engine-items.test.ts asserted "Studio must be
     last" in COMPRESSION_CONTEXT_GROUP.
   Updated both to the real, intentional order: Settings -> Combos ->
   engines -> Studio -> Exclusions (Studio now second-to-last,
   Exclusions last).

Validation (red -> green):
- check:error-helper gate: red ("1 entrada(s) obsoleta(s)") -> green
  ("OK (898 files scanned, 0 known-missing frozen)")
- tests/unit/check-error-helper.test.ts: 31/32 -> 32/32
- tests/unit/sidebar-visibility.test.ts: 6/7 -> 7/7
- tests/unit/ui/sidebar-engine-items.test.ts: 13/14 -> 14/14

Refs #8233
Refs #8064

* test: realign catalog snapshot tests to current deliberate catalog state (#8386)

* test: realign catalog snapshot tests to current deliberate catalog state

Six catalog/snapshot tests drifted behind deliberate catalog changes that
were already validated by newer sibling tests. No production code touched;
every change aligns a stale snapshot to behavior already validated by newer
sibling tests.

Root causes (all confirmed against the current code before editing):

- tests/unit/providers-constants-split.test.ts: APIKEY_PROVIDERS grew from
  187 to 195 entries via #8077 (clova-studio/internlm/ant-ling, regional),
  #8161 (sarvam/plamo → regional, writer → frontier-labs) and #8170
  (typhoon → regional, inception → frontier-labs). Family counts verified
  to sum to 195 (gateways 60, frontier-labs 24, inference-hosts 28,
  enterprise-cloud 17, regional 40, specialty-media 26) with no duplicates.
  Updated the two assertions and extended the changelog comment.

- tests/unit/qianfan-provider.test.ts: the expected Baidu Qianfan website
  URL was the pre-#8128 wenxinworkshop path. #8128/#6271 moved it to
  https://cloud.baidu.com/product-s/qianfan_home, already locked by the
  sibling regression test tests/unit/baidu-qianfan-website-urls-6271.test.ts.

- tests/unit/t31-t33-t34-t38-model-specs.test.ts and
  tests/unit/auto-combo-credentialed-model-pool.test.ts: the Antigravity
  catalog refactor (#8013) retired gemini-3-pro-preview/claude-sonnet-5 and
  renamed the Gemini 3.5 Flash tiers (low/medium/high ->
  extra-low/low/gemini-3-flash-agent), confirmed against
  ANTIGRAVITY_PUBLIC_MODELS and tests/unit/antigravity-retired-public-models.test.ts.
  Swapped the retired IDs for currently-registered ones
  (gemini-3.6-flash-high, claude-sonnet-4-6, gemini-3-flash-agent,
  gemini-3.5-flash-low/extra-low) and moved the wildcard-exclusion prefix
  test from the now-2-tier "gemini-3.5-*" group to "gemini-3.6-*", which has
  3 real tiers today (same >=3 semantics, just pointed at a prefix that
  still has 3 members).

- tests/unit/model-alias-seed.test.ts: getModelInfo("gemini-3.1-pro") now
  canonicalizes through ALIAS_TO_PROVIDER_ID["agy"] = "antigravity" (#8050),
  the same pattern already applied to opencode -> opencode-zen. Updated the
  expected provider id.

- tests/unit/video-dashscope.test.ts (deleted, 216 lines): #8266
  reorganized the Alibaba video catalog so the flat wan2.7-t2v id no longer
  exists under the plain "alibaba" provider (only the dated
  wan2.7-t2v-2026-06-12 does); the flat id now lives only under
  "qwen-cloud". All 6 tests in the file failed because they built  requests
  against alibaba/wan2.7-t2v, which the new allowlist now rejects with 400
  ("unsupported alibaba video model") - verified directly against
  VIDEO_PROVIDERS in open-sse/config/videoRegistry.ts. Coverage already
  exists and was confirmed passing pre-deletion in
  tests/unit/alibaba-video-media.test.ts (including an explicit
  "Alibaba rejects video models outside its own allowlist" case for this
  exact id) and tests/unit/qwen-cloud-video-media.test.ts (covers the same
  id under qwen-cloud). Note: the deleted file's DashScope upstream
  error-path assertions (401 missing credentials, 502 missing task_id, 502
  FAILED status, 504 poll timeout) don't have a byte-for-byte equivalent in
  the two replacement files, though the shared dashscopeHandler.ts code
  path they exercise remains covered by several sibling *-media.test.ts
  files for the happy path and local validation.

- tests/unit/authz/spawn-capable-prefixes-client-safe.test.ts: #7892 added
  /api/vnc-session to the SPAWN_CAPABLE_PREFIXES deny-list (Hard Rules
  #15/#17 hardening). Bumped the expected length 10 -> 11 and added the
  entry to the test's named list for documentation.

Refs #8013, #8050, #8266, #7892, #8128

* chore(quality): allowlist the video-dashscope.test.ts deletion with its replacements

check:test-masking (pr-test-policy CI gate) requires a _deletedWithReplacement
entry for any deleted test file, even when the deletion is a verified-legitimate
supersession. Documents the same #8266 rationale from the prior commit in the
machine-checked allowlist so the deletion is not flagged as unexplained masking.

Refs #8266

* test: hermetic notion thread-session mocks + drop duplicated usage-analytics suite (#8392)

* test: hermetic TLS mock for notion thread-session suite + drop duplicated usage-analytics file

Root cause A (#8159): sendNotionInferenceRequest() in
open-sse/executors/notion-web.ts was migrated from fetch() to
tlsFetchNotion() (open-sse/services/notionTlsClient.ts, native
tls-client-node binary) to get past Notion's Cloudflare TLS
fingerprinting. #8159 updated the mock in the sibling
tests/unit/executor-notion-web.test.ts (installNotionTlsMock, wired
through __setTlsFetchOverrideForTesting) but never touched
tests/unit/executor-notion-web-thread-sessions.test.ts (split out
earlier by #7900) — its 3 execute()-driven tests still mocked
globalThis.fetch, which tlsFetchNotion() never calls once the native
TLS client loads successfully. Confirmed live: all 3 tests hit real
https://app.notion.com with a fake cookie and got a real 401
(~8.1-8.5s each here; on a network with blocked/slow egress this
would instead hang up to the client's ~190s timeout+grace per test —
a CI-hang risk).

Fix: replicate installNotionTlsMock verbatim from the sibling file
into executor-notion-web-thread-sessions.test.ts so the 3 tests mock
the TLS override point instead of global fetch. Suite is now fully
hermetic — 8/8 pass, no network I/O, total runtime 31.0s -> 8.0s.

Root cause B (#7700): tests/unit/usage-analytics-route-extra.test.ts
was created as a byte-for-byte duplicate of 10 of the 22 tests in
tests/unit/usage-analytics-route.test.ts. #7300 later fixed a fixture
bug in the retention-window boundary test ("does not double-count raw
and aggregated rows") in the main file only — reading
getUserDatabaseSettings().retention.usageHistory live instead of a
hardcoded 30-day cutoff (default retention is 365 days) — leaving the
duplicate copy on the stale hardcoded value, which now fails (1 !== 2).

Fix: delete the duplicate file. All 10 of its test names exist
verbatim in the main file (verified with comm -12) and that file
passes 22/22:
  - does not double-count raw and aggregated rows
  - does not persist guessed API key attribution
  - does not throw Unknown named parameter on short range (needsAggregated=false)
  - does not throw Unknown named parameter with apiKey filter on long range
  - groups renamed API key usage by stable ID
  - includes activityMap for heatmap
  - includes cost by API key
  - omits global aggregates when filtering by API key
  - returns 500 on database errors
  - returns weeklyPattern for the costs dashboard
No coverage loss — same production code, same assertions, one fewer
redundant file.

Validation:
  - RED executor-notion-web-thread-sessions.test.ts: 5 pass / 3 fail
    (401 !== 200, real network hit), 31.0s
  - RED usage-analytics-route-extra.test.ts: 9 pass / 1 fail
    (1 !== 2), 18.7s
  - GREEN executor-notion-web-thread-sessions.test.ts: 8/8 pass, 8.0s,
    hermetic (no network)
  - GREEN executor-notion-web.test.ts (sibling, untouched): 37/37
    pass, byte-identical diff
  - GREEN usage-analytics-route.test.ts (untouched): 22/22 pass,
    byte-identical diff
  - npx eslint on the changed file: clean
  - npm run typecheck:core: clean (exit 0)

Refs #8159
Refs #7300
Refs #7700

* chore(quality): register usage-analytics-route-extra deletion in test-masking allowlist

check:test-masking hard-flags any deleted test file without a
_deletedWithReplacement entry. The deletion is legitimate (100% duplicate
suite, coverage retained verbatim in tests/unit/usage-analytics-route.test.ts)
-- same registration pattern as the video-dashscope entry.

Refs #7700
Refs #7300

* chore(ci): cancel superseded runs, skip DAST on docs-only PRs, persist TIA shadow evidence (#8379)

Runner-cost pass grounded in the #8084 review of the current pipeline:

- dast-smoke.yml: add concurrency cancel-in-progress (25-min advisory builds were
  stacking on force-push storms) and paths-ignore for docs/**+**/*.md — a docs-only
  PR cannot change DAST behavior but was paying the 6-11min CLI-bundle build.
- semgrep.yml: add concurrency cancel-in-progress. No paths filter on purpose:
  p/secrets must keep scanning docs-only diffs (credentials leak in .md too).
- quality.yml (TIA step): persist the per-PR impacted-test selection to a
  tia-selection artifact + GITHUB_STEP_SUMMARY line. This is the shadow-evidence
  phase: TIA false negatives become measurable against fast-unit's full-suite
  verdict across releases BEFORE any gate authority moves off ordinary PRs.

Refs #8084

* docs: one golden path across PR template, CONTRIBUTING, GEMINI, AGENTS + CI milestones in ROADMAP (#8380)

Contributor guidance contradicted itself in four places (found in the #8084 review):

- pull_request_template.md + CONTRIBUTING.md asked contributors to run the FULL
  unit suite + coverage gate locally, while the maintainer's stated golden path
  (#8273/#8329) is: focused tests for the change locally; full suite, coverage,
  and build are CI's job. On 16GB hosts the full local chain has saturated
  machines (#8084 incident report).
- GEMINI.md demanded coverage >= 75/75/75/70 while the official CI gate is
  60/60/60/60 (quality-baseline ratchet on top).
- AGENTS.md fork workflow said to branch from upstream/main; the default branch
  is the active release/vX.Y.Z line (main only receives release squash-merges).

Also makes the #8084 CI direction explicit in the public ROADMAP: lane
consolidation (3.8.51), one CI policy for release/** and main (3.8.52),
full-regression authority -> merge queue after TIA shadow evidence (3.8.54),
preview-artifact + build-once rehearsal inside the 3.8.58 dry-run.

Refs #8329
Refs #8084

* fix(translator): cap thinking budget on explicit budget_tokens path (#8312)

* fix(translator): cap thinking budget on explicit budget_tokens path

* fix(translator): stop dropping thinkingConfig on cap-0 reasoning_effort path

The thinking-budget-cap guard added in this branch skipped thinkingConfig
entirely whenever a model's thinkingBudgetCap was 0 (e.g. gemini-3-flash),
including on the reasoning_effort/budgetMap path. That regressed the
pre-#6943 native-defaults contract (thinkingBudget 0 / includeThoughts
false must still be present) and crashed callers that read
`.thinkingConfig.thinkingBudget` unconditionally
(translator-openai-to-gemini-defaults.test.ts).

Also restore includeThoughts:true on the Claude-format explicit
thinking.budget_tokens path (openai-to-gemini.ts's Claude-format field and
claude-to-gemini.ts's native thinking field): budget_tokens:0 there is the
client's dynamic-thinking sentinel (#6813), not an off-switch, and must
stay true even after the new capping — the cap must only clamp positive
explicit values, never flip the zero sentinel's semantics.

Updates two tests this branch added that encoded the incorrect
"omit thinkingConfig / includeThoughts:false for the 0 sentinel" behavior,
to match the pre-existing, still-required contracts above.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* fix(providers): revert scope-creep flip of Gemini 3.5/3.6 Flash supportsThinking

The thinking-budget-cap fix accidentally expanded 5 shorthand modelSpecs
entries (gemini-3.5-flash, gemini-3.5-flash-low, gemini-3.6-flash-high/
medium/low) into explicit objects setting supportsThinking:true and
thinkingBudgetCap:24576. That flip was unrelated to the two proven test
regressions (translator-openai-to-gemini-defaults.test.ts and
claude-to-gemini-budget-tokens-zero-6813.test.ts, which only exercise
gemini-3-flash-preview, gemini-3.1-pro and gemini-2.5-pro) and reopens a
deliberately closed path from #8013: Antigravity still rejects
client-supplied thinking params for these Gemini 3.5/3.6 Flash tier ids,
so supportsThinking must stay false (inherited from
GEMINI_35_FLASH_MODEL_SPEC).

Reverted all 5 entries back to the release shorthand
`{ ...GEMINI_35_FLASH_MODEL_SPEC }`. gemini-3-flash, gemini-3.1-pro and
gemini-2.5-pro (the models the regression tests actually exercise) were
already correctly specced in the release baseline and are untouched.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* fix(providers): reconcile Kimi K3 vision when attachment contradicts modalities (#8250) (#8313)

* fix(providers): reconcile Kimi K3 vision when attachment contradicts modalities

Synced models.dev rows for kimi-coding*/k3 can ship attachment=false while
modalities_input still lists image/video. Prefer the modality signal (and
normalize at sync + resolve) so supportsVision, attachment, and exposed
modalities agree.

Closes #8250

* fix(providers): keep Kimi K3 static fallback text-only (#8250)

The Kimi K3 vision reconciliation added supportsVision=true directly
to the kimi-coding registry entry for id "k3". That entry is the
static/stable fallback catalog used when discovered capabilities are
unavailable, and it must stay text-only per #4071 — the vision fix is
already applied correctly on the discovered path via
MODEL_SPECS["kimi-k3"] (aliases: ["k3"]) and
modelCapabilities.ts::resolveVisionCapability.

Restores the invariants guarded by
tests/unit/kimi-k2.7-code-registration.test.ts ("Kimi Code k3 fallback
leaves discovered capabilities unset") and
tests/unit/catalog-updates-v3829-kimi-qwen.test.ts ("kmca stable
fallback only carries documented static capabilities").

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* docs: update provider icons and enhance API interface documentation

* docs: update section headers in README for improved clarity

* docs: improve formatting and structure in README for better readability

* feat(opencode-plugin): auto-discover models while running + force sync (#8101)

* feat(opencode-plugin): auto-discover models while running + force sync

Add Pi-parity discovery for OpenCode:
- autoSyncIntervalMs background refresh (default 5m, min 60s, 0=off)
- omniroute_sync_models tool to force cache invalidate + /v1/models refetch
- /omni-sync and /omni-autosync command templates (OpenCode has no slash API)

* docs(opencode-plugin): document auto model discovery + /omni-sync

Document Pi-parity catalog refresh for OpenCode:
- autoSyncIntervalMs background discovery (default 5m)
- omniroute_sync_models force-refresh tool
- /omni-sync and /omni-autosync command templates

---------

Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com>

* chore(deps): bump next to 16.2.11 (9 security advisories) (#8265)

Closes 9 Dependabot alerts (#135-#143) — Next.js 16.0.0..<16.2.11:
SSRF in Server Actions/rewrites, cache confusion, DoS (Server Actions,
Image Optimization SVG, Edge payload), middleware/proxy bypass, and
unauthenticated Server Function endpoint disclosure.

Lockfile bump within the existing ^16.2.6 range (now floored at
^16.2.11); no production code touched.

Co-authored-by: rafaumeu <rafael.zendron22@gmail.com>

* fix(antigravity): add missing gemini-3.6-flash pricing rows to ag OAuth pricing (#8290)

release/v3.8.49 already ships the Gemini 3.6 Flash catalog entries
(AGY_PUBLIC_MODELS, ANTIGRAVITY_PUBLIC_MODELS, MODEL_SPECS with
supportsThinking: false — Antigravity still rejects client-supplied
thinking params) via #8013. What was still missing: the `ag` pricing
rows in DEFAULT_PRICING_OAUTH, so getPricingForModel("ag", id)
returned null for the three tiers and cost/quota calculations
silently fell back to $0.

Pricing: $1.50 input / $7.50 output / $0.15 cached per MTok (Google's
2026-07-21 announcement), matching the existing 3.5-flash schedule
shape. Thinking tokens billed at output rate.

Extends the existing pricing-ag-flash-tiers.test.ts (RED-first: all
three tiers failed the "non-null pricing row" assertion before this
change) rather than re-adding the already-shipped catalog/modelSpecs
entries.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* feat(combo): enforce provider and model family invariants (#8304)

* feat(combo): enforce provider and model family invariants

Closes #8279

Co-Authored-By: Ravi Tharuma <ravitharuma@users.noreply.github.com>

* fix(combo): complete invariant enforcement paths

Map invariant failures to structured API errors, correct target diagnostics, and validate restored combos inside the existing migration transaction.

Co-Authored-By: Ravi Tharuma <noreply@github.com>

---------

Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com>
Co-authored-by: Ravi Tharuma <noreply@github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* fix(memory): self-heal upsertVector/deleteVector from a raced vec_memories drop (#8337)

Live incident: memory.vec.upsert.fail {"error":"no such table: vec_memories"}
recurred repeatedly right after restarts, even though ensureReady() is called
immediately beforehand. ensureReady()'s signature-check-then-maybe-recreate
logic (resetForSignature does DROP TABLE IF EXISTS + CREATE VIRTUAL TABLE) is
not synchronized against a concurrent caller's upsertVector/deleteVector -- a
second in-flight memory write that independently decides (from a stale read
of memory_vec_meta) it also needs to reset the table can drop it out from
under another write's insert. Confirmed live: memory_vec_meta showed
vec_loaded=0 for the entire session across many restarts, then flipped to 1
mid-investigation once one attempt finally completed without interruption --
consistent with an intermittent race, not a permanently broken path (verified
the underlying sqlite-vec extension and CREATE VIRTUAL TABLE statement work
correctly in isolation, both on the host and inside the production container).

Rather than chase the exact interleaving (every underlying SQLite call is
synchronous via better-sqlite3, so the race window is narrow and did not
reproduce under simple Promise.all stress tests), makes the write path
resilient to arriving after the table was dropped: on a "no such table"
error, recreate vec_memories from the last-known-good memory_vec_meta
dimension and retry once.

Co-authored-by: Markus Hartung <markus.hartream@gmail.com>

* fix(sse): stop stripInternalReasoningPlaceholder from eating inter-word spaces (#8341)

Live incident: streamed assistant text was losing the spaces BETWEEN words
(e.g. "Bilden är en riktig JPEG nu" -> "Bildenärenriktig JPEG nu") on the
Responses-API and Claude streaming paths.

stripInternalReasoningPlaceholder() (#8081/#8162) is called on every
individual delta.content chunk, and unconditionally called .trim() even when
its sentinel ("(prior reasoning summary unavailable)") was never present in
that chunk. Tokenizers commonly emit sub-word tokens with a leading space as
part of the token (e.g. " en", " riktig") -- each such chunk got its only
whitespace character (the inter-word space) silently trimmed away before
being appended to the accumulated message, while the words themselves stayed
intact. Punctuation-only chunks were largely unaffected, matching what was
observed live.

Only trims when the sentinel is actually present -- preserves the original
#8081 intent (collapse a placeholder-only chunk to "") without touching the
overwhelming majority of chunks that never contain it.

Co-authored-by: Markus Hartung <markus.hartream@gmail.com>

* fix(dashboard): show custom provider_nodes providers in the Topology view instead of only AI_PROVIDERS (#8328) (#8357)

* fix(sse): strip third-party-agent signals from the Hermes system prompt that trigger Anthropic 400 extra-usage (#8350) (#8358)

* fix(api): accept the current compatible-provider connection id scheme in the models test route (#8326) (#8359)

* fix(backend): keep combo routing from dispatching image requests to text-only targets (#8332) (#8360)

* fix(api): stop leaking the internal provider UUID in /v1/models and honor the configured prefix (#8327) (#8361)

* refactor(sse): classify SSE critical-path empty catches + add CONTRIBUTING convention (#8142) (#8364)

* fix(api): stop the 2000-token safety buffer from inflating usage.prompt_tokens in the client response (#8331) (#8356)

* fix(api): stop the 2000-token safety buffer from inflating usage.prompt_tokens in the client response (#8331)

* fix(sse): scope #8331's usage-buffer fix around Claude-Code-compatible providers

The #8331 fix correctly stopped folding the 2000-token context-window safety
margin into client-visible prompt_tokens/input_tokens/total_tokens for normal
API metering clients. But it also silently changed the response shape for
Claude-Code-compatible providers, whose own context accounting reads the
buffered number straight out of usage — regressing
tests/unit/cc-compatible-provider.test.ts (expected 2007, got 7).

Fold the computed context_budget_* fields back into the visible usage fields
for that one path only (applyClientUsageBuffer's new
preserveContextBudgetInVisibleUsage option, gated on the existing
isClaudeCodeCompatible flag in chatCore.ts). Every other caller keeps the
real, unbuffered #8331 numbers.

* docs: enhance README formatting with tables for better structure and readability

* docs: enhance README with tables for improved structure and readability

---------

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: growab <nekron@icloud.com>
Co-authored-by: KooshaPari <42529354+KooshaPari@users.noreply.github.com>
Co-authored-by: Chirag Singhal <76880977+chirag127@users.noreply.github.com>
Co-authored-by: Chirag Singhal <chirag127@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: Xiangzhe <32761048+xz-dev@users.noreply.github.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: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.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: Septianata Rizky Pratama <19322988+ianriizky@users.noreply.github.com>
Co-authored-by: NOXX - Commiter <artur1992123@mail.ru>
Co-authored-by: backryun <bakryun0718@proton.me>
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: Jan Leon <Jan.gaschler@gmail.com>
Co-authored-by: Someres <168349709+quanturbo@users.noreply.github.com>
Co-authored-by: MikeTuev <ra9ftm@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Imam Wahyu Widodo <120608486+hajilok@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: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.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: Paijo <14921983+oyi77@users.noreply.github.com>
Co-authored-by: oyi77 <oyi77@users.noreply.github.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>
Co-authored-by: Xiangzhe <xiangzhedev@gmail.com>
Co-authored-by: Rafael Dias Zendron <rafael.zendron22@gmail.com>
Co-authored-by: KooshaPari <62650152+KooshaPari@users.noreply.github.com>
Co-authored-by: Wibias <37517432+Wibias@users.noreply.github.com>
Co-authored-by: huohua-dev <celentanohertor@gmail.com>
Co-authored-by: huohua-dev <258873123+huohua-dev@users.noreply.github.com>
Co-authored-by: CitrusIce <31264099+CitrusIce@users.noreply.github.com>
Co-authored-by: minisforum <no@mail.com>
Co-authored-by: Ravi Tharuma <25951435+RaviTharuma@users.noreply.github.com>
Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Prudhvi Vuda <53619858+Prudhvivuda@users.noreply.github.com>
Co-authored-by: Ridho Pratama <p.ridho9@gmail.com>
Co-authored-by: Bob.Hou <houminxi@gmail.com>
Co-authored-by: Ravi Tharuma <noreply@github.com>
Co-authored-by: Markus Hartung <markus.hartream@gmail.com>
This commit is contained in:
ikelvingo
2026-07-25 13:51:07 +08:00
committed by GitHub
parent 58ab8b1d2c
commit 9dcbbd18c8
3 changed files with 426 additions and 425 deletions

View File

@@ -0,0 +1 @@
- **fix(i18n):** Restore brand/model proper nouns (Claude, OpenAI, Anthropic, Gemini, MiniMax, etc.) in zh-CN and zh-TW — replace Chinese phonetic/translation forms (克劳德/打开Ai/人择/双子座) with original English, unify "provider" translation to "供应商/供應商", and apply zh-TW localized terminology (網路/設定/檔案/新增/啟用/搜尋/儲存) instead of mainland defaults (#8355 — thanks @ikelvingo).

View File

@@ -539,7 +539,7 @@
"mcpQuickStartTitle": "MCP 快速开始",
"comboUpdated": "Combo 已更新",
"weighted": "加权",
"providers": "Provider",
"providers": "供应商",
"ccCompatibleLabel": "CC 兼容",
"noFallbackChainsDesc": "创建一条链路,用于定义某个模型的提供者回退顺序。",
"yesImport": "确认导入",
@@ -583,7 +583,7 @@
"cacheCleared": "缓存已清除",
"searchTypeNews": "搜索类型News",
"durationMillisecondsShort": "时长毫秒短标签",
"addOpenAICompatible": "添加打开Ai兼容",
"addOpenAICompatible": "添加OpenAI 兼容",
"chatTesterTab": "聊天测试标签页",
"queued": "排队中",
"domainPlaceholder": "域名占位符",
@@ -1444,15 +1444,15 @@
"auth.login.success": "登录成功",
"auth.logout.success": "注销成功",
"compliance.cleanup": "合规清理",
"provider.credentials.applied": "提供者凭据已应用",
"provider.credentials.batch_revoked": "提供者凭据批次已撤销",
"provider.credentials.bulk_created": "提供者凭据批量创建",
"provider.credentials.bulk_imported": "提供者凭据已批量导入",
"provider.credentials.created": "提供者凭据已创建",
"provider.credentials.imported": "提供者凭据已导入",
"provider.credentials.revoked": "提供者凭据已被撤销",
"provider.credentials.updated": "提供者凭据已更新",
"provider.validation.ssrf_blocked": "提供者 SSRF 被阻止",
"provider.credentials.applied": "供应商凭据已应用",
"provider.credentials.batch_revoked": "供应商凭据批次已撤销",
"provider.credentials.bulk_created": "供应商凭据批量创建",
"provider.credentials.bulk_imported": "供应商凭据已批量导入",
"provider.credentials.created": "供应商凭据已创建",
"provider.credentials.imported": "供应商凭据已导入",
"provider.credentials.revoked": "供应商凭据已被撤销",
"provider.credentials.updated": "供应商凭据已更新",
"provider.validation.ssrf_blocked": "供应商 SSRF 被阻止",
"quota.plan.updated": "配额计划已更新",
"quota.pool.created": "配额池已创建",
"quota.pool.deleted": "配额池已删除",
@@ -2219,7 +2219,7 @@
"emptyFilterTitle": "没有密钥匹配当前筛选条件",
"emptyFilterClear": "清除筛选",
"disableNonPublicModels": "禁用非公开模型",
"disableNonPublicModelsDesc": "拒绝对未发现或未标记为公共的模型在提供者目录中的请求",
"disableNonPublicModelsDesc": "拒绝对未发现或未标记为公共的模型在供应商目录中的请求",
"normalKeysSection": "普通键",
"quotaKeysSection": "配额密钥",
"bypassProviderQuota": "绕过服务商配额限制",
@@ -2871,7 +2871,7 @@
"visibleToolsCount": "{count} 个工具可用",
"customCliBuilderTitle": "兼容 OpenAI 的 CLI 构建器",
"customCliBuilderDescription": "为任何接受 OpenAI 兼容的基本 URL、API 密钥和模型 ID 的 CLI 或 SDK 生成环境变量和 JSON 片段。",
"customCliNoModels": "连接至少一个提供者以填充模型选择器。",
"customCliNoModels": "连接至少一个供应商以填充模型选择器。",
"customCliNameLabel": "CLI 名称",
"customCliNamePlaceholder": "例如我的团队 CLI",
"customCliDefaultModelLabel": "默认型号",
@@ -2886,7 +2886,7 @@
"customCliEndpointHintLabel": "如何连接端点",
"customCliEndpointHint": "将任何 OpenAI 兼容客户端指向 OmniRoute /v1 基本 URL。原始聊天完成端点是 {endpoint}。当工具需要提供程序对象时使用 JSON 块,或者在读取 OPENAI_* 变量时使用 env 脚本。",
"customCliEnvBlockTitle": "环境 / shell 片段",
"customCliJsonBlockTitle": "提供者 JSON 块",
"customCliJsonBlockTitle": "供应商 JSON 块",
"networkError": "网络错误",
"other": "其他",
"preview": "预览",
@@ -4086,15 +4086,15 @@
},
"embedding": {
"autoLabel": "自动",
"autoDesc": "使用最佳可用选项:远程提供者 > 静态 > 转换器",
"remoteLabel": "远程提供者",
"remoteDesc": "通过提供者 API 使用嵌入(需要 API 密钥)",
"autoDesc": "使用最佳可用选项:远程供应商 > 静态 > 转换器",
"remoteLabel": "远程供应商",
"remoteDesc": "通过供应商 API 使用嵌入(需要 API 密钥)",
"staticLabel": "静态本地(药水)",
"staticDesc": "不使用WASM或外部依赖的本地嵌入",
"transformersLabel": "Transformers.js (MiniLM)",
"transformersDesc": "通过 @huggingface/transformers 本地嵌入 (~400MB RAM)",
"providerModelLabel": "提供者 / 模型",
"noRemoteProviders": "没有配置 API 密钥的提供者",
"providerModelLabel": "供应商 / 模型",
"noRemoteProviders": "没有配置 API 密钥的供应商",
"selectProviderModel": "选择一个模型",
"staticEnabledLabel": "启用静态药水",
"staticEnabledDesc": "在本地下载并使用 potion-base-8M 模型",
@@ -4208,9 +4208,9 @@
"enableLabel": "启用重新排序",
"enableDesc": "在搜索后使用重新排序模型对结果进行重新排序",
"warning": "Rerank 会增加 +200-500ms 的延迟和每个请求的额外成本。请谨慎使用。",
"providerModelLabel": "重新排序提供者 / 模型",
"noProviderWithKey": "没有配置 API 密钥的提供者。请配置一个提供者以使用 rerank。",
"selectProviderModel": "选择一个提供者/模型"
"providerModelLabel": "重新排序供应商 / 模型",
"noProviderWithKey": "没有配置 API 密钥的供应商。请配置一个供应商以使用 rerank。",
"selectProviderModel": "选择一个供应商/模型"
},
"save": "保存",
"saving": "保存中...",
@@ -5228,18 +5228,18 @@
"applyClaudeAuthLocal": "申请授权",
"exportClaudeAuthFile": "导出授权",
"importClaudeAuth": "导入授权",
"claudeApplyModalTitle": "适用于当地克劳德代码",
"claudeApplyModalTitle": "适用于当地Claude Code",
"claudeApplyTargetLabel": "目标路径",
"claudeApplyBackupLabel": "备份",
"claudeApplyMcpHint": "现有的 MCP OAuth 状态将被保留。",
"claudeApplyWarning": "这将取代现有的 claudeAiOauth 部分。继续?",
"claudeApplyConfirmCheckbox": "我确认我想替换现有的 claudeAiOauth 部分",
"claudeApply": "申请",
"claudeAuthAppliedLocal": "克劳德授权在本地应用",
"claudeAuthAppliedLocal": "Claude授权在本地应用",
"claudeAuthApplyFailed": "本地申请Claude auth失败",
"claudeAuthExported": "克劳德授权文件导出",
"claudeAuthExported": "Claude授权文件导出",
"claudeAuthExportFailed": "无法导出 Claude 身份验证文件",
"claudeImportModalTitle": "导入克劳德·奥特",
"claudeImportModalTitle": "导入Claude·奥特",
"claudeImportTabSingle": "单人",
"claudeImportTabBulk": "散装",
"claudeImportTabUpload": "上传文件",
@@ -5250,12 +5250,12 @@
"claudeImportNameLabel": "连接名称(可选)",
"claudeImportOverwriteLabel": "如果帐户已存在,则替换现有连接",
"claudeImportSubmit": "进口",
"claudeImportSuccess": "克劳德连接导入成功",
"claudeImportSuccess": "Claude连接导入成功",
"claudeImportInvalidJson": "无法将文件解析为 JSON",
"claudeImportInvalidShape": "该文件不是有效的 .credentials.json",
"claudeImportDuplicate": "帐户已存在 - 启用“替换现有”以覆盖",
"claudeImportIdentityUnverified": "Bootstrap 无法验证该帐户。启用“替换现有”或提供电子邮件。",
"claudeImportFailed": "导入克劳德授权失败",
"claudeImportFailed": "导入Claude授权失败",
"claudeImportBulkModeUpload": "上传文件",
"claudeImportBulkModePaste": "粘贴 JSON 数组",
"claudeImportBulkModeZip": "上传ZIP",
@@ -5618,7 +5618,7 @@
"providerDetailCallbackUrl": "回调 URL",
"providerDetailValidClaudeCredentialsFile": "有效的 Claude 凭据文件",
"providerDetailPathAutoDetectedAllOs": "路径按操作系统自动检测Linux/Mac/Windows。",
"providerDetailMyClaudeAccountPlaceholder": "我的克劳德账户",
"providerDetailMyClaudeAccountPlaceholder": "我的Claude 账户",
"providerDetailPathAutoDetected": "根据操作系统 (Linux/Mac) 自动检测路径。",
"compatBlockedParamsPlaceholder": "thinking, … (逗号分隔)",
"compatAllowedParamsPlaceholder": "reasoning, … (逗号分隔)",
@@ -5670,7 +5670,7 @@
"onboardingSearchProviders": "搜索提供者...",
"onboardingApiKeyOptional": "API 密钥可选",
"onboardingProviderConnected": "提供者已连接",
"onboardingProviderSavedWithWarnings": "提供者保存时带有警告",
"onboardingProviderSavedWithWarnings": "供应商保存时带有警告",
"onboardingProviderFinished": "提供者入职完成",
"onboardingYourProviderConnection": "您的提供者连接",
"onboardingTestPassed": "测试通过",
@@ -5682,12 +5682,12 @@
"onboardingValidatingCredentials": "正在验证凭据...",
"onboardingSavingConnection": "正在保存提供者连接...",
"onboardingProviderFailed": "提供者加入失败",
"onboardingCreatingCompatibleProvider": "创建兼容的提供者...",
"onboardingCreatingCompatibleProvider": "创建兼容的供应商...",
"onboardingSavingCompatibleConnection": "正在保存兼容的提供者连接...",
"onboardingCustomProviderFallbackName": "定制提供者",
"onboardingCustomProviderFailed": "自定义提供者加入失败",
"onboardingLoadingOAuthConnection": "正在加载 OAuth 连接...",
"onboardingOAuthNoConnectionFound": "OAuth 已完成,但未找到提供者连接。",
"onboardingOAuthNoConnectionFound": "OAuth 已完成,但未找到供应商连接。",
"onboardingOAuthFailed": "OAuth 登录失败",
"onboardingAddProvider": "添加 {provider}",
"onboardingConnectionName": "连接名称",
@@ -5706,7 +5706,7 @@
"onboardingProtocol": "协议",
"onboardingOpenAiCompatible": "兼容 OpenAI",
"onboardingAnthropicCompatible": "人类兼容",
"onboardingClaudeCodeCompatible": "克劳德代码兼容",
"onboardingClaudeCodeCompatible": "Claude Code兼容",
"onboardingProviderPrefix": "提供者前缀",
"onboardingProviderPrefixHint": "用于生成托管提供者 ID。",
"onboardingChatPath": "聊天路径",
@@ -5916,9 +5916,9 @@
"codex": "使用现有的 OAuth 流程连接 OpenAI Codex。",
"qwen": "使用现有的 OAuth 流程连接 Qwen Code。"
},
"passthroughModelsDescription": "{provider} 接受提供者本机模型 ID。从 /models 导入或添加用于路由的自定义 ID。",
"passthroughModelsDescription": "{provider} 接受供应商本机模型 ID。从 /models 导入或添加用于路由的自定义 ID。",
"bedrockModelsDescription": "Amazon Bedrock 模型的范围按 AWS 区域划分。从 /models 导入或添加在所选区域中启用的基岩模型 ID。",
"bedrockModelPlaceholder": "人类.克劳德十四行诗-4-6",
"bedrockModelPlaceholder": "anthropic.Claudesonnet-4-6",
"addProviderSessionCookieTitle": "添加 {provider} 会话 cookie",
"openWebProviderSite": "打开 {host}",
"addProviderWebTokenTitle": "添加 {provider} 网络令牌",
@@ -7283,7 +7283,7 @@
"memorySkillsSkillsmpMarketplace": "SkillsMP 市场",
"memorySkillsFailedToSave": "保存失败",
"memorySkillsApiKey": "API密钥",
"memorySkillsActiveSkillsProvider": "主动技能提供者",
"memorySkillsActiveSkillsProvider": "主动技能供应商",
"cliproxyapiFallback": "CLIProxyAPI 后备",
"cliproxyapiEnableFallback": "启用 CLIProxyAPI 回退",
"cliproxyapiUrl": "CLIProxyAPI URL",
@@ -7357,12 +7357,12 @@
"codexFastTierModelsLabel": "快速层模型",
"codexFastTierModelsHint": "启用快速层后,只有勾选的模型会附带 service_tier。",
"codexFastTierModelCheckbox": "为 {model} 启用快速层",
"claudeFastModeTitle": "克劳德快速模式",
"claudeFastModeDesc": "选择选定的克劳德请求进入人择快速模式(速度:“快速”)。",
"claudeFastModeTitle": "Claude快速模式",
"claudeFastModeDesc": "选择选定的Claude请求进入Anthropic快速模式(速度:“快速”)。",
"claudeFastModeHint": "Anthropic 并未正式支持 SDK 样式客户端的快速模式。启用后OmniRoute 会转发 X-CPA-Force-Fast-Mode 标头,以便配对的 CLIProxyAPI 构建可以选择欺骗入口点。只有列出的 Opus 模型才会受到 Anthropic 客户端检查的控制。订阅层、最大计划和快速模式信用余额仍然在服务器端强制执行 - 即使打开切换Anthropic 也可能返回 out_of_credits。",
"claudeFastModeModelsLabel": "应用于模型 ({count})",
"claudeFastModeModelCheckbox": "为 {model} 启用快速模式",
"claudeFastModeSaveError": "无法更新克劳德快速模式设置",
"claudeFastModeSaveError": "无法更新Claude快速模式设置",
"authz": {
"cors": {
"wildcard": {
@@ -7453,7 +7453,7 @@
"resilienceWaitForCooldownScope": "当前客户请求",
"resilienceWaitForCooldownTrigger": "当所有候选连接已经冷却时",
"resilienceWaitForCooldownEffect": "等待服务器并在第一个冷却时间到期时重试",
"resilienceWaitForCooldownDesc": "这仅影响当前请求。它不存储连接或提供者状态。",
"resilienceWaitForCooldownDesc": "这仅影响当前请求。它不存储连接或供应商状态。",
"resilienceEnableServerWait": "启用服务器端等待",
"resilienceEnableServerWaitDesc": "启用后OmniRoute 会等待第一次冷却时间到期并自动重试。",
"resilienceMaxAttempts": "最大尝试次数",
@@ -8323,14 +8323,14 @@
"techniques": "技巧:",
"friendlyTitle": "翻译器",
"friendlySubtitle": "使用您现有的应用程序与任何提供者 — 无需重写代码。",
"conceptHeadline": "您的应用程序使用一个 API 的“语言”。翻译器将其转换为使用另一个提供者。",
"conceptHeadline": "您的应用程序使用一个 API 的“语言”。翻译器将其转换为使用另一个供应商。",
"conceptDiagramAppLabel": "您的应用程序",
"conceptDiagramSourceLabel": "源格式",
"conceptDiagramHubLabel": "OpenAI (中心)",
"conceptDiagramTargetLabel": "目标提供者",
"conceptDiagramTargetLabel": "目标供应商",
"conceptDiagramExampleApp": "例如 Anthropic SDK",
"conceptDiagramExampleSource": "Claude",
"conceptDiagramExampleTarget": "双子座",
"conceptDiagramExampleTarget": "Gemini",
"conceptHowItWorksToggle": "它是如何工作的",
"conceptHowItWorksBody": "您的应用以其自己的格式发送请求。翻译器检测该格式,通过 OpenAI 作为中介中心进行转换(或在可用的情况下直接进行转换),将其发送到所选提供者,并将响应转换回您应用的格式。",
"tabTranslate": "翻译",
@@ -8340,7 +8340,7 @@
"simpleAppUsesLabel": "我的应用程序使用",
"simpleAppUsesHint": "您的应用程序使用的 API 格式例如Anthropic SDK = claude。",
"simpleSendToLabel": "发送到",
"simpleSendToHint": "实际将请求发送到哪里(在 OmniRoute 中连接的提供者)。",
"simpleSendToHint": "实际将请求发送到哪里(在 OmniRoute 中连接的供应商)。",
"simpleStartWithLabel": "开始于",
"simpleStartWithExamplePlaceholder": "选择一个现成的示例",
"simpleStartWithCustomOption": "粘贴您的请求(高级)",
@@ -8379,17 +8379,17 @@
"pipelineStepFormatDetectedDesc": "自动检测到的源格式",
"pipelineStepOpenAIIntermediate": "OpenAI 中级",
"pipelineStepOpenAIIntermediateDesc": "翻译为 OpenAI hub 格式",
"pipelineStepProviderFormat": "提供者格式",
"pipelineStepProviderFormatDesc": "翻译为提供者目标格式",
"pipelineStepProviderResponse": "提供者响应",
"pipelineStepProviderResponseDesc": "来自提供者的流式响应",
"pipelineStepProviderFormat": "供应商格式",
"pipelineStepProviderFormatDesc": "翻译为供应商目标格式",
"pipelineStepProviderResponse": "供应商响应",
"pipelineStepProviderResponseDesc": "来自供应商的流式响应",
"conceptDiagramArrow1": "说话",
"conceptDiagramArrow2": "翻译",
"conceptDiagramArrow3": "转换",
"conceptDiagramExampleHub": "OpenAI",
"conceptDiagramHubTooltip": "翻译器用于在没有直接映射的格式之间转换的中间枢纽。",
"conceptDiagramSourceTooltip": "您的应用程序使用的 API 格式例如Anthropic SDK = claude。",
"conceptDiagramTargetTooltip": "请求将实际发送到的提供者。",
"conceptDiagramTargetTooltip": "请求将实际发送到的供应商。",
"compressionEmptyHint": "填写“翻译”标签页上的输入字段(简单控件或原始 JSON以启用预览。",
"compressionModeLabel": "压缩模式",
"compressionPreviewButton": "预览压缩",
@@ -10401,10 +10401,10 @@
"kpiAvgUtilization": "平均利用率",
"kpiBorrowingNow": "现在借款",
"conceptTitle": "配额分成是如何工作的",
"conceptIntro": "配额共享通过节约型公平分享将提供者的配额分配给多个 API 密钥:每个密钥获得一个按比例分配的份额,但可以在不超过全球上限的情况下从自由余额中借用。",
"conceptIntro": "配额共享通过节约型公平分享将供应商的配额分配给多个 API 密钥:每个密钥获得一个按比例分配的份额,但可以在不超过全球上限的情况下从自由余额中借用。",
"conceptFairShare": "公平共享:每个键接收与其配置权重成比例的配额",
"conceptBorrowing": "借用:密钥可以在不违反上限的情况下消耗他人的自由余额",
"conceptGlobalCap": "硬性全球上限:提供者的绝对限制永远不会被超越",
"conceptGlobalCap": "硬性全球上限:供应商的绝对限制永远不会被超越",
"conceptWindows": "Windows: 5小时按小时、按日、按周、按月 — 每个独立跟踪",
"conceptKeyHowTitle": "为配额启用密钥",
"conceptKeyHowDesc": "在 API 管理器中正常创建密钥 — 它会自动出现在向导的密钥步骤中。在那里勾选独占以使其仅限配额。没有单独的启用步骤。",
@@ -10429,7 +10429,7 @@
"wizardStep2Label": "限制",
"wizardStep3Label": "密钥",
"wizardStep1Title": "选择提供者连接",
"wizardStep1Subtitle": "选择此池将共享配额的提供者帐户,设置名称和默认策略。",
"wizardStep1Subtitle": "选择此池将共享配额的供应商帐户,设置名称和默认策略。",
"wizardStep2Title": "配置配额维度",
"wizardStep2Subtitle": "为所选连接定义配额计划维度(单位、窗口、限制)。保持不变以保持当前设置。",
"wizardStep3Title": "分配 API 密钥",
@@ -10443,10 +10443,10 @@
"wizardExclusiveLabel": "独占配额",
"wizardExclusiveHint": "启用后,这些 API 密钥将仅允许使用此池的虚拟模型(在保存时应用 allowedQuotas 对账)。",
"wizardPreviewLabel": "虚拟模型名称预览",
"wizardConnectionsLabel": "提供者连接",
"wizardConnectionsLabel": "供应商连接",
"wizardPrimaryBadge": "主要",
"wizardAdditionalConnectionsNote": "附加连接使用其目录默认限制(稍后可编辑)。",
"wizardSingleProviderNote": "一个池使用单一提供者",
"wizardSingleProviderNote": "一个池使用单一供应商",
"wizardPreviewMoreModels": "+{count} 更多",
"accountQuotaTitle": "账户配额",
"accountQuotaNone": "—",
@@ -10528,7 +10528,7 @@
"quotaPlans": {
"title": "计划与配额",
"description": "为每个提供者配置配额计划 — 维度(%、请求、令牌、$)和时间窗口",
"providerLabel": "提供者 / 连接",
"providerLabel": "供应商 / 连接",
"detectedPlanLabel": "检测到的计划",
"manualPlanLabel": "手动覆盖",
"unconfiguredLabel": "未配置 — 需要手动设置",
@@ -10560,11 +10560,11 @@
"title": "活动",
"description": "最近事件动态",
"emptyTitle": "尚无活动",
"emptyDescription": "当您添加提供者、创建组合或旋转密钥时,事件将出现在这里。",
"emptyDescription": "当您添加供应商、创建组合或旋转密钥时,事件将出现在这里。",
"todayHeader": "今天",
"yesterdayHeader": "昨天",
"filterAll": "全部",
"filterProviders": "提供者",
"filterProviders": "供应商",
"filterCombos": "组合",
"filterApiKeys": "API 密钥",
"filterSettings": "设置",
@@ -10585,9 +10585,9 @@
"daysAgo": "{n} 天前"
},
"eventVerb": {
"providerAdded": "{actor} 添加了提供者 {target}",
"providerRemoved": "{actor} 移除了提供者 {target}",
"providerTested": "{actor} 测试了提供者 {target}",
"providerAdded": "{actor} 添加了供应商 {target}",
"providerRemoved": "{actor} 移除了供应商 {target}",
"providerTested": "{actor} 测试了供应商 {target}",
"comboCreated": "{actor} 创建了组合 {target}",
"comboUpdated": "{actor} 更新了组合 {target}",
"comboDeleted": "{actor} 移除了组合 {target}",
@@ -10698,8 +10698,8 @@
"toggling": "切换中…",
"viewTraffic": "查看流量",
"emptyNoProvidersTitle": "尚未配置提供程序",
"emptyNoProvidersBody": "要使用 AgentBridge首先连接至少一个提供者。它将是 IDE 请求路由的目标。",
"emptyGoToProviders": "前往提供者",
"emptyNoProvidersBody": "要使用 AgentBridge首先连接至少一个供应商。它将是 IDE 请求路由的目标。",
"emptyGoToProviders": "前往供应商",
"wizardTitle": "设置向导",
"wizardSubtitle": "3步设置",
"wizardStep1Label": "验证",
@@ -10879,7 +10879,7 @@
"sessionsDropdown": "会话",
"annotationPlaceholder": "添加备注…",
"contextFingerprint": "上下文指纹",
"llmProvider": "检测到的提供者",
"llmProvider": "检测到的供应商",
"llmApiKind": "API 类型",
"llmModel": "模型",
"llmMessages": "消息",
@@ -10959,13 +10959,13 @@
"code": {
"title": "CLI 代码的",
"phrase": "指向 OmniRoute 的代码工具",
"flow": "你 → CLI 代码 → OmniRoute → 提供者",
"flow": "你 → CLI 代码 → OmniRoute → 供应商",
"seeOther": "查看 →"
},
"agent": {
"title": "CLI 代理",
"phrase": "通用自主CLI代理您可以指向OmniRoute",
"flow": "您 → CLI 代理 → OmniRoute → 提供者",
"flow": "您 → CLI 代理 → OmniRoute → 供应商",
"seeOther": "查看 →"
},
"acp": {
@@ -10982,7 +10982,7 @@
"code": {
"title": "代码工具",
"desc": "指向 Omni",
"flow": "你 → CLI → Omni → 提供者",
"flow": "你 → CLI → Omni → 供应商",
"examples": "例如claudecodex"
},
"agent": {
@@ -11027,9 +11027,9 @@
"baseUrlLabel": "基础 URL",
"apiKeyLabel": "API 密钥",
"modelMappingLabel": "模型映射",
"noActiveProviders": "没有活动的提供者。",
"noActiveProvidersDesc": "请前往 Providers 连接至少 1 个提供者,然后再配置 CLI。",
"openProviders": "打开提供者 →"
"noActiveProviders": "没有活动的供应商。",
"noActiveProvidersDesc": "请前往 Providers 连接至少 1 个供应商,然后再配置 CLI。",
"openProviders": "打开供应商 →"
}
},
"cliCode": {

File diff suppressed because it is too large Load Diff