Commit Graph

5622 Commits

Author SHA1 Message Date
Diego Rodrigues de Sa e Souza
f6d6ad6047 feat(dashboard): Kimi sponsor banner, Kimi Coding preset, official logomarks and partner links (#8039)
Official Kimi (Moonshot AI) partnership rollout on the dashboard: a
dismissible home-page sponsor banner (version-gated through v3.8.60), a
one-click "Kimi Coding" combo preset (kimi-k3 primary via moonshot,
fallback to kimi-coding/kimi-web), the official theme-aware logomark
wired into ProviderIcon and the README sponsors card, and aff-tagged
partner links (aff=omniroute) across the 3 visible Kimi provider cards'
top-of-page header link. The moonshot provider's dashboard display name
is rebranded to "Kimi" (id/alias/routing untouched — DB connections,
combos and /dashboard/providers/moonshot still address it by id).

Refs: Kimi partnership pilot.
2026-07-21 15:53:17 -03:00
Diego Rodrigues de Sa e Souza
311fd35d8b docs(readme): Kimi partner tracking links (aff=omniroute) + first-Brazilian-project line + disclosure (#8028) 2026-07-21 14:39:58 -03:00
nguyenha935
4012bac41d fix(i18n): preserve remaining Vietnamese localization (#7935)
* fix(i18n): preserve remaining Vietnamese localization

* chore(quality): rebaseline file-size cap for 9 dashboard components (i18n wiring)

Restoring the Vietnamese localization on 9 dashboard components (useTranslations
wiring + t()/tc() call-site swaps for previously hardcoded strings) grows each
file by a small, irreducible amount. Bumps the frozen file-size-baseline.json
caps to match, with a justification entry per the project's own ratchet policy.

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

* fix(i18n): wire weekday localization + add missing qwen CLI description

Two gaps left by this PR's own new contract tests, caught while
reconciling the branch against the release tip:

- CostOverviewTab.tsx added formatWeekdayLabel() but never called it;
  the Weekly Usage Pattern chart still showed raw English day
  abbreviations regardless of locale. Now maps weeklyPattern rows
  through it before handing them to WeeklyPatternCard.
- cliTools.toolDescriptions was missing an entry for "qwen" (a
  baseUrlSupport:"full" tool) in both en.json and vi.json, failing
  the PR's own cli-catalog-display-contract.test.ts.

Covered by the PR's existing tests/unit/dashboard-localization-contract.test.ts
and tests/unit/cli-catalog-display-contract.test.ts (both now pass).

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

* i18n(vi): backfill the 4 proxySubscription keys #7299 added to en.json

#7299 (proxy subscriptions) merged while this branch was rebasing, adding
settings.proxySubscriptionsTab and settings.proxySubscription.error.{LOCAL_CORE_ENDPOINT_INVALID,
NEEDS_CORE_NOT_CONFIGURED,NO_USABLE_NODES} to en.json. This PR's own
i18n-vi-completeness contract asserts full en↔vi key parity, so the merge of the
current release tip surfaced them as missing. Adds the Vietnamese translations,
keeping the SS/VMess/Trojan/VLESS/SOCKS5 technical terms verbatim.

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

---------

Co-authored-by: nguyenha935 <208228297+nguyenha935@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>
Co-authored-by: nguyenha935 <nguyenha935@users.noreply.github.com>
2026-07-21 13:41:02 -03:00
Navaneet Ramabadran
eab59d4048 fix(translators): normalize TitleCase tool names for non-Anthropic models (#7926)
* fix(translators): normalize TitleCase tool names for non-Anthropic models

* fix(translator): parse XML <invoke> blocks in OpenAI→Claude response translator

* fix(gemini-web): mark gemini-3.1-flash-lite as toolCalling: false

* fix(gemini-web): mark all models as toolCalling: false, add to no-tools combo

* fix(web-providers): mark all web-cookie models as toolCalling: false

* fix(nvidia): disable tool calling on models that can't handle it

* fix(lint): drop explicit any annotation on extractXmlInvokeBlocks state param

Removes the no-explicit-any violation introduced alongside the TitleCase
tool-name normalization work so the merged branch stays lint-clean
(state stays implicitly typed like its sibling helpers in this file).

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>
2026-07-21 13:40:57 -03:00
NOXX - Commiter
55549bfe5a feat(sse): add PromptQL playground provider (unofficial) (#7911)
* 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)

* feat(sse): add PromptQL playground provider (unofficial)

Reverse-engineered prompt.ql.app session bridge:
- GraphQL start_thread / send_thread_message + poll thread_events for AgentMessage
- Sticky thread_id for OpenAI multi-turn (X-PromptQL-Thread-Id)
- Live FetchLlmConfigs catalog with offline seed fallback
- Credits via promptql_project_credit_summary (USD micros to Limits)
- Optional JWT refresh via auth.pro.ql.app (requires session cookie; verify in prod)

NOTE: token refresh against auth.pro.ql.app/ddn/project/token still needs
production verification with real browser session cookies.

* feat(promptql): live FetchLlmConfigs model discovery on providers API

Wire promptql/pql into /api/providers/[id]/models like notion-web:
JWT → GraphQL FetchLlmConfigs; seed catalog fallback when missing/expired.

* fix(promptql): surgical models discovery patch on main

Replace polluted models/route.ts copy with a clean origin/main-based patch
that only adds PromptQL FetchLlmConfigs discovery.

* fix(promptql): sticky multi-turn by history prefix + surface USD credits on Limits

Thread continuity (SkillsManager / multi-session):
- Root cause: cache key was sha256(projectId + first user message only). Shared
  greetings and agentic/UREW system pins made unrelated chats collide, so a
  follow-up was randomly send_thread_message'd into an older PromptQL thread.
- Fix: prefer body.promptql_thread_id / X-PromptQL-Thread-Id; else fingerprint
  the full non-system history prefix (messages before last user, requiring
  prior assistant content). First turn always start_thread. After each reply,
  store under fingerprint(full history + assistant) for the next prefix match.
- Stale client thread ids fall back to start_thread instead of guessing another
  sticky hit. System/developer/tool roles are excluded from fingerprints.

Limits page (getCreditSummary already implemented but never synced):
- promptql/pql were missing from USAGE_SUPPORTED_PROVIDERS,
  PROVIDER_LIMITS_APIKEY_PROVIDERS, and USAGE_FETCHER_PROVIDERS, so
  isSupportedUsageConnection returned false for JWT/apikey PromptQL connections
  and /api/usage/provider-limits never cached quotas.credits USD.
- Registered all three allowlists so scheduled + manual limits sync call
  getPromptQlUsage (micros → USD).

Tests: 18/18 executor-promptql (5 new continuity cases + allowlist guard).

* fix(sse): mechanical pre-merge fixes for PromptQL provider (#7911)

Docs/env sync for the 4 new PROMPTQL_* env vars, regenerate the
translate-path GOLDEN snapshot for the new provider, fix a promptql
model-discovery type narrowing error, and restore CHANGELOG.md /
eslint-suppressions.json to the release tip (both were incorrectly
resolved to the PR's stale branch copy during the merge).

Co-authored-by: artickc <artur1992123@mail.ru>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouzapw@users.noreply.github.com>

* fix(promptql): credits micros display + multi-turn sticky against rewrites

Follow-up for #7911 against live SPA captures (ge_balance / send1 / send2):

Credits (Limits card showed 100%/0 used):
- Map available/drawn/remaining micros to total/used/remaining USD
- displayName Credits (USD); resetAt null (last_drawdown is not a hard reset)
- Optional Cookie header + better HTTP error bodies for getCreditSummary

Thread continuity (every multi-turn started a new chat):
- normalizeForFingerprint strips agent_mention + User request: wrappers
- last-assistant rolling sticky key survives UREW last-user rewrites
- tighten send_thread dead-thread fallback (no bare 400/invalid match)

Tests: 21/21 executor-promptql

* fix(promptql): dual JWT credits + tool-result multi-turn sticky

Live diagnosis after #7911 packaging:

Credits (0 used / 100% remaining):
- Real playground JWT often is DDN/lux (iss=auth.pro.hasura.io) with project id
  only in aud — not x-hasura-project-id. Extract aud UUID for projectId.
- data.pro.ql.app getCreditSummary accepts DDN tokens; enrich-token is rejected.
- Prefer providerSpecificData.luxJwt/ddnToken for credits; pass connection.projectId.
- Clear dual-token messages when only enrich is present.

Chat (Missing projectId + tool follow-ups starting new threads):
- Reject DDN-only tokens for playground with paste-enrich instructions.
- Sticky keys: last-assistant text + tool-name signature; read OpenAI tool_calls
  when content is null; treat tool/function as turn boundaries.
- normalizeForFingerprint strips soft pin, tool-result wrappers, @mentions.

Tests: 30/30 executor-promptql (tool_calls sticky, DDN aud, luxJwt credits).

* refactor(sse): split promptql executor into semantic leaf modules (file-size cap)

open-sse/executors/promptql.ts had grown to 1283 lines (cap 800 for new/tracked
files). Split by responsibility, no behavior change:

- open-sse/services/promptql/jwt.ts: JWT decode/expiry, project-id extraction,
  token classification (playground vs DDN/lux), resolvePromptQlCredentials.
  Also de-dupes decodeJwtPayload/extractProjectIdFromToken, previously
  copy-pasted into open-sse/services/usage/promptql.ts (flagged in review) —
  that file now imports the shared helpers instead of reimplementing them.
- open-sse/executors/promptql/messageText.ts: OpenAI message/content text
  extraction (content parts, tool_calls, function_call).
- open-sse/executors/promptql/eventTree.ts: AgentMessage event-tree walk +
  final_response XML fallback parsing.
- open-sse/executors/promptql/threadSticky.ts: multi-turn thread session
  cache (fingerprinting, disk/memory binding store, resolve/store).

The executor keeps the GraphQL client, queries, and PromptQlExecutor class
wiring (now 698 lines). All original exports remain reachable from the
executor module (re-exported) so no consumer/test import changed.

Also fixes two defects surfaced while revalidating the two commits the author
pushed on top of the last pre-green:
- open-sse/services/promptqlModels.ts: discoverPromptQlModels()'s .map/.filter
  pair failed typecheck (TS2322/TS2677) because the mapped literal's inferred
  type didn't structurally match PromptQlModel's optional configId — annotate
  the map callback's return type instead of `satisfies`.
- src/lib/usage/providerLimits.ts: registering the promptql/pql apikey-limits
  providers pushed this frozen-at-1000-lines file to 1001; reflowed the new
  entries onto one fewer line to stay within the frozen cap (no baseline bump).

Validated: typecheck:core clean, eslint clean on touched files, check:cycles
(explicit open-sse/executors + open-sse/services roots) shows no promptql
file in any cycle, file-size gate OK (0 violations), golden provider
translate-path test green, check-env-doc-sync clean (only the pre-existing,
unrelated OMNIROUTE_DATA_DIR gap remains), check-changelog-integrity OK,
tests/unit/executor-promptql.test.ts 30/30 passing (byte-equivalent asserts).

Co-authored-by: artickc <artur1992123@mail.ru>

---------

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: 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>
2026-07-21 13:16:58 -03:00
FenjuFu
19cbe8ae14 fix(providers): route iflytek/sparkdesk to Spark's OpenAI-compatible host (#7942)
Both entries declare format: "openai" with authHeader: "bearer", but
pointed at spark-api.xf-yun.com — Spark's WebSocket host, which
authenticates with an HMAC-SHA256 signature over app_id/apiKey/apiSecret
and rejects bearer tokens. The OpenAI-compatible HTTP API lives on
spark-api-open.xf-yun.com/v1, so neither provider could complete a
request as configured.

sparkdesk also listed a "general" model; that is a WebSocket domain
value and is not accepted by the HTTP endpoint, so it becomes "lite"
(Spark Lite). The free-model catalog's sparkdesk row is updated to
match, and a regression test locks both baseUrls, the removed "general"
model id, and catalog/registry cross-reference.

Reconstructed against release/v3.8.49 (folds in the same-PR follow-up
"point sparkdesk free-catalog row at lite").

Signed-off-by: FenjuFu <92919259+FenjuFu@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-21 13:16:52 -03:00
NOXX - Commiter
a865fddb26 fix(perplexity-web): multi-step empty content + advanced-quota cooldown (#7930)
Perplexity's live multi-step/copilot streams can surface the
advanced_models_quota_low upsell instead of any answer text when the
account's weekly advanced-model budget is exhausted. Detect it and
return HTTP 429 with reset_seconds/Retry-After (mapped to
rate_limited_until) instead of a silent empty-content error.

Also fixes plan-goal (thinking) extraction for live multi-step streams
that deliver the plan as an RFC-6902 diff patch against plan_block
instead of a materialized plan_block object — those goals were
previously dropped.

Reconstructed against release/v3.8.49: most of the original "empty
content" fix in this PR was independently and differently addressed on
release already (extractAnswerFromFinalText + longestMarkdownAnswer),
so only the two non-overlapping pieces above are ported here.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-21 13:16:47 -03:00
Adam
f31f3c081e feat(proxy): operator-level proxy subscriptions (Karing-style) — hardened, ready for review (#7299)
* feat(proxy-subscriptions): src/lib/proxySubscription/parse.ts

* feat(proxy-subscriptions): src/lib/proxySubscription/subscriptionService.ts

* feat(proxy-subscriptions): src/lib/proxySubscription/index.ts

* feat(proxy-subscriptions): src/lib/db/migrations/123_proxy_subscriptions.sql

* feat(proxy-subscriptions): src/app/api/v1/management/proxy-subscriptions/route.ts

* feat(proxy-subscriptions): src/app/api/v1/management/proxy-subscriptions/[id]/route.ts

* feat(proxy-subscriptions): src/app/api/v1/management/proxy-subscriptions/[id]/refresh/route.ts

* feat(proxy-subscriptions): src/app/api/v1/management/proxy-subscriptions/[id]/nodes/route.ts

* feat(proxy-subscriptions): src/app/(dashboard)/dashboard/settings/components/proxy/SubscriptionTab.tsx

* feat(proxy-subscriptions): tests/unit/proxySubscription.parse.test.ts

* feat(proxy-subscriptions): tests/unit/proxySubscription.service.test.ts

* feat(proxy-subscriptions): docs/proxy-subscriptions.md

* feat(proxy-subscriptions): src/lib/db/proxies/types.ts

* feat(proxy-subscriptions): src/lib/db/proxies/mappers.ts

* feat(proxy-subscriptions): src/lib/db/proxies.ts

* feat(proxy-subscriptions): src/app/(dashboard)/dashboard/settings/components/ProxyTab.tsx

* i18n(proxy-subscriptions): add proxySubscriptionsTab key + use in ProxyTab

* i18n(proxy-subscriptions): add proxySubscriptionsTab key + use in ProxyTab

* i18n(proxy-subscriptions): add proxySubscriptionsTab key + use in ProxyTab

* i18n(proxy-subscriptions): add proxySubscriptionsTab key + use in ProxyTab

* test(proxy-subscriptions): extract isSubscriptionDue + unit tests

* test(proxy-subscriptions): extract isSubscriptionDue + unit tests

* test(proxy-subscriptions): extract isSubscriptionDue + unit tests

* test(proxy-subscriptions): add global->rule switch re-bind integration test

* feat(proxy-subscriptions): inline needs-local-core guidance in SubscriptionTab

* i18n: add proxySubscriptionsTab to en (rebased on current main)

* i18n: add proxySubscriptionsTab to zh-CN (rebased on current main)

* i18n: add proxySubscriptionsTab to pt-BR (rebased on current main)

* test(proxy-subscriptions): extract needsCore detection into pure module + unit tests

* test(proxy-subscriptions): extract needsCore detection into pure module + unit tests

* test(proxy-subscriptions): extract needsCore detection into pure module + unit tests

* refactor(proxy-subscriptions): extract scopes.ts into pure module + unit tests (#65)

* refactor(proxy-subscriptions): extract coreEndpoint.ts into pure module + unit tests (#65)

* refactor(proxy-subscriptions): extract subscriptionService.ts into pure module + unit tests (#65)

* refactor(proxy-subscriptions): extract proxySubscription.scopes.test.ts into pure module + unit tests (#65)

* refactor(proxy-subscriptions): extract proxySubscription.coreEndpoint.test.ts into pure module + unit tests (#65)

* security(proxy-subscriptions): fetchGuard.ts — SSRF guard + core scheme (#P0)

* security(proxy-subscriptions): coreEndpoint.ts — SSRF guard + core scheme (#P0)

* security(proxy-subscriptions): subscriptionService.ts — SSRF guard + core scheme (#P0)

* security(proxy-subscriptions): proxySubscription.fetchGuard.test.ts — SSRF guard + core scheme (#P0)

* security(proxy-subscriptions): proxySubscription.coreEndpoint.test.ts — SSRF guard + core scheme (#P0)

* refactor(proxy-subscriptions): subscriptionService.ts — concurrency lock / resilience / url redaction (#P1)

* refactor(proxy-subscriptions): url.ts — concurrency lock / resilience / url redaction (#P1)

* refactor(proxy-subscriptions): index.ts — concurrency lock / resilience / url redaction (#P1)

* refactor(proxy-subscriptions): route.ts — concurrency lock / resilience / url redaction (#P1)

* refactor(proxy-subscriptions): route.ts — concurrency lock / resilience / url redaction (#P1)

* refactor(proxy-subscriptions): proxySubscription.url.test.ts — concurrency lock / resilience / url redaction (#P1)

* i18n(proxy-subscriptions): subscriptionService.ts — stable error codes + locale keys (#P2-6)

* i18n(proxy-subscriptions): SubscriptionTab.tsx — stable error codes + locale keys (#P2-6)

* i18n(proxy-subscriptions): en.json — stable error codes + locale keys (#P2-6)

* i18n(proxy-subscriptions): zh-CN.json — stable error codes + locale keys (#P2-6)

* i18n(proxy-subscriptions): pt-BR.json — stable error codes + locale keys (#P2-6)

* i18n(proxy-subscriptions): en.json — normalize to LF line endings (#P2-6)

* i18n(proxy-subscriptions): zh-CN.json — normalize to LF line endings (#P2-6)

* i18n(proxy-subscriptions): pt-BR.json — normalize to LF line endings (#P2-6)

* enhance(proxy-subscriptions): reject subscription fetch if ANY resolved DNS address is blocked (P3-1)

* enhance(proxy-subscriptions): add withRetry() exponential-backoff helper (P3-2)

* enhance(proxy-subscriptions): DNS multi-record guard + retry/backoff fetch + batch scope writes + observability (P3-1..P3-4)

* enhance(proxy-subscriptions): batch addProxiesToScopePool() to drop N+1 writes (P3-3)

* enhance(proxy-subscriptions): add last_error_at + consecutive_failures observability columns (P3-4)

* enhance(proxy-subscriptions): surface consecutive failures + last error time in subscription cards (P3-4)

* test(proxy-subscriptions): cover multi-record DNS SSRF (block if ANY address internal) (P3-1)

* test(proxy-subscriptions): cover withRetry() first-success / retries / backoff / non-retryable stop (P3-2)

* fix(proxy-subscriptions): resolve js-yaml import + missing backup/generation-bump imports

Two bugs made the feature non-functional and its own test suite false:

1. parse.ts used `import yaml from "js-yaml"` (default import), but
   js-yaml@^5 is ESM-only with no default export — this threw a
   SyntaxError at module load, crashing every caller (index.ts
   re-exports parse.ts, so every API route hit this too). Switch to
   `import * as yaml`, matching how the rest of the codebase already
   imports js-yaml (hermes-agent.ts, openapiParser.ts, openapi/spec
   route.ts, guide-settings route.ts).

2. subscriptionService.ts's unapplySubscription() called backupDbFile()
   and bumpProxyRegistryGeneration() without importing either —
   backupDbFile exists but wasn't imported; bumpProxyRegistryGeneration
   was a private, non-exported function in db/proxies.ts. This threw a
   ReferenceError whenever a subscription with bound proxies was
   disabled/deleted (the normal path). Import backupDbFile from
   ../db/backup and export+import bumpProxyRegistryGeneration from
   ../db/proxies, matching the identical backup+bump pattern already
   used by deleteProxyById for the same proxy_assignments/proxy_registry
   mutation shape.

Fixing both unmasked a third, previously-unreachable bug (both crashes
happened before any test assertion could run): recomputeProxyEnabled()
checked `proxy_subscriptions.enabled = 1` alone, which stays true across
an unapply/disable cycle since unapplySubscription() never touches that
column — the proxyEnabled flag would get stuck on `true` even after the
subscription's proxies were fully detached. Changed the check to require
an actually-bound proxy_assignments row for an enabled subscription,
matching hasNonSubscriptionGlobalProxy()'s existing bound-check pattern.
Traced all 3 production call sites (mode/rule switch, disable, delete)
to confirm this doesn't change their outcome — only the previously-wrong
"unapply in isolation" case.

Also fixed the test file's own pre-existing bug: its provider_connections
inserts omitted created_at/updated_at (NOT NULL, no default in the
schema since 001_initial_schema.sql), which 0 assertions had ever
reached before because the SyntaxError always crashed the file first.

All 9 proxySubscription test files now run clean: 49/49 pass (previously
2 files crashed outright at import time, 0 assertions ever ran).

Separately confirmed via testing against the pristine PR head: this PR
has 3 more pre-existing gate failures unrelated to the above (file-size
on db/proxies.ts, cognitive-complexity, complexity, changelog-integrity
vs the current release tip) plus 3 pre-existing TS2345 errors in
parse.ts (lines 228/233/263, unrelated to the yaml import). All are the
PR's own scope/base-drift, out of scope for this fix — the PR has never
had a real CI run (base=main), so no gate has ever surfaced them; they
need the base retarget + a full CI pass called out in the plan file's
own remaining mandatory items.

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

* fix(db): renumber proxy-subscriptions migrations to avoid version collision

release/v3.8.49 already ships 123_quota_auto_ping.sql and
124_generic_session_affinity_ttl.sql; this PR's 123/124 files collided,
tripping migrationRunner's version-collision guard and failing all
proxySubscription.service tests. Renumber to 127/128 (next free slots
after 126_reasoning_routing_rules.sql).

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouzapw@users.noreply.github.com>

* refactor(db): extract proxySubscriptions + registryGeneration to keep proxies.ts under file-size cap

Moves addProxiesToScopePool to ./proxySubscriptions.ts and the registry-generation
helpers to ./proxies/registryGeneration.ts (re-exported from proxies.ts), keeping the
module under its frozen 1177-line cap after the operator-proxy-subscriptions feature.

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

* fix(db): renumber proxy-subscriptions migrations past release collision

Rebasing onto release/v3.8.49 surfaced a migration version collision: this
branch's 127_proxy_subscriptions.sql and 128_proxy_subscriptions_meta.sql
now collide with 127_usage_history_account_identity.sql and
128_auto_candidate_overrides.sql that landed on release since this branch
last synced. Renumbered to 131/132 (next free prefixes after the current
130_remove_unregistered_qwen_data.sql) and updated the in-file header
comments to match. No schema/behavior change.

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

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouzapw@users.noreply.github.com>
Co-authored-by: xier2012 <xier2012@users.noreply.github.com>
2026-07-21 13:16:41 -03:00
Tmone Nguyen
124557cc4b fix(combo): context-aware fallback ignores model_context_override (#7933)
* fix(combo): context-aware fallback ignores model_context_override

filterTargetsByRequestCompatibility resolved a target's context capacity through the override-free getResolvedModelCapabilities, so a persisted model_context_override (Feature 5004) never reached the compatibility filter. A provider whose catalog maxInputTokens is a deliberately small client-facing hint (below the real window so coding agents auto-compact, #6191) is then dropped from the fallback pool for large-context requests even when an operator recorded its true larger capacity. With only that provider left after Claude quota is exhausted, the combo returns a hard 503 with no fallback.

evaluateContextLimit now consults the raw override (getModelContextOverride, null when unset) before catalog limits, only when an override exists - so a genuinely-too-small maxInputTokens is still enforced for non-overridden models. Consistent with two other override-aware call sites in this file. Registry values (#6191 client hint) untouched. Tests: override rescues small-catalog target; without override too-small still dropped; existing #6191/#7039 tests pass (14/14).

* fix(quality): extract context-fit evaluation to keep comboStructure.ts under cap

The model_context_override fix grew open-sse/services/combo/comboStructure.ts
past the 800-line file-size cap. Extract evaluateContextLimit() (the
override-then-catalog context-fit check) into a new leaf,
open-sse/services/combo/contextOverrideGate.ts, so comboStructure.ts only
keeps the two call-site wires. No behavior change.

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

---------

Co-authored-by: tmone <25759142+tmone@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-21 12:19:06 -03:00
backryun
2d1801985c feat(cline): align ClinePass catalog and request protocol (#7914)
* feat(cline): align catalogs and official protocol

* fix(models): clean imports after final connection removal

* fix(quality): extract Cline/ClinePass auth-header wiring to shrink default.ts

open-sse/executors/default.ts grew to 894 lines against the frozen
890-line cap after adding the ClinePass official-protocol import plus
two Object.assign header-merge blocks. Extract the merge logic into a
new applyClineAuthHeaders() helper in src/shared/utils/clineAuth.ts so
the executor's case "clinepass" / case "cline" branches shrink to a
single call each, dropping default.ts back to 875 lines.

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

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-21 12:19:01 -03:00
Adrian A. Firmansyah
a20771d6ac fix(auto): pool accounts by provider model (#7928)
* fix(auto): pool accounts by provider model

* test(auto): update provider-family-combos to the #7928 Cartesian pool shape

Since #7928 the auto-combo candidate pool is a connections × models Cartesian
product, so createBuiltinAutoCombo("auto/<family>") now surfaces each backend's
full family line-up rather than exactly one default model per connection. The
#6453 invariant is unchanged and still asserted — which providers span the
family and that unrelated providers (the connected openai/gpt-4o-mini, the
connected glm on auto/zai) are excluded — but the two exact-count assertions are
relaxed to the provider SET and the detectModelFamily() family check, matching
the pattern the sibling auto/minimax case already uses.

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

---------

Co-authored-by: Your Name <you@example.com>
Co-authored-by: adrianaryaputra <adrian.arya@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-21 12:17:18 -03:00
Diego Rodrigues de Sa e Souza
11b5ce1f0e feat(providers): add 5 free-tier providers (ainative, aion, sealion, routeway, nara) (#7887)
Five OpenAI-compatible free-tier aggregators OmniRoute did not cover yet, added
as a registry entry plus a canonical provider each.

  ainative  api.ainative.studio/api/v1  — 84-model public catalog, passthrough
  aion      api.aionlabs.ai/v1          — 5 models, public catalog w/ pricing
  sealion   api.sea-lion.ai/v1          — AI Singapore, pinned models (10 RPM)
  routeway  api.routeway.ai/v1          — 236-model catalog; browser UA pinned
                                          because Cloudflare 1010s non-browser UAs
  nara      router.bynara.id/v1         — shared 5M/day pool, pinned free models

All five /models endpoints were probed live 2026-07-20 (200 for the public ones;
sealion/nara 401 without a key, as expected) and every pinned model id was
confirmed to exist upstream.

Free-catalog honesty: ainative ("~10M tok/mo claimed"), aion (20k tok/day),
sealion (10 RPM) and routeway (200 RPD) have no verifiable monthly TOKEN quota,
so they are recurring-uncapped — real access, never summed into the headline.
Only nara publishes a token figure (5M tokens/day shared = 150M/month), recorded
as one deduped pool. Net: 484 -> 514 models, 1.376B -> 1.526B tokens, the +150M
coming solely from nara.
2026-07-21 12:01:52 -03:00
Innokentiy Solntsev
f909b1d45e fix(resilience): don't cool down accounts or trip the breaker on client aborts (#7908)
* fix(resilience): don't cool down accounts or trip the breaker on client aborts

When the caller drops the connection mid-stream, the in-flight request surfaces
request_signal_aborted, "Client disconnected", or a DOM AbortError with no
upstream status code. These shapes were counted as provider failures: the
serving connection went into cooldown, the provider circuit breaker accrued
failures, and healthy accounts ended up marked unavailable from client-side
cancellations alone.

Treat client aborts as local stream lifecycle events (#4602 policy): extend
isLocalStreamLifecycleError() to recognize abort shapes and skip connection
disable and breaker accounting for them. Genuine upstream failures (5xx/429/401)
are still counted.

Fixes #7907.

* fix(resilience): guard the two remaining breaker-trip call sites against client aborts (#7907)

PR #7908 correctly wired isLocalStreamLifecycleError() into
shouldSkipConnDisable() and chatHelpers.ts's onStreamFailure, but two
separate breaker._onFailure()-triggering call sites were purely
status-code gated and never checked it, so a client-side abort (no
upstream status, defaults to 502, error='request_signal_aborted')
still tripped the whole-provider circuit breaker — the highest
blast-radius of the 3 resilience mechanisms:

- src/sse/handlers/chat.ts: the single-model, non-combo terminal-failure
  path called breaker._onFailure() directly, bypassing the isFailure
  option (which only applies inside breaker.execute()). Extracted the
  predicate into shouldTripProviderBreakerForResult() and added the
  missing isLocalStreamLifecycleError guard.
- open-sse/services/combo/comboPredicates.ts::shouldRecordProviderBreakerFailure(),
  used by handleComboChat's executeTarget (open-sse/services/combo.ts),
  gained the same guard via a new optional `error` field.

Added tests/unit/circuit-breaker-abort-provider-trip-7907.test.ts
exercising both real predicates directly (not just the isolated
isLocalStreamLifecycleError() helper) — confirmed red on the unfixed
code (missing export) and green after the fix, alongside the existing
#4602/#7908/combo-breaker-429 suites (24/24 pass, no regressions).

file-size-baseline.json: +1 combo.ts (irreducible call-site wiring for
the new `error` field) and +1 chatHelpers.ts (own growth from the PR's
already-verified onStreamFailure guard, surfaced only now since
fast-gates PR->release skip check:file-size).

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouzapw@users.noreply.github.com>
Co-authored-by: insoln <insoln@ya.ru>

* test(quality): register circuit-breaker abort tests in stryker tap.testFiles

The two new tests (circuit-breaker-abort-provider-trip-7907, circuit-breaker-client-abort)
import mutated modules (circuitBreaker.ts, comboPredicates.ts) but were not listed in
stryker.conf.json tap.testFiles, tripping check:mutation-test-coverage --strict.

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

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouzapw@users.noreply.github.com>
2026-07-21 12:01:47 -03:00
Diego Rodrigues de Sa e Souza
4e57c1dc9a fix(electron): derive macOS Helper name from execPath to remove 2nd Dock icon (#7941) (#8002)
resolveNodeExecutable() built the macOS Helper path from app.getName() (package.json
name = "omniroute-desktop"), but electron-builder names the Helper.app bundles from
build.productName ("OmniRoute"). The two diverged, so the probe never matched a real
Helper and fell through to process.execPath — spawning the main Electron binary via
ELECTRON_RUN_AS_NODE, which macOS renders as a second, inert Dock icon.

Extracted the resolution into electron/lib/resolveNodeHelper.js (pure, unit-testable),
deriving the Helper name from path.basename(process.execPath) so it always tracks the
productName-generated bundle. Registered the new module in electron build.files.

Reported by Carlos Espinoza (Carloss616) with runtime instrumentation of a packaged
.app pinpointing the exact field divergence.
2026-07-21 12:00:36 -03:00
Paijo
3238df3204 perf: lazy provider init, P2C quota cache, structuredClone elimination, getSettings→getCachedSettings (batch 2) (#7893)
* perf: startup parallelization, stream TextEncoder lift, auth middleware bottlenecks

Startup (~100-300ms faster cold start):
- Parallelize 4 early imports via Promise.all() in registerNodejs()
- Parallelize 10 independent background services via Promise.allSettled()
- Each service has independent try/catch — no failure domino effect

Streaming pipeline (8 fewer TextEncoder GC allocations per SSE event):
- Lift new TextEncoder() from per-chunk inside buildClaudeStreamingResponse
  to function scope alongside existing decoder singleton

Auth middleware bottlenecks (from PerfBottleneckAnalysis):
- Backoff decay loop: replace updateProviderConnection (full CRUD:
  SELECT+encrypt+cache-invalidate+backup) with resetConnectionBackoff
  (targeted UPDATE of backoff/error columns only)
- Dual .filter() for quota: replace two passes calling
  isQuotaExhaustedForRequest per connection with a single for loop
  partitioning into withQuota/exhaustedQuota
- Debug-log filter recomputation: capture connectionFilterStatus Map
  during the filter pass; debug loop reads 6 string comparisons instead
  of 6 function calls per connection

Supporting:
- Add resetConnectionBackoff to src/lib/db/providers.ts (patterned after
  clearConnectionErrorIfUnchanged, no CAS check)
- Re-export resetConnectionBackoff from src/lib/localDb.ts
- Update integration-wiring.test.ts regex for parallelized dynamic import

* perf: P2C quota cache, lazy provider init, structuredClone elimination, getSettings→getCachedSettings

- **auth.ts: P2C quota re-evaluation cache** — quotaResults Map threaded
  through selectPoolSubset → compareP2CConnections → getP2CConnectionScore.
  Populated during filter + partition passes, eliminating redundant
  evaluateQuotaLimitPolicy / isQuotaExhaustedForRequest calls when the
  P2C comparator re-evaluates previously-scored connections.
- **constants.ts: lazy PROVIDERS via Proxy** — replaces eager
  generateLegacyProviders() + loadProviderCredentials() at module load
  with Proxy delegating to deferred init on first property access.
- **providerModels.ts: lazy PROVIDER_MODELS + PROVIDER_ID_TO_ALIAS** —
  same Proxy pattern for both exports; generateModels()/generateAliasMap()
  deferred until first read.
- **stream.ts: structuredClone → minimal object spread** — replaces
  O(n) deep clone of SSE response chunks with targeted reconstruction
  of only mutated fields (usage, delta.content, finish_reason).
- **progressTracker.ts: TextDecoder lift** — module-level decoder
  instead of per-chunk new TextDecoder().
- **Route files: getSettings() → getCachedSettings()** — 13 API route
  files converted from uncached per-request DB reads to TTL-cached
  wrapper (5s default), eliminating redundant queries on every request.
- **settings.ts: re-export getCachedSettings** from readCache for
  non-localDb consumers.
- **Remove settingsCache.ts** — dead file, no imports reference it.

TS compile: 0 errors. Auth tests: 225/225 pass. Services: 269/269 pass.

* perf: Phase 1 tangible wins — egressCache eviction, mmap_size PRAGMA, composite indexes, proxyFallback lazy import

- egressCache: lazy TTL eviction on getCachedEgressIp access (bounds memory
  leak to distinct proxy URLs, typically <100)
- mmap_size: apply stored PRAGMA from key_value table (256MiB default) after
  applyStoredDatabaseOptimizationSettings — setting was stored but never applied
- schemaColumns: add idx_uh_provider_model_timestamp (covers getModelLatencyStats)
  and idx_pc_provider_auth_type (covers 6+ provider_connections queries)
- proxyFallback: convert static import to dynamic import() inside error handler
  (defers 210ms module load from startup to first proxy-retry scenario)

* perf: add dedup expression index, unref() sweep timers

- Add COALESCE expression index idx_uh_dedup on usage_history
  matching the exact dedup query pattern. Eliminates FULL TABLE
  SCAN on every saveRequestUsage insert.
- Add composite idx_uh_provider_model_timestamp on usage_history.
- Add composite idx_pc_provider_auth_type on provider_connections.
- Add .unref() to setInterval in batchProcessor.ts (polling loop).
- Add .unref() to setInterval in runtimeHeartbeat.ts (heartbeat).

* perf: bump SQLite cache_size default from 16MB to 64MB

New installs now start with 64MB page cache (was 16MB). Existing
users' stored settings are unchanged. Reduces disk reads for the
typical ~250MB database by keeping ~25% of pages in memory.

Also resolved pre-existing merge conflict in webhooks.ts.

* docs: add Redis production config guide and proxy port clash investigation report

- docs/redis-production-config.md: comprehensive Redis tuning guide
  covering client options, server config, Docker settings, scaling,
  and monitoring for all three Redis workloads (rate limiting,
  auth cache, quota store)
- docs/proxy-port-clash-report.md: investigation confirming proxy
  subsystem has no port binding issues; real EADDRINUSE history
  traced to process supervisor crash-loop restart race (#4425) and
  live-dashboard port clash (#6324), both already fixed

* fix: address PR #7893 review — add Proxy traps, extract migrations to reduce providers.ts size

- Add set trap to PROVIDER_ID_TO_ALIAS Proxy (providerModels.ts)
- Add deleteProperty traps to all three lazy Proxies (PROVIDERS,
  PROVIDER_MODELS, PROVIDER_ID_TO_ALIAS)
- Extract autoMigrateLegacyEncryptedConnections and getGheCopilotHosts
  from providers.ts (1129→1036 lines, -93) into providers/migrations.ts
- Both functions re-exported via providers.ts for backward compat

File-size ratchet resolved: src/lib/db/providers.ts now 1036 lines.

* fix: resolve merge conflict markers in 3 route/test files

- model-combo-mappings/route.ts: kept upstream version (Zod pagination
  via validateBody + isValidationFailure), restored missing return
  statement for GET handler
- playground/presets/route.ts: kept stashed version details (satisfies
  type-narrowing + inlined Response) — functionally identical
- error-sanitization.test.ts: matches upstream exactly (no diff)

Test verification: same 7 pre-existing failures confirmed on upstream
baseline (c1bdd91e7). Zero regressions from conflict resolution.

Closes remaining uncommitted work from PR #7046 rebase.

* test(db): add resetConnectionBackoff coverage + fix file-size ratchet regression

- Add tests/unit/reset-connection-backoff.test.ts: covers the new
  resetConnectionBackoff lightweight-UPDATE helper (clears backoff/error
  columns and re-activates a connection, unconditional-write behavior on
  terminal statuses, no-op for empty/unknown ids). Zero prior coverage
  per pre-merge review of PR #7893 (Hard Rule #8).
- open-sse/services/batchProcessor.ts: fold the new unref() call into the
  existing setInterval(...).unref() chain instead of a separate statement,
  keeping the file at the frozen 915-line ratchet (Timeout.unref() already
  returns `this`, so no type cast is needed).
- src/lib/localDb.ts: drop one blank separator line so the new
  resetConnectionBackoff re-export stays within the frozen 808-line ratchet.

Pre-merge fix for PR #7893 (perf/startup-stream-auth-optimizations) per
/green-prs plan-file _tasks/pipeline/prs/1-analyzed/7893-*.plan.md.

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouzapw@users.noreply.github.com>
2026-07-21 11:50:14 -03:00
Sulistyo Fajar Pratama
246b87f739 fix(mitm): gate Agent Bridge DNS and Trust Cert on sudo password (#7938) (#7939)
* fix(mitm): gate Agent Bridge DNS and Trust Cert on sudo password (#7938)

Extend the #7865 sudo gate to setup wizard DNS, Start DNS, and Trust Cert.
Start server still runs without a password but skips privileged cert/DNS
steps instead of spawning sudo -S with an empty string. Add a shared sudo
password modal on the Agent Bridge page for DNS and trust-cert actions.

Fixes #7938

* fix(mitm): use .tsx extension for MitmSudoPasswordModal hook

JSX in a .ts file broke dashboard typecheck and ESLint on CI.

* fix(mitm): skip DNS teardown on stop when sudo password missing (#7938)

stopMitm() no longer invokes removeDNSEntry with an empty password when
the server was started in skip mode. The MITM process is still killed.

* refactor(mitm): extract privileged step helpers to satisfy file-size cap

manager.ts exceeded the 800-line cap after #7938 gates. Move DNS teardown
and the shared sudo skip runner into dedicated modules; behavior unchanged.
2026-07-21 11:50:09 -03:00
Erick Kinnee
567736fac5 fix(ccr): resolve principal via OMNIROUTE_API_KEY env var on stdio MCP transport (#7932)
CCR stores blocks keyed by principalId (the API key's DB row id). On
stdio transport there is no HTTP context, so resolveMcpCallerApiKeyId()
always returned undefined and the fallback resolved to 'anonymous' —
a store-key miss ('block not found').

Add resolvePrincipalFromEnv() that reads OMNIROUTE_API_KEY or
ROUTER_API_KEY from the environment and resolves through the same
getApiKeyMetadata() lookup that storage uses. Both storage and retrieval
now get the same principal id, so the store key matches.

Closes #7883

Co-authored-by: Erick Kinnee <erick@ekinnee.dev>
2026-07-21 11:50:02 -03:00
ToastedPatatas
448257f8f7 fix(autostart): adopt 9Router VBS startup to suppress console flash on Windows (#7925)
Windows auto-start previously wrote a HKCU\Run registry entry that
launched node.exe directly, causing a visible console window at logon.
Closing that window also killed the background server.

Adopt the same approach as 9Router: write a OmniRoute.vbs script to
the Windows Startup folder that calls WScript.Shell.Run with SW_HIDE (0)
so OmniRoute starts fully hidden. The VBS runs serve --no-open --tray
via the existing buildServeExecLine helper.

Legacy migration — enableWin() removes the old HKCU\Run entry so stale
entries don't linger, and isEnabledWin() falls back to checking the
registry for users who enabled autostart before this change.

Co-authored-by: tientien17 <tientien17@users.noreply.github.com>
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-07-21 11:49:57 -03:00
Diego Rodrigues de Sa e Souza
7c39a0e065 chore(deps): bump js-yaml, brace-expansion, shell-quote, tar (security) (#7915)
Resolves all 11 open Dependabot alerts (lockfile-only; no runtime code change):

- js-yaml 4.2.0 -> 4.3.0 (CVE-2026-59869 / GHSA-52cp-r559-cp3m, high) - root + electron
- brace-expansion 1.1.x/2.1.1/5.0.6 -> 1.1.16/2.1.2/5.0.7 (CVE-2026-13149 /
  GHSA-3jxr-9vmj-r5cp, high) - root + electron
- shell-quote 1.8.4 -> 1.10.0 (CVE-2026-13311 / GHSA-395f-4hp3-45gv, high) - root.
  concurrently (latest 10.0.3) pins shell-quote exactly at 1.8.4, so this adds a
  per-parent override, same pattern as @yarnpkg/parsers.js-yaml
- tar 7.5.16 -> 7.5.20 (CVE-2026-59871 / GHSA-w8wr-v893-vjvp, medium) - root + electron
2026-07-21 11:49:51 -03:00
Erick Kinnee
62d46ba37c fix(api): resolve local provider models via dashboard catalog fallback (#7927)
The /api/v1/providers/{id}/models endpoint returns invalid_provider for
local self-hosted providers (Ollama, LM Studio, vLLM, etc.) because it
only resolves providers via getRegistryEntry() from the open-sse
registry, which does not include LOCAL_PROVIDERS.

After getRegistryEntry() misses, fall back to the dashboard-facing
provider catalog (getProviderById / getProviderByAlias from
@/shared/constants/providers), which covers LOCAL_PROVIDERS and all
other dashboard provider categories. Registry hits retain existing
precedence.

Closes #7910

Co-authored-by: Erick Kinnee <erick@ekinnee.dev>
2026-07-21 11:49:46 -03:00
Diego Rodrigues de Sa e Souza
43fac40b28 docs(guides): document Kaspersky PDM behavioral false positive on the Desktop installer (#7903) (#7923)
Adds a Kaspersky subsection to the existing 'Antivirus False Positives'
troubleshooting section: explains PDM:Trojan.Win32.Generic is a behavioral
heuristic on the unsigned installer, which files it rolls back (playwright /
tls-client native DLL) and why, plus how to verify the sha512 against latest.yml,
restore + exclude, and report the FP to Kaspersky.

Refs #7903, #5946
2026-07-21 11:49:41 -03:00
Diego Rodrigues de Sa e Souza
3f2d88b679 fix(cli): spawn opencode.cmd shim with shell:true on win32 (#7913) (#7964)
* fix(cli): spawn opencode.cmd shim with shell:true on win32 (#7913)

runOpenCodeAuth spawned the opencode.cmd shim with shell:false on win32,
which throws spawnSync EINVAL under Node's hardened child_process
handling (post CVE-2024-27980). Mirrors the fix already applied to
codex (resolveCodexSpawn in launch-codex.mjs, #6263) and
qodercli/Auggie (#6263/#6304): shell:isWin, unchanged elsewhere.

Exported runOpenCodeAuth for testability and added a regression test
covering both the win32 shell:true path and the non-regressing
linux/darwin bare-binary path.

* fix(cli): make opencode --auth win32 spawn testable without module mocks (#7913)

The regression test used t.mock.module, which needs --experimental-test-module-mocks
and fails to even run in CI (TypeError: t.mock.module is not a function → the fix was
unvalidated). Extract a pure resolveOpenCodeAuthSpawn(providerId, platform) helper and
test it directly (win32 → shell:true, linux/darwin → shell:false). Production behavior
of runOpenCodeAuth is unchanged.
2026-07-21 09:15:39 -03:00
Diego Rodrigues de Sa e Souza
4eea1cd14f fix(auth): restore configurable HEALTHCHECK_BATCH_SIZE dropped by #7719 (#7875) (#7970)
* fix(auth): restore configurable HEALTHCHECK_BATCH_SIZE dropped by #7719 (#7875)

* docs(env): document HEALTHCHECK_BATCH_SIZE in .env.example (#7875)

* docs(env): document HEALTHCHECK_BATCH_SIZE in .env.example + ENVIRONMENT.md (#7875)
2026-07-21 09:15:33 -03:00
Diego Rodrigues de Sa e Souza
39ccfcf28c fix: parse Gemini 429 RetryInfo.retryDelay for model lockout (#7940) (#7961)
* fix(sse): parse Gemini 429 RetryInfo.retryDelay for model lockout (#7940)

Gemini free-tier 429 bodies carry a short explicit retry hint --
error.details[].{"@type": google.rpc.RetryInfo, retryDelay: "26s"} plus a
"Please retry in Ns." message -- but parseRetryFromErrorText only matched
'reset after'/'will reset after' text, so quotaResetHintMs came back null and
recordModelLockoutFailure fell back to getMsUntilTomorrow() for
quota_exhausted, locking the model out for ~19h instead of ~26s.

parseRetryHintFromJsonBody (retryAfterJson.ts) now walks error.details[] for
a google.rpc.RetryInfo entry and parses its retryDelay via a shared
parseDelayString helper (moved out of accountFallback.ts so
parseRetryAfterFromBody and the model-lockout path use the same grammar).
parseRetryFromErrorText also gained a 'please retry in Ns' text fallback for
bodies without a parseable details[] array. Both new paths are capped by a
dedicated MAX_SHORT_RETRY_HINT_MS (24h), independent of the existing 30-day
MAX_PROVIDER_COOLDOWN_MS, since RetryInfo/please-retry-in are short
throttling hints, not long-lived quota resets like Antigravity's 160h.

Regression test: tests/unit/bug-7940-gemini-retrydelay.test.ts (RED before
the fix: parseRetryFromErrorText returned null and the resulting lockout was
~19h; GREEN after: ~26s).

* chore(quality): register bug-7940-gemini-retrydelay test in stryker tap.testFiles
2026-07-21 09:15:27 -03:00
Diego Rodrigues de Sa e Souza
0d4fbfeaec fix(sse): preserve parallel_tool_calls for GPT-5.6 delegation under Codex Responses Lite (#7821) (#7957)
* fix(sse): preserve parallel_tool_calls for GPT-5.6 ultra/max delegation under Codex Responses Lite (#7821)

* fix(codex): drop over-broad parallel_tool_calls allowlist entry — keep #2608 stripping intact (#7821)

The static RESPONSES_API_ALLOWLIST addition made parallel_tool_calls survive for
ALL models, breaking the #2608 non-passthrough stripping guarantee for gpt-5.5.
The real #7821 fix (isCodexDelegationDependentModel gating in
enforceCodexResponsesLiteParallelToolCalls) is model/effort-scoped and does not
need the allowlist entry — native Codex traffic returns before the allowlist runs.
2026-07-21 09:03:38 -03:00
Diego Rodrigues de Sa e Souza
a47ce51d4c fix(api): classify /api/acp/agents as loopback-only (#7948) (#7966)
* fix(api): classify /api/acp/agents as loopback-only (#7948)

/api/acp/agents is spawn-capable (POST registers a client-chosen `binary`
via src/lib/acp/registry.ts; GET / POST {action:"refresh"} runs
detectInstalledAgents() -> execFileSync(probe.command, probe.args, { shell })
transitively) but was never added to LOCAL_ONLY_API_PREFIXES in
src/server/authz/routeGuard.ts, unlike every sibling spawn-capable route
(Hard Rules #15/#17). A leaked JWT via tunnel could reach the version-probe
execFileSync path.

Adds the prefix to the loopback gate plus a direct regression test
(tests/unit/route-guard-acp-agents-local-only.test.ts) and an assertion in
tests/unit/check-route-guard-membership.test.ts documenting why the existing
source-scan subcheck cannot catch this class of gap (the spawn call is
transitive via registry.ts, not in the route file itself).

* chore(quality): register route-guard-acp-agents-local-only test in stryker tap.testFiles
2026-07-21 09:03:31 -03:00
Diego Rodrigues de Sa e Souza
68aa977593 fix(test): widen ratelimit-admission pollUntil deadline to 10s (#7842) (#7971)
The nightly-compat Node 24/26 shard failures were 17/18 base-red debt already
fixed forward on the release tip (same pattern as #7025/#7140/#7675/#7742,
tracked in #6949). The one genuine still-reproducing failure was a test-harness
timing-margin bug: ratelimit-admission-control-6593's pollUntil helper used a
fixed 2000ms deadline that races Bottleneck's QUEUED->EXECUTING event-loop-tick
transition under CI/devbox contention. Widened the default deadline to 10000ms;
the admission logic itself (checkQueueAdmission / RATE_LIMIT_QUEUE_FULL / 429) is
unchanged and covered by 3 passing pure-unit tests.
2026-07-21 08:51:41 -03:00
Diego Rodrigues de Sa e Souza
ce80af6c3d fix(dashboard): correct block-extra-Claude-usage toggle copy to match quarantine behavior (#7918) (#7965) 2026-07-21 08:39:49 -03:00
Diego Rodrigues de Sa e Souza
68a883f0c6 fix(cli): translate missing sqlite bindings error into actionable guidance (#7868) (#7963)
createSqliteNativeError() in bin/cli/sqlite.mjs only recognized the ABI-mismatch
native error class (NODE_MODULE_VERSION/ERR_DLOPEN_FAILED). It silently passed through
the 'Could not locate the bindings file' class thrown by the bindings package when the
better-sqlite3 native addon was never built/downloaded - the exact case hit by
'npx omniroute reset-password', since npx runs a fresh ephemeral install that never
builds the addon. Users got a raw multi-line path dump instead of guidance.

Widen the condition to also match 'Could not locate the bindings file',
MODULE_NOT_FOUND, and "Cannot find module 'better-sqlite3'", and point users at the
existing self-heal command `omniroute runtime repair`, same as the ABI-mismatch branch
already does.

Regression test: tests/unit/cli-sqlite-bindings-not-found-7868.test.ts - RED against the
reporter's exact error text before the fix, GREEN after.
2026-07-21 08:39:43 -03:00
Diego Rodrigues de Sa e Souza
ec5b24b986 fix(providers): copilot-m365-web fails loudly on empty turns + tier-aware enterprise invocation (#7858, #7870) (#7958)
#7858 — accumulateBotContent() silently returned an empty delta for any
unrecognized frame shape, and finish() only had a fallback for the
type:2 finalResultMessage case; a turn with no content in ANY known
shape closed with a bare `stop` + `[DONE]`, indistinguishable from a
genuine empty answer. finish() now emits a sanitized error (Hard Rule
#12) naming the resolved tier and the likely causes, and unrecognized
update-frame shapes are logged by argument KEY only (never content,
tokens, or cookies).

#7870 — the enterprise tier only changed buildWsUrl() query params;
buildChatInvocation() always fell back to the consumer
M365_DEFAULT_OPTION_SETS (which declares the MSA-only enable_msa_user
flag) and tone:"". resolveConnectionParams()/resolveTierOverrides() now
also resolve and surface the tier itself, threaded through wsChat() ->
sendChat() -> buildChatInvocation() via a new
resolveChatInvocationOverrides() helper, so an enterprise-tier
invocation declares the enterprise_*/bizchat_* option sets, the wider
allowedMessageTypes captured from the real enterprise HAR (Discussion
#7850), and tone:"Magic" — while individual and EDU payloads stay
byte-identical to today.

Regression tests: tests/unit/copilot-m365-web-silent-empty-7858.test.ts,
tests/unit/copilot-m365-enterprise-invocation-7870.test.ts.
2026-07-21 08:39:37 -03:00
Diego Rodrigues de Sa e Souza
5996acdcc7 fix(providers): treat unreliable web-cookie /models probe status as unsupported, not valid (#7857) (#7959) 2026-07-21 08:39:32 -03:00
Diego Rodrigues de Sa e Souza
25499bf94d fix(quality): tolerate ESLint's trailing unpruned-suppressions text in validate-release-green (#7837) (#7962)
Root cause: validate-release-green.mjs's ESLint gate runs
`npx eslint . --format json --suppressions-location ...` without
--pass-on-unpruned-suppressions. ESLint 9.x prints the valid JSON report
to stdout first, then (if the suppressions file has any stale entries)
appends 'There are suppressions left that do not occur anymore...' to
stderr and exits 2. The gate concatenates stdout+stderr, so
parseEslintJson() received valid JSON immediately followed by that
sentence, JSON.parse() threw, and the caller reported the generic
"could not parse eslint json" HARD failure instead of the real
(harmless) unpruned-suppressions housekeeping condition.

Fix: add --pass-on-unpruned-suppressions to the gate's eslint invocation
(unpruned suppressions are release-time housekeeping, not a contributor
defect), and harden parseEslintJson() with a bracket-depth scan so it
recovers the JSON array even when trailing non-JSON text is glued on.

Regression test: tests/unit/validate-release-green.test.ts — 'parseEslintJson
tolerates ESLint's trailing unpruned-suppressions stderr sentence (#7837)',
feeding parseEslintJson() the exact byte shape ESLint's own cli.js produces
in this scenario. Confirmed RED before the fix (parsed === null), GREEN after.

Gates run: node --import tsx/esm --test tests/unit/validate-release-green.test.ts (20/20 pass),
npm run typecheck:core (clean), eslint --suppressions-location on changed files (clean),
check-complexity.mjs + check-cognitive-complexity.mjs (OK, no regression),
check-mutation-test-coverage.mjs --strict (no drift), check-changelog-integrity.mjs (OK),
check-file-size.mjs (OK, no frozen files grown).

Closes #7837
2026-07-21 08:39:26 -03:00
Diego Rodrigues de Sa e Souza
fe94529306 fix(api): add amazon-q to the static model catalog (#7820) (#7960) 2026-07-21 08:39:20 -03:00
Jan Leon
c1bdd91e7b Hide internal reasoning replay placeholders (#7912)
* fix: hide internal reasoning replay placeholder

* fix: hide internal reasoning replay placeholder in OpenAI→Claude translation too

Mirror the isInternalReasoningPlaceholder() guard already applied to the
Responses-API and OpenAI-Responses reasoning paths in
openaiToClaudeResponse() (open-sse/translator/response/openai-to-claude.ts).
The internal reasoning-replay sentinel was still leaking into the Claude
"thinking" content block on this translation path.

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

* test: close both-add merge of #7912 and #7905 reasoning/tool tests

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>
2026-07-20 22:52:54 -03:00
WITALO ROCHA
1175746d4f fix(antigravity): collect native functionCall parts in SSE collector (#7902)
Rebuilt clean on release/v3.8.49 (branch carried old-main drift) — applies only
the 2 real commits' delta: SSE collector now captures native functionCall parts
in non-streaming, plus the test (typed emptyCollected() as AntigravityCollectedStream).

Co-authored-by: Wital <wital@example.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-20 22:52:48 -03:00
Andrew B.
eadcbea1c9 fix(combo): strip boolean reasoning field for opencode-go providers (#7891)
* fix(combo): strip boolean reasoning field for opencode-go providers

opencode-go backed providers (ollama-cloud, opencode-go, opencode,
opencode-zen) use a Go ChatCompletionRequest struct where the reasoning
field is typed as openai.Reasoning (a structured type). When a client
sends reasoning: true or reasoning: false — valid per the OpenAI API —
the Go JSON decoder rejects it with:

  400: json: cannot unmarshal bool into Go struct field
  ChatCompletionRequest.reasoning of type openai.Reasoning

This strips the boolean reasoning field before forwarding to these
providers, allowing the upstream to apply its own default reasoning
behavior. Object/string forms are left untouched.

Observed in production: 3 consecutive 400 errors from ollama-cloud/glm-5.2
in a 30-second window, each with the unmarshal error.

* fix(opencode): add null/primitive guard in stripBooleanReasoning

Adds defensive check for null, undefined, and non-object inputs
as suggested in review. Added unit test coverage for these edge cases.
2026-07-20 22:26:13 -03:00
Diego Rodrigues de Sa e Souza
387ebc3e41 fix(dashboard): safely render structured error objects in Request Logs detail (#7845) (#7920) 2026-07-20 22:22:55 -03:00
Diego Rodrigues de Sa e Souza
583d3ebe1d fix(providers): read reasoning_text in Claude-format response translator (#7856) (#7919) 2026-07-20 22:22:49 -03:00
Diego Rodrigues de Sa e Souza
b15f343e67 fix(providers): treat public-host 302 as valid in Gemini Web connection test (#7859) (#7917) 2026-07-20 22:22:43 -03:00
Nathan
62cbbcd2c0 fix(dashboard): preserve quota cutoff drafts (#7909)
Co-authored-by: Bryan Nathan <bryan@users.noreply.github.com>
2026-07-20 22:22:36 -03:00
Innokentiy Solntsev
d3f8bbe555 fix(sse): recover invalid Anthropic thinking signatures once (#7906) 2026-07-20 22:22:30 -03:00
Jan Leon
6b59e814da Restore Responses API custom tool calls (#7905)
* fix: restore Responses custom tool calls

* fix: preserve nested Responses custom tool calls

* fix: reset superseded tool call state

* fix: preserve tool precedence and buffered Responses tool arguments

* test: verify top-level tool descriptions take precedence

* fix: preserve custom Responses tool streaming semantics

* test: cover declared custom tool streaming round trips

* test: cover Responses custom tool metadata collection

* test: cover active Responses custom tool stream

* test: isolate Responses active stream regression

* fix: complete Responses custom tool round trips
2026-07-20 22:22:24 -03:00
Diego Rodrigues de Sa e Souza
10823bcf83 fix(dashboard): repair monaco deep import broken by 0.56 exports map (#7897) (#7922)
monaco-editor 0.56.0 (bumped in #7897) ships a restrictive `exports` map
("./*.js" -> "./esm/vs/*.js", "./*" -> "./esm/vs/*.js") that rewrites every
subpath by prepending esm/vs/. The pre-0.56 deep import
`monaco-editor/esm/vs/editor/editor.api` therefore resolved to the doubled,
non-existent `esm/vs/esm/vs/editor/editor.api.js` and broke the production
Turbopack build (Module not found in MonacoEditor.tsx).

Switch to the 0.56-compatible specifier `monaco-editor/editor/editor.api.js`,
which resolves to the same file (esm/vs/editor/editor.api.js) as before.

Adds tests/unit/monaco-editor-import-path.test.ts as a regression guard:
asserts the specifier has no esm/vs/ prefix and resolves to editor.api.js
against the installed monaco 0.56.
2026-07-20 21:17:22 -03:00
dependabot[bot]
3ab66e0ec0 deps: bump the development group with 5 updates (#7898)
Bumps the development group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [@tailwindcss/postcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-postcss) | `4.3.2` | `4.3.3` |
| [c8](https://github.com/bcoe/c8) | `11.0.0` | `12.0.0` |
| [lint-staged](https://github.com/lint-staged/lint-staged) | `17.0.8` | `17.1.0` |
| [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) | `4.3.2` | `4.3.3` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.64.0` | `8.65.0` |


Updates `@tailwindcss/postcss` from 4.3.2 to 4.3.3
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.3/packages/@tailwindcss-postcss)

Updates `c8` from 11.0.0 to 12.0.0
- [Release notes](https://github.com/bcoe/c8/releases)
- [Changelog](https://github.com/bcoe/c8/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bcoe/c8/compare/v11.0.0...v12.0.0)

Updates `lint-staged` from 17.0.8 to 17.1.0
- [Release notes](https://github.com/lint-staged/lint-staged/releases)
- [Changelog](https://github.com/lint-staged/lint-staged/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lint-staged/lint-staged/compare/v17.0.8...v17.1.0)

Updates `tailwindcss` from 4.3.2 to 4.3.3
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.3/packages/tailwindcss)

Updates `typescript-eslint` from 8.64.0 to 8.65.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.65.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@tailwindcss/postcss"
  dependency-version: 4.3.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
- dependency-name: c8
  dependency-version: 12.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: development
- dependency-name: lint-staged
  dependency-version: 17.1.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development
- dependency-name: tailwindcss
  dependency-version: 4.3.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
- dependency-name: typescript-eslint
  dependency-version: 8.65.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>
2026-07-20 19:28:38 -03:00
dependabot[bot]
8158d170d7 deps: bump the production group with 8 updates (#7897)
Bumps the production group with 8 updates:

| Package | From | To |
| --- | --- | --- |
| [@aws-sdk/client-bedrock-runtime](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-bedrock-runtime) | `3.1088.0` | `3.1090.0` |
| [@lobehub/icons](https://github.com/lobehub/lobe-icons) | `5.13.0` | `5.14.0` |
| [@toon-format/toon](https://github.com/toon-format/toon) | `2.3.0` | `2.3.1` |
| [ink](https://github.com/vadimdemedes/ink) | `7.1.0` | `7.1.1` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `1.24.0` | `1.25.0` |
| [monaco-editor](https://github.com/microsoft/monaco-editor) | `0.55.1` | `0.56.0` |
| [smol-toml](https://github.com/squirrelchat/smol-toml) | `1.6.1` | `1.7.0` |
| [undici](https://github.com/nodejs/undici) | `8.7.0` | `8.8.0` |


Updates `@aws-sdk/client-bedrock-runtime` from 3.1088.0 to 3.1090.0
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-bedrock-runtime/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1090.0/clients/client-bedrock-runtime)

Updates `@lobehub/icons` from 5.13.0 to 5.14.0
- [Release notes](https://github.com/lobehub/lobe-icons/releases)
- [Changelog](https://github.com/lobehub/lobe-icons/blob/master/CHANGELOG.md)
- [Commits](https://github.com/lobehub/lobe-icons/compare/v5.13.0...v5.14.0)

Updates `@toon-format/toon` from 2.3.0 to 2.3.1
- [Release notes](https://github.com/toon-format/toon/releases)
- [Commits](https://github.com/toon-format/toon/compare/v2.3.0...v2.3.1)

Updates `ink` from 7.1.0 to 7.1.1
- [Release notes](https://github.com/vadimdemedes/ink/releases)
- [Commits](https://github.com/vadimdemedes/ink/compare/v7.1.0...v7.1.1)

Updates `lucide-react` from 1.24.0 to 1.25.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.25.0/packages/lucide-react)

Updates `monaco-editor` from 0.55.1 to 0.56.0
- [Release notes](https://github.com/microsoft/monaco-editor/releases)
- [Changelog](https://github.com/microsoft/monaco-editor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/microsoft/monaco-editor/compare/v0.55.1...v0.56.0)

Updates `smol-toml` from 1.6.1 to 1.7.0
- [Release notes](https://github.com/squirrelchat/smol-toml/releases)
- [Commits](https://github.com/squirrelchat/smol-toml/compare/v1.6.1...v1.7.0)

Updates `undici` from 8.7.0 to 8.8.0
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v8.7.0...v8.8.0)

---
updated-dependencies:
- dependency-name: "@aws-sdk/client-bedrock-runtime"
  dependency-version: 3.1090.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@lobehub/icons"
  dependency-version: 5.14.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@toon-format/toon"
  dependency-version: 2.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: ink
  dependency-version: 7.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: lucide-react
  dependency-version: 1.25.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: monaco-editor
  dependency-version: 0.56.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: smol-toml
  dependency-version: 1.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: undici
  dependency-version: 8.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>
2026-07-20 19:28:30 -03:00
backryun
0df0ff2ddb feat(grok-cli): align with official Grok Build client (#7358)
Rebuilt clean on release/v3.8.49 (branch forked from old main, ~drift). Resolved
2 real conflicts against the current tip: providerModelsConfig.ts keeps BOTH the
tip's DashScope text-model helpers (#7882) and this PR's ProviderModelsHeaderContext
type; OAuthModal.tsx takes this PR's DEVICE_CODE_PROVIDERS set (superset of the
tip's hardcoded chain + grok-cli), dropping the now-dead qwen entry (#7866 removed
qwen OAuth).

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-20 19:28:21 -03:00
Diego Rodrigues de Sa e Souza
cec2f58c24 chore(quality): prune stale ESLint suppression (db-migration-runner-account-identity)
#7843's test reorganization removed the 2 no-explicit-any usages that this
suppression covered, leaving a stale entry that fails lint:json --max-warnings 0
(exit 2, 'suppressions left that do not occur anymore') on the release tip for
every fresh PR run. Pruning tightens the gate — no rebaseline.
2026-07-20 18:58:35 -03:00
Ravi Tharuma
161de7de4a fix(opencode-plugin): support separate management read token (#7885)
* fix(opencode-plugin): separate management read token

* fix(opencode-plugin): scope inference auth and snapshots

* fix(opencode-plugin): scope auth to base path

* docs(changelog): format opencode management-read-token fragment as a bullet

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

---------

Co-authored-by: Ravi Tharuma <RaviTharuma@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>
2026-07-20 18:48:35 -03:00
backryun
00677044be [Part 3/3] feat(qwen): add regional Alibaba and Qwen Cloud providers (#7882)
* feat(qwen): add Qwen3.8 Max Preview catalogs [Part 2/3]

Rebuilt clean on release/v3.8.49 after Part 1 (#7866) squash-merged — applies
only the Part-2 delta (Qwen Web / Qoder qwen3.8-max-preview registration +
required-thinking allowlist + Qoder client rework) onto the current tip. No
migration in this part (that was Part 1).

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

* feat(qwen): add regional Alibaba and Qwen Cloud providers [Part 3/3]

Rebuilt clean on top of Part 2 (#7874) over the current release tip — applies
only the Part-3 delta (alibaba Model Studio, Alibaba Token Plan, qwen-cloud,
qwen-cloud-token-plan with region selector). No migration in this part.

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

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-20 18:48:24 -03:00
backryun
ccdbc89290 feat(qwen): add Qwen3.8 Max Preview catalogs [Part 2/3] (#7874)
Rebuilt clean on release/v3.8.49 after Part 1 (#7866) squash-merged — applies
only the Part-2 delta (Qwen Web / Qoder qwen3.8-max-preview registration +
required-thinking allowlist + Qoder client rework) onto the current tip. No
migration in this part (that was Part 1).

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-20 18:48:16 -03:00