Commit Graph

112 Commits

Author SHA1 Message Date
Ravi Tharuma
08a21bcf27 fix(backend): add structure-aware chat admission (#8296)
Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-23 11:59:20 -03:00
Rafael Dias Zendron
1b42044c15 fix(combo): skip remaining same-provider targets on 401/403 auth failure (#8195)
* fix(ci): resolve upstream-inherited check failures

* fix(combo): skip remaining same-provider targets on 401/403 auth failure (#8133)

When a provider returns 401/403 (auth failure), every remaining model behind
the same provider will fail identically. Previously the combo engine continued
trying sibling models on the dead connection, wasting attempts.

Now auth-level failures (401, 403) are classified as provider-level exhaustion,
marking exhaustedProviders so subsequent same-provider targets are skipped via
the existing exhaustion-skip mechanism.

Tests: 4 new cases in combo-target-exhaustion.test.ts covering 401, 403,
unknown-provider guard, and per-model-quota provider (auth is provider-wide).

* fix(combo): connection-level exhaustion on 401/403, not whole-provider (#8137)

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

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-23 05:33:57 -03:00
Ravi Tharuma
4fd1f0f15c fix(guardrails): align INPUT_SANITIZER request masking gate (#8093) (#8124)
Scoped to guardrails/security: dropped the unrelated js-yaml/tar/shell-quote/
brace-expansion override bumps, and isolated sanitizer-residual-policy.test.ts
to a tmp DATA_DIR so it no longer touches the real storage.sqlite.

Co-authored-by: RaviTharuma <RaviTharuma@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: rafaumeu <rafael.zendron22@gmail.com>
2026-07-23 04:50:43 -03:00
Rafael Dias Zendron
ef708e1236 docs(security): correct prompt-injection severity table + heuristic-limitations disclaimer (#8097) (#8113)
Scoped to the docs-truthfulness fix: reverted the erroneous INPUT_SANITIZER_ENABLED
default flip (flag is intentionally on-by-default per the #8093 ruling) and dropped
5 unrelated bundled changes. Keeps only the accurate SECURITY.md correction plus the
sanitizerFixtures / security-docs-truthfulness test.

Co-authored-by: rafaumeu <rafaumeu@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-23 04:33:48 -03:00
Diego Rodrigues de Sa e Souza
38fd4d34d9 feat(sse): restrict auto-combo no-auth pool to allowlist (opencode, felo) + docs (#8183)
Restrict the auto-combo no-auth (keyless) candidate pool to an allowlist —
opencode + felo — the only keyless backends verified to answer without any
credential on the reference egress (VPS .15). Excluded no-auth providers stay
usable via direct <alias>/<model> calls; they are just no longer auto-routed to.
Guard: tests/unit/noauth-autocombo-allowlist.test.ts.

Docs: add a "works the second you install it" free section near the top of the
README; sync the compression stack count 11 → 12 engines; document 13 env vars
(VNC browser-login knobs + VIBEPROXY_DATA_DIR) in .env.example / ENVIRONMENT.md.
2026-07-23 00:31:15 -03:00
backryun
888c872459 refactor(antigravity): align official clients and callable catalog (#8013)
* fix(antigravity): preserve protocol fidelity and fail closed

* chore: add PR-numbered changelog fragment

* test: split oversized Antigravity suites

* refactor(antigravity): align official IDE and CLI identities

* fix(antigravity): align catalog with callable models

* test(antigravity): update 2 test files to renamed version-cache API (#8013 fix)

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

---------

Co-authored-by: nguyenha935 <208228297+nguyenha935@users.noreply.github.com>
Co-authored-by: backryun <backryun@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: nguyenha935 <nguyenha935@users.noreply.github.com>
Co-authored-by: Probe Test <probe@example.com>
2026-07-22 20:41:19 -03:00
Ravi Tharuma
7ae17168db fix(security): decouple request PII redaction from injection mode (#8102)
* fix(security): decouple request PII redaction from injection mode

PII_REDACTION_ENABLED now rewrites request PII independently of
INPUT_SANITIZER_MODE, so the enterprise recipe (MODE=block + PII on)
actually redacts. Also cover Responses API string input/prompt shapes
and correct docs that claimed MODE=redact strips injection text.

Refs: #8092 #8093 #8094 #8096 #8097

* test(security): drop no-explicit-any in sanitizer unit tests

Unblocks CI lint/quality ratchet on the PII redaction PR by typing
chat-like payloads instead of casting to any.

---------

Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com>
2026-07-22 17:38:20 -03:00
NOXX - Commiter
1b010f6c40 feat(sse): add HyperAgent (hyperagent.com) unofficial web provider (#7994)
* feat(sse): add HyperAgent (hyperagent.com) unofficial web provider

Reverse-engineered from live SPA captures (hyperagent/*.txt):

Chat:
- Cookie session auth (full Cookie header)
- New thread via GET /threads/new (or POST /api/threads)
- POST /api/threads/{id}/chat with SPA feature flags + content
- SSE parse of text/session_start/session_end/done events
- Multi-turn sticky threadId + sessionId cache (history prefix + last assistant)

Models:
- Hardcoded catalog from SPA pricing map
- Pretty display names (Claude Fable 5) while wire modelId stays fable etc.
- /v1/models exposes pretty name; chat uses modelId

Limits:
- GET /api/settings/billing/usage → creditBlocks initialUsd/remainingUsd/usedUsd
- USD Credits quota for Limits page

Tests: 15/15 unit/executor-hyperagent

* fix(sse): HyperAgent execution mode + fable-latest wire model (no plan mode)

* fix(sse): document HyperAgent env vars + regenerate golden snapshot

Addresses pre-merge review feedback on #7994:
- Documents HYPERAGENT_USAGE_URL in .env.example and ENVIRONMENT.md
  (OMNIROUTE_DATA_DIR was already documented via the sibling PromptQL
  provider) so check-env-doc-sync.test.ts passes.
- Regenerates the provider-translate-path golden snapshot to include
  the new hyperagent/ha registry entries.
- Swaps the local toNumber() helper in usage/hyperagent.ts for the
  canonical @/shared/utils/numeric import (#7879 no-restricted-syntax
  rule landed on the release branch after this PR was opened).
- Freezes file-size baseline entries for the new
  open-sse/executors/hyperagent.ts (937 LOC, new-file cap 800) and the
  +3 LOC growth in src/lib/usage/providerLimits.ts (1000->1003), both
  irreducible to this PR's own provider-registration wiring.

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>
2026-07-22 08:08:46 -03:00
Diego Rodrigues de Sa e Souza
287802cf86 fix: repair pre-existing red gates on the release/v3.8.49 tip (#8055)
* fix(dashboard): resolve Kimi banner casing collision + shrink frozen test file (release tip)

- Rename src/app/(dashboard)/dashboard/kimiSponsorBanner.ts to
  kimiSponsorBannerGate.ts so it no longer differs from
  KimiSponsorBanner.tsx only by the first letter's case (breaks next
  build on case-insensitive filesystems). Updates the sole importer
  (KimiSponsorBanner.tsx) and the two tests that reference it.
- Extract the 8 Kimi/Moonshot featured-ordering tests out of the
  frozen tests/unit/providers-page-utils.test.ts (grown 3 lines past
  its 1294 cap by #8039's rebrand-comment update) into a new sibling
  file tests/unit/providers-page-utils-kimi.test.ts. No assertions
  dropped; both files pass in full (24 + 8 = 32 tests).

* fix(sse): register PromptQlExecutor in the executor registry (release tip)

getExecutor("promptql") had no entry in open-sse/executors/index.ts, so it
silently fell through to DefaultExecutor's provider fallback, which issues a
raw fetch() and returns the bare upstream Response instead of the executor
wrapper shape {response, url, headers, transformedBody}. The real
PromptQlExecutor class (open-sse/executors/promptql.ts) already honors the
contract correctly — it was just never wired into the registry.

Fixes tests/unit/executor-web-cookie-sweep.test.ts "promptql executor
returns wrapper shape".

* fix(i18n): backfill 2220 missing pt-BR keys to restore en.json parity (release tip)

pt-BR.json fell behind after #7935 restored +2220 keys into en.json and
vi.json but left pt-BR.json unmodified. Translated all missing entries to
Brazilian Portuguese, preserving ICU/interpolation placeholders and existing
terminology, and merged them mirroring en.json's key order so the diff is
additions-only (the small comma-only deletions are pure JSON reformatting
from new sibling keys).

* fix(providers): repair 4 pre-existing catalog/registry reds on release tip

- providers-constants-split.test.ts: APIKEY_PROVIDERS grew 182->187 (PR #7887
  added 5 free-tier providers: ainative/aion/sealion/routeway/nara). Verified
  no dup/loss (6-family partition sums exactly to 187) and updated the stale
  expected count + comment trail to match.
- cline registry: added the missing minimax/minimax-m3 free OpenRouter entry
  (#3321) and fixed the neighbouring nemotron-3-ultra-550b-a55b entry, which
  carried a stray ":free" id suffix and an imprecise 1_000_000 contextLength
  instead of the 1_048_576 the test (and every sibling 1M-context entry in
  this catalog) expects.
- promptqlModels.ts / registry/promptql/index.ts: PROMPTQL_FALLBACK_MODELS's
  minimax-m3 entry was missing supportsVision, and the registry mapping
  dropped it entirely (only id/name were passed through) — it was the sole
  minimax-m3 entry across the whole registry not flagged multimodal, despite
  every other provider (minimax, minimax-cn, ollama-cloud, trae, bazaarlink,
  clinepass, codebuddy-cn, opencode-zen/go, synthetic, huggingchat, lmarena)
  agreeing MiniMax-M3 supports vision. Added the field to the PromptQlModel
  type and threaded it through.
- tests/snapshots/provider/translate-path.json: regenerated the golden via
  UPDATE_GOLDEN=1. Diffed old vs new — zero providers removed, 5 added
  (ainative/aion/nara/routeway/sealion, matching #7887), and the only
  changed entry (cline) reflects the already-merged #7914 ClinePass header
  protocol change (Cline/<version> User-Agent + X-Task-ID) that a prior
  narrow golden touch-up missed capturing.

* fix(docs): repair docs-sync/env-sync/repo-contract gates (release tip)

Six pre-existing reds on release/v3.8.49, all "repo drifted from its own
documented contract":

- check-docs-counts-sync: free-tier headline was stale (~1.4B/~2.0B) vs the
  live catalog (~1.53B steady / ~2.15B first month, 43 pools). Updated
  README.md and docs/reference/FREE_TIERS.md to the live numbers and added a
  v3.8.49 correction note explaining the pool-count delta (39->43, #7840).
  Also fixed a soft executors-count drift in ARCHITECTURE.md (84->86,
  268->271 providers) while touching that line.
- release-green-docs-drift-7253: docs/proxy-subscriptions.md referenced a
  fabricated migration filename (123_proxy_subscriptions.sql); the real file
  is 131_proxy_subscriptions.sql. Fixed all 3 occurrences.
- check-env-doc-sync + issue-7793-env-doc-sync-repro: OMNIROUTE_DATA_DIR
  (DATA_DIR fallback alias read by
  open-sse/executors/promptql/threadSticky.ts) was undocumented. Added to
  .env.example and docs/reference/ENVIRONMENT.md.
- check-db-rules: src/lib/db/proxySubscriptions.ts (#7299) is a db-internal
  split of proxies.ts (kept under the frozen file-size cap) whose one export
  is already re-exported via proxies.ts -> localDb.ts. Added it to
  INTENTIONALLY_INTERNAL with the same db-internal justification used for
  identical split modules (apiKeyColumnFallbacks, providerNodeSelect,
  webSessionDedup) rather than a redundant direct re-export from localDb.ts.
- mcp-server-hollow-dist-deps: the sanity test expected better-sqlite3 among
  the MCP bundle's static top-level external imports. That's been stale
  since the pre-#7878 migration to a cascading SqliteAdapter driver factory
  (createRequire()-based lazy require, not a static import); better-sqlite3
  already has its own native-asset copy guarantee in assembleStandalone.mjs,
  unrelated to this test's EXTRA_MODULE_ENTRIES concern. Updated the
  assertion to a still-genuinely-static external (zod) with a comment
  explaining the change.

No production runtime behavior changed — docs, .env.example, and a checker
allowlist/test-expectation only.

* fix(dashboard): repair stale UI component-shape test assertions (release tip)

Two pre-existing reds in the dashboard UI component-contract cluster were
caused by test assertions that had gone stale after intentional, correct
refactors — not by real defects in the components:

- quota-pool-wizard-multi.test.ts: the step-3 preview assertion required
  the literal single-line substring "connectionIds.map((cid)". Prettier
  (100-char width, project config) legitimately breaks the
  connectionIds.map(...).filter(...) chain across lines because of the
  multi-line callback body, so the literal never matches. PoolWizard.tsx
  still builds previewByProvider correctly by mapping over connectionIds;
  updated the assertion to a regex that tolerates the line break.

- v388-phase1-screen-fixes.test.ts: the shared Select placeholder-guard
  assertion required the literal "!children && placeholder". An earlier,
  intentional i18n commit changed the hardcoded "Select an option" default
  to a translated fallback (`placeholder ?? t("selectOption")`), which
  requires parens around the ?? expression for operator precedence. The
  guard behavior is unchanged (still gated on !children); updated the
  assertion to match the current, correct guard shape.

Both fixes are read-only test-file changes; no production behavior changed.

review-reviews-v3814-fixes.test.ts still has one pre-existing, unrelated
red (LEDGER-4: minimax-m3 registry entries missing supportsVision) that
requires editing the promptql provider registry/catalog — out of this
cluster's scope, left untouched and reported separately.

* fix(providers): reconcile cline catalog contradictions + deterministic golden (release tip)

The first tip-green pass introduced 3 regressions caught by CI on sibling guard tests:

- clinepass-provider + cline-catalog-models-3321 encoded OPPOSITE expectations of
  the same cline model list (minimax presence, nvidia :free suffix). Reference
  upstream (OpenRouter free lineup) confirms nvidia/nemotron-3-ultra-550b-a55b:free
  (with :free, 1M ctx) is correct, so restore that id and fix #3321's stale no-:free
  assertion; add minimax/minimax-m3 (the real #3321 gap) to clinepass-provider's list.
- check-db-rules-classification froze INTENTIONALLY_INTERNAL at 35; proxySubscriptions
  was the intentional 36th entry — add it + bump the count.
- provider-translate-path golden stored a LITERAL Cline/3.8.49: clineAuth resolves the
  version from APP_CONFIG.version (stable), but the golden sanitizer collapsed only
  process.env.npm_package_version (unset under `node`, set under `npm run`) — so the
  golden was shard-dependent. Resolve APP_VERSION from APP_CONFIG.version like clineAuth
  and regenerate; now Cline/<APP> normalizes identically in every shard.

* fix(services): type execFile signal/killed in classifyError + ratchet dashboard baseline (release tip)

Pre-existing base-red on the tip's Fast Quality Gates (dashboard-typecheck), missed
in the first inventory:

- src/lib/services/installers/utils.ts TS2339 — `err.signal` was read off a value typed
  as NodeJS.ErrnoException, which @types/node does not declare `signal`/`killed` on
  (those belong to execFile's ExecFileException). Widen classifyError's param to type
  both, and drop the now-redundant `(err as … { killed })` cast.
- Ratchet config/quality/dashboard-typecheck-baseline.json down: 5 baselined errors were
  fixed by already-merged PRs but never ratcheted (OAuthModal TS2769 4→3 / TS2345 4→3,
  CliproxyModelMappingEditor TS2339, CompressionPreviewAccordion TS4104, MonacoEditor
  TS2307). Baseline now 254, matching live — gate exits 0.
2026-07-21 21:25:00 -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
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
backryun
65e0aeda79 [Part 1/3]refactor(qwen): replace legacy Qwen Code and remove OAuth provider (#7866)
* refactor(cli): remove legacy Qwen Code integration

* refactor(qwen): remove deprecated Qwen OAuth provider

* feat(cli): rebuild Qwen Code integration for upstream V4

* fix(qwen): clear stale CLI auth on reset

* test(qwen): align retired provider coverage

* fix(db): renumber qwen-cleanup migration 129 -> 130

release/v3.8.49 tip took slot 129 via #7843 (usage_history_codex_strong_identity,
itself renumbered from 128 during the #7838/#7840 base-red cleanup) after this
branch forked; renumber remove_unregistered_qwen_data to 130.

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

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>
2026-07-20 17:53:04 -03:00
Ravi Tharuma
8b78bc361e fix: reserve chat admission before body parsing (#7853)
* fix: reserve chat admission before parsing

* fix: release admission on handler failure

* fix: reconcile chat admission with the #7862 parse-once route

Post-#7862 rebase: the admission-rebuilt request is json()-parsed directly over
the already-buffered bytes — the clone()+json() pair is gone, and the
parse-once regression tests now pin the post-admission contract (original
request: zero json() calls, zero clone() calls; the single materialization is
admitChatRequest()'s bounded byte reader).

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 16:10:35 -03:00
Diego Rodrigues de Sa e Souza
eb02d4d266 fix(docs): document CREDENTIAL_REDACTION_ENABLED and GHE_COPILOT_OAUTH_CLIENT_ID (#7793) (#7833) 2026-07-20 02:18:14 -03:00
Diego Rodrigues de Sa e Souza
5d1ad576a1 feat(quality): gate the free-tier headline so it can never silently drift again (#7798)
* docs(free-tiers): correct the headline to the 1.37B the catalog actually computes

The 2026-06-17 honesty correction landed 1.54B, but v3.8.42 reclassified longcat
from a 150M/mo recurring grant to a one-time 10M signup credit and the doc was
never resynced. Verified against computeFreeModelTotals() at every release tag
from 3.8.13 to HEAD: no free provider was lost by mistake.

* feat(quality): gate the free-tier headline against the live catalog

The README headlined ~1.6B free tokens/mo for seven releases after the catalog had
already been corrected down to 1.37B. No gate watched that number, so the drift was
invisible — check:docs-counts only covered providers, locales, executors, strategies,
oauth, a2a skills and cloud agents.

Adds a STRICT check that runs computeFreeModelTotals() (the same function behind
/api/free-tier/summary) and fails the build when README.md or FREE_TIERS.md publish a
headline that no longer rounds to it. Degrades to a skip if tsx is unavailable rather
than going falsely red.

The extractor is a whitelist: the theoretical ceiling (~10B), the historical ~1.94B and
per-model rows (~1.00B) are legitimate figures that must never trip the gate.

Also adds the biweekly-audit note under the README headline, so readers know the number
moves both ways and is what the catalog computes rather than a rounded-up best case.

* feat(quality): extend the counts gate to engines, MCP tools/scopes and CLI tools

The v3.8.49 audit found four more numbers that had silently drifted, all invisible to
CI because check:docs-counts only watched providers/locales/executors/strategies/oauth/
a2a/cloud-agents: 10->11 compression engines, 94->104 MCP tools, 30->31 scopes,
26->33 CLI tools.

Adds a generic makeNumberClaimValidator that reads every fact in ONE tsx subprocess via
the same functions the app serves (ENGINE_IDS, countUniqueMcpTools, the live scope union,
CLI_TOOLS) — never a hardcoded copy — with DATA_DIR redirected to a throwaway dir so
importing the MCP tool modules can't touch the operator's SQLite. Each check declares a
skip pattern so legitimate non-aggregate figures never trip it: per-module tool counts
('Memory tool definitions (3 tools)') and the CLI catalog total sitting next to the MCP
total. Degrades to a skip when tsx is unavailable rather than a false red.

7 new unit tests (all pass) covering the exact stale values this audit found and proving
per-module counts are ignored.

* docs(diagrams): sync the animated cards and mermaid sources to the audited v3.8.49 numbers

The README text was fixed in #7795 but the SVG cards and mermaid sources kept the
old numbers baked in — exactly the drift the readers see first.

- compression-pipeline.svg: 10 -> 11 engine cells (Omniglyph added as #8, matching
  the README alt text), re-spaced 51px cells, highlight cascade re-timed, the
  Caveman kill-dot repositioned inside its cell, default-stack bracket recentered
- free-tier-budget.svg: bar and grid rebuilt from computeFreeModelTotals() — 21 -> 19
  countable pools (LongCat-2.0 moved to one-time credit, Inclusion provider removed),
  huggingchat entry is now ERNIE 4.5 VL, kiro shows Claude Sonnet 4.5, signup credits
  ~616M -> ~626M (+longcat 10M pill), aria said 'about 1.6 billion' -> 1.4/2.0,
  lower sections shifted up 30px (viewBox 872 -> 842)
- promise-pillars.svg: 26 -> 33 coding agents
- mcp-tools-94.mmd -> mcp-tools-104.mmd: real per-collection unique contributions
  (42 base + memory 3 + skill 4 + githubSkill 3 + pool 6 + gamification 8 + plugin 8
  + notion 6 + obsidian 22 + compression 2), exported SVG regenerated, zh-CN ref synced
- request-pipeline.mmd: 17 -> 18 strategies, exported SVG regenerated
- README free-tier alt + docs/diagrams/README.md synced to the same numbers

Both edited cards pass validate-svg.sh and were render-verified at 4 timestamps
(animation runs; first frame is the finished composition).

* docs(env): register the 4 env vars missing from the .env.example contract (base-red unblock)

FREE_PROXY_AUTO_SYNC_ENABLED / FREE_PROXY_AUTO_SYNC_INTERVAL_MS (scheduler.ts) and
MITM_ROOT_CA_ENABLED / MITM_CERT_MODE (mitm manager/server, #6684) landed on
release/v3.8.49 without their .env.example + ENVIRONMENT.md entries, turning the
docs-gates job red for every PR on the branch. Documented with their real defaults
and the set-by-manager caveat for MITM_CERT_MODE.

* fix(dashboard): narrow the Codex session ParseResult with an equality check (base-red unblock)

#7725 landed 'if (!result.ok)' in OAuthModal — under this repo's strict:false,
tsc 6 only narrows a discriminated union on the equality form, so the negation
raised TS2339 (Property 'error' does not exist on ParseResult) and turned the
dashboard-typecheck gate red for every PR on release/v3.8.49. Runtime semantics
are identical (ok is a strict boolean).

Also ratchets the frozen baseline down 260 -> 259: the real fix here plus 3
baselined errors that other merges already fixed (CostOverviewTab TS2304,
SidebarTab TS2322, FreePoolTab TS2304). Baseline diff is deletions-only.

* fix(dashboard): keep OAuthModal within the frozen file-size cap

The narrowing comment pushed the file to 1032 > 1030 frozen; the rationale lives
in the previous commit message and the dashboard-typecheck gate itself guards the
'=== false' form from being refactored back to '!result.ok'.

* test(providers): align the grok-web credential assertion with the #7567 hint (base-red unblock)

#7713 added hintKey/hintFallback (proactive cf_clearance/User-Agent guidance) to the
grok-web web-session metadata without touching this test's deepEqual, turning unit
shard 2/4 red for every PR on release/v3.8.49. Rewritten in the same contract-only
style the file already uses for lmarena: structural fields stay strictly asserted,
the hint asserts key + intent (cf_clearance / User-Agent) without freezing operator
copy. Net stronger than before — the old assertion never checked the hint at all.

* test(golden): regenerate translate-path snapshot for the notion-web endpoint move (base-red unblock)

#7768 switched notion-web to app.notion.com without regenerating the golden,
turning unit shard 3/4 red for every PR on release/v3.8.49. Two-line regen,
reflects the deliberate production change.
2026-07-19 19:07:44 -03:00
Diego Rodrigues de Sa e Souza
f048d98b46 docs(base-red): document missing env vars (healthcheck jitter, issue-agent timeout, DNS opt-outs, SSE comments) 2026-07-18 22:20:45 -03:00
Isiah Wheeler
987b6448f7 feat(resilience): guard OmniRoute peer routing loops (#7555)
* feat(resilience): guard OmniRoute peer routing loops

* refactor(resilience): fold peer-loop log+response into rejectPeerRequest helper (file-size budget on chat.ts)

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

---------

Co-authored-by: Isiah Wheeler <2122839+isiahw1@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 21:19:50 -03:00
KooshaPari
f657c7865a feat(issue-agent): surface RecordedTriageTimeoutError as 504 (#7315)
* chore(ci): add .mergify.yml to main — Mergify only reads config from the default branch (#7168)

* feat: scaffold issue agent and router eval provenance

* feat: wire recorded issue triage runner

* feat: ingest recorded issue context

* feat: persist issue agent audit log

* feat: import recorded github issue exports

* docs: document issue agent env toggle

* fix(issue-agent): validate run requests

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

* fix: validate issue agent run requests

* docs: add issue agent execution traceability

* feat(issue-agent): route recorded triage through chat

* test(issue-agent): verify recorded triage through chat route

* docs(issue-agent): add executable triage session artifacts

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

* feat(issue-agent): surface RecordedTriageTimeoutError as 504

When the recorded-triage chat completion times out, the AbortController
fires an AbortError that previously surfaced as a generic 400 to the
caller. This change:

  * Adds a `RecordedTriageTimeoutError` that wraps the AbortError
    with the timeoutMs context.
  * Re-throws it from `executeRecordedTriageChatCompletion` so the
    caller can distinguish timeouts from other failures.
  * In the runs route, catches it and returns a 504 with code
    `ISSUE_AGENT_TIMEOUT` so clients can render a useful error.

Tests:
  * issue-agent-execution.test.ts        — verifies the typed error
  * issue-agent-route-execution.test.ts  — covers timeout path
  * issue-agent-runs-route.test.ts       — verifies 504 mapping

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

* fix(issue-agent): dryRun-explicit test fixture + sanitize the generic error catch

Two independent Hard Rule #12/#18 fixes on the recorded-triage runs route:

1. tests/unit/issue-agent-route-execution.test.ts's "preserves the normal
   chat route provider failure response" test omitted dryRun from its
   request body. createRecordedTriageRun() computes `dryRun: input.dryRun
   !== false`, so an omitted dryRun defaults to true (dry-run mode) and the
   route returns the deterministic dry-run summary WITHOUT ever calling
   executeRecordedTriageChatCompletion() -- the mocked 429 fetch was never
   invoked, so the test always observed the 200 dry-run response instead.
   Add the missing `dryRun: false`, matching the sibling test above it. The
   normal chat-completions route also enriches upstream errors with a
   "[provider/model] [status]:" prefix and a connection-cooldown hint
   (RESILIENCE_GUIDE.md) rather than passing them through byte-for-byte, so
   the assertion now checks the original message survives (status +
   substring) instead of exact-matching the mocked JSON shape.

2. src/app/api/issue-agent/runs/route.ts's generic (non-timeout) catch
   returned raw `error.message` straight to the client. Validation failures
   thrown by this module (bad issue URL, malformed GitHub export) are safe,
   curated messages -- but appendIssueAgentAuditRecord()'s mkdir/appendFile
   under DATA_DIR can throw a real Node fs error (ENOENT/EACCES/EEXIST, ...)
   whose raw `.message` embeds the server's absolute filesystem path. Add
   isNodeSystemError() (keys off NodeJS.ErrnoException's `.code`, which only
   Node's own fs/system errors set) to replace that class of error with a
   generic message, and route everything else through sanitizeErrorMessage()
   per Hard Rule #12. Add a regression test that forces a real audit-write
   failure (pre-creating a file where audit.ts expects to mkdir) and asserts
   the response contains neither the errno code nor the DATA_DIR path.

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

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: KooshaPari <koosha@example.com>
Co-authored-by: growab <nekron@icloud.com>
2026-07-18 15:13:35 -03:00
Diego Rodrigues de Sa e Souza
13e57b2b35 feat: rate-limit queue admission control (maxQueueDepth + 15s default) (#6593) (#7649)
Validated in merge-train 2026-07-18 @ 9084b408b: 12198/12201 pass; single red = earlyStreamKeepalive timer test, confirmed load-flake (6/6 green isolated on release tip AND the merged tree; no boarded PR touches keepalive)
2026-07-18 03:07:20 -03:00
Diego Rodrigues de Sa e Souza
5bacb719d3 feat: add Microsoft Designer as image provider (#6672) (#7609)
* feat(sse): add Microsoft Designer as image provider (#6672)

Adds `microsoft-designer-web` — an unofficial, reverse-engineered
Bearer-token web-session image provider, modeled on the existing
`chatgpt-web`/`copilot-m365-web` "-web" provider category.

- Registers the provider in WEB_COOKIE_PROVIDERS (src/shared/constants/
  providers/web-cookie.ts) and IMAGE_PROVIDERS (open-sse/config/
  imageRegistry.ts, new "designer-web" format).
- New handler open-sse/handlers/imageGeneration/providers/designerWeb.ts
  implements the submit-then-poll DallE.ashx flow (Bearer access_token +
  ClientId/SessionId/UserId headers -> form POST -> poll for
  image_urls_thumbnail), wired into handleImageGeneration()'s dispatch.
- The upstream ClientId header is a fixed, publicly-shared value (not a
  secret) — routed through resolvePublicCred() per Hard Rule #11, never
  as a string literal.
- Registers the token-based credential requirement in
  webSessionCredentials.ts so the provider-connect UI asks for the
  right field; connection validation falls back to the existing generic
  web-cookie session-ping validator (no dedicated validator needed).
- Extracted the KIE image-model catalog into a co-located
  open-sse/config/providers/registry/kie/models.ts module (mirrors the
  existing lmarena/directModels.ts pattern) to keep imageRegistry.ts
  under the file-size cap while adding the new provider entry.
- Regenerated docs/reference/PROVIDER_REFERENCE.md (250 -> 251
  providers) and updated the plain-text counts in README.md, AGENTS.md,
  CLAUDE.md.

Tests (tests/unit/microsoft-designer-web-6672.test.ts, 16 cases):
registry-entry shape assertions, the resolvePublicCred() shape
assertion (Hard Rule #11), and the pure header/form-body/response-
parsing helpers plus the handler's submit/poll/error/timeout paths
against a mocked fetch — no live Designer session required.

Reverse-engineered from the g4f MicrosoftDesigner.py provider reference
(researched during #6672 triage); the exact upstream response shape has
not been validated against a live Designer session, so the poll-loop
implementation follows the documented g4f contract as closely as
possible without a live capture.

* fix(providers): satisfy web-cookie executor contract + document designer-web env vars (#6672)
2026-07-17 12:09:12 -03:00
Diego Rodrigues de Sa e Souza
6bb3207912 feat(api): structured X-Routing-Fallback-Reason header for relay routing (#6872) (#7262) 2026-07-17 10:41:30 -03:00
Diego Rodrigues de Sa e Souza
3df06e5552 fix: stop opencode-go quota lookup defaulting to Z.AI endpoint (#7022) (#7187)
* fix: stop opencode-go quota lookup defaulting to Z.AI endpoint (#7022)

getOpenCodeGoUsage() defaulted OPENCODE_GO_QUOTA_URL to
https://api.z.ai/api/monitor/usage/quota/limit, a Zhipu AI (Z.AI/GLM)
endpoint unrelated to opencode.ai. Whenever a connection had no
dashboard-scraping config (workspaceId/authCookie), the user's real
OpenCode Go API key was sent as a Bearer token to that third-party host
by default, with no operator opt-in.

Remove the hardcoded default: the quota-by-API-key fetch now only runs
when the operator explicitly sets OMNIROUTE_OPENCODE_GO_QUOTA_URL. With
it unset (the default), getOpenCodeGoUsage() returns a descriptive
message and makes zero outbound calls, since OpenCode Go has no public
quota API.

Also updates .env.example and both EN/zh-CN copies of
docs/reference/ENVIRONMENT.md to drop the stale Z.AI default value and
fix the stale open-sse/services/usage.ts source-file reference.

Regression test: tests/unit/opencode-go-quota-no-zai.test.ts (RED on
current code, GREEN after the fix).

* fix: align opencode-go-usage tests with opt-in quota URL contract (#7022)

The prior commit removed the hardcoded api.z.ai default from
OPENCODE_GO_QUOTA_URL, making the quota-by-API-key path opt-in via
OMNIROUTE_OPENCODE_GO_QUOTA_URL. Six pre-existing tests in
opencode-go-usage.test.ts still asserted the old default-fetch
behavior and the old Z.AI-specific error wording, so they broke.

Set OMNIROUTE_OPENCODE_GO_QUOTA_URL before the module import (the
value is read once at load time) to simulate an operator who opted
in, and update the two error-message assertions to the new generic
wording ("the configured OMNIROUTE_OPENCODE_GO_QUOTA_URL endpoint"
instead of "the Z.AI quota API"). Each test still verifies exactly
the same behavior it did before (invalid key, fetch failure, 200
with auth error in body, invalid JSON, quota shape) — only the
opt-in setup and message wording changed.
2026-07-14 21:45:23 -03:00
Diego Rodrigues de Sa e Souza
271bf52da1 chore(base): fix 2 mechanical release-tip base-reds (relayProbeStats re-export + OMNI_MAX_CONCURRENT_CONNECTIONS docs) 2026-07-12 18:07:20 -03:00
Diego Rodrigues de Sa e Souza
0d832f7d79 fix(sse): wire shared quota-fetch throttle into all provider fetchers (#6911) (#6963)
OMNIROUTE_QUOTA_FETCH_MIN_INTERVAL_MS / quotaFetchThrottle.ts documents
itself as used by 'the provider quota fetchers' (plural), but only
codexQuotaFetcher.ts ever called throttleQuotaFetch(). N accounts on
one IP for DeepSeek, Bailian, OpenCode, or Crof still burst
simultaneously.

Wire throttleQuotaFetch() into fetchDeepseekQuota, fetchBailianQuota
(both the primary and China-region retry fetch sites),
fetchOpencodeQuota, and fetchCrofUsage, placed after the existing
cache short-circuit so cache hits stay unaffected (mirrors the
codexQuotaFetcher.ts pattern).

PROVIDER_LIMITS_SYNC_SPACING_MS / providerLimits.ts's OAuth vs
non-OAuth split is left unchanged — that split is intentional by
design and already regression-guarded by
tests/unit/provider-limits-oauth-sequential-sync.test.ts.

The generic usage.ts::getUsageForProvider dispatch path (github,
glm, minimax, nanogpt, xai, etc.) is intentionally out of scope for
this fix to avoid scope creep; ENVIRONMENT.md now documents the
actual post-fix coverage instead of the prior overclaim.
2026-07-12 10:23:19 -03:00
backryun
67a0b99240 feat(providers): add GPT-5.6 model family (#6862)
* feat(providers): add GPT-5.6 model family

* fix(chatgpt-web): resume temporary chat handoffs

* fix(codex): auto-merge discovery, filter denylist, revalidate on lifecycle

Restore live/GitHub auto-merge for Codex catalogs, drop models via
explicit denylist (GPT-5.4 family), and run scrub+live re-sync once on
first-start, app upgrade, or setup completion. Success log:
kill deprecated models complete.

* fix(codex): preserve live catalog reconciliation

Expose remote-only Codex models without dropping user custom entries, and complete lifecycle revalidation only after a successful internal sync. Keep credentialed self-fetches pinned to the active dashboard listener.

---------

Co-authored-by: backryun <backryun@daonlab.local>
2026-07-12 03:30:47 -03:00
Diego Rodrigues de Sa e Souza
a49ac1755d fix(docs): document Turbopack build memory tradeoff for RAM-constrained machines (#6409) (#6885) 2026-07-11 11:20:56 -03:00
Ray Doan
691dae7079 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>
2026-07-11 03:47:10 -03:00
NOXX - Commiter
0a7b58b20f \ 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>
2026-07-11 01:05:07 -03:00
Septianata Rizky Pratama
45310f202d 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>
2026-07-11 01:01:27 -03:00
Andrew Munsell
9d3a2528bc 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>
2026-07-10 19:04:51 -03:00
KooshaPari
abfced8b28 feat(sandbox): native Apple Container, WSL, OrbStack, Podman runtime support (#6611)
* feat(sandbox): native Apple Container, WSL, OrbStack, Podman runtime support

* fix(skills): align sandbox fallback kill container-name convention

sandbox.ts's docker-fallback kill path (used only when cachedProvider is
unexpectedly null) still targeted the pre-PR omniroute-sandbox-${id}
container name, while containerProvider.ts's SANDBOX_NAME now produces
omniroute-${id}. Align the fallback naming so it matches the provider
convention, with a regression test covering kill()/killAll() before a
provider has ever been resolved.

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

* fix(docs): document SKILLS_SANDBOX_RUNTIME and drop unrelated env leftovers

Two fixes surfaced by CI's env/docs contract gate:

- Add the SKILLS_SANDBOX_RUNTIME row to docs/reference/ENVIRONMENT.md so
  the new container-runtime override introduced by this PR is documented,
  matching .env.example.
- Remove the Substrate/Bifrost/OTEL .env.example blocks that leaked in
  from this branch's stale main-based history during the release-branch
  sync merge — none of that belongs to this PR (native container
  runtimes for the skill sandbox) and none of it exists on
  release/v3.8.47 yet.

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

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-09 16:48:49 -03:00
KooshaPari
fc01f53f94 chore(cli): harden empty catches in completion.mjs with env-gated error logging (#6257)
chore(cli): harden empty catches in completion.mjs with env-gated error logging (#6257). Reconstructed cleanly onto release/v3.8.47; env var documented. Integrated into release/v3.8.47.
2026-07-07 23:57:44 -03:00
Diego Rodrigues de Sa e Souza
dd12539a2c fix(api): Zod-validate POST /api/github-skills + document new gate envs + pin merge-integrity actions
Three latent heavy-CI reds surfaced by the VPS validation dispatch (the fast
path never runs these gates):

- t06 route-validation: POST /api/github-skills destructured request.json()
  blind — a non-array 'targets' would .map-crash. Now validateBody(zod)
  with defaults preserved (Hard Rule #7). Guard:
  tests/unit/github-skills-route-validation.test.ts (4/4).
- env-doc-sync: document OMNIROUTE_SKIP_SYSTEM_TRUST (#6310) and the
  changelog-integrity gate envs CHANGELOG_BASE_REF/ALLOW_CHANGELOG_REMOVALS
  (#6300) in .env.example + ENVIRONMENT.md.
- zizmor ratchet: the new merge-integrity job's checkout/setup-node uses were
  unpinned (+1 finding, 160 > baseline 159); pinned by SHA -> 158 (< baseline).
2026-07-05 20:04:49 -03:00
Diego Rodrigues de Sa e Souza
234956ddf5 fix: bug-fix sweep — log path, AgentBridge DNS, opencode-go headers, GitLab Duo tools, M365 EDU (#6197 #6127 #6198 #5997 #6220 #6210) (#6234)
Merged into release/v3.8.45. Bug-fix sweep (#6197 log path, #6127/#6198 AgentBridge DNS, #6210 M365 EDU, #6220 GitLab Duo tools, #5997 opencode-go headers). 27 testes verdes. CHANGELOG restaurado net +5. Reds: dast-smoke + file-size drift.
2026-07-05 17:37:21 -03:00
Diego Rodrigues de Sa e Souza
b6ffe8ce20 fix(proxy): make "Test All" read-only + add bulk enable/disable (#6246) (#6299)
Merged into release/v3.8.45. Delta do par #6246 (Test-All read-only + bulk enable/disable), reconciliado com o núcleo #6296 já mergeado. CHANGELOG restaurado (ambos bullets do #6246 coexistem). Reds pré-existentes: dast-smoke (infra) + file-size drift.
2026-07-05 17:32:05 -03:00
Diego Rodrigues de Sa e Souza
509fd5425c chore(release-green): clear test-masking + docs-all HARD reds for the v3.8.45 pre-flight
- test-masking: allowlist the 4 verified-legitimate assert reductions of the
  cycle (#6248 MiMo V2 removal, #6170 Kiro catalog correction, #6154 Copilot
  catalog refresh) and register the #6164 AutoRoutingBanner test deletion with
  a real replacement guard (tests/unit/home-no-autorouting-banner.test.ts —
  asserts the banner stays out of home/page.tsx and the component stays deleted)
- docs-sync: executors count 68 -> 73 in ARCHITECTURE.md + CODEBASE_DOCUMENTATION.md
- env-doc-sync: document OMNIROUTE_NO_SUDO (#6249/#6122) in .env.example +
  docs/reference/ENVIRONMENT.md
2026-07-05 14:33:54 -03:00
Diego Rodrigues de Sa e Souza
046093b9cb feat(build): make Turbopack the default bundler for dev and build (#6283)
Turbopack (stable in Next 16) becomes the code default in the three entry
points that previously required an explicit OMNIROUTE_USE_TURBOPACK=1:

- scripts/build/build-next-isolated.mjs (production build)
- scripts/dev/run-next.mjs (dev server)
- scripts/dev/run-next-playwright.mjs (playwright dev runner)

OMNIROUTE_USE_TURBOPACK=0 remains the webpack escape hatch (Windows /
native-binding / bundler-compat issues), and only the documented '0'
opts out — junk values keep the default.

Benchmarked on this codebase (same tree, Next 16.2.9): webpack 1035s vs
Turbopack 539s on a 32-core box; ~20min vs 6min59s on ubuntu-latest.
Artifact validated end-to-end (standalone smoke + e2e/package-artifact/
electron-package-smoke CI jobs, Docker amd64+arm64 builds clean with the
v3.8.27 ImportTracer panic gone on 16.2.9).

TDD: tests/unit/build-bundler-default-turbopack.test.ts (new) +
run-next-playwright.test.ts extended with the unset-env default case;
both red before the flip, green after. ENVIRONMENT.md updated.
2026-07-05 11:14:25 -03:00
R. Beltran
b074c6d75e fix(cli): detect POSIX auto-set HOSTNAME via os.hostname() to fix bind address (#6194) (#6195)
POSIX shells (bash/zsh) always set HOSTNAME to the machine name. The
.env loader uses first-wins semantics, so HOSTNAME=0.0.0.0 in .env is
silently ignored. This causes the server to bind to the LAN hostname
instead of 0.0.0.0, breaking localhost access and all internal
self-requests (ModelSync, HealthCheck, cloud sync).

The fix compares process.env.HOSTNAME against os.hostname(): when they
match, it's the POSIX auto-set signature and HOSTNAME is ignored.
OMNIROUTE_SERVER_HOST takes precedence as the dedicated escape hatch.

Backward compatibility is preserved: users who set HOSTNAME to a value
that doesn't match the machine name (e.g. Windows CMD/PowerShell users
with HOSTNAME in .env) will still have their value honoured.

Closes #6194
2026-07-05 02:30:16 -03:00
Diego Rodrigues de Sa e Souza
1bda6c15dc Release v3.8.44 (#5925)
* fix(install): add pnpm-workspace.yaml allowBuilds + pnpm.json for pnpm 11+

pnpm 11 introduced ERR_PNPM_IGNORED_BUILDS for native addon packages.
Without explicit allowBuilds approval, these packages silently skip build scripts
and OmniRoute fails to start with missing native modules.

Changes:
- pnpm-workspace.yaml: Set allowBuilds=true for all 13 native addon packages
  (@parcel/watcher, @swc/core, better-sqlite3, core-js, esbuild, keytar, koffi,
  libxmljs2, onnxruntime-node, protobufjs, sharp, tls-client-node, unrs-resolver)
- pnpm.json: Migrate onlyBuiltDependencies from package.json (deprecated field)
  to the new pnpm.json config file per pnpm 11 spec.

Tested on: pnpm 11.9.0, Node 24, Windows 11.

Fixes: pnpm install ERR_PNPM_IGNORED_BUILDS on fresh clone with pnpm 11.

* chore(release): open v3.8.44 development cycle

* test(security): parse Kimi Web URL host instead of substring match (CodeQL #689) (#5928)

Alert js/incomplete-url-substring-sanitization: the Kimi Web executor
test asserted result.url.includes("www.kimi.com"), which a hostile host
like www.kimi.com.evil.net would also satisfy. Parse the URL and assert
on the exact hostname (new URL(result.url).hostname === "www.kimi.com"),
which is both a stronger check and clears the CodeQL warning.

* refactor(translator): extract thinking-budget fitting from openai-to-claude (#5932)

Extract the thinking-budget fitting cluster (fitThinkingToMaxTokens +
private safeCapMaxOutputTokens + MIN_* constants) verbatim into the pure
leaf openai-to-claude/thinkingBudget.ts. Host re-exports fitThinkingToMaxTokens
so external importers keep working and imports it back for internal use.

Host 822 -> 738 LOC (under the 800 cap). No behavior change: byte-identical
bodies, public export set unchanged. Adds a split-guard test; all consumer
tests stay green (translator-openai-to-claude, strip-empty, minimax-m3, passthrough).

* chore(release): pipeline hardening — test-masking pre-flight gate + contributors/uncovered helpers (#5926)

* chore(ci): add test-masking PR-context gate to release-green pre-flight

Reproduce check:test-masking (vs origin/main) inside validate-release-green so
non-allowlisted net-assert reductions surface in the local pre-flight instead of
in a ~40-min CI layer on the release PR. run() now merges a per-gate opts.env so
GITHUB_BASE_REF reaches the child. HARD gate; skipped under --quick.

Context: v3.8.43 release cost 3 CI round-trips for PR-context gates (test-masking,
file-size, pr-evidence) that check:release-green did not reproduce locally.

* chore(release): add contributors generator + uncovered-commit reconciliation helpers

- scripts/release/gen-contributors.mjs: reproducible `### 🙌 Contributors` table for a
  CHANGELOG version (parenthetical-group parser → accurate per-PR attribution, noise-handle
  denylist). v3.8.43 shipped without the section (a real miss) because it was hand-built.
  npm run release:contributors <version> [--inject].
- scripts/release/list-uncovered-commits.mjs: lists commits since the last tag with no
  CHANGELOG bullet (v3.8.43 had 123/176 uncovered at reconciliation start). Advisory,
  maintainer-side. npm run release:uncovered.
- 20 unit tests (parenthetical attribution, noise exclusion, idempotent injection, coverage window).

* chore(quality): absorb web-cookie-providers-new file-size drift from #5928 (base-red on release/v3.8.44)

* refactor(translator): split openai-responses request translator into pure leaves (#5940)

Extract the shared pure primitives and the chat->Responses direction out of the
894-line openai-responses.ts request translator:
- openai-responses/helpers.ts: pure primitives (toRecord/toString/clampCallId/
  normalizeVerbosity/etc + markers/regexes/JsonRecord), zero host imports
- openai-responses/toResponses.ts: openaiToOpenAIResponsesRequest (chat->Responses),
  imports the helpers leaf

Host keeps openaiResponsesToOpenAIRequest (Responses->chat, imported by production)
plus both register() directions, and re-exports openaiToOpenAIResponsesRequest so
external importers (tests) keep working.

Host 894 -> 529 LOC (under the 800 cap). Verbatim bodies (multiset check: leaf A 54/54,
leaf B 294 lines, fn1 intact), public export set unchanged, leaves never import the host
(no cycle). Adds a split-guard test; all consumer tests stay green (responses-translation-fixes
37, verbosity 4, reasoning-effort 4, orphaned-tool-filter 8, empty-tool-name-loop 8,
headroom-responses-format 3).

* chore(ci): pr-evidence FAIL output tells you to push (body edit does not re-run the gate) (#5944)

ci.yml ignores the 'edited' event, so adding the Evidence block to the PR body after a
push does not re-run check:pr-evidence — you need another commit. The FAIL report now
says so, at the exact place someone sees the red check. + 5 unit tests (classification +
hint-on-fail / no-hint-on-pass). Decided against a separate edited-triggered workflow:
pr-evidence is not a required check (no ruleset gates it; release PRs merge UNSTABLE, not
BLOCKED), so the gap is cosmetic and the generate-release skill already puts Evidence in
the body before the first push.

* fix(providers): Perplexity Web emits real tool_calls in streaming mode (mirror chatgpt-web toolMode) (#5927) (#5937)

Perplexity Web (Pro/Max) only converted <tool>{...}</tool> text into
OpenAI tool_calls for non-streaming requests (hasTools && !stream).
Streaming requests -- the default for agentic coding clients -- got
the raw <tool> text as plain delta.content and never emitted a
tool_calls SSE delta, so clients could not execute tools.

Reuses the provider-agnostic buildToolModeResponse()/
toolCompletionToSseStream() helpers already shipped for chatgpt-web
(#5240): when tools are requested, buffer the full completion and
convert it into either a JSON completion or a terminal SSE replay
carrying delta.tool_calls + finish_reason: tool_calls, regardless of
the caller's stream flag. Extended buildToolModeResponse()'s idSeed
to be caller-supplied (default 'cgpt', perplexity-web passes 'pplx')
so tool_call ids stay provider-specific without duplicating the
helper. Non-tool streaming is unchanged (still lives token-by-token
via buildStreamingResponse).

* fix(discovery): resolve duplicate /v1 paths and redirect aborts (#5904)

Integrated into release/v3.8.44. Thanks @hamsa0x7 for diagnosing the doubled /v1 discovery path and the REDIRECT_BLOCKED probe-loop abort (#5899). De-scoped to the discovery fix (the #5903 session-affinity work is handled by #5943) and added Rule #18 regression guards.

* docs(changelog): record #5926 + #5944 (release-pipeline hardening) under v3.8.44 Maintenance (#5952)

* docs(claude): add Hard Rule #22 — cross-session safety (git stash + in-flight PRs) (#5955)

Integrated into release/v3.8.44 — Hard Rule #22 (cross-session safety).

* refactor(translator): extract pure helpers from response/openai-responses (#5949)

Extract the 5 stateless helpers (normalizeToolName, stripEmptyOptionalToolArgs,
normalizeOutputIndex, normalizeUpstreamFailure, extractResponsesReasoningSummaryText)
verbatim into the pure leaf openai-responses/pureHelpers.ts (no stream state, no host
import). Host imports them back and re-exports normalizeUpstreamFailure for external
importers (tests).

Host 1091 -> 1001 LOC. The stateful streaming core stays in the host (out of scope).
Byte-identical bodies (multiset 73/73), no cycle. Adds a split-guard; consumer tests
stay green (responses-translation-fixes 37, combo-param-validation-fallback-4519 5).

* docs(compression): document upstream sync policy for RTK/Caveman engines (#5830) (#5948)

Integrated into release/v3.8.44 — docs-only upstream sync policy for RTK/Caveman engines (closes #5830). All 7 checks green.

* fix(sse): strip ANSI/VT100 codes from gemini-cli stream frames (#5934)

Integrated into release/v3.8.44 — ReDoS-safe ANSI/VT100 strip for gemini-cli stream frames (port of upstream #2273, thanks @anki1kr). PR test green (5/5), file-size gate OK.

* fix(translator): strict Anthropic content-block compliance in antigravity→openai request (#5935)

Integrated into release/v3.8.44 — strict Anthropic content-block compliance in antigravity→openai (port upstream #2296). PR test green (9/9). UNSTABLE red is the pre-existing environmental setup-claude base-red (opencode-plugin dist not built in fast-path), not a regression from this PR.

* fix(mcp): auto-recover stale streamable HTTP sessions on initialize (#5957)

Integrated into release/v3.8.44 — MCP stale streamable-HTTP session auto-recovery (thanks @Chewji9875).

* fix(providers): validate v0 Platform API keys via chats endpoint (#5954)

Integrated into release/v3.8.44 — v0-vercel Platform API key validation (thanks @vittoroliveira-dev).

* fix(api): relax provider-scoped chat completion validation (#5907)

Integrated into release/v3.8.44 — relaxed provider-scoped chat validation + regression test (thanks @nickwizard).

* fix(providers): strip /v1 unconditionally to avoid /v1/v1/models fetch error (#5899) (#5920)

Integrated into release/v3.8.44 — unconditional /v1 strip in both models-discovery paths + regression test (thanks @anki1kr).

* fix(resilience): per-window is_exhausted + honor quota-exhaustion preflight for priority combos (#5923) (#5941)

Integrated into release/v3.8.44.

* fix(resilience): honor active codex session affinity over per-request reset-aware re-scoring (#5903) (#5943)

Integrated into release/v3.8.44.

* fix(thinking): only inject redacted_thinking replay block when tool_use present and thinking enabled (#5945) (#5953)

Integrated into release/v3.8.44.

* feat(providers): add ClinePass API-key provider (#5942)

Integrated into release/v3.8.44 — ClinePass API-key (BYOK) provider (port upstream 9router#2304, co-authored @adentdk). Validated locally: 16 clinepass tests green; fixed the APIKEY count 158→159 + translate-path golden snapshot (clinepass is a genuine new provider). Remaining UNSTABLE red is the pre-existing environmental setup-claude base-red (opencode-plugin dist not built in fast-path). Supersedes stub #5541.

* feat(api): add /v1/ocr endpoint (Mistral OCR) + Mistral moderation (#5950)

Integrated into release/v3.8.44 — /v1/ocr endpoint (Mistral OCR) + Mistral moderation (port upstream 9router#2064, co-authored @waguriagentic). Validated locally: 14 ocr-route tests + moderation/servicekind/endpoint-category suites green (CORS→Zod→handler + no-stack-leak assertion). Reds are inherited DRIFT only: cognitive-complexity ratchet (none from OCR files — pre-existing cycle drift, rebaselined at release) + environmental setup-claude base-red.

* fix(codex): convert chat json schema to responses text format (#5933)

Integrated into release/v3.8.44 — converts Chat Completions json_schema response_format → Responses API text.format on the Codex path, and preserves existing text.format through verbosity normalization. Base redirected main→release; the openai-responses.ts split that landed this cycle was reconciled by re-applying the delta onto openai-responses/toResponses.ts. Validated locally: 48 translator-openai-responses-req + 8 codex-verbosity tests green.

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

* feat(providers): add Claude Sonnet 5 support across the model pipeline (#5833)

Integrated into release/v3.8.44 — wires claude-sonnet-5 end-to-end (registries, modelSpecs, pricing ×3, cost, Sonnet-family fallback, 1M-ctx, static models). Reconciled the add/add overlap with the already-merged #5796 (kept the PR's superset test with the family-fallback assertion). Validated locally: kiro-sonnet-5 + catalog + pricing/modelSpecs/fallback suites all green. Thanks @ggiak!

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

* feat(relay): gate bifrost auto routing by provider manifest (#5870)

Integrated into release/v3.8.44 — gates Bifrost auto-routing by the provider plugin manifest (only manifest-eligible providers reach the sidecar; ineligible/unknown fall back to the TS path with explicit reasons). Superset of #5869 (carries the full manifest + registry + docs). Resolved an integration-test conflict in favor of the release (which already subsumes this PR's readiness/removeDirWithRetry improvements). Validated locally: 4 provider-plugin-manifest + 11 relay-routing-backend tests green. Thanks @KooshaPari!

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

* refactor(translator): extract pure message helpers from openai-to-kiro (852→751) (#5947)

* refactor(translator): extract pure message helpers from openai-to-kiro

Extract the pure tool/message helpers (parseToolInput, normalizeKiroToolSchema,
serializeToolResultContent) verbatim into the leaf openai-to-kiro/messageHelpers.ts.
The host imports them back for convertMessages. They were module-private, so the
public export set is unchanged (no re-export needed).

Host 852 -> 751 LOC. Byte-identical bodies (multiset 99/99), leaf has zero imports
(no cycle). Adds a split-guard; consumer tests stay green (translator-openai-to-kiro 33,
translator-ai-sdk-image-parts 3).

* chore: re-trigger CI (stuck runner on 2/2 shard)

* refactor(executors): extract pure prompt + composer helpers from cursor (#5960)

Extract two pure clusters from the cursor executor into sibling leaves:
- cursor/prompt.ts: isRecordLike + toolChoiceDirectiveLine + buildCursorOutputConstraints
- cursor/composer.ts: composer thinking-as-content decoding (isComposerModel,
  visibleComposerContentFromThinking, composerReasoningRemainder + markers)

Host imports both back for internal use and re-exports the 3 composer helpers for
external importers (tests). Host 1576 -> 1451 LOC. Byte-identical bodies (verbatim
multiset prompt 65/65, composer 32/32), leaves have zero imports (no cycle). Adds a
split-guard; consumer tests stay green (cursor-composer-thinking, cursor-streaming,
cursor-agent-tool-calls, translator-openai-to-cursor, cursor-agent-system-prompt).

* refactor(executors): extract pure SSE-collect parsing from antigravity (#5962)

Extract the pure SSE-payload -> collected-stream parser (AntigravityCollectedStream,
stripZeroWidth, parseAntigravityTextualToolCall, addAntigravityTextualToolCall,
processAntigravitySSEPayload/Text, flushAntigravitySSEText) verbatim into the leaf
antigravity/sseCollect.ts. Host imports the helpers it uses and re-exports
processAntigravitySSEPayload for external importers (tests).

Host 1812 -> 1671 LOC. Byte-identical bodies (verbatim multiset 135/135), leaf does
not import the host (no cycle). Credit/quota state, auth, and HTTP dispatch untouched.
Adds a split-guard; consumer tests stay green (executor-agy 8, executor-antigravity 26,
antigravity-sse-collect-socket-release, copilot-agent-antigravity-parity 6).

* refactor(executors): extract pure model maps + resolvers from chatgpt-web (#5967)

Extract the static model maps (MODEL_MAP, MODEL_FORCED_EFFORT, THINKING_CAPABLE_SLUGS)
and the pure thinking-effort resolvers (isThinkingCapableModel, normalizeThinkingEffort,
resolveThinkingEffort, ResolvedChatGptModel, resolveChatGptModel) verbatim into the pure
leaf chatgpt-web/models.ts. Host imports the two resolvers it uses back.

Host 3205 -> 3076 LOC. Byte-identical bodies (verbatim multiset 120/120), leaf has zero
imports (no cycle). Auth/PoW/session/HTTP dispatch and all module caches untouched.
Adds a split-guard; consumer tests stay green (chatgpt-web 86, chatgpt-web-tools-5240 4,
chatgpt-web-sha3-boringssl-5531 5).

* refactor(executors): decompose grok-web into pure tool/markup leaves (#5994)

Extract the pure OpenAI<->Grok tool-translation, native-tool mapping, markup cleanup,
and NDJSON stream types out of the 1872-line grok-web executor into 4 sibling leaves:
- grok-web/types.ts: GrokStreamResponse/GrokStreamEvent (stream types)
- grok-web/tool-bridge.ts: OpenAI<->Grok tool translation + registry + classifiers
- grok-web/native-tools.ts: native-tool selection/scoring + native->OpenAI mapping
- grok-web/text-cleanup.ts: Grok markup stripping + GrokMarkupFilter

Layered, acyclic: types <- tool-bridge <- native-tools; text-cleanup <- types; host
imports the leaves. All symbols module-private (no host re-export). Host 1872 -> 887 LOC.
Byte-identical bodies (verbatim per-leaf), no cycle, all new leaves <= 800 cap
(tool-bridge split at line 753 to stay under). Auth/cookie/TLS/HTTP dispatch untouched.
Adds a split-guard; consumer tests stay green (grok-web 62, grok-cli-oauth 15,
grok-cli-strip-params 2).

* refactor(executors): extract pure quota parsing from codex (#5999)

Extract the pure Codex quota-snapshot parsing + reset/cooldown scheduling
(CodexQuotaSnapshot, parseCodexQuotaHeaders, getCodexResetTime,
getCodexDualWindowCooldownMs) verbatim into the leaf codex/quota.ts. Host re-exports
the 4 symbols so handlers/chatCore/codexQuota.ts + tests keep resolving.

Host 1539 -> 1427 LOC. Byte-identical bodies (verbatim 98/98), leaf has zero imports
(only Date, no cycle). WS transport, auth, HTTP dispatch untouched. Adds a split-guard;
consumer tests stay green (executor-codex 40, codex-quota-fetcher 7, chatcore-codex-quota 5).

* refactor(executors): extract pure stream formatters from deepseek-web (#6000)

Extract the pure content/citation formatters (isThinkingModel, isSearchModel,
cleanDeepSeekToken, formatStreamContent, DeepSeekSearchResult, appendSearchCitations)
verbatim into the leaf deepseek-web/stream-format.ts. Host imports the 5 it uses back
into transformSSE/collectSSEContent (cleanDeepSeekToken stays internal to the leaf).

Host 1147 -> 1108 LOC. Byte-identical bodies (verbatim 34/34), leaf has zero imports
(no cycle), all module-private (no re-export). PoW/auth/token-cache/HTTP dispatch
untouched. Adds a split-guard; consumer tests stay green (deepseek-web 35,
deepseek-web-rolling-window-2942 5, deepseek-web-tools-execute 3).

* refactor(api): add validatedJsonBody helper (salvage #5075) (#5931)

Fuses JSON body parsing + Zod validation into a single call that returns
either type-narrowed data or a ready-to-return 400 NextResponse with the
standard error envelope. Salvaged as the Tier 1 portable helper from the
closed refactor PR #5075; the bulk route migration is intentionally not
ported. Adds a focused 6-case regression test.

Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com>

* feat(qoder): drive PAT auth via qodercli, add dashboard quota, fix connection display (#5816)

Integrated into release/v3.8.44 — Qoder PAT auth via qodercli binary + dashboard quota + dual-auth connection fix. Thanks @AgentKiller45 (co-author @judy459)!

Validated locally (release-green on its own merits): lint 0, typecheck:core 0, 104 qoder/usage/UI tests green, file-size gate OK (owner-approved qoderCli.ts baseline-freeze 666→989), env-doc-sync fixed (documented QODER_CLI_CONFIG_DIR).

The 2 remaining CI reds are INHERITED base-reds, not caused by this PR: (1) LEDGER-4 minimax-m3 supportsVision (minimax-m3 base + cline-pass/minimax-m3 from the already-merged #5942); (2) mutation-test-coverage missing 3 tests in stryker.conf (#5903/#5942/#5923). Both cleaned up separately.

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

* fix(providers): minimax-m3 supportsVision (LEDGER-4) + stryker tap.testFiles drift (#6012)

Release-green cleanup — clears LEDGER-4 minimax-m3 supportsVision + stryker tap.testFiles drift base-reds. Validated locally.

* fix(registry): flag cline-pass/minimax-m3 as multimodal (supportsVision) (#6003)

The cline-pass provider's minimax-m3 entry was missing supportsVision, breaking the
LEDGER-4 registry-consistency test (all minimax-m3 entries must set supportsVision to
match lite.ts — minimax-m3 is multimodal). Every other minimax-m3 registry entry
(trae, bazaarlink, cline, ollama-cloud, ...) already sets it. This was a base-red on
release/v3.8.44 inherited by every open PR.

Validated by the existing failing-then-passing guard tests/unit/review-reviews-v3814-fixes.test.ts
(LEDGER-4).

* refactor(executors): extract pure payload construction from claude-web (#6006)

Extract the pure Claude-web payload types + transforms + default tools/style
(ClaudeWebRequestPayload, ClaudeWebStreamChunk, DEFAULT_CLAUDE_MODEL,
generateMessageUUIDs, getDefaultTools, getDefaultPersonalizedStyle, transformToClaude,
transformFromClaude) verbatim into the leaf claude-web/payload.ts. Host imports the 3
it uses back (ClaudeWebRequestPayload type + the two transforms).

Host 1056 -> 835 LOC. Byte-identical bodies (verbatim 149/149), leaf imports only
randomUUID (no host import, no cycle), all module-private (no re-export). Cookie/auth/
Turnstile/TLS/HTTP dispatch untouched. Adds a split-guard; consumer tests stay green
(claude-web 13, claude-web-auto-refresh 6).

* refactor(executors): extract pure upstream-header helpers from base (#6008)

Extract the pure upstream-header helpers (mergeUpstreamExtraHeaders, getCustomUserAgent,
setUserAgentHeader, applyConfiguredUserAgent, isOpenAICompatibleEndpoint,
stripStainlessHeadersForOpenAICompat) verbatim into the leaf base/headers.ts. base.ts is
imported by ~18 executors, so the host re-exports all 6 to keep those import paths intact;
it also imports the 4 it uses internally in the BaseExecutor class. The trivial JsonRecord
type alias is redefined locally in the leaf to avoid a base<->leaf cycle.

Host 1539 -> 1451 LOC. Byte-identical bodies (verbatim 78/78), leaf does not import the
host (no cycle). typecheck:core validates all base importers still resolve via the
re-export. Adds a split-guard; consumer tests stay green (executor-base-utils 22,
executor-default-base 49, executor-strip-stainless-openai-compat 6, plus executor sanity
via typecheck).

* refactor(executors): extract pure wire protocol from perplexity-web (#6014)

Extract the pure Perplexity wire protocol (consts, SSE stream types, SSE parsing,
OpenAI<->Perplexity message translation, request/query builders, content extraction,
sseChunk) verbatim into the leaf perplexity-web/protocol.ts. Host imports back the 10
symbols it uses; everything module-private (no re-export). Session cache, TLS fetch,
auth, and the executor class stay in the host.

Host 1028 -> 534 LOC. Byte-identical bodies (verbatim), leaf imports only randomUUID
(no host import, no cycle). Adds a split-guard; consumer tests stay green
(perplexity-web 26, streaming-tools-5927 2, tls-client 6, key-validation-models 2).

* refactor(executors): extract pure URL normalizers from default (#6015)

Extract the pure per-provider chat-URL normalizers (normalizeBailianMessagesUrl,
normalizeDataRobotChatUrl, normalizeAzureAiChatUrl, normalizeWatsonxChatUrl,
normalizeOciChatUrl, normalizeSapChatUrl, normalizeXiaomiMimoChatUrl,
normalizeOpenAIChatUrl, getOpenRouterConnectionPreset) verbatim into the leaf
default/urlNormalizers.ts. Host imports them back into buildUrl/transformRequest; the
now-dead build*ChatUrl/normalizeBaseUrl imports move to the leaf. All module-private
(no re-export).

Host 864 -> 815 LOC (shrunk below its frozen baseline). Byte-identical bodies (verbatim
45/45), leaf does not import the host (no cycle). buildHeaders/execute/auth untouched.
Adds a split-guard; consumer tests stay green (executor-default-base 49,
anthropic-compatible-bearer 3, strip-client-metadata 3).

* feat(webfetch): support self-hosted FireCrawl instances (#5793)

Integrated into release/v3.8.44 — self-hosted FireCrawl support (FIRECRAWL_BASE_URL/FIRECRAWL_TIMEOUT_MS). Re-cut clean onto the release tip (branch was fossilized from a pre-v3.8.40 snapshot). Validated: 4 firecrawl tests green, env-doc-sync + docs-sync pass. UNSTABLE red is the inherited environmental setup-claude base-red.

* feat(xai): register XaiExecutor with reasoning-effort suffix parsing (#5800)

Integrated into release/v3.8.44 — XaiExecutor with reasoning-effort suffix parsing. Re-cut clean onto the release tip (branch was fossilized). Validated: 6 xai-executor tests green, provider-consistency OK, typecheck:core 0 errors, env-doc-sync in sync. UNSTABLE red is the inherited environmental setup-claude base-red.

* feat(discovery): Phase 2 — reporter, /api/discovery/* routes (strict loopback-only) + dashboard UI (#5939)

* feat(discovery): Phase 2 reporter — discoveryResults DB module + service wiring

Adds src/lib/db/discoveryResults.ts (CRUD over the discovery_results table
from migration 074) and wires the opt-in discovery service to persist and read
findings through it: persistDiscoveryResult / getDiscoveryResults /
getDiscoveryResultById / markVerified / deleteDiscoveryResult, with
(provider, method, endpoint) upsert de-duplication. Re-exported from localDb.

The service stays opt-in / default-off. The /api/discovery/* routes and the
dashboard UI tab are intentionally deferred to Phase 2b — they need the
local-only enforcement model (Hard Rules #15/#17 territory) decided first.

TDD: tests/unit/db/discovery-results.test.ts (8 cases, DB + service delegation),
isolated DATA_DIR with resetDbInstance cleanup.

* feat(discovery): Phase 2b — /api/discovery/* routes (strict loopback-only)

Adds the discovery HTTP surface on top of the reporter DB module:
  GET    /api/discovery/results            list findings (optional ?providerId)
  GET    /api/discovery/results/:id        one finding (404 if absent)
  DELETE /api/discovery/results/:id        delete a finding
  POST   /api/discovery/scan               scan a provider + persist findings
  POST   /api/discovery/verify/:id         mark a finding verified

Authorization: strict loopback-only. "/api/discovery/" is added to
LOCAL_ONLY_API_PREFIXES so the central authz pipeline (proxy.ts →
runAuthzPipeline → managementPolicy) rejects non-loopback callers with a 403
LOCAL_ONLY before any handler runs. It is deliberately NOT in
LOCAL_ONLY_MANAGE_SCOPE_BYPASS_PREFIXES — no remote manage-scope bypass —
because POST /scan issues outbound probes to provider endpoints (SSRF-adjacent)
and must never be tunnel-reachable. Handlers also call requireManagementAuth
(defense in depth) and return sanitized errors via createErrorResponse.

Tests:
- tests/unit/authz/discovery-routes-local-only.test.ts (8) — security guard:
  isLocalOnlyPath true + not manage-scope-bypassable for all four paths.
- tests/unit/api/discovery-routes.test.ts (6) — handler integration over an
  isolated DATA_DIR: list/filter, by-id 200/404/400, scan persist + 400 on
  empty/malformed body, verify 200/404, delete 200/404, no stack-trace leak.

* feat(discovery): Phase 2c — dashboard UI tab (Tools → Discovery)

Adds the /dashboard/discovery page (DiscoveryPageClient) that consumes the
Phase 2b /api/discovery/* routes: scan a provider, list findings, verify or
delete them. Registered in the sidebar under the Tools group (icon
travel_explore) and given a "discovery" i18n namespace + sidebar keys in
en.json (other locales fall back to en via next-intl until synced — the
locale files are in a pre-existing coverage deficit unrelated to this change).

Registers the UI test path in vitest.config.ts (advisory ui suite).

Tests: src/app/(dashboard)/dashboard/discovery/__tests__/DiscoveryPageClient.test.tsx
(3 cases: loads+renders results, empty state, fetches /api/discovery/results on
mount; stable useTranslations mock to avoid the fetch-loop). NOTE: the ui vitest
suite cannot run in this workspace — @testing-library/dom (a @testing-library/
react peer dep) is absent from node_modules, which fails ALL existing ui tests
equally; the test runs in CI. Component verified locally via typecheck + lint.

* test(discovery): register discovery-routes-local-only in stryker tap.testFiles

The mutation-test-coverage gate (--strict) flags any unit test covering a
mutated module that isn't listed in stryker.conf.json tap.testFiles. This PR's
tests/unit/authz/discovery-routes-local-only.test.ts covers src/server/authz/
routeGuard.ts (a mutated module, which this PR edits by adding the
/api/discovery/ local-only prefix), so it must be registered for its mutant
kills to count. No behavior change.

* refactor(discovery): split DiscoveryPageClient to satisfy max-lines-per-function

The complexity ratchet (max-lines-per-function: 80) flagged the single
184-line DiscoveryPageClient function (+1 over baseline). Extract the data
layer into two hooks (useDiscoveryResults for list/loading/feedback,
useDiscoveryActions for scan/verify/delete), a shared callApi helper, and two
presentational sub-components (DiscoveryScanForm, DiscoveryResultCard). Every
function is now under the 80-line ceiling; complexity gate back to baseline
1995. No behavior change — same exported component, same endpoints, same props.

* test(sidebar): include discovery in omni-proxy item-order snapshot

Adding the Discovery item to the Tools group (this PR's sidebar entry) extends
the ordered omni-proxy section list. Update the exact-match deepEqual snapshot
in sidebar-visibility.test.ts to include "discovery" in its position (after
traffic-inspector). The assertion stays exact — this reflects the intentional
new item, it does not weaken the check.

* docs(changelog): restore release bullets eaten by merge auto-resolve; re-add discovery bullet additively

* chore(quality): bump testFrozen for translator-openai-responses-req.test.ts (1097 -> 1172)

Base-red inherited from #5933, which grew the test file to 1171 lines
(Hard Rule #18 regression tests) without adjusting the frozen cap. The
release tip itself fails check:file-size; this unblocks every PR into
release/v3.8.44. File untouched by this PR.

* chore(quality): restore stryker tap.testFiles entries eaten by merge auto-resolve

The merge of origin/release/v3.8.44 silently dropped the 3 entries added
on the release side (#5903, clinepass, #5923). Took the release version
verbatim and re-added only this PR's entry (discovery-routes-local-only)
in alphabetical order. check:mutation-test-coverage green locally.

* chore(quality): reconcile inherited v3.8.44 merge-burst drift + include discovery in tools-group order test

- complexity 1995->2003 and cognitive 856->859: both measure IDENTICAL on
  the pristine release tip (3a3d618fe) and this PR's merged HEAD — the PR
  is complexity-net-zero; drift is from the 2026-07-02 merge burst
  (notes added to both baselines, same family as prior reconciliations).
- sidebar-tools-group.test.ts: append 'discovery' to the expected
  TOOLS_GROUP order — the intentional new sidebar item this PR adds
  (same expected-value update already made in sidebar-visibility.test.ts).

* feat(providers): custom icon URL for compatible provider nodes (#5815)

Integrated into release/v3.8.44 — custom icon URL for compatible provider nodes (DB migration 113 + nodes.ts + Zod schema + API routes + catalog + ProviderIcon UI). Re-cut onto the release tip (branch was fossilized ~13 real files); reconciled icon_url into the release's evolved nodes.ts/routes via 3-way. Validated: 14 backend + 5 frontend(vitest) + 24 page-utils tests green, typecheck:core 0, provider-consistency OK, file-size/env-doc-sync pass. UNSTABLE red is the inherited environmental setup-claude base-red.

* feat(api): add /v1/audio/translations endpoint (#5809)

Integrated into release/v3.8.44 — /v1/audio/translations endpoint (Whisper-style audio translation) + audioTranslation handler + translation providers in audioRegistry. Re-cut clean onto the release tip (branch was fossilized). Validated: 8 route tests (incl. no-stack-leak), typecheck:core 0, route-guard-membership OK, docs gates pass. UNSTABLE red is the inherited environmental setup-claude base-red.

* feat(dashboard): wildcard-CORS runtime warning + CORS security doc (#5602) (#5759)

Integrated into release/v3.8.44 — wildcard-CORS runtime warning banner + docs/security/CORS.md security guide (#5602). Re-cut clean onto the release tip (branch was fossilized). Validated: 20+9 backend + 2 banner(vitest) tests green, typecheck:core 0, docs-sync/symbols/fabricated/doc-links pass. UNSTABLE red is the inherited environmental setup-claude base-red.

* refactor(executors): extract pure JSONL stream translation from huggingchat (#6016)

Extract the pure JSONL->OpenAI-SSE translation (sseChunk, parseJsonlLine,
streamJsonlToOpenAi, readJsonlResponse) verbatim into the leaf huggingchat/jsonlStream.ts.
They consume a passed-in ReadableStream (no fetch/network/state). Host imports back the
two it uses; all module-private (no re-export).

Host 812 -> 594 LOC. Byte-identical bodies (verbatim), leaf has zero imports (no cycle).
Cookie/auth/multipart/execute untouched. Adds a split-guard; consumer tests stay green
(executor-huggingchat 6, huggingchat-model-catalog 3).

* refactor(executors): extract pure Meta AI response parser from muse-spark-web (#6017)

Extract the pure Meta AI SSE/JSON response parsing + content/reasoning/error extraction
(parseMetaSseFrames, readMetaJsonPayloads, collect*/extract*/classify* helpers,
parseMetaAiResponseText, isRecord, the reasoning/renderer key arrays, MetaSseFrame/
ParsedMetaAiResponse types) verbatim into the leaf muse-spark-web/response-parser.ts.
Host imports back the 3 it uses; all module-private (no re-export).

Host 1301 -> 925 LOC. Byte-identical bodies (verbatim), leaf has zero imports (no cycle).
Conversation cache, cookie/auth, fetch, executor class untouched. Adds a split-guard;
consumer tests stay green (muse-spark-cookie-copy-5449 2, muse-spark-web-continuation 6).

* refactor(executors): extract pure EventStream framing from kiro (#6018)

Extract the pure AWS EventStream binary framing (ByteQueue, CRC32 table + crc32,
TEXT_ENCODER/TEXT_DECODER, KIRO_VERIFY_FULL_CRC, parseEventFrame, EventFrame type)
verbatim into the self-contained leaf kiro/eventstream.ts (local JsonRecord alias to avoid
a cycle). Host imports back the 3 it uses (ByteQueue, TEXT_ENCODER, parseEventFrame).

Host 943 -> 758 LOC. Byte-identical bodies (verbatim 145/145), leaf has zero host imports
(no cycle). Auth/token-refresh/streaming-state/executor class untouched; the test-imported
flushBufferedToolArgs/resolveKiroRegion/kiroRuntimeHost stay exported on the host. Adds a
split-guard; consumer tests stay green (executor-kiro 9, kiro-tool-args-streaming 7,
kiro-iam-region 10).

* refactor(executors): extract challenge solver from duckduckgo-web (#6020)

Extract the DuckDuckGo anti-abuse challenge solver + FE signals (CHALLENGE_STUBS,
countHtmlElements, buildHtmlLookup, sha256Base64, solveDuckDuckGoChallenge,
makeDuckDuckGoFeSignals) verbatim into the leaf duckduckgo-web/challenge.ts. The vm
sandbox + 5s timeout (SECURITY note) are preserved. Host imports back the two it uses.

Host 924 -> 788 LOC. Byte-identical bodies (verbatim 132/132), leaf does not import the
host (no cycle). The now-dead createHash/parse5 host imports are removed; vm stays (still
used in host). Auth/cookie/warm/seed/executor untouched. Adds a split-guard; consumer
tests stay green (duckduckgo-web-executor 15, duckduckgo-domain-4037 8).

* test(cli): deflake setup-claude.test.ts — silence console to stop stdout/report interleaving (#5959) (#6019)

Integrated into release/v3.8.44. Deflakes tests/unit/cli/setup-claude.test.ts (#5959) — verified in CI: setup-claude now passes in Unit Tests fast-path (2/2).

Merged with --admin over two PRE-EXISTING base-reds proven independent of this test-only change (this PR only touches setup-claude.test.ts + CHANGELOG):
- Fast Quality Gates → check:test-discovery: tests/unit/executors/{firecrawl-fetch,xai-executor}.test.ts are orphaned on release/v3.8.44 (added by #5793/#5800); the shard glob 'tests/unit/{api,...,ui}/**' omits 'executors'. Both blobs exist on the pristine base.
- Unit Tests fast-path (2/2): tests/unit/settings-i18n-keys.test.ts → 'direct translation calls have English messages' fails on the pristine base too (unrelated i18n base-red).

* fix(cli): stabilize setup-claude.test.ts flake — inject dry-run log sink (#6021)

* fix(cli): stabilize setup-claude.test.ts flake — inject dry-run log sink (#5959)

Root cause (isolated empirically, 5/10 fail on the pristine base): the
dry-run path of syncClaudeProfilesFromModels console.log's a multi-byte
box-drawing heading ("── [dry-run] … ──"). Under the node:test runner
that write lands on the test child's stdout and corrupts the runner's
V8-serialized event stream ~50% of the time ("Unable to deserialize
cloned data due to invalid or unsupported version"), killing the file at
the first logging test. ASCII-only logging never reproduced it (0/20);
the unicode heading alone reproduced it (10/20).

Fix: syncClaudeProfilesFromModels accepts an injectable log sink
(opts.log, CLI default unchanged: console.log). The dry-run test injects
a collector — keeping unicode off the child's stdout — and gains
assertions on the dry-run report (path + parsed settings content), which
FAIL on the old code (log ignored) and PASS on the new one.

Validation: 0/30 failures post-fix vs 5/10 pre-fix on the same tree.

Baselines: complexity 2003->2006 and cognitive 859->860 are inherited
post-3a3d618fe release drift — measured identical on the pristine base
with and without this change (notes added in both files).

* test(ci): collect the orphaned tests/unit/executors/ directory (base-red unblock)

#5800 created tests/unit/executors/ outside every unit-runner brace glob,
so its 2 test files (firecrawl-fetch, xai-executor) never ran anywhere and
check:test-discovery flags them as NEW orphans on the pristine base,
red-flagging every PR into release/v3.8.44. Added 'executors' to the
runner globs in package.json (7 scripts), ci.yml unit shards, quality.yml
TIA glob, build-test-impact-map.mjs, and the test-discovery gate's
COLLECTORS (the gate enforces those stay in sync). Both files pass when
actually collected (10/10); cli+executors under suite flags: 99/99.

* chore(quality): complexity baseline 2006 -> 2007 (CI-observed value)

The GitHub fast-gates runner measures 2007 where local measures 2006 —
the same local-vs-CI off-by-one documented in the 2026-06-26 note. Pin
the CI-observed value so the gate is deterministic where it runs.

* fix(i18n): add the 6 missing en.json keys flagged by settings-i18n-keys (base-red unblock)

providers.iconUrlLabel/iconUrlHint (referenced by AddCompatibleProviderModal
and EditCompatibleNodeModal) and settings.authz.cors.wildcard.title/desc
(the #5602 CORS_ALLOW_ALL banner in AuthzSection) shipped without their
en.json messages — 'direct translation calls have English messages' fails
on the pristine release tip, red-flagging every PR. git log -S proves the
keys never existed (not a merge-eat). Scanner test: 10/10 green.

* refactor(executors): extract reasoning-effort (base) + tool-normalization (codex) leaves (#6030)

Two pure-leaf follow-ups closing the Block H tail:

- base/reasoningEffort.ts: provider-aware reasoning_effort sanitation
  (MISTRAL/GITHUB reject patterns, supportsMaxEffortForProvider,
  sanitizeReasoningEffortForProvider). Deps are config/services only
  (PROVIDER_CLAUDE, isClaudeCodeCompatible, supportsClaudeMaxEffort/supportsXHighEffort)
  so the leaf never imports the host — no cycle. base.ts re-exports
  sanitizeReasoningEffortForProvider for its external importers (mimoThinking + tests).
  base.ts 1466 -> 1312 LOC.

- codex/tools.ts: Responses-API tool normalization (CODEX_HOSTED_TOOL_TYPES hosted-tool
  passthrough, isCodexFreePlan gating, normalizeCodexTools). Self-contained
  (console.debug only). codex.ts re-exports isCodexFreePlan + normalizeCodexTools for
  external importers (tests + provider services). codex.ts 1430 -> 1268 LOC.

Byte-identical bodies (verbatim: base 100/100, codex 126/126); both leaves have zero host
imports. Adds two split-guards asserting the leaf owns the symbol and both import paths
resolve to the same function. Consumer tests stay green (base-executor-sanitize-effort 34,
executor-codex 40, mimoThinking 9, codex-free-plan-image-generation 3, issue-fixes 6).

* test(ci): move orphaned executor tests to top-level so a runner collects them (#6027)

Integrated into release/v3.8.44 — collect orphaned executor tests (check:test-discovery base-red).

* test(cli): deflake cli-setup-opencode.test.ts — silence console (#5959-class landmine) (#6033)

The command under test prints CLI progress with multi-byte glyphs
(printSuccess "✔" in the happy paths, printError "✖" in the dist-missing
path that test 4 exercises) via console.log. Under the node:test runner
those child-stdout writes interleave with the V8-serialized report frames
and can corrupt the stream — the exact #5959 mechanism proven for
setup-claude.test.ts; this file's ✖ line was already visible entangled in
red CI runs. No test here asserts on stdout, so silence console.log/info/
warn for the file (same pattern as #6019/#6021, restored in after()).

Validation: pre-fix the ✖/✔ lines reach stdout every run (grep-able);
post-fix stdout is clean, 4/4 tests green, 0/20 failures across 20 runs.

* feat(agy): support Google Cloud project ID settings (#5905)

* feat(agy): support Antigravity project ID settings

* refactor(agy): collapse Antigravity family project gate

---------

Co-authored-by: Nikolay Alafuzov <alafuzov_nn@rusklimat.ru>

* feat(proxy): add Webshare proxy pool import and sync (#5993)

* feat(proxy): add Webshare proxy pool import and sync

Adds Webshare (https://proxy.webshare.io) as a fourth source in the
free-proxy provider framework alongside 1proxy, Proxifly, and IPLocate.
WebshareProvider paginates the account's `/api/v2/proxy/list/` endpoint
(Authorization: Token <key>), upserts proxies into the shared
`free_proxies` table via the existing db/freeProxies.ts helpers, and
tombstones proxies the account no longer lists (recycled/retired IDs)
while never touching rows already promoted into the live proxy pool.

Unlike the other sources, Webshare is a paid per-account list, so it is
gated on FREE_PROXY_WEBSHARE_API_KEY rather than a plain on/off flag.
No DB migration needed — reuses the existing free_proxies table and
proxy_registry-on-promote path.

Co-authored-by: ricatix <d.enistraju155@gmail.com>
Inspired-by: https://github.com/decolua/9router/pull/1176

* chore(changelog): restore release entries + add webshare bullet

---------

Co-authored-by: ricatix <d.enistraju155@gmail.com>

* feat(api-keys): add per-key device/connection tracking (#5998)

* feat(api-keys): add per-key device/connection tracking

Tracks distinct client devices (SHA-256 fingerprint of IP + User-Agent)
seen with each API key, with a 30-minute TTL and per-key/global caps. The
tracker is in-memory only (module-scoped Map, same pattern as
sessionManager.ts — no global.* singleton) and never stores the raw IP:
it is masked before being written.

Hooked into open-sse/handlers/chatCore.ts (the real chat entry) rather
than the legacy src/sse/handlers path. New GET /api/keys/[id]/devices
management route exposes masked device details for a key, and the
API Keys dashboard tab gets a "Devices" count badge alongside the
existing Sessions badge.

This is a new granularity distinct from the existing maxSessions cap
(src/lib/db/apiKeys.ts), which limits concurrent sticky-routing sessions
rather than tracking device identity.

Co-authored-by: Muhammad Mugni Hadi <mugni@rukita.co>
Inspired-by: https://github.com/decolua/9router/pull/931

* chore(changelog): restore release entries + add api-keys device-tracking bullet

---------

Co-authored-by: Muhammad Mugni Hadi <mugni@rukita.co>

* fix(providers): only apply openai-family model inference fallback when no cataloged provider serves the id (#5852) (#5938)

resolveModelByProviderInference() in open-sse/services/model.ts had an
unconditional /^gpt-/i heuristic that hijacked any model id starting with
gpt-/o1/o3 into provider openai, even when the id is cataloged under other
providers. This broke bare (non-combo) requests for open-weight models like
gpt-oss-120b (served by fireworks/cerebras/scaleway/byteplus/sambanova/
heroku), which don't exist on openai's catalog, producing a 404 with no
fallback.

Gate the heuristic on providers.length === 0 so it only fires for genuinely
uncataloged openai-family ids, letting cataloged ids fall through to the
existing single-candidate / ambiguous-candidate resolution paths.

Regression guard: tests/unit/gptoss-provider-inference-5852.test.ts

* fix(cc-compatible): send SSE accept for streamed requests (#5958)

Integrated into release/v3.8.44 — SSE Accept header for streamed cc-compatible requests (thanks @rdself).

* fix: deepseek-web reliability — auto-refresh on 401/403, refresh v2.0.0 client headers, fix token-kind bulk import (#5988)

Integrated into release/v3.8.44 — deepseek-web auto-refresh + v2.0.0 headers + token-kind bulk import (thanks @backryun).

* feat(providers): support Vercel AI Gateway embeddings and images (#5968)

* feat(providers): support Vercel AI Gateway embeddings and images

Extends the existing vercel-ai-gateway (alias vag) provider — currently
chat-only — with embeddings and image generation support, since the
gateway's OpenAI-compatible /v1 API also exposes /embeddings and
/images/generations. Adds entries to EMBEDDING_PROVIDERS
(embeddingRegistry.ts) and IMAGE_PROVIDERS (imageRegistry.ts) modeled
on the existing openai entries.

Out of scope for this PR (tracked as follow-ups): the /v1/credits
usage reader, retry:{429:2} tuning, and claude->reasoning_effort
mapping.

Co-authored-by: Ngô Tấn Tài <tantai@newnol.io.vn>
Inspired-by: https://github.com/decolua/9router/pull/1704

* chore(changelog): restore release entries + add vercel-gateway media bullet

---------

Co-authored-by: Ngô Tấn Tài <tantai@newnol.io.vn>

* feat(cli-tools): add Crush CLI tool to the dashboard (#5970)

* feat(cli-tools): add Crush CLI tool to the dashboard

Add a `crush` entry to the dashboard CLI-Tools catalog and a new
`/api/cli-tools/crush-settings` route (GET/POST/DELETE), cloned from the
`pi` tool's route as a template. OmniRoute already ships a `crush` CLI
command path (bin/cli/commands/setup-crush.mjs) but the dashboard catalog
had no matching entry.

The new route writes the real Crush config shape (providers.omniroute as
an openai-compat provider block) to the same canonical config path
(~/.config/crush/crush.json) that setup-crush.mjs's resolveCrushTarget()
already writes to, so the dashboard and the CLI command agree on one
location. Adds CLI_TOOL_RUNTIME_CONFIG.crush for detection/status, and
bumps EXPECTED_CODE_COUNT (18 -> 19) plus the catalog-count/schema tests
that enumerate the full tool list.

Co-authored-by: dopaemon <polarisdp@gmail.com>
Inspired-by: https://github.com/decolua/9router/pull/1233

* chore(changelog): restore release entries + add crush cli bullet

---------

Co-authored-by: dopaemon <polarisdp@gmail.com>

* feat(dashboard): suggest HuggingFace Hub media models (#5990)

* feat(dashboard): suggest HuggingFace Hub media models

MVP scope:
- imageRegistry.ts: add an image kind entry for the huggingface provider
  (HF Inference API text-to-image), with a dedicated "huggingface-image"
  format since the endpoint returns raw image bytes rather than JSON.
- New handler open-sse/handlers/imageGeneration/providers/huggingface.ts,
  wired into imageGeneration.ts's format dispatch.
- New pure helper module open-sse/services/hfModelSuggestions.ts: maps a
  dashboard media kind to an HF Hub pipeline_tag and sorts/limits raw HF
  Hub search results (unit-tested directly).
- New route GET /api/v1/providers/suggested-models proxies the public HF
  Hub models search API server-side (Zod-validated query, buildErrorBody
  on every error path, no HF token exposed client-side — this project has
  no server-side HF search token config, so it calls unauthenticated).
- UI: ImageExampleCard now fetches suggested HF Hub models for the
  huggingface provider and merges them into the model picker as a
  selectable chip row, alongside the existing static provider models list.
- i18n: adds media.suggestedModels to en.json only.

Co-authored-by: yicone <yicone@gmail.com>
Inspired-by: https://github.com/decolua/9router/pull/1633

* chore(changelog): restore release entries + add hf-hub media suggest bullet

---------

Co-authored-by: yicone <yicone@gmail.com>

* feat(dashboard): collapse and sort provider quota rows by remaining (#5977)

* feat(dashboard): collapse and sort provider quota rows by remaining

Sort the expanded quota list by remaining percentage (highest first)
and collapse it to the first 3 rows by default, with a "Show N more" /
"Show less" toggle when a connection reports more than 3 quotas. This
keeps the most at-risk quotas out of view below a long list of
healthy ones.

Extracts the sort/slice logic into pure helpers
(sortQuotasByRemaining, getVisibleQuotas) exported from
QuotaCardExpanded.tsx and unit-tests them directly.

Co-authored-by: CườngNH <j2.cuong@gmail.com>
Inspired-by: https://github.com/decolua/9router/pull/1919

* chore(changelog): restore release entries + add quota collapse/sort bullet

---------

Co-authored-by: CườngNH <j2.cuong@gmail.com>

* feat(providers): refresh The Old LLM (Free) model catalog (#5181)

* feat(dashboard): add tool-source diagnostics settings toggle (#5978)

* feat(dashboard): add tool-source diagnostics settings toggle

Adds a Settings > Advanced card (cloned from DebugModeCard) that lets
operators flip the existing `logToolSources` flag from the UI instead
of editing the DB row directly. The backend gate (chatCore.ts) and DB
default were already present but had no toggle. Also adds
`logToolSources` to the /api/settings Zod PATCH schema (it is `.strict()`,
so the key was previously rejected) and en-only i18n strings.

Co-authored-by: DuyPrX <93126969+DuyPrX@users.noreply.github.com>
Inspired-by: https://github.com/decolua/9router/pull/1825

* chore(changelog): restore release entries + add tool-source toggle bullet

---------

Co-authored-by: DuyPrX <93126969+DuyPrX@users.noreply.github.com>

* feat(oauth): import Codex connection from a raw ChatGPT access token (#5995)

* feat(oauth): import Codex connection from a raw ChatGPT access token

OmniRoute's only Codex import path (/api/oauth/codex/import) required both
access_token and refresh_token, leaving no import path for a user who only
has a bare ChatGPT website access token (no refresh token).

- src/lib/db/providers.ts: createProviderConnection gains an explicit
  authType "access_token" branch — intentionally never deduped (no stable
  long-lived identity to match on) — and derives the connection name from
  email/name the same way "oauth" does.
- src/lib/oauth/services/codexImport.ts: export extractCodexAccountInfo so
  the new import path reuses the existing JWT decode instead of duplicating
  one.
- New route POST /api/oauth/codex/import-token (Zod-validated body
  { accessToken, name? }); errors routed through buildErrorBody /
  sanitizeErrorMessage. The executor's refreshCredentials() already
  degrades safely to null when there is no refresh token, forcing re-auth
  on expiry instead of a refresh exchange.
- OAuthModal.tsx: the callback-URL manual-paste path for codex now detects
  an eyJ-prefixed pasted token and posts it to the new endpoint, mirroring
  the existing grok-cli raw-token paste pattern.

Co-authored-by: ryanngit <74137224+ryanngit@users.noreply.github.com>
Inspired-by: https://github.com/decolua/9router/pull/1290

* chore(changelog): restore release entries + add codex token-import bullet

---------

Co-authored-by: ryanngit <74137224+ryanngit@users.noreply.github.com>

* fix(resilience): parse Retry-After from 429 JSON body for cooldown (#5974)

Integrated into release/v3.8.44 — parse Retry-After from 429 JSON body for cooldown (incl. #6013 retry-after-json extraction by @KooshaPari).

* fix(embeddings): forward connection-level proxy to embedding requests (#5975)

Integrated into release/v3.8.44 — forward connection-level proxy to embedding requests.

* fix(api): guard shared API client against non-JSON error responses (#5973)

Integrated into release/v3.8.44 — guard shared API client against non-JSON error responses.

* feat(dashboard): surface Codex banked reset credits per account (#5199)

* feat(providers): add NVIDIA NIM image generation (#5971)

* feat(providers): add NVIDIA NIM image generation

NVIDIA already exists as a chat provider (integrate.api.nvidia.com,
OpenAI-compatible) but image generation is served on a different host
(ai.api.nvidia.com/v1/genai/<model>) with a native NIM body shape, so it
gets a dedicated `nvidia-nim` image format and handler rather than reusing
the OpenAI image path.

Adds the 4 FLUX models (flux.1-dev, flux.1-schnell, flux.1-kontext-dev,
flux.2-klein-4b) to IMAGE_PROVIDERS, plus handleNvidiaNimImageGeneration()
which shapes the per-model NIM request body (flux.1-dev's mode/cfg_scale
and 768-1344px/64px-increment dimension validation, flux.1-kontext-dev's
required input image + aspect_ratio, schnell/klein's optional array-form
edit image) and normalizes the NIM response (artifacts[]/images[]/data[]/
single-value shapes) into the OpenAI `{created, data}` shape.

Co-authored-by: eng2007 <aleksey.semenov@gmail.com>
Inspired-by: https://github.com/decolua/9router/pull/1195

* chore(changelog): restore release entries + add nvidia-nim image bullet

---------

Co-authored-by: eng2007 <aleksey.semenov@gmail.com>

* feat(providers): add Augment (Auggie CLI) local provider (#5972)

* feat(providers): add Augment (Auggie CLI) local provider

Adds a new local, no-auth provider that spawns the user's local `auggie`
CLI (`auggie --print --quiet --model <m> --`) and pipes a flattened prompt
via stdin, wrapping stdout as an OpenAI-compatible SSE stream or a single
chat.completion JSON body depending on the request's `stream` flag.

Auth is delegated entirely to `auggie login` outside OmniRoute — the
connection is registered `noAuth: true` and `refreshCredentials()` is a
no-op, matching the existing `NOAUTH_PROVIDERS` credential-less flow
(synthetic connection, no DB row required). An optional connection row is
still admitted via `FREE_APIKEY_PROVIDER_IDS` for display/priority
tracking, consistent with `opencode`. The dashboard "Test Connection"
flow spawns `auggie --version` to confirm the CLI is installed and
runnable, since there is no API key to validate upstream.

Security hardening (spawn is an untrusted-input sink):
- Command injection: spawn no longer passes `shell: true` on Windows. The
  binary is resolved to a concrete path/name and argv is handed straight to
  the OS loader, so no cmd.exe metacharacter interpretation is possible.
- Argument injection (flag smuggling): `model` is validated against the
  registry allowlist (`auggieProvider.models`) before any spawn — a model
  that is unknown or starts with "-" is rejected with a sanitized error and
  the subprocess is never started. A trailing `--` marks end-of-options in
  the argv as belt-and-suspenders.

Co-authored-by: chamdanilukman <16629923+chamdanilukman@users.noreply.github.com>
Inspired-by: https://github.com/decolua/9router/pull/1200

* test(golden): regenerate translate-path for auggie provider

---------

Co-authored-by: chamdanilukman <16629923+chamdanilukman@users.noreply.github.com>

* feat(providers): add ModelScope OpenAI-compatible provider (#5965)

* feat(providers): add ModelScope OpenAI-compatible provider

Ports ModelScope (Alibaba 魔搭) as a new API-key, OpenAI-compatible
provider — upstream 9router PR #1764. The upstream PR hardcoded
`https://api-inference.modelscope.ai/...` (`.ai` TLD); verified against
ModelScope's own API-Inference docs and third-party integration guides
that the real production domain is `api-inference.modelscope.cn`
(`.cn` TLD) and shipped that instead. Also drops the PR's static
5-model snapshot in favor of `passthroughModels: true` with an empty
seed list + `modelsUrl`, since ModelScope's open-model catalog moves
fast.

Updates the providers-constants-split characterization test's hardcoded
APIKEY_PROVIDERS count (159 -> 160) to match the new entry.

Co-authored-by: Umar Javed <114807145+tn5052@users.noreply.github.com>
Inspired-by: https://github.com/decolua/9router/pull/1764

* chore(changelog): restore release entries + add modelscope bullet

* test(golden): regenerate translate-path for modelscope provider

---------

Co-authored-by: Umar Javed <114807145+tn5052@users.noreply.github.com>

* feat(providers): add Qiniu OpenAI-compatible provider (#5966)

* feat(providers): add Qiniu OpenAI-compatible provider

Wires Qiniu (七牛云) AI inference gateway as a BYOK API-key provider.
Qiniu proxies many upstream models (DeepSeek V3/V4, Claude, Kimi and
more) behind a single key, so it ships with an empty static seed and
relies on passthroughModels + the live /v1/models catalog instead of a
single stale hardcoded model id.

- metadata: src/shared/constants/providers/apikey/gateways.ts
- registry entry: open-sse/config/providers/registry/qiniu/index.ts
  (format openai, executor default, bearer auth, baseUrl
  https://api.qnaigc.com/v1/chat/completions, modelsUrl
  https://api.qnaigc.com/v1/models)
- added to NAMED_OPENAI_STYLE_PROVIDERS so model import serves the live
  catalog and falls back to the (empty) local catalog on error, same
  pattern as the existing dgrid/zenmux/orcarouter gateways
- tests: tests/unit/qiniu-provider.test.ts (metadata, registry
  resolution, passthrough validation, live /v1/models fetch + fallback)

Co-authored-by: JiangZhuo <jiangzhuo@qiniu.com>
Inspired-by: https://github.com/decolua/9router/pull/911

* chore(changelog): restore release entries + add qiniu bullet

* test(golden): regenerate translate-path for qiniu provider

* test(providers): bump APIKEY count 160→161 for qiniu

---------

Co-authored-by: JiangZhuo <jiangzhuo@qiniu.com>

* feat(providers): add b.ai OpenAI-compatible provider (#5969)

* feat(providers): add b.ai OpenAI-compatible provider

Adds bai as a new OpenAI-compatible BYOK provider, distinct from the
existing thebai/theb.ai provider, using passthrough model discovery
(no hardcoded model list, live catalog served from api.b.ai/v1/models).

Co-authored-by: Delynn Assistant <zhen@dkzhen.org>
Inspired-by: https://github.com/decolua/9router/pull/963

* test(golden): regenerate translate-path for b.ai provider

* test(providers): bump APIKEY count 161→162 for b.ai

---------

Co-authored-by: Delynn Assistant <zhen@dkzhen.org>

* feat(providers): add Nube.sh OpenAI-compatible provider (#5936)

* feat(providers): add Nube.sh OpenAI-compatible provider

Nube.sh is a live BYOK OpenAI-compatible gateway (LiteLLM proxy) at
https://ai.nube.sh/api/v1, Bearer/API-key auth. Registered as an apikey
inference-host with an OpenAI-format, default-executor registry entry.

Its live model catalog is only reachable with a valid key
(/api/v1/models returns 401 unauthenticated), so no model IDs are
hardcoded — the entry uses passthroughModels + modelsUrl for live
enumeration instead of shipping unverifiable IDs.

Co-authored-by: whale9820 <87256750+whale9820@users.noreply.github.com>
Inspired-by: https://github.com/decolua/9router/pull/2294

* test(golden): regenerate translate-path for nube provider

* test(providers): bump APIKEY count 162→163 for nube

---------

Co-authored-by: whale9820 <87256750+whale9820@users.noreply.github.com>

* feat(providers): add Charm Hyper OpenAI-compatible provider (#5961)

* feat(providers): add Charm Hyper OpenAI-compatible provider

Registers Charm Hyper (hyper.charm.land) as a new API-key gateway
provider: OpenAI-compatible chat completions format, bearer auth,
free tier (100 monthly Hypercredits). Models are resolved via
passthrough (modelsUrl + live /v1/models import) instead of a
hardcoded upstream model list, since the specific model catalog is
not publicly documented.

Co-authored-by: whale <admin@dyntech.cc>
Inspired-by: https://github.com/decolua/9router/pull/2006

* test(golden): regenerate translate-path for charm-hyper provider

* test(providers): bump APIKEY count 163→164 for charm-hyper

---------

Co-authored-by: whale <admin@dyntech.cc>

* feat(providers): add SumoPod and X5Lab OpenAI-compatible providers (#5963)

* feat(providers): add SumoPod and X5Lab OpenAI-compatible providers

Both are OpenAI-compatible BYOK aggregator gateways, wired via the
default executor with bearer API-key auth. Neither ships a hardcoded
model list — both use passthroughModels with an empty seed list and a
live /v1/models fetcher, so the catalog always reflects what each
gateway actually serves instead of speculative model IDs.

- SumoPod: https://ai.sumopod.com/v1/chat/completions (sk- keys)
- X5Lab: https://api.x5lab.dev/v1/chat/completions (x5- keys)

Regression guard: tests/unit/sumopod-x5lab-provider.test.ts.

Co-authored-by: Rigel Ramadhani Waloni <rigel8911@gmail.com>
Inspired-by: https://github.com/decolua/9router/pull/1288

* chore(changelog): restore release entries + add sumopod/x5lab bullet

* test(golden): regenerate translate-path for sumopod + x5lab providers

* test(providers): bump APIKEY count 164→166 for sumopod + x5lab

---------

Co-authored-by: Rigel Ramadhani Waloni <rigel8911@gmail.com>

* feat(server): support reverse-proxy basePath deployment (#5992)

* feat(server): support reverse-proxy basePath deployment

Adds OMNIROUTE_BASE_PATH (opt-in, empty by default) to next.config.mjs
using Next.js's native basePath support so a deployment behind a
reverse-proxy subpath (e.g. https://host/omniroute/) works without
manual header stripping. Next.js strips the configured prefix from
nextUrl.pathname before route classification, so classifyRoute() and
isLocalOnlyPath() keep matching un-prefixed paths.

The two hardcoded auth redirect targets in
src/server/authz/pipeline.ts (root "/" -> "/dashboard" and
unauthenticated dashboard -> "/login") now prefix with
request.nextUrl.basePath so they stay inside the deployed subpath.
Default empty basePath is a no-op for existing root-path deployments.

Co-authored-by: zocomputer <help@zocomputer.com>
Inspired-by: https://github.com/decolua/9router/pull/1810

* docs(env): document OMNIROUTE_BASE_PATH in .env.example + ENVIRONMENT.md; restore changelog

* docs(env): document AUGGIE_BIN + CLI_AUGGIE_BIN (base-red from #5972 auggie)

---------

Co-authored-by: zocomputer <help@zocomputer.com>

* refactor(combo): extract buildTargetTimeoutRunner from handleComboChat (#6036)

Bloco J (hot-path decomposition), Task 1. Extract the per-target-timeout dispatch wrapper
(handleComboChat's handleSingleModelWithTimeout closure) verbatim into the leaf
combo/targetTimeoutRunner.ts as a factory buildTargetTimeoutRunner({handleSingleModel,
comboTargetTimeoutMs, log}). The per-model abort still comes from target.modelAbortSignal,
so the outer request signal is intentionally not a dependency. Host call-sites unchanged.

combo.ts shrinks ~60 LOC; leaf is 91 LOC (<800). Body byte-identical (verbatim), no cycle.
This is the first slice toward extracting the shared attempt-loop/success/error handlers
(Tasks 3-4) that de-duplicate handleComboChat and handleRoundRobinCombo. Adds a dedicated
test (5) so the failover path can be mutated independently. Consumer tests stay green
(combo-strategy-fallbacks 24, combo-499-abort 5, empty-content-failover 3, body-400-stop 1,
priority-quota-exhaustion 2, rr-streaming-lock 1, rr-session-stickiness 2).

Plan: _tasks/superpowers/plans/2026-07-03-blocoJ-combo-hotpath-decomposition.md

* feat(cli-tools): add CodeWhale CLI tool (#5996)

CodeWhale (https://github.com/Hmbown/CodeWhale) is the actively-maintained
successor to DeepSeek TUI — same author, renamed project. Added as a dual
entry alongside the existing "deepseek-tui" catalog entry (rather than a
hard rename) so users who still run the old DeepSeek TUI binary keep a
working dashboard card, while new users are steered to "codewhale".

New /api/cli-tools/codewhale-settings route writes the primary
~/.codewhale/config.toml and keeps an existing legacy
~/.deepseek/config.toml in sync (read fallback + best-effort write sync),
mirroring deepseek-tui-settings/route.ts. CLI_TOOLS and cliRuntime catalogs
updated; catalog cardinality tests/constants bumped accordingly (18→19
visible code tools, 28→29 total).


Inspired-by: https://github.com/decolua/9router/pull/1761

Co-authored-by: aristorinjuang <aristorinjuang@gmail.com>

* feat(i18n): auto-detect browser language on first visit (#5979)

* feat(i18n): auto-detect browser language on first visit

Adds a pure detectBrowserLocale() matcher (exact match, zh-HK/zh-MO
folded to zh-TW, language-prefix match, else null) plus a client-only
LocaleAutoDetect component mounted once in the root layout. On first
visit (no locale cookie set), it reads navigator.languages, computes a
match against the supported locales, and persists it via the same
cookie/localStorage writer LanguageSelector already used for manual
selection (now extracted to shared/lib/persistLocale.ts) before
refreshing the router.

Co-authored-by: anmingwei <anmingwei@dobest.com>
Inspired-by: https://github.com/decolua/9router/pull/1324

* chore(changelog): restore release entries + add browser-lang-detect bullet

---------

Co-authored-by: anmingwei <anmingwei@dobest.com>

* fix(dashboard): render Update-now API errors as text, not the raw envelope object (#5991) (#6028)

Integrated into release/v3.8.44 — fix(dashboard) render Update-now API errors as text, not the raw envelope object (#5991).

Merged with --admin: the fix is a one-line frontend change funneling the error body through the already-tested extractApiErrorMessage() helper, guarded by tests/unit/ui/home-update-error-render-5991.test.ts (3/3 pass, 3/3 fail on pre-fix source). The release branch is under a heavy parallel-merge storm (tip advanced ~6× mid-CI), so the branch is synced to the latest tip and landed atomically to avoid perpetual CONFLICTING; unit-shard reds seen earlier were pre-existing base-reds/flakes unrelated to this source-scan-only change.

* feat(api): expose provider plugin manifest (#6001)

* feat(api): expose provider plugin manifest

* test(translator): split responses chat request coverage

* test(mutation): register provider coverage tests

* feat(api): expose provider plugin manifest

* fix(ci): fail closed for prerelease latest promotion

* chore(ci): reconcile provider manifest complexity gate

* feat(api): expose provider plugin manifest

* test(translator): split responses chat request coverage

* test(mutation): register provider coverage tests

* fix(ci): fail closed for prerelease latest promotion

* chore: rebase onto release tip; drop out-of-scope translator test split + promote-script tweak

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

* docs(changelog): add provider plugin manifest entry

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

* chore(stryker): register account-fallback-retry-after-json test (base-red)

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

---------

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

* feat(providers): add CN sign-up geo-restriction notices for SenseNova & StepFun (#5462)

* feat(sidecar): advertise provider manifest url (#6007)

* feat(sidecar): advertise provider manifest url via X-OmniRoute-Provider-Manifest-Url header

Re-cut onto release tip: manifest-url feature only (dropped stale-base noise).

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

* docs(changelog): add sidecar manifest-url entry

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

* chore(complexity): rebaseline 2009->2015 (inherited release-tip drift; feature adds 0)

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

---------

Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* feat(autoCombo): latency/speed-optimized routing mode + omniroute_pick_fastest_model MCP tool (#6011)

* feat(autoCombo): latency/speed-optimized routing mode + omniroute_pick_fastest_model MCP tool

* test(translator): split responses chat request coverage

* refactor(mcp): extract fastest-model tool modules

* fix(i18n): cover provider icon and cors labels

* test(mutation): register latency coverage files

* test(ci): collect executor unit tests

* refactor(ci): reduce latency path complexity

* fix(mcp): include models catalog module

* feat(autoCombo): latency/speed-optimized routing + omniroute_pick_fastest_model MCP tool

Re-cut onto release tip: keep speed-routing + MCP tool + supporting catalog split;
drop out-of-scope translator split, en.json/ci.yml/package.json orphans, and unrelated
proxyFetch/responsesStreamHelpers/tokenLimitCounter refactors.

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

---------

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

* docs(changelog): restore #5181/#5199/#5462 feature bullets eaten by merge

* feat(usage): on-demand period-scoped usage-data reset (re-cut onto release tip) (#5831)

* chore(quality): rebaseline eslintWarnings 4199->4256 + cognitiveComplexity 860->861 (v3.8.44 cycle drift)

Inherited v3.8.44 cycle drift measured on release tip 72ee80649 by the release-green
pre-flight during the /review-prs fix-batch round. The Quality Ratchet does NOT run on
PR->release fast-gates, so eslint warnings + cognitive complexity accrue unmeasured
across the cycle. Cyclomatic complexity is already green (2012 < baseline 2015) and
needs no bump. Each value carries a dated justification note; no production code touched.

* feat(claude-code): opt-in auto-permission classifier compat mode (re-cut onto release tip) (#5810)

* feat(providers): client-identity header profiles for compatible nodes (re-cut) + forbid cookie in custom headers (#5812)

* docs(openapi): document 9 newly-added routes to restore coverage ratchet (v3.8.44)

Documents the routes added this cycle that dropped openapiCoverage 36.9%->36.2%
below the ratchet baseline: 2 public v1 endpoints (/v1/ocr Mistral-OCR-compatible,
/v1/audio/translations Whisper-compatible) with full request/response specs, plus 7
dashboard/CLI-local routes marked x-internal:true (suggested-models, provider-plugin-
manifest, keys/{id}/devices, settings/purge-usage-history, oauth/codex/import-token,
cli-tools crush-settings + codewhale-settings). Coverage 36.2%->37.8% (207/547),
above baseline 36.9. check:openapi-routes/security-tiers/fabricated-docs all pass.

* refactor(sse): decompose handleComboChat auto-strategy region (Block J Task 2 — parseAutoConfig + resolveAutoStrategyOrder) (#6049)

* refactor(sse): extract pure parseAutoConfig leaf from handleComboChat

Block J Task 2 (safe slice): the auto-strategy config-resolution block in
handleComboChat is a pure function of (combo, eligibleTargets) with no side
effects, no early returns and no mutation. Extract it verbatim into
open-sse/services/combo/autoConfig.ts::parseAutoConfig so the god-function
shrinks and the derivation is independently unit-testable.

Behavior is byte-identical (verbatim-audited); combo.ts 3309->3280 LOC.
Adds tests/unit/combo-auto-config-split.test.ts (5 cases) pinning the
strategy-precedence, candidate-pool, weights and fallback derivations.

* refactor(sse): extract resolveAutoStrategyOrder leaf from handleComboChat

Block J Task 2 (coupled slice): the ~215-line `if (strategy === "auto")`
branch of handleComboChat is extracted into
open-sse/services/combo/resolveAutoStrategy.ts::resolveAutoStrategyOrder.

The branch is a control-flow region (mutates orderedTargets +
autoUsedExplicitRouter, early-returns 429, side-effect _registerExecutionCandidates),
so it is not a pure byte-identical move: the two `return unavailableResponse(...)`
exits become `{ earlyResponse }` and the mutated locals are returned instead of
closed over. Every other logic line is verbatim (semantic diff = only those
wrappers + the deeper getLKGP import path). `buildAutoCandidates` lives in
combo.ts, so it is injected via deps to keep the leaf acyclic (same DI pattern as
buildTargetTimeoutRunner) — which also makes the branch independently testable.

combo.ts 3280->3065 LOC. typecheck:core + check:cycles clean; dead host imports
removed. 60/60 consumer tests (router-strategies / auto-combo-engine /
combo-strategy-fallbacks / scoring-clamp / candidate-expansion / hidden-models)
cover the routable path end-to-end; new tests/unit/combo-resolve-auto-strategy-split.test.ts
pins the DI contract + the early-429 and default-ordering exits.

* test(sse): point quota-bypass source scan at resolveAutoStrategy leaf

The 'auto combo disables hard provider quota cutoffs when relay requests bypass'
source scan asserted combo.ts contains the bypass logic
(relayOptions?.bypassProviderQuotaPolicy === true + quotaPreflight enabled:false).
That block was extracted verbatim into combo/resolveAutoStrategy.ts (Block J
Task 2), so the scan now reads the leaf. Behavior unchanged.

* fix(ci): release-green base-reds — #5695 test regex + file-size rebaseline (#6093)

- tests/unit/ui/quick-start-api-keys-link-5695.test.ts: tolerate Prettier
  splitting <Link href=...> across lines (\s+) so the step1Desc regex matches
  the multi-line /dashboard/api-manager Link instead of skipping to step2's
  single-line /dashboard/providers Link. Code is correct; the test was brittle.
- config/quality/file-size-baseline.json: rebaseline 5 files that grew via
  already-merged PRs on the release tip (ApiManagerPageClient 3017->3058,
  OAuthModal 969->989, cliRuntime 1090->1100, webProvidersA 805->809,
  deepseek-web.test 1081->1092). Dated note added; shrink tracked in #3501.

* fix(translator): wrap Kiro system prompt in <system-reminder> (port from 9router#2306) (#6053)

Kiro/CodeWhisperer has no system role, so system messages were normalized to a
user turn with no wrapper — the full Claude Code system prompt then appeared as
raw user text, polluting the model context. Wrap system-origin content in
<system-reminder> tags before merging it into the Kiro user message. Real user
turns are unaffected. Existing history-merge tests aligned to the wrapped value.

Reported-by: VitzS7 (https://github.com/decolua/9router/issues/2306)

* fix(translator): strip multipleOf from antigravity/gemini tool schemas (port from 9router#2309) (#6052)

`multipleOf` is not part of the Gemini/antigravity OpenAPI 3.0 schema subset, so
leaving it in function_declaration parameters triggered a hard upstream 400
("Unknown name multipleOf"). Add it to GEMINI_UNSUPPORTED_SCHEMA_KEYS so it is
stripped at every schema level; minimum/maximum stay (Gemini accepts them).

Reported-by: abil0321 (https://github.com/decolua/9router/issues/2309)

* fix(kimi-web, qwen-web): align model catalog with live /models + map scenario per model (#5915)

* fix(kimi-web): align catalog with live models

Update the kimi-web catalog and request scenario selection to match
www.kimi.com's live GetAvailableModels response.

* fix(qwen-web): stop aliasing qwen3-coder-plus

Keep qwen3-coder-plus as its own model because it is present in the
live Qwen web models catalog.

* feat(minimax): extract M3 <think> to reasoning_content on OpenAI-format tiers (#6050)

MiniMax M3 is registered with format:"openai" on 8 provider tiers (trae,
huggingchat, bazaarlink, ollama-cloud, opencode, cline, opencode-zen,
codebuddy-cn), where its raw <think>...</think> tags leaked directly into
`content` instead of surfacing as a separate `reasoning_content` field.

OmniRoute already has the extraction primitive
(extractThinkingFromContent in responseSanitizer/reasoning.ts); it was just
gated to deepseek-r1/r1-distill/qwq. Extend the allowlist
(isTextualReasoningTagNativeRoute) with a minimax-m3-only pattern, excluding
the two direct minimax/minimax-cn tiers, which stay on Anthropic's Messages
format (targetFormat: "claude") and already surface reasoning natively.


Inspired-by: https://github.com/decolua/9router/pull/2231

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: zmf963 <19422469+zmf963@users.noreply.github.com>

* fix: unwrap Cline response envelope (#6046)

Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com>

* refactor(sse): extract applyStrategyOrdering leaf from handleComboChat (Block J Task 3) (#6063)

* refactor(sse): extract applyStrategyOrdering leaf from handleComboChat

Block J Task 3: the ~177-line else-if chain covering every non-auto combo
strategy (lkgp / strict-random / random / fill-first / p2c / least-used /
cost-optimized / reset-aware / reset-window / context-optimized / headroom /
quota-share) is extracted into
open-sse/services/combo/applyStrategyOrdering.ts::applyStrategyOrdering.

Each branch only reorders orderedTargets (no early returns, no other mutable
state), so the extraction is a clean verbatim move returning the reordered list;
the host replaces the chain with `else { orderedTargets = await
applyStrategyOrdering(strategy, orderedTargets, deps); }`. Semantic diff vs the
original chain = only the leading `if` (was `} else if`), the trailing return
and the deeper getLKGP import path — no logic line changed. None of the 13 strategy
helpers live in combo.ts, so no DI/cycle (unlike the auto branch).

combo.ts 3065->2883 LOC (3309->2883 across Task 2+3). typecheck:core + check:cycles
clean; 9 dead host imports removed (targetSorters block emptied). 47/47 consumer
tests (router-strategies / combo-strategy-fallbacks / rr-session-stickiness /
tag-routing) cover the DB-backed branches end-to-end; new
tests/unit/combo-apply-strategy-ordering-split.test.ts pins random / fill-first /
unknown exits.

* test(sse): point #2359 modelStr-guard scans at applyStrategyOrdering leaf

The LKGP fallback + non-auto strategy ordering (the two target.modelStr string-
method call sites) were extracted verbatim from combo.ts into the
applyStrategyOrdering leaf (Block J Task 3). The #2359 source scans now read the
leaf that owns those usages; the guard and the no-unguarded-usage assertions are
unchanged in intent.

* chore(ci): scan combo strategy leaves in check:known-symbols

Block J decomposed the combo dispatch: the `strategy === "..."` branches for
the 12 non-auto strategies moved to combo/applyStrategyOrdering.ts and the auto
branch to combo/resolveAutoStrategy.ts. The known-symbols gate previously scanned
only combo.ts, so it would report those strategies as canonicalNotHandled. Scan
all three dispatch files. Verified: 18/18 canonical strategies via dispatch.

* fix(combo): fallback to sibling model on 500 for per-model-quota providers (#5976)

* fix(combo): fallback to sibling model on 500 for per-model-quota providers

Two issues prevented combo fallback when gemini/gemma-4-31b-it returned 500:

1. targetExhaustion: connection-level exhaustion marked the shared gemini
   connection as exhausted, skipping the sibling model (gemma-4-26b-a4b-it).
   Skip markConnectionLevelExhaustion for per-model-quota providers (gemini,
   github, passthrough, compatible) since a model-level 500 does not mean
   the connection is bad.

2. combo retry loop: the auth layer records a model lockout on 500, but the
   retry loop did not check isModelLocked before retrying — it retried the
   same locked model instead of falling back. Add isModelLocked guard before
   the transient-retry decision.

* fix tests timeout

* fix: clear quota fallback CI gates

* quality-gate: extract test SSE stream helpers

* drop scope creep

* fix(combo): retry sibling models only on 500 errors

* fix(combo): reconcile onto release/v3.8.44 — keep targetExhaustion 500 fix, drop slow integration test

Reconciled by maintainer onto the current release tip:
- kept the core fix (targetExhaustion.ts model-500 guard for per-model-quota
  providers + the isModelLocked retry early-return in combo.ts) and its unit test
- dropped tests/integration/combo-concurrent-failure-recovery.test.ts +
  _sseTestHelpers.ts: they use Math.random()-based delays and 30s timeouts, run
  >3min and are flake-prone in the test:integration CI job; the unit test
  (tests/unit/combo/combo-target-exhaustion.test.ts, 21 cases) fully covers the fix
- CHANGELOG entry added

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

---------

Co-authored-by: Koosha Pari <kooshapari@gmail.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@outlook.com>
Co-authored-by: hartmark <hartmark@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* feat(xai): surface Grok usage on quota dashboard via local usageHistory aggregation (#5806)

xAI has no public per-account quota API (the billing console requires a
session cookie, not an API key). Add getXaiUsage(connectionId), mirroring
the existing Xiaomi MiMo self-track pattern: sum tokens routed to the
connection from usage_history via getMonthlyProviderTokensForConnection
and surface them as a cumulative, uncapped quota (unlimited: true,
remaining: 100 — xAI has no fixed monthly cap). Register 'xai' in
USAGE_FETCHER_PROVIDERS and wire a switch case in getUsageForProvider.


Inspired-by: https://github.com/decolua/9router/pull/2150

Co-authored-by: ron <devestacion@gmail.com>

* feat(services): add Mux managed embedded service (#6034)

Adds Mux (coder/mux — local agent-orchestration daemon) as a fourth-tier
embedded service built on the existing ServiceSupervisor framework, the
same shape as 9Router and CLIProxyAPI:

- Installer (src/lib/services/installers/mux.ts): npm install/update via
  runNpm (array args + env-based prefix, no shell interpolation), modeled
  on ninerouter.ts. Mux ships an npm package (`mux`) with a documented
  headless `mux server --host <host> --port <port>` mode, so no
  git-clone+build path was needed.
- Registered in bootstrap.ts (SERVICES[] + buildSpawnArgsFactory).
- DB seed migration 113 (version_manager row, not_installed/auto_start=0).
- 7 API endpoints under /api/services/mux/ (install/start/stop/restart/
  update/status/auto-start) plus the shared [name]/logs SSE endpoint,
  mirroring the cliproxy route shape and delegating errors through
  createErrorResponse().
- Dashboard tab (MuxServiceTab) reusing ServiceStatusCard,
  ServiceLifecycleButtons, AutoStartToggle, ServiceLogsPanel.
- Docs: EMBEDDED-SERVICES.md (service table, architecture diagram, API
  reference, key-injection section), openapi.yaml, ENVIRONMENT.md,
  .env.example.

Security:
- Every /api/services/mux/* route is covered by the existing
  LOCAL_ONLY_API_PREFIXES "/api/services/" prefix (Hard Rule #17);
  added an explicit isLocalOnlyPath regression test for all 8 routes.
- Mux binds to 127.0.0.1 explicitly (never 0.0.0.0) as defense-in-depth,
  since it orchestrates AI agents that can execute host commands.
- The bearer token is generated the same way as 9Router's key
  (getOrCreateApiKey) and injected via MUX_SERVER_AUTH_TOKEN (mux's
  documented env form) rather than a CLI flag, so it never appears in
  `ps`/process listings.
- No shell interpolation anywhere in the installer (Hard Rule #13): all
  npm/spawn args are static arrays; the install prefix and auth token
  travel via the env option.


Inspired-by: https://github.com/decolua/9router/pull/1802

Co-authored-by: Ansh7473 <Ansh7473@users.noreply.github.com>

* feat(services): promote Bifrost to embedded/supervised service (#5670) (#5817)

Promotes Bifrost (@maximhq/bifrost — Go AI-gateway) from an env-only relay
sidecar to a first-class embedded/supervised service, matching the existing
cliproxy/9router model. Implements item #2 of #5670; the broader RouterBackend
contract (items #1, #3-#5) stays out of scope.

- Installer (npm-style, ninerouter model): install/update/getInstalledVersion/
  getLatestVersion (1h cache)/resolveSpawnArgs (Go single-dash flags, pinned
  BIFROST_TRANSPORT_VERSION), needsApiKey=false
- Bootstrap SERVICES entry (healthPath /v1/models) + spawn-args factory branch
- Migration 113 seeds the version_manager row (not_installed, port 8080,
  auto_update=1, provider_expose=1)
- 7 lifecycle API routes under /api/services/bifrost/ (verbatim from cliproxy,
  errors sanitized) — loopback-only via existing LOCAL_ONLY_API_PREFIXES
- Shared [name]/logs branch for bifrost
- Dashboard tab + registration in the services page shell
- Relay auto-wiring: getBifrostRoutingConfig defaults BIFROST_BASE_URL to the
  supervised port when the instance is running; explicit env still wins; the
  env-only relay path (/v1/relay/.../bifrost) stays unchanged (compat layer)
- Docs (EMBEDDED-SERVICES, openapi) + unit tests (installer/route-guard/routing,
  19 tests) + RUN_SERVICES_INT-gated integration lifecycle

Note: the actual Go-binary install/start/health path requires a documented VPS
live-test before merge (Hard Rule #18 / spec section 7); the gated integration
harness is the vehicle for that run.

* fix(ci): document BIFROST_PORT to clear env-doc-sync base-red

The Bifrost embedded-service merge referenced process.env.BIFROST_PORT
(src/lib/services/bootstrap.ts, default 8080) without adding it to
.env.example / ENVIRONMENT.md, so check:env-doc-sync failed on the release
tip and reddened Fast Quality Gates for every open PR->release. Docs-only.

* fix(providers): emulate OpenAI tool_calls in GitLab Duo executor (#6051) (#6111)

Co-authored-by: felssxs <felssxs@users.noreply.github.com>

* fix(providers): strip orphan tool_result on Antigravity MITM path (#6026) (#6115)

* fix(registry): update grok-cli model context lengths (#5913)

grok-build 128k→256k, grok-composer-2.5-fast 128k→200k to match actual Grok CLI /context capacities so context-aware routing stops filtering these models out. Registry-only.

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

* feat(proxy): batch delete, auto-test, health scheduler + transitive alias fix (#5918)

Proxy-registry batch management (batch-delete, auto-test, background health scheduler) + fix resolveProviderAlias to follow the alias chain transitively (oc -> opencode -> opencode-zen). Probe target now operator-configurable via PROXY_HEALTH_TEST_URL. Scope-creep files from the original branch dropped.

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

* feat(minimax): extract M3 reasoning_content on OpenAI-format tiers (#6073)

MiniMax M3 leaks raw <think>...</think> into content on 8 OpenAI-format provider tiers; extract it into reasoning_content, leaving the direct minimax/minimax-cn (Claude-format) tiers untouched. Replacement for the stale #5804 branch.

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

* fix(ci): harden provider translate-path golden across CI runners (#6076)

Normalize OS/arch-derived request headers (X-Stainless-Os/Arch, (OS;arch) UAs, and Antigravity's os.platform()-derived platform substring) in the golden so the test is runner-independent. Fixes the Mac-literal Antigravity UA that would have failed on Linux CI. Supersedes stale #6002.

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

* test(embeddings): pin seeded connection to direct egress in route-edge-coverage (#5975 collateral)

#5975 made the embeddings service honor the connection-level proxy. The pre-existing
route-edge-coverage embeddings edge-case tests seed an openai connection while the
settings-proxy suite has left a provider-level proxy (provider.local:8080) in the shared
DATA_DIR that resetStorage() does not clear — inert before #5975, but now the leaked
proxy fast-fails the embedding upstream with PROXY_UNREACHABLE.

These tests do not exercise proxying, so seedOpenAIConnection now pins the connection to
proxyEnabled:false, making resolveProxyForConnection return a direct egress regardless of
leaked global proxyConfig. No assertions weakened; 16/16 in the file pass. Regression
surfaced by the concurrency=1 full-suite run; passes on #5975's parent, red after it.

* fix(config): externalize ws for copilot-m365-web executor (#6130, closes #6062)

Re-lands the #6098 ws-externalization fix onto release/v3.8.44 (it had merged to main by mistake and was reverted). Externalize ws/bufferutil/utf-8-validate so the copilot-m365-web WebSocket masking path works at runtime.

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

* fix(providers): update Perplexity Web models (#6106)

Refresh the Perplexity Web model catalog + mode/model_preference mappings to the current live set. Regression guard: perplexity-web.test.ts.

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

* fix(providers): update Gemini Web cookies and models (#6095)

Refresh Gemini Web cookie handling + model catalog. Regression guard: gemini-web.test.ts.

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

* fix(models): normalize GLM-5.2 provider context (#6091)

Hosted GLM-5.2 provider aliases now respect their declared context caps instead of inheriting the native 1M; native/bare + verified OpenCode/ZenMux routes stay at 1M. Regression guards added.

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

* fix(combo): prefer known context capacity over unknown (#6088)

When a combo filters a target for exceeding a known context limit, prefer remaining known-compatible targets over unknown-metadata ones. Regression guard: combo-context-window-filter.test.ts.

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

* fix: keep Claude tool results adjacent (#6035)

Reattach OpenAI tool_result adjacent to tool_use before Claude send (#6026). Integrated into release/v3.8.44.

* fix(security): persist IP filter config + enforce it in the authz pipeline (#6131) (#6132)

Integrated into release/v3.8.44 — IP filter persistence + authz-pipeline enforcement (closes #6131). HARD-neutro: validate-release-green on the merge shows the same 3 pre-existing base-reds as the release baseline (test-masking cycle-wide, unit red-herring, integration batch-E2E env); #6131's own tests + ip-filter/pipeline suites all green.

* fix(codex): use access_token.exp instead of id_token.exp for import expiresAt (#6075) (#6084)

Prefer access_token.exp over id_token.exp for Codex auth import (#6075). Integrated into release/v3.8.44.

* fix(compression): send patch-only to PUT /api/settings/compression in CompressionHub (#6039) (#6077)

Send patch-only to PUT /api/settings/compression in CompressionHub (#6039). Integrated into release/v3.8.44.

* fix: reqId ReferenceError in safety-net redirect, dead code, filename typo (#6097)

Fix reqId ReferenceError in safety-net combo redirect + dead-code + DESING→DESIGN rename. Integrated into release/v3.8.44.

* fix(combo): expand fingerprint-based providers into per-fingerprint combo targets (#6082)

Expand fingerprint-based providers into per-fingerprint combo targets. Integrated into release/v3.8.44.

* fix(auth): persist quota preflight account lockouts (#6090)

Persist quota preflight account lockouts until reset window. Integrated into release/v3.8.44.

* fix(combos): expand OpenCode/MiMo fingerprint accounts in combo builder (#6087) (#6092)

Expand OpenCode/MiMo fingerprint accounts in combo builder (#6087). Integrated into release/v3.8.44.

* chore(quality): rebaseline v3.8.44 release-green drift (eslint/cognitive/cyclomatic/file-size)

Measured on release tip 32e4c906e during the #6131/#5975 release-green pass:
eslintWarnings 4256->4270 (+14), cognitiveComplexity 861->867 (+6), cyclomatic
count 2015->2026 (+11), and testFrozen caps for models-catalog-route (1507->1600),
perplexity-web (959->999), route-edge-coverage (1234->1241, my #5975 comment +7).
Inherited cycle drift (the Quality Ratchet does not run on PR->release fast-gates);
compression 'bun not found' is a local-env false and codeql is within baseline, so
neither is rebaselined. No production code touched.

* fix(accountFallback): persist per-account 429 cascade + classify 'Monthly usage limit. Resets in N days.' (#6061)

Persist per-account 429 cascade + classify 'Monthly usage limit. Resets in N days'. Integrated into release/v3.8.44.

* feat(build): backend-only fast build (skip the dashboard frontend) (#6119)

Backend-only fast build (skip dashboard frontend). Integrated into release/v3.8.44.

* fix(provider-limits): clear transient rate-limit state when quota recovers (#6128)

Clear transient rate-limit state when quota recovers. Integrated into release/v3.8.44.

* docs: Normalize mixed-language documentation content (#6105)

Normalize mixed-language documentation to English. Integrated into release/v3.8.44.

* chore docs

* i18n(zh-CN): translate CHANGELOG entries and section headings (#6043)

Adopt zh-CN as a translated locale: translate CHANGELOG + supporting docs. Integrated into release/v3.8.44.

* chore(quality): rebaseline residual eslint + file-size drift (v3.8.44)

Residual drift on release tip 716041223 (moving target): eslintWarnings 4270->4279
(+9 as the branch advanced past the prior rebaseline) and testFrozen/frozen file-size
caps for providerLimits.ts (955->982), accountFallback.ts (1790->1864) and
sse-auth.test.ts (1553->1600). All inherited from parallel-session merges (e.g. #6128);
the two production god-files ideally warrant decomposition rather than a bump (tracked
as debt). No production code touched.

* fix(repo): remove Windows case-conflicting DESIGN duplicate (#6140)

Remove stale root DESIGN.md (Windows case-conflict with design.md). Integrated into release/v3.8.44.

* fix(provider-limits): close TOCTOU race in quota recovery clear (I2) (#6139)

Close TOCTOU race in quota recovery clear via CAS primitive (I2 from #6128). Integrated into release/v3.8.44.

* fix(glm): suppress </think> close marker leak in GLM Anthropic transport (#6133)

Suppress </think> close-marker leak in GLM Anthropic transport. Integrated into release/v3.8.44.

* fix(cli): give setup-claude a fallback profile generator like setup-codex (#6138)

Give setup-claude a fallback profile generator like setup-codex. Integrated into release/v3.8.44.

* fix(onboarding): route provider-details link by node id, not provider slug (#6145) (#6145)

Route onboarding provider-details link by node id (#6145). Integrated into release/v3.8.44.

* fix(translator): strip Responses-only truncation field before Chat Completions forwarding (#6109)

Strip Responses-only truncation field before Chat Completions forwarding (#2311). Integrated into release/v3.8.44.

* fix(mitm): guard against concurrent MITM server starts (#6107)

Guard against concurrent MITM server starts (#2316). Integrated into release/v3.8.44.

* feat(models): add claude-sonnet-5 to Antigravity catalog (#6103)

Add claude-sonnet-5 to Antigravity catalog. Integrated into release/v3.8.44.

* fix(providers): strip thinking param for minimax-m2.7 on NVIDIA NIM (#6102)

Strip unsupported thinking param for minimax-m2.7 on NVIDIA NIM. Integrated into release/v3.8.44.

* feat(providers): add Kenari OpenAI-compatible gateway (#6104)

Add Kenari OpenAI-compatible gateway (BYOK). Integrated into release/v3.8.44.

* feat(sse): per-request Auto-Combo controls (X-OmniRoute-Mode / X-OmniRoute-Budget) — closes #6023 #6024 #6025 (#6057)

Per-request Auto-Combo controls (X-OmniRoute-Mode / X-OmniRoute-Budget). Integrated into release/v3.8.44.

* feat(resilience): throttle concurrent upstream quota fetches — closes #6009 (#6058)

Throttle concurrent upstream quota fetches (#6009). Integrated into release/v3.8.44.

* fix(oauth): graceful 400 for keychain-import-only providers (zed) (#6041) (#6054)

Graceful 400 for keychain-import-only providers on OAuth route (zed, #6041). Integrated into release/v3.8.44.

* fix(dashboard): resolve broken Card import breaking next build (base-red from #6061) (#6155)

* fix(dashboard): resolve broken Card import breaking next build (base-red from #6061)

CoolingConnectionsPanel imported `Card` from `@/components/ui/card`, a path
that does not exist in this repo (there is no shadcn-style `src/components/ui/`).
The PR->release fast-gates do not run `next build`, so the broken import slipped
in and `next build` failed with:

  Module not found: Can't resolve '@/components/ui/card'

Fix: the <Card> here was only a styled container, so replace it with a <div>
carrying the equivalent Tailwind classes (border/bg/padding + rounded-card
shadow-sm). Also normalize the file from CRLF to LF (it shipped with CRLF).

Adds a vitest/jsdom regression test (tests/unit/ui/CoolingConnectionsPanel.test.tsx)
that fails-without-fix (Vite: 'Failed to resolve import @/components/ui/card')
and passes with it, plus renders/empty-state coverage. Rule #18.

* fix(dashboard): stop client CoolingConnectionsPanel dragging server DB barrel into browser bundle

Second base-red from #6061, surfaced once the broken Card import was fixed:

  ./node_modules/ioredis/built/connectors/StandaloneConnector.js
  Module not found: Can't resolve 'net'
  Import trace: ioredis <- rateLimiter.ts <- apiKeys.ts <- @/lib/localDb
                <- CoolingConnectionsPanel.tsx (a "use client" component)

The client panel imported `formatResetCountdown` from `@/lib/localDb` — the
server-side DB re-export barrel — which transitively pulls better-sqlite3/ioredis
(node:net) into the browser bundle. That violates the CLAUDE.md rule 'never
barrel-import from localDb'.

`formatResetCountdown` is a pure date-formatting function, so move its
implementation to the client-safe `@/shared/utils/formatting` (alongside
formatTime/formatDuration) and re-export it from db/providers/rateLimit.ts for the
existing server callers + barrel. The panel now imports it directly from the
shared util — no server code in the client bundle.

Tests (Rule #18):
- tests/unit/format-reset-countdown.test.ts (node:test, blocking test:unit) —
  pure-function coverage: null/past/invalid, s, m+s, h+m, ISO string.
- tests/unit/ui/CoolingConnectionsPanel.test.tsx mock updated to the new module.

* fix(release): v3.8.44 Phase-0 pre-flight — base-red sweep + ratchet absorption

- fix(models): stop resolveProviderAlias at registered provider ids so oc/
  reaches the no-auth opencode provider again (#2901 contract, regressed by
  #5918's transitive chain; transitivity kept across alias-only hops)
- fix(auggie): handle async EPIPE 'error' events on child stdin so a
  fast-exiting CLI surfaces a sanitized error instead of crashing (both
  spawn sites); deflakes auggie-executor tests
- test: align provider family count 166->167 (Kenari #6104), regenerate
  translate-path golden on Linux (+kenari), opencode quota scope
  provider->connection (#6061)
- quality(test-masking): add _deletedWithReplacement allowlist support to
  check-test-masking.mjs (deletion exempt ONLY when the declared replacement
  test exists in HEAD; 5 new gate unit tests) + reduction allowlist entries
  for the verified #5958/#6088/#5816 migrations + targetExhaustion->
  combo-target-exhaustion replacement (#5976, 21 cases/52 asserts vs 13/37)
- quality(file-size): absorb v3.8.44 cycle drift (oauth route 960,
  providerLimits 998, chat 1662, auth 2426) with justification; #6158 will
  restore the oauth-route freeze
- changelog: bullets for the above + the #6155 cooling-panel build fix

* chore(release): v3.8.44 — 2026-07-04

Release reconciliation + close (generate-release Phases 0a/1):
- CHANGELOG [3.8.44]: 21 PR refs added to existing bullets, 62 new bullets
  (incl. restoration of ~10 bullets erased by the stale-branch merge in
  1f6ec5bc8), 3 Maintenance rollups, #6061/#6130 credit fixes, 🙌 Contributors
  table (35 external contributors) — coverage 144/153 cycle commits by #ref
- 42 docs/i18n CHANGELOG mirrors synced (EN content; i18n workflow translates)
- README: What's New refreshed for v3.8.44 highlights
- build scope: exclude electron/node_modules + electron/dist-electron + .build
  from tsconfig (local build-output leak poisoned next build with 8GB OOM —
  same class as the 2026-06-25 incident; scope 14765→5207, gate green)
- quality: cyclomatic baseline 2026→2028 (+2 inherited end-of-cycle drift;
  verified the release-captain code fixes add 0 new violations)

* fix(release): v3.8.44 one-pass release-PR CI sweep

- fix(dashboard): /dashboard/system/proxy 500'd on EVERY render — #5918 put
  useProxyBatchOperations(load) before the const load declaration (TDZ
  ReferenceError, digest 539380095). Hook block moved after load; SSR
  renderToString regression test added (the exact crash mode).
- fix(server): TRACE/TRACK/CONNECT crashed Next's middleware adapter
  (undici cannot represent them) into a raw 500 on every route — the raw
  HTTP method guard now answers 405 + Allow up-front (dast-smoke
  Schemathesis finding on /api/keys/{id}/devices); guard test added.
- fix(api): restore Zod validation on the provider-scoped chat route via a
  .passthrough() schema preserving #5907's relaxed semantics (t06 gate).
- docs(openapi): /api/keys/{id}/devices 401 now refs the management error
  envelope (Schemathesis schema-conformance).
- quality: rebaseline i18nUiCoverage 77.5->76.8 (+~1352 new en.json UI keys
  from the cycle await the async translation workflow; v3.8.39 precedent).
- CodeQL: dismissed 2 incomplete-url-substring FPs on unit-test asserts
  (v3.8.35 precedent) with Hard Rule #14 justifications.
- changelog: bullets for the above + 42 i18n mirrors re-synced

* fix(release): round-2 CI findings — LocaleAutoDetect refresh gating + ratchet tighten

- fix(i18n): LocaleAutoDetect (#5979) refreshed the router on EVERY cookie-less
  first visit, even when the detected locale matched the server-rendered
  <html lang> — re-navigating mid-interaction (flaky e2e 'execution context
  destroyed' + visible flash for new visitors). Refresh now only fires when
  the locale actually differs; regression test added.
- quality: tighten openapiCoverage.pct 36.9->39.3 (require-tighten gate on the
  release PR; value measured by the CI Quality Ratchet on 00c55afcb)
- quality(file-size): shrink the ProxyRegistryManager TDZ note to fit the
  1117-line freeze (prettier reflow added a line at commit time)
- changelog bullet + 42 i18n mirrors re-synced

* test(release): collect the #6082 fingerprint-expansion ghost test

check:test-discovery (Lint job, layered behind the round-1 t06 fix) flagged
tests/e2e/fingerprint-expansion.test.ts as a NEW orphan — it is a node:test
server-boot test that no runner collected, so it had never run. Moved to
tests/integration/ (the collector for this shape), fixed the helper import,
and verified it actually passes (3/3 on first-ever run). CHANGELOG ref updated.

---------

Co-authored-by: Chirag Singhal <76880977+chirag127@users.noreply.github.com>
Co-authored-by: Hamsa_M <116961508+hamsa0x7@users.noreply.github.com>
Co-authored-by: Chewji <126886556+Chewji9875@users.noreply.github.com>
Co-authored-by: Vittor Guilherme Borges de Oliveira <vittoroliveira.dev@gmail.com>
Co-authored-by: nickwizard <35692452+nickwizard@users.noreply.github.com>
Co-authored-by: Ankit <177378174+anki1kr@users.noreply.github.com>
Co-authored-by: Fadhil Yusuf <33994304+yusufrahadika@users.noreply.github.com>
Co-authored-by: Giorgos Giakoumettis <giorgos@yiakoumettis.gr>
Co-authored-by: KooshaPari <42529354+KooshaPari@users.noreply.github.com>
Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com>
Co-authored-by: AgentKiller45 <jamalzzj45@gmail.com>
Co-authored-by: Nikolay Alafuzov <alafuzov_nn@rusklimat.ru>
Co-authored-by: ricatix <d.enistraju155@gmail.com>
Co-authored-by: Muhammad Mugni Hadi <mugni@rukita.co>
Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
Co-authored-by: backryun <bakryun0718@proton.me>
Co-authored-by: Ngô Tấn Tài <tantai@newnol.io.vn>
Co-authored-by: dopaemon <polarisdp@gmail.com>
Co-authored-by: yicone <yicone@gmail.com>
Co-authored-by: CườngNH <j2.cuong@gmail.com>
Co-authored-by: DuyPrX <93126969+DuyPrX@users.noreply.github.com>
Co-authored-by: ryanngit <74137224+ryanngit@users.noreply.github.com>
Co-authored-by: eng2007 <aleksey.semenov@gmail.com>
Co-authored-by: chamdanilukman <16629923+chamdanilukman@users.noreply.github.com>
Co-authored-by: Umar Javed <114807145+tn5052@users.noreply.github.com>
Co-authored-by: JiangZhuo <jiangzhuo@qiniu.com>
Co-authored-by: Delynn Assistant <zhen@dkzhen.org>
Co-authored-by: whale9820 <87256750+whale9820@users.noreply.github.com>
Co-authored-by: whale <admin@dyntech.cc>
Co-authored-by: Rigel Ramadhani Waloni <rigel8911@gmail.com>
Co-authored-by: zocomputer <help@zocomputer.com>
Co-authored-by: aristorinjuang <aristorinjuang@gmail.com>
Co-authored-by: anmingwei <anmingwei@dobest.com>
Co-authored-by: janeza2 <49841619+janeza2@users.noreply.github.com>
Co-authored-by: zmf963 <19422469+zmf963@users.noreply.github.com>
Co-authored-by: Markus Hartung <mail@hartmark.se>
Co-authored-by: Koosha Pari <kooshapari@gmail.com>
Co-authored-by: hartmark <hartmark@users.noreply.github.com>
Co-authored-by: ron <devestacion@gmail.com>
Co-authored-by: Ansh7473 <Ansh7473@users.noreply.github.com>
Co-authored-by: felssxs <felssxs@users.noreply.github.com>
Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com>
Co-authored-by: Arthur Bodera <abodera@gmail.com>
Co-authored-by: Semianchuk Vitalii <fix20152@gmail.com>
Co-authored-by: PizzaV <103120356+pizzav-xyz@users.noreply.github.com>
Co-authored-by: NOXX - Commiter <artur1992123@mail.ru>
Co-authored-by: Milan Soni <123074437+Iammilansoni@users.noreply.github.com>
Co-authored-by: Devin <studyzy@gmail.com>
Co-authored-by: Raxxoor <manker_lol@hotmail.com>
Co-authored-by: derhornspieler <15236687+derhornspieler@users.noreply.github.com>
2026-07-04 13:00:30 -03:00
Diego Rodrigues de Sa e Souza
604afeacf4 Revert "fix(config): externalize ws for copilot-m365-web executor (#6098, closes #6062)"
This reverts commit e61b75f007.
2026-07-03 16:40:11 -03:00
Ankit
e61b75f007 fix(config): externalize ws for copilot-m365-web executor (#6098, closes #6062)
Externalize ws / bufferutil / utf-8-validate in serverExternalPackages so the copilot-m365-web WebSocket masking path works at runtime (bundling ws → TypeError: b.mask is not a function → 80s chat timeout). Regression guard in next-config.test.ts.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
2026-07-03 16:36:35 -03:00
Diego Rodrigues de Sa e Souza
b729a8f273 Release v3.8.43 (#5609)
* chore(release): open v3.8.43 development cycle

* docs(relay): clarify backend routing contract (#5621)

Integrated into release/v3.8.43 (drift-shed: cherry-picked the real change onto the release tip; stale-base drift dropped).

* fix(security): avoid rendering error stacks (#5624)

Integrated into release/v3.8.43 (drift-shed: cherry-picked the real change onto the release tip; stale-base drift dropped).

* fix(chatgpt-web): restore dot-form Pro model ids (#5549)

Integrated into release/v3.8.43 (drift-shed: cherry-picked the real change onto the release tip; stale-base drift dropped).

* feat(commandCode): add multimodal image support for CC vision models (#5557)

Integrated into release/v3.8.43 (drift-shed: cherry-picked the real change onto the release tip; stale-base drift dropped).

* fix(providers): validate M365 Copilot web credentials (#5432)

Integrated into release/v3.8.43 (drift-shed: cherry-picked the real change onto the release tip; stale-base drift dropped).

* fix(sse): bound chat hot-path heap — pressure-aware admission + response cap + clone reductions (#5152) (#5425)

Integrated into release/v3.8.43 (drift-shed: cherry-picked the real change onto the release tip; stale-base drift dropped).

* fix: model lockout not recording for 429 rate_limit_exceeded from Antigravity

## Problem

When Antigravity returns HTTP 429 with `rate_limit_exceeded` error code,
the model lockout system never records the failure, so the model is not
cooled down despite being rate-limited.

### Root Cause

Antigravity's 429 error text is: `"Resource has been exhausted (e.g. check
quota)."`

The QUOTA_PATTERNS in `classify429.ts` contained overly broad regexes:
- `/resource.*exhaust/i` — matches "Resource has been exhausted"
- `/check.*quota/i` — matches "check quota"

This caused `classifyErrorText()` to return `QUOTA_EXHAUSTED` (wrong),
which set `providerExhausted = true` in the combo target exhaustion logic.
With `providerExhausted`, the retry path was skipped entirely, and while
the "done retrying" path should still record lockout, the misclassification
cascaded into incorrect provider-level exhaustion state.

Additionally, `targetExhaustion.ts` used the raw error text string instead
of the structured error code (`rate_limit_exceeded`) that was already
parsed from the response body.

## Fix

1. **classify429.ts** — Removed overly broad `/resource.*exhaust/i` and
   `/check.*quota/i` from QUOTA_PATTERNS. Antigravity's rate-limit wording
   is not a true quota exhaustion signal.

2. **targetExhaustion.ts** — Added optional `structuredError` to
   `ApplyComboTargetExhaustionOptions`. When available, the structured
   error code (e.g. `rate_limit_exceeded`) takes precedence over raw error
   text for exhaustion classification.

3. **combo.ts** — Passes `structuredError` to both `applyComboTargetExhaustion`
   call sites (dispatch path + retry-or-rotate path).

## Effect

`structuredError.code = "rate_limit_exceeded"` → classified as rate-limit
(not quota) → `providerExhausted = false` → retry proceeds →
`recordModelLockoutFailure` called → model enters lockout with proper
cooldown (120s base, exponential backoff).

## Tests

Added 2 new tests for `structuredError.code` precedence in exhaustion
classification. All 28 related tests pass.

* fix(checks): normalize route paths on windows (#5613)

Integrated into release/v3.8.43. Windows path-normalization fix for the route-guard membership gate + regression test (Rule #18). Co-authored test added by maintainer.

* fix: truncate tool list when provider limit exceeds MAX_TOOLS_LIMIT (grok-cli 200)

- Add proactive PROVIDER_TOOL_LIMITS map with grok-cli: 200
- Fix regex to capture 'maximum is 200' (not '427 tools provided')
- Remove broken truncation gate that skipped limits >= MAX_TOOLS_LIMIT (128)
- Add tests for Grok regex, proactive limits, and limits above threshold

Refs #5563

* test(chatcore): cover grok-cli tool-list truncation via prepareUpstreamBody (#5563)

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

* fix(security): v3.8.15 hardening follow-ups (Seg2/Seg3/Seg4/Bug3) (#5512)

Security v3.8.15 hardening follow-ups: Seg2 (CHANGEME boot warn), Seg3 (auth_token cookie maxAge 30d), Seg4 (VS Code path-token once-per-process warning), Bug3 (real global install path resolution), Bug1 (segment-match node_modules in auto-update detection). All 5 carry TDD regression guards.

* Fix HuggingChat web session routing (#5592) (#5592)

Integrated into release/v3.8.43. HuggingChat web session-routing fix (root parent-message fetch + cookie propagation + encrypted-credential guard) + 24-model catalog refresh. Maintainer adjustments (co-authored): reverted the freeModelCatalog.data.ts whole-file reformat down to the surgical 24-record huggingchat change (preserving the auto-generated compact format), and added a 502 regression test for the null parent-message-id path (Rule #18).

* fix: preserve system role for GLM 5.1/5.2 (#5610) (#5663)

* fix: restore Codex Responses WS TLS profile + apply proxy (#5591, #5611) (#5668)

* fix: allow saving providers without a live validator (#5565, #5567) (#5669)

* fix: static model catalog for jules/linkup/ollama/searchapi search providers (#5569, #5571, #5573, #5575) (#5672)

* fix: live AI/ML API catalog + deprecate dead CablyAI (#5570, #5568) (#5673)

* fix: correct 404 provider setup links for ollama/searchapi/you.com (#5572, #5574, #5576) (#5674)

* fix: page call_logs cleanup queries to avoid startup OOM on large DBs (#5618) (#5675)

* fix: use PowerShell Expand-Archive on Windows for embedded-service install (#5590) (#5678)

* fix: treat array content blocks as valid output in detectMalformedNonStream (#5559) (#5680)

* fix: render memory engine status detail strings in English (#5596) (#5685)

* fix: free proxy pool silent sync failure — iplocate txt + per-source isolation + surface errors (#5595) (#5686)

* chore(quality): close QG v2 tail — drop orphan semcheck.yaml + Fase 9 maturity re-eval (#5681)

- Remove semcheck.yaml: orphan config (zero workflow/script wiring) with stale
  rule counts; deterministic doc-accuracy coverage already exists
  (check:fabricated-docs --strict + docs-counts-sync + docs-symbols). Drop the
  REPOSITORY_MAP row referencing it.
- Add docs/ops/MATURITY_REEVAL.md (Fase 9): re-measures maturity post-Ondas 0-3.
  The two biggest structural weaknesses from QUALITY_GATE_PLAYBOOK (2026-06-16) are
  now closed: fast-gates hole (quality.yml runs typecheck:core + impacted TIA unit
  tests + vitest + shards) and mutation-score-as-ratchet (check-mutation-ratchet.mjs
  + seeded baseline + nightly blocking job). Residual gap is owner/infra-gated
  (branch-protection main, SLSA L3, CodeQL advanced).
- Record agent-lsp as deferred/opt-in (doc-only scaffold, no wiring).

* fix(ci): stabilize nightly-mutation — guard tap.testFiles drift + anti-flake eps (#5682)

Root cause (NOT a timeout): the nightly-mutation run fails on cold-cache nights
because the blocking mutation-ratchet job measures modules below baseline, while
warm-cache nights pass — the verdict tracked GitHub Actions cache state, not code
quality. Proven via a local Stryker probe on headers.ts: covering unit tests
(no-memory-header, strip-reasoning) had drifted OUT of stryker.conf.json
tap.testFiles, so their mutants went covered-but-unkilled = Survived on a cold
full run (COVERED score 61.73 vs 94.29 baseline); adding them restores the kills.

- Add scripts/check/check-mutation-test-coverage.mjs: guards that every UNIT test
  importing a Stryker-mutated module is listed in tap.testFiles. Advisory by
  default, --strict in CI (wired in quality.yml fast-gates). Prevents recurrence.
- Add the 38 drifted covering unit tests to stryker.conf.json tap.testFiles
  (138 -> 176). Monotonically safe: more covering tests only raise/hold the score.
- Add MUTATION_RATCHET_EPS (1.0pt) anti-flake tolerance to check-mutation-ratchet
  so sub-point tap-runner jitter no longer false-fails the gate. Lowers no baseline.
- Tests: check-mutation-test-coverage (3) + eps cases in check-mutation-ratchet.

Residual: a clean post-merge nightly confirms scores return to/above baseline;
any marginal residual gets a baseline re-seed (operator).

* refactor(dashboard): split sidebarVisibility god-file into types + sections leaves (#5683)

Behavior-preserving decomposition: src/shared/constants/sidebarVisibility.ts
1197 -> 291 LOC by extracting two leaves under sidebarVisibility/:
- types.ts (160): HIDEABLE_SIDEBAR_ITEM_IDS + all sidebar types (self-contained).
- sections.ts (762): section building-block consts + SIDEBAR_SECTIONS (imports
  types only — cycle-safe). COMPRESSION_CONTEXT_GROUP + SIDEBAR_SECTIONS stay
  exported; host re-exports both + 'export *' of types, so every consumer import
  path is unchanged.

Byte-identical data verified via JSON.stringify of HIDEABLE_SIDEBAR_ITEM_IDS /
SIDEBAR_ICON_ACCENTS / COMPRESSION_CONTEXT_GROUP / SIDEBAR_SECTIONS / SIDEBAR_PRESETS
+ getSectionItems output (identical before/after). typecheck:core, check:cycles
(no cycles), check:file-size (3 files <800), and the 3 sidebar suites (20/20) pass.
No logic changed.

Note: file-size frozen baseline for sidebarVisibility.ts (1198) can ratchet to 291
to lock the shrink (left for the release ratchet / operator).

* fix: surface fusion-specific config on the Global Routing tab (#5598) (#5688)

* fix(executor): route OpenAI-compatible MCP Responses requests to /responses (#5483)

Closes #5483. OpenAI-compatible providers receiving a Responses-shaped request carrying MCP / tool_search tools now route to the upstream /responses endpoint instead of downgrading to /chat/completions, preserving Codex deferred tool discovery. Detection helpers extracted to open-sse/executors/forceResponsesUpstream.ts. Thanks to @KooshaPari.

* fix(ci): make release-green pre-flight gates visible + bounded so unit reds are not missed (#5644)

Integrated into release/v3.8.43.

* fix(body-size): raise LLM API payload limit for responses routes (#5652)

Integrated into release/v3.8.43. Thanks @JxnLexn!

* fix(test): use lightweight health probe for batch e2e (#5651)

Integrated into release/v3.8.43. Thanks @KooshaPari!

* feat(compression): T05/C5 — preserveSystemPrompt mode enum + legacy back-compat (#5653)

Integrated into release/v3.8.43. Includes the legacy-boolean back-compat derivation so existing preserveSystemPrompt=false installs keep whenNoCache behavior.

* routing: optimize latency strategy with perf metrics (#5629)

Integrated into release/v3.8.43. Thanks @KooshaPari!

* feat(db): models/5004 — self-correcting model context-window overrides (#5667)

Integrated into release/v3.8.43.

* feat(providers): complete SenseNova free Token Plan — chat + Text-to-Image (port from 9router#2233) (#5679)

Integrated into release/v3.8.43.

* feat(api): routing/4985 — configurable response-body validation + failover (#5684)

Integrated into release/v3.8.43.

* fix(chatcore): default Claude tool type to "custom" when missing (#5662)

Integrated into release/v3.8.43. Port from 9router#2196.

Co-authored-by: warelik <warelik@users.noreply.github.com>

* fix(translator): merge consecutive same-role contents for Gemini (port from 9router#2191) (#5661)

Integrated into release/v3.8.43. Port from 9router#2191.

* chore(bun): add locked bun runtime dependency (#5615)

Integrated into release/v3.8.43. Bun 1.3.10 pinned via npm lockfile (adopt-partial decision). Thanks @KooshaPari!

* chore(bun): run validated ts scripts with bun (#5612)

Integrated into release/v3.8.43. Thanks @KooshaPari!

* chore(bun): run CI script checks with bun (#5617)

Integrated into release/v3.8.43. Validated bun==node output for all 3 gates (provider-consistency, compression-budget, known-symbols). Thanks @KooshaPari!

* fix(build): make pack validator bun safe (#5643)

Integrated into release/v3.8.43. Forward-compat guard; node/npm path unchanged. Thanks @KooshaPari!

* docs: document Bun as the allow-listed build/dev script runner (Node stays the published runtime) (#5703)

Integrated into release/v3.8.43.

* feat(analytics): show $0 cost for flat-rate subscription/cookie providers (#5552) (#5704)

* refactor(api): extract unified-catalog helpers into cohesive leaf modules (#5699)

BLOCO E2 of the god-files campaign. The module-level pure/standalone helpers in
src/app/api/v1/models/catalog.ts (1611 LOC) were lifted out verbatim into five
cohesive leaf modules so the catalog host shrinks toward the 800-LOC file-size cap
without any behavior change (host now 1345 LOC; the heavy getUnifiedModelsResponse
orchestrator is untouched — its in-function closures stay put):

- catalogHelpers.ts   — pure numeric/array/shape helpers + shared catalog types
- catalogOpenrouter.ts — OpenRouter id/modality/free-model/display-name helpers
- catalogVision.ts     — vision-capability field derivation (+ isVisionModelId re-export)
- catalogProviderMaps.ts — alias<->providerId resolution maps (buildAliasMaps)
- catalogRequest.ts    — /v1/models API-key auth gating + Codex CLI client detection

The host re-exports getCustomVisionCapabilityFields and isVisionModelId so the public
API consumed by other tests (llm-selector-custom-vision-models, vision-detection-
consistency) is unchanged; all 9 catalog/vision suites stay green.

Adds tests/unit/catalog-helpers-extraction.test.ts: characterization tests for every
extracted helper + a guard asserting the host preserves its public exports.

Validated: typecheck:core, 50 catalog characterization tests, 12 new leaf tests,
integration-wiring, check:cycles, check:file-size (no new violations), ESLint, Prettier.

* feat(mcp): T07 — expose RTK learn/discover as MCP tools (#5691)

Adds two read-only MCP tools wrapping the existing RTK discovery primitives: omniroute_rtk_discover (discoverRepeatedNoise/suggestFilter over recently captured raw tool output → candidate noise patterns + suggested filter) and omniroute_rtk_learn (listRtkCommandSamples + commandToId). Scope read:compression, MCP audit-logged, no new engine logic. Regression guard: tests/unit/compression/rtk-mcp-tools.test.ts. gaps v3.8.42 — T07.

* feat(compression): T05/C3 — opt-in LLM-tier compression engine (#5702)

Adds an opt-in, default-off LLM-tier compression engine ('llm') that condenses non-system message prose via a pluggable chat-completion backend, mirroring the llmlingua contract. Safe by construction: no-op default backend (pass-through out of the box), not in the default stacked pipeline, enabled defaults false, fenced code blocks + system messages never sent to the model, fail-open everywhere, minTokens floor. Real production backend is a VPS-validated follow-up (Hard Rule #18). Regression guard: tests/unit/compression/llm-compressor-engine.test.ts (8). gaps v3.8.42 — T05/C3.

* refactor(db): extract compat/aliases/mitm helpers from db/models.ts into leaf modules (#5705)

BLOCO E3 of the god-files campaign. db/models.ts (1250 LOC) mixed six concerns; the
three cleanly-separable ones plus the shared key_value helpers were lifted out verbatim
into a new src/lib/db/models/ subdirectory, leaving the tightly-coupled custom/synced/
flags trio in the host (host now 936 LOC). The host re-exports every moved public symbol
so the module's public API (consumed by ~29 test files + localDb) is unchanged.

- models/shared.ts      — asRecord / toNonEmptyString / getKeyValue + JsonRecord (19 LOC)
- models/compat.ts      — model-compat overrides + sanitizeUpstreamHeadersMap (249 LOC)
- models/aliases.ts     — model-alias CRUD + cascade delete (61 LOC)
- models/mitmAlias.ts   — MITM alias get/set (32 LOC)

The custom/synced/flags trio stays in the host because it is genuinely coupled
(flags->getCustomModelRow, flags->readCompatList, custom->removeModelCompatOverride,
synced->getModelIsDeleted, setModelIsHidden->updateCustomModel) — splitting it cleanly
is a follow-up. Dependency DAG is acyclic (verified by check:cycles).

Adds tests/unit/db-models-split.test.ts: characterization of the pure extracted helpers
+ a guard asserting the host preserves its full public export surface.

Validated: typecheck:core, check:cycles (no cycles), 77 existing db/models consumer
tests (db-models-crud/extended/aliases-cascade + 7 more) green, 7 new tests, ESLint,
Prettier, check:file-size (host 936 < frozen 1259; no new violations).

* refactor(db): extract pricing/lkgp/cache-metrics from db/settings.ts into leaf modules (#5709)

BLOCO E3 of the god-files campaign. db/settings.ts (1154 LOC) mixed five concerns; the
three cleanly-separable ones plus the shared toRecord/JsonRecord helper were lifted out
verbatim into a new src/lib/db/settings/ subdirectory, leaving the Settings-core + Proxy
config concerns in the host (host now 646 LOC). The host re-exports every moved public
symbol so the module's public API (consumed by ~93 test files + localDb) is unchanged.

- settings/shared.ts      — toRecord + JsonRecord (9 LOC)
- settings/pricing.ts     — pricing layers/sources/per-model + update/reset (254 LOC)
- settings/lkgp.ts        — Last-Known-Good-Provider get/set/clear (49 LOC)
- settings/cacheMetrics.ts — cache metrics + trend (235 LOC)

Settings-core + the Proxy-config concern stay in the host: proxy is the most tangled
(245-line resolveProxyForConnection, resolution cache, imports from ./proxies) and
getSettings is the most central function — leaving them is the correct coupled-core stop.
Pricing/LKGP/Cache have NO dependency on Settings/Proxy helpers (verified); the
dependency DAG is acyclic (check:cycles).

Adds tests/unit/db-settings-split.test.ts: characterization of the shared toRecord helper
+ a guard asserting the host preserves its full public export surface.

Validated: typecheck:core, check:cycles (no cycles), 149 existing+new db/settings consumer
tests green (db-settings-crud/extended, 8 pricing suites, cache-metrics, 2 proxy-resolution
suites + 29 new), ESLint, Prettier, check:file-size (host 646 < frozen 1155).

* fix(translator): re-apply lost defensive hardening for Gemini merge + Claude tool defaults (#5706)

Re-applies two dropped gemini-code-assist hardening fixes (defaultClaudeToolType non-object passthrough; mergeConsecutiveSameRoleContents shallow-copy) with regression tests. Follow-up to #5661/#5662. Integrated into release/v3.8.43.

* feat(codex): generate fallback profiles for compatible models (#5701)

setup-codex now generates Codex profiles for compatible text models from the live /v1/models catalog when the model id doesn't match a hand-tuned pattern, skipping media/embedding models. Integrated into release/v3.8.43.

* docs(changelog): credit @Chewji9875 for #5563 + #5579

Add CHANGELOG credit bullets for grok-cli tool-limit (#5563) and Antigravity 429 lockout (#5579). Documentation-only.

* test(dashboard): repoint sidebar quota-share placement scan to sections.ts (#5711)

The D1 god-file split (#5683) moved the nav-item id definitions out of
src/shared/constants/sidebarVisibility.ts into the extracted leaf
src/shared/constants/sidebarVisibility/sections.ts. This source-scan test
still read the old monolith path, so it found 0 occurrences of
id: "costs-quota-share" and failed (base-red on release/v3.8.43).

Repoint SIDEBAR_PATH to sections.ts where the ids now live. All four
placement assertions (quota-share after quota, same array, far from
costs-budget, exactly one occurrence) hold against the new source.

* refactor(db): extract columns/nodes/rate-limit leaves from db/providers.ts (#5714)

db/providers.ts was a 1106-line god-file mixing four concerns. Extract the
three acyclic, cohesive slices into sibling leaf modules under
src/lib/db/providers/, leaving the tightly-coupled connection-CRUD core in
the host:

  - providers/columns.ts   (116)  10 pure column-normalizer helpers (DB-free)
  - providers/nodes.ts      (163)  6 provider-node CRUD functions
  - providers/rateLimit.ts  (177)  6 rate-limit/quota runtime helpers + formatResetCountdown

Host providers.ts: 1106 -> 719 lines. The connection-CRUD core does not call
any node or rate-limit function (verified), so the host re-exports the 12
moved public symbols via `export { ... } from './providers/<leaf>'` — the
module's public API stays IDENTICAL (23 symbols). Bodies moved verbatim
(byte-identical); the only edit to a moved line is the added `export` on the
10 previously-private normalizers.

Behavior-preserving: 122 existing provider/quota/rate-limit consumer tests
stay green; new tests/unit/db-providers-split.test.ts guards the re-export
barrel + characterizes the pure column helpers (38 assertions).

Refs #3501 (god-file structural shrink).

* refactor(db): extract types + pure mappers from db/proxies.ts (#5717)

db/proxies.ts was a 1059-line god-file. Extract the two acyclic, DB-free
slices into sibling leaf modules under src/lib/db/proxies/, leaving the
tightly-coupled CRUD + assignment + resolution core in the host:

  - proxies/types.ts    (65)   10 proxy type/interface declarations
  - proxies/mappers.ts  (180)  pure row mappers / scope normalizers / payload
                               coercers (toRecord, mapProxyRow, mapAssignmentRow,
                               isRelayProxyType, extractRelayAuth,
                               toRegistryProxyResolution, normalizeScope,
                               normalizeAssignmentScopeId, toLegacyProxyLevel,
                               coerceProxyPayload, redactProxySecrets)

Host proxies.ts: 1059 -> 847 lines. The resolution functions call
createProxy/assignProxyToScope, so the CRUD+resolution core CANNOT be
extracted without an import cycle and stays in the host. The host re-exports
the 2 moved public functions (extractRelayAuth, redactProxySecrets) via
`export { ... } from './proxies/mappers'` — the public API stays IDENTICAL
(20 functions; no types were ever publicly exported). Bodies moved verbatim;
the only host edits are the new leaf imports, the re-export, dropping the now
unused `import { decrypt }`, and two prettier line-wrap reflows of retained
ternary/union lines (token-identical).

Behavior-preserving: 69 existing proxy/registry/relay/family consumer tests
stay green; new tests/unit/db-proxies-split.test.ts guards the re-export
barrel + characterizes the pure mappers (35 assertions).

Refs #3501.

* refactor(db): extract static migration data tables from migrationRunner.ts (#5721)

migrationRunner.ts (1124 lines, frozen-baselined) is the startup migration
orchestrator. As a conservative, zero-behaviour-risk first slice, extract the
six static migration-compatibility DATA tables (verbatim) into a pure-data
leaf, leaving the entire orchestrator + all SQL-running helpers in the host:

  - migrationRunner/constants.ts (118)  RENAMED_MIGRATION_COMPATIBILITY,
    LEGACY_VERSION_SLOT_MIGRATIONS, SUPERSEDED_DUPLICATE_MIGRATIONS,
    PHYSICAL_SCHEMA_SENTINELS, INITIAL_SCHEMA_SENTINELS,
    OPTIONAL_FTS5_MIGRATION_VERSIONS

Host migrationRunner.ts: 1124 -> 1023. The runtime fts5SupportCache (a
WeakMap, mutable state) stays in the host. No public API change (these consts
were module-internal). Data moved byte-identical (sed-extracted, verbatim
verified); the only host edits are the leaf import + one prettier collapse of
a pre-existing 2-line union type annotation to 1 line (token-identical,
typecheck-confirmed).

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

Refs #3501.

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

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

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

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

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

Refs #3501.

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

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

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

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

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

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

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

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

- Acknowledgments: refreshed star counts; fix headroom repo rename

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

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

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

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

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

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

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

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

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

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

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

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

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

Refs #3501.

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

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

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

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

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

Refs #3501.

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

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

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

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

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

Refs #3501.

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

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

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

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

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

Refs #3501.

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

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

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

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

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

Refs #3501.

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

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

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

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

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

Refs #3501.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Docs-only; SECURITY.md already links here.

Closes #5599

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

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

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

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

Closes #5600

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

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

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

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

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

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

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

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

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

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

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

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

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

Closes #5597

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

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

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

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

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

* Persist batch item checkpoints during recovery (#5753)

* fix(sse): checkpoint batch item recovery

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

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

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

---------

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

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

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

* feat(cli): show version in startup banner

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

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

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

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

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

---------

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

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

* chore(release): open v3.8.42 cycle

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

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

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

* chore: remove unused codegraph exports (#5400)

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

* chore: remove unused settings types (#5398)

Integrated into release/v3.8.42

* chore: remove unused combo types (#5396)

Integrated into release/v3.8.42

* chore: remove unused provider types (#5393)

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

* chore: remove unused usage types (#5397)

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

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

Integrated into release/v3.8.42

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

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

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

Closes #5452

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

Closes #5427

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

Closes #5406

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* fix post-merge ci regressions (#5467)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

#5420: the 'Import Models' button now hides for tool-only providers
(web search / web fetch) via a capability check over resolved serviceKinds,
not just the -search suffix — firecrawl/jina-reader (webFetch) no longer
show an Import button that 400s. No LLM/media provider is affected.

#5426: Coze key validation no longer leaks the raw upstream envelope
({code,msg,logId,from}) into the UI; the Coze error becomes a friendly
message, scoped to provider === 'coze' so no other provider is affected.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Integrated into release/v3.8.42 (round 3). T05/C2 caveman packs de/fr/ja

* feat(compression): T05/C6 — Chinese (zh / wenyan) caveman pack + detection (#5532)

Integrated into release/v3.8.42 (round 3). T05/C6 zh/wenyan pack + detection

* feat(compression): T07/R9 — gradle + dotnet RTK catalog filters (#5537)

Integrated into release/v3.8.42 (round 3). T07/R9 RTK gradle+dotnet filters

* refactor(dashboard): T11 — drop duplicate caveman on/off toggle from the compression settings tab (#5524)

Integrated into release/v3.8.42 (round 3). T11 consolidate duplicate caveman controls; i18n'd the panel hint string (source key).

* test relay routing fallback headers (#5526)

Integrated into release/v3.8.42 (round 3). Relay fallback header extraction + tests (drift-shed: dependabot #5415 commit dropped).

* fix(opencode-plugin): bump to 0.2.0 + auto-publish on release (#5363)

- Bump @omniroute/opencode-plugin from 0.1.0 to 0.2.0 so CI publishes
  the accumulated fixes (auto combos, schema fields, debug logging) that
  were merged after the initial 0.1.0 publish on May 24.
- Add auto-bump step in npm-publish.yml: detects if the plugin dir
  changed since the last release tag and auto-increments patch version,
  so the plugin never falls behind again on future releases.

Co-authored-by: herjarsa <herjarsa@users.noreply.github.com>

* [codex] add bifrost auto fallback cooldown (#5519)

Integrated into release/v3.8.42 (round 3). Bifrost auto fallback cooldown; header reconciled with #5526 helper + env-doc.

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

* fix onboarding schema client import (#5525)

Integrated into release/v3.8.42 (round 3). Browser-safe onboarding schema import (drift-shed: dependabot #5415 dropped).

* docs: add relay backend strategy guide (#5547)

Port #5533 relay strategy guide to release/v3.8.42 (doc-only).

* fix(chatgpt-web): support GPT-5.5 Pro handoff (#5536)

Integrated into release/v3.8.42 (round 3). GPT-5.5 Pro async stream_handoff support (drift-shed: dependabot #5415 dropped).

* fix(providers): persist Configured filter across page reloads (#5510)

Integrated into release/v3.8.42 (round 3). Persist Configured filter across reloads; extracted shouldSyncProviderDisplayMode race guard + TDD test (Closes #4059).

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

* fix(mimocode): route per-account traffic through SOCKS5 proxy dispatchers (#5521)

Integrated into release/v3.8.42 (round 3). Per-account SOCKS5 dispatcher routing — completes #3837's stored proxy config with the actual undici dispatcher layer. Rebased onto .42 (dropped the CI-workflow-deletion commits; merged proxyUrlMap dispatch with #3837's acct.proxy storage).

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

* fix(chatgpt-web): portable SHA3-512 for sentinel PoW under Electron/BoringSSL (#5531) (#5540)

* fix(build): keep ioredis out of the client/CLI bundle via SPAWN_CAPABLE_PREFIXES leaf (#5546)

Fix the dast-smoke ioredis client-bundle regression (proven: dast-smoke green). Remaining reds are pre-existing base-reds/flakes (base.ts file-size, GOLDEN provider drift, shard-1 compression flakes) inherited by all PRs — not from this change.

* chore(release): finalize v3.8.42 CHANGELOG + cycle-close reconciliation

- Reconcile CHANGELOG.md for v3.8.42: 40 bullets covering all 89 commits
  since v3.8.41 (4 features, 26 fixes, 10 maintenance incl. 2 rollups for
  the 35-PR dead-code sweep + 17-PR DRY consolidation), dedup the merge-
  artifact duplicate New Features headers, set release date 2026-06-30.
- Sync 42 docs/i18n/*/CHANGELOG.md mirrors.
- Document 3 new chatgpt-web/TLS env vars in .env.example + ENVIRONMENT.md
  (OMNIROUTE_CGPT_WEB_PRO_TIMEOUT_MS, _PRO_POLL_INTERVAL_MS,
  OMNIROUTE_CHATGPT_STREAM_FIRST_BYTE_TIMEOUT_MS).
- Cycle-close ratchet rebaselines: eslintWarnings 4116->4121, file-size
  base.ts/chatgpt-web.ts/strategySelector.ts/chatgpt-web.test.ts (all
  inherited drift, justified inline).
- Regenerate provider translate-path golden snapshot for the merged
  bytez/friendliai/novita endpoint fixes.

* chore(changelog): cover #5415 dev-deps bump merged from main

The release/v3.8.42 ↔ main merge (c4c1b56ba) brought #5415 (development
dependency group, 9 updates) and #5533 (relay backend guide) from main.
#5533's content is already covered by the #5547 port bullet; add a
Maintenance bullet for #5415 and re-sync the 42 i18n CHANGELOG mirrors.

* test: relocate 2 orphaned test files to collected runner paths

check:test-discovery flagged two cycle-merged tests that no runner
collects (they never ran → false coverage confidence):
- compression-settings-tab-consolidation.test.tsx (#5524) → tests/unit/ui/
  (vitest UI runner collects tests/unit/ui/**/*.test.tsx); 3/3 pass.
- providers/providerPageStorage.test.ts (#5510) → tests/unit/dashboard/
  ('providers' is not a collected subdir; 'dashboard' is, same ../../../
  import depth); 30/30 pass under the node runner.

Both confirmed green when actually executed; no assertions weakened.

* fix(release): repair inherited base-red tests from #5480/#5527/#5427/#5521

The fast-path (PR->release/**) does not run the full unit+integration suites,
so four merged feature PRs shipped with stale/incorrect tests that only surface
on the release PR (PR->main). Repairs (features are correct; align tests to the
new behavior — no assertions weakened):

- #5480 (gate claude adaptive thinking): adaptive thinking is now injected only
  for a real Claude Code client (x-app:cli / claude-code UA), not for any bare
  Claude OAuth token. claude-thinking-tool-choice-guard + base-thinking-budget-5312
  now identify as a Claude Code client to exercise the adaptive path (3 tests).

- #5527 (T02 inflation guard): the guard reverts a stacked body that did not
  shrink in tokens. The bail-out/advancement fixtures used growth-appending mock
  engines; they now carry a droppable padding message the engines empty, so the
  body realistically shrinks and the marker assertions survive. bailout (5),
  stacked-async (3), engine-enabled-toggle (2).

- #5427 (render onboarding wizard at /providers/new): integration-wiring asserted
  the old redirect stub; now asserts the route renders ProviderOnboardingWizard.

- #5521 (mimocode SOCKS5 per-account proxy): the constructor's default account
  omitted the proxy field (undefined), breaking the 'all proxies null' backward
  compat guard. Default it to null, mirroring syncAccountsFromCredentials().

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

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: Jan Leon <Jan.gaschler@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: KooshaPari <42529354+KooshaPari@users.noreply.github.com>
Co-authored-by: KooshaPari <koosha@example.com>
Co-authored-by: backryun <bakryun0718@proton.me>
Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com>
Co-authored-by: herjarsa <herjarsa@users.noreply.github.com>
Co-authored-by: Arthur Bodera <abodera@gmail.com>
Co-authored-by: PizzaV <103120356+pizzav-xyz@users.noreply.github.com>
Co-authored-by: OpenClaw Auto <openclaw-auto@example.invalid>

* Move CLI profile sync toggles to CLI Code (#5778)

* move CLI profile sync toggles to CLI Code

* test CLI profile auto-sync toggles

* Document CLI profile auto-sync flags

* docs(changelog): note CLI profile auto-sync card moved to CLI Code (#5778)

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>

* fix(grok-cli): parse expires_at from auth.json and exp from JWT to fix auto-refresh (#5775)

* fix(grok-cli): parse expires_at from auth.json and exp from JWT to fix auto-refresh

* docs(changelog): note grok-cli token auto-refresh fix (#5775)

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>

* fix(providers): import intentional local-catalog-only providers instead of 502 (#5460, #5465) (#5787)

The model-sync route returned a hard 502 ('Remote model discovery failed;
local catalog fallback not synced') for every provider whose local catalog is
its ONLY discovery source (Reka #5460, t3.chat #5465, embedding/rerank like
voyage-ai/jina-ai, Qwen-OAuth, and web-cookie providers).

The /models route now flags catalogs that are the provider's intended source
(no remote /models endpoint) with intentional:true; model-sync imports those
instead of 502-ing, while a genuinely degraded remote fallback still surfaces.
New dependency-free leaf degradedLocalCatalog.ts.

Also fixes t3.chat's confusing add-credential hint: it no longer renders the
circular 'Required cookie: convex-session-id + Cookie header...' copy and wires
the step-by-step DevTools hint (t3ChatWebCookieHint) already translated in every
locale.

Regression guards: tests/unit/sync-models-degraded-local-catalog-5460-5465.test.ts,
tests/unit/t3chat-web-cookie-hint-5465.test.ts, + intentional-flag assertions in
tests/unit/provider-models-route.test.ts.

* fix(api): self-hydrate model aliases from DB on GET after restart (#5777)

* Fix grammatical errors in readme (#5738)

* fix(api): self-hydrate model aliases from DB on GET when in-memory state is empty

In the standalone production build, webpack creates two separate copies of
modelDeprecation.ts — one hydrated by the startup path (used for request
routing) and one used by the /api/settings/model-aliases API route.  The
API route's copy starts with an empty _customAliases after each server
restart, causing the Settings → Routing UI to show 'No exact-match aliases
configured' even though the aliases are persisted in the DB.

The GET handler now detects an empty _customAliases state and reads the
modelAliases key from the settings blob in the DB, calling
setCustomAliases() to hydrate this module instance.  This is a best-effort
fallback — when _customAliases is already populated (e.g. by the startup
path in dev mode), no DB read occurs.

Regression test: tests/unit/model-aliases-settings-route-selfheal.test.ts
- Verifies hydration from DB when in-memory state is empty
- Verifies no hydration when in-memory state is already populated
- Verifies graceful handling when no modelAliases exist in DB

---------

Co-authored-by: Chirag Singhal <76880977+chirag127@users.noreply.github.com>
Co-authored-by: marcelpeterson <marcelpeterson@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>

* refactor(usage): extract 5 provider usage families into leaves (#5782)

Split open-sse/services/usage.ts (1723 -> 901 LOC) by moving the Cursor, Kimi,
Codex, Claude and Kiro usage-fetcher families into cohesive leaves under
open-sse/services/usage/ (mirroring the existing glm/minimax/antigravity/quota/
scalars leaves):

- usage/cursor.ts   getCursorUsage (+ CURSOR_USAGE_CONFIG, decodeCursorJwtSub)
- usage/kimi.ts     getKimiUsage (+ KIMI_CONFIG, getKimiPlanName)
- usage/codex.ts    getCodexUsage (+ CODEX_CONFIG)
- usage/claude.ts   getClaudeUsage / getClaudePlanLabel (+ CLAUDE_CONFIG, legacy)
- usage/kiro.ts     getKiroUsage / buildKiroUsageResult / discoverKiroProfileArn (+ helpers)

The host keeps the getUsageForProvider dispatcher and imports the fetchers back;
the public export set is unchanged — buildKiroUsageResult + discoverKiroProfileArn
are re-exported from the kiro leaf (the kiro-* tests import them from
services/usage) and __testing stays wired to the moved claude/kiro internals.
Bodies are verbatim: the code-line multiset of host + leaves equals the original.

Adds tests/unit/usage-families-split.test.ts pinning the leaf surface, the kiro
re-export identity, the __testing wiring, and getClaudePlanLabel's pure logic.

* chore(docs): sync i18n CHANGELOG mirrors with root [3.8.43] section (#5789)

Regenerate the docs/i18n/<locale>/CHANGELOG.md [3.8.43] blocks from the root
CHANGELOG so the mirror body size returns within the 25% docs-sync tolerance.
Clears a pre-existing release-time drift (mirrors were ~26% smaller than root)
that was failing check-docs-sync and blocking every local commit on the
release branch.

* fix(providers): correct stale/broken provider metadata (#5487, #5461, #5534, #5470) (#5790)

- #5487 Qoder: replace the untranslated i18n stubs (personalAccessTokenLabel,
  qoderPatHint, qoderPatPlaceholder) with real copy; extend the STUB_KEYS guard.
- #5461 Scaleway: website pointed at scaleway.com/en/ai/generative-apis (HTTP 404);
  repoint at the live docs URL /en/docs/ai-data/generative-apis/.
- #5534 Microsoft 365 Copilot: rewrite the vague authHint with concrete DevTools
  WebSocket steps (the token lives on the Chathub WS URL, not an Authorization header).
- #5470 Together AI: retired the $25 signup credit and is now fully prepaid (min $5);
  hasFree false + a prepaid notice instead of the stale free-tier freeNote (verified live).

Regression guards: tests/unit/provider-metadata-5461-5470-5534.test.ts + Qoder keys
added to tests/unit/provider-add-ux-i18n-import-warning.test.ts.

* fix(dashboard): neutral badge for unsupported validation + clickable OAuth error links (#5442, #5486) (#5795)

- #5442 LMArena (and any provider with no live validator) returns
  { unsupported: true } from /api/providers/validate and Save succeeds, but the
  Add-API-Key modal only had success/failed states so it rendered a red 'Invalid'
  badge. Add an 'unsupported' result → neutral info 'N/A' badge via the pure leaf
  validationBadgeProps(); both validate handlers now map data.unsupported to it.
- #5486 GitLab Duo's OAuth setup error embeds a registration URL
  (gitlab.com/-/profile/applications) but the OAuth error step rendered it as dead
  red text. New LinkifiedText component (+ pure ReDoS-safe linkify util) makes any
  http(s) URL in an OAuth error clickable; the GitLab Duo backend message already
  carries the full setup steps.

Regression guards: tests/unit/validation-badge-unsupported-5442.test.ts,
tests/unit/oauth-error-linkify-5486.test.ts. Frozen god-files kept within cap
(AddApiKeyModal 868/868, OAuthModal 968/969).

* fix(system): route in-app auto-update npm calls through the win32 shell helper (#5542) (#5797)

The in-app auto-update flow called execFileAsync("npm", ...) directly for the
version lookup (versionCheck.getLatestVersionFromNpmCli), dependency install,
global install, and native rebuild. On Windows npm is npm.cmd and Node >=24
refuses to execFile a .cmd without a shell (nodejs/node#52554), so those calls
threw 'spawn npm ENOENT'. Route them through buildNpmExecOptions (the same
win32-shell helper the embedded-services installer uses, fix #5379). The global
install spec is validated with SERVICE_VERSION_PATTERN before it is shell-joined
(Hard Rule #13). Not the pnpm/npx swap the issue proposed — that is the wrong
direction for an 'npm install -g' flow already solved elsewhere in-repo.

Regression guard: tests/unit/autoupdate-npm-win32-5542.test.ts.

* refactor(sse): extract cursor protobuf wire primitives into a leaf (#5794)

Split open-sse/utils/cursorAgentProtobuf.ts (1520 -> 1400 LOC) by moving the
low-level protobuf wire-format primitives — varint/tag/length-delimited encode+
decode + the generic field walker (encodeVarint, encodeTag, encodeBytes,
encodeString, encodeMessage, encode{UInt32,Bool,Double}Field, decodeVarint,
checkedLen, decodeFields, findField, decode{String,Varint}Field, the Field type
and the WT_VARINT/WT_LEN wire-type constants) — into cursorAgentProtobuf/wire.ts.

These primitives were module-private, so the host's public API is unchanged; the
host imports them back internally. Bodies are verbatim: the code-line multiset of
host + wire.ts equals the original. First layer of the codec decomposition — the
value/framing codec and the message encoders/decoders build on this and stay in
the host (they share host-retained helpers; splitting them is a separate step).

Adds tests/unit/cursor-protobuf-wire-split.test.ts pinning the leaf surface, the
encode/decode round-trip invariants, the buffer-overrun guard, and the host wiring.

* test(runtime): guard tsx/esm→esbuild transform path on boot (#5757) (#5773)

#5757 reported that a fresh `npm install omniroute` pulls `esbuild@0.28.1`
transitively via `tsx` (a runtime dependency the CLI registers at boot in
`bin/omniroute.mjs`), and proposed forcing `esbuild@0.27.4`.

That override is unsafe: `tsx@4.22.4` requires `esbuild@~0.28.0` and
`fumadocs-mdx@15` (also a runtime dep) requires `esbuild@^0.28.0`; forcing 0.27.x
pushes esbuild below both, and 0.28.1 is currently the latest release. The
reported transform failure also does not reproduce — OmniRoute targets ES2022,
its minimum supported Node is 22.2 (destructuring is native), and tsx targets the
running Node, so esbuild never lowers to an unsupported target.

Instead of an unsafe version pin, add two regression guards:
- functional: spawn the real `node --import tsx/esm` loader on a fixture packed
  with modern syntax (destructuring/spread, class+private fields, optional
  chaining, nullish, logical assignment, async + top-level await) and assert it
  transforms + runs correctly. Fails if a future esbuild regresses the boot path.
- dependency-shape: assert the resolved esbuild stays within tsx's declared
  range, so nobody reintroduces the out-of-range override this issue proposed.

No production code changed; no esbuild version pinned.

* fix(deps): add missing runtime deps @toon-format/toon and safe-regex (#5771)

Both packages are imported at runtime but were only declared for their
type shims (safe-regex was via @types/safe-regex; @toon-format/toon
had no declaration at all). Missing runtime deps mean:

- open-sse/services/compression/engines/headroom/toon.ts imports
  @toon-format/toon → MODULE_NOT_FOUND on cold pnpm/npm install
- open-sse/services/compression/engines/ccr/ccrQuery.ts imports
  safe-regex → MODULE_NOT_FOUND

Both engines are wired into the stacked compression pipeline (default
enabled), so a fresh clone that does not have a stale node_modules
from a previous version crashes as soon as the pipeline runs.

Verified with pnpm ls / grep before/after.

* fix(oauth): clamp grok-cli expired-token expiresIn to a positive value (#5775 follow-up) (#5820)

An already-expired grok-cli token (real expires_at/exp in the past) produced a
negative expiresIn, which is truthy in the import-token route and maps to a PAST
expiresAt — AutoCombo then reads that as 'already expired' and excludes the
connection instead of refreshing it. Clamp with Math.max(1, expiresIn) so an
expired token is treated as due-for-refresh. Extends #5775 (thanks @Chewji9875).

Regression: 2 new cases in tests/unit/grok-cli-oauth.test.ts (expired JWT exp +
expired JSON expires_at), both failing-then-passing.

* fix(model-aliases): back custom-alias store with globalThis (#5777 follow-up) (#5821)

#5777 self-healed the GET /api/settings/model-aliases symptom at the route layer,
but the root cause remained: modelDeprecation.ts held _customAliases in a plain
module-level let, which webpack duplicates across the startup and app-route module
graphs (same class as #5312). Startup hydration landed on one copy; the API route
read the other (empty) one.

Back the store with globalThis (__omniroute_customAliases__) so both instances share
one store — the exact pattern already used by thinkingBudget.ts/backgroundTaskDetector.ts
(#5312). The route-layer DB self-heal from #5777 stays as a harmless fallback.

Extends #5777 (thanks @jleonar2). Regression: tests/unit/model-aliases-globalthis-5777.test.ts
(fails on the plain-let store: never populates globalThis, never reads a sibling
instance's write).

* chore(release): rebaseline file-size + test-masking ratchets for v3.8.43 (#5609)

DRIFT acumulado dos 109 commits do ciclo v3.8.43 (fast-gate PR->release
nao roda check:file-size/test-masking; base-reds so afloram na release-PR):
- file-size: 8 god-files existentes cresceram + 2 arquivos novos acima do cap
  + 4 test files cresceram -> frozen ajustado ao estado atual.
- test-masking: chatgpt-web.test.ts 281->280 asserts allowlisted (#5549
  consolidou 2 assert.equal num unico map-driven; refactor legitimo, nao masking).
Modularizacao dos god-files deferida (#3501).

* refactor(sse): extract openai-to-gemini pure helpers into a leaf (#5824)

Split open-sse/translator/request/openai-to-gemini.ts (873 -> 756 LOC, back under
the 800-line cap) by moving the module-private pure helpers — the historical-tool-
context string builders (stringifyHistoricalToolArguments, buildInertHistorical*,
escapeHistoricalContext*, buildHistoricalToolResultContext), deepCleanUndefined,
extractClientThoughtSignature, buildChangedToolNameMap, isVertexGeminiProvider, and
applyAntigravityGenerationDefaults (with its GeminiGenerationConfig shape) — into
openai-to-gemini/helpers.ts.

These were module-private, so the translator's public API is unchanged; the host
imports them back internally. Bodies are verbatim: the code-line multiset of host +
leaf equals the original. Adds tests/unit/openai-to-gemini-helpers-split.test.ts
pinning the leaf's pure behaviour (escaping, undefined-pruning, signature extraction,
antigravity generation-config defaults) and the host wiring.

* fix(db): re-export modelContextOverrides from localDb (check:db-rules #5609)

* test(discovery): wire tests/unit/memory into node runner glob (#5609)

typed-decay.test.ts (TV6 typed memory decay, 15 asserts) sat in
tests/unit/memory/ which no runner glob collected -> orphan (never ran).
Adds 'memory' to the subdir brace-glob in all runner sources (package.json
scripts + ci.yml shards) and the COLLECTORS mirror in check-test-discovery.mjs
(drift-check keeps them in sync). Passes standalone (15/15); DATA_DIR isolation
handled per-file by tests/_setup/isolateDataDir.ts.

* test: align 3 stale release tests to landed behavior (#5609)

Base-reds surfaced on the release PR (fast-gate PR->release skips these shards):
- api-manager-page-static: Self-service Visibility now has 5 switches (added the
  API-key provider quota-policy bypass toggle, #5731); bump inventory 4->5 while
  keeping the invariant that every switch declares type=button (verified 5/5 typed).
- security-hardening (callLogs PII): #5725 extracted sanitizeErrorForLog into
  callLogs/format.ts; assert the new wiring (callLogs imports it + format.ts imports
  piiSanitizer) instead of the removed direct import — PII sanitization still intact.
- memory-glm-injection: #5610 made GLM 5.1+ ACCEPT the system role (z.ai docs), so
  glm-5.1 must PRESERVE system, not fold it. Flip the stale #1701-era assertion.

* test(shared): align t3-web web-session expected metadata with hintKey (#5835)

The t3-web provider metadata intentionally carries `hintKey: "t3ChatWebCookieHint"`
(#5465 — the generic cookie hint reads circular for t3.chat), but the metadata
assertion in web-session-credentials was never updated, so it deep-equals against
an object missing the field. This is a stale-test base-red on release/v3.8.43 that
turns the whole PR queue's "Unit Tests fast-path (1/2)" red. Align the expected
object to the shipped source of truth.

* test(compression): de-flake rtk_discover sample seeding

seedSamples() persisted two byte-identical raw outputs. The raw-output
filename is keyed on Date.now() (ms) + a content hash (rawOutput.ts), so
two identical captures landing in the same millisecond collapse to one
file (the 2nd write overwrites the 1st) -> sampleCount 1 instead of 2.
Reproduced at ~25% (501/2000 trials), matching the intermittent
Coverage Shard (5/8) failure on fast CI runners. Seed two DISTINCT
captures so the store deterministically holds 2 samples regardless of
timing (0/2000 collisions after the change).

* test(e2e): anchor compression-studio smoke on play-input, not async play-lane

The T03 smoke asserted `play-lane` visible on mount, but those per-lane
buttons only render after a preview-compression run populates
`batch.lanes` (usePreviewCompression keeps batch null until run(); there
is no mount auto-run). The smoke intentionally does not drive a
compression cascade, so `play-lane` can never appear -> the E2E added in
 #5727 failed all 3 retries (E2E Tests 4/9). Anchor on the always-present
`play-input` panel, which proves the studio body mounted without needing
async lane data.

* fix(security): explicit http(s) scheme allowlist in linkifyText href

CodeQL flagged the <a href> in LinkifiedText (#5486) with js/xss (high)
and js/client-side-unvalidated-url-redirection (medium) because href
traces back to user-provided text. URL_RE already requires an http(s)://
prefix, so a javascript:/data: scheme can never reach href — but that
guarantee was only implied by the regex. Validate the scheme explicitly
via new URL().protocol before exposing href (non-http(s) degrades to
plain text): defense-in-depth that also makes the sink provably safe to
static analysis. Regression test added.

* fix(ci): register mark-account-unavailable test in stryker tap.testFiles

check:mutation-test-coverage --strict (Fast Quality Gates) flagged
tests/unit/mark-account-unavailable-numeric-epoch-guard.test.ts as a
covering unit test missing from stryker.conf.json tap.testFiles, so its
mutant kills would not count (--strict). Add it. Pre-existing tap.testFiles
drift on the release tip that fails Fast Quality Gates on every PR into
release/v3.8.43, not just this branch.

* chore(release): rebaseline eslintWarnings ratchet 4121->4158 (v3.8.43 cycle drift)

* chore(release): rebaseline complexity 1981->1982 + cognitive-complexity 842->845 (v3.8.43 cycle drift)

* chore(release): rebaseline deadExports 225->227 (v3.8.43 cycle drift)

* fix(dashboard): add error boundaries for Combos and MITM Proxy pages (#5788)

Integrated into release/v3.8.43

* fix(cli): rename process title to omniroute (#5791)

Integrated into release/v3.8.43

* fix(providers): add claude-sonnet-5 to Kiro model catalog (#5796)

Integrated into release/v3.8.43

* fix(kiro): bound Claude id dash->dot minor group to protect date-suffixed ids (#5825)

Integrated into release/v3.8.43

* fix(db): allowlist modelContextOverrides as intentionally-internal to green release DB-rules gate (#5798) (#5827)

Integrated into release/v3.8.43

* fix(sse): stop reasoning-summary drop + duplicated deltas on claude→codex streaming (#5786) (#5832)

Integrated into release/v3.8.43

* fix(dashboard): guard null modelAliases values in model picker (#5792)

Integrated into release/v3.8.43

* fix(github): drop trailing assistant prefill for Copilot chat (#5802)

Integrated into release/v3.8.43

* fix(oauth): disambiguate OAuth connections on username to prevent cross-IdP overwrites (#5803)

Integrated into release/v3.8.43

* fix(translator): strip orphaned tool results across request formats (#5805)

Integrated into release/v3.8.43

* fix(kiro): stop injecting placeholder user turn on tool-result turns (#5807)

Integrated into release/v3.8.43

* fix(mitm): clean up privileged hosts entries on exit when possible (#5808)

Integrated into release/v3.8.43

* fix(translator): prevent doubled tool args in OpenAI-to-Claude (#5828)

Integrated into release/v3.8.43

* fix(usage): keep tool definitions visible when request log is truncated (#5829)

Integrated into release/v3.8.43

* fix(db): preserve healthCheckInterval=0 across create/update (#5822)

Integrated into release/v3.8.43

* fix: unify dashboard csrf origin fallback (#5856)

Integrated into release/v3.8.43

* fix(kimi-web): migrate to www.kimi.com Connect-RPC API (kimi.moonshot.cn retired) (#5858)

Integrated into release/v3.8.43

* fix(qwen-web): unblock validator + chat completion (retired endpoint + missing SPA version header) (#5855)

Integrated into release/v3.8.43

* fix(antigravity): 429 hang on credit exhaustion and precise reset time lockout (Cleaned) (#5846)

Integrated into release/v3.8.43

* fix(cli): correct rootDir resolution in doctor.mjs on Windows (#5844) (#5845)

Integrated into release/v3.8.43

* Show startup time in ready banner (#5799)

Integrated into release/v3.8.43

* extracted CorrelationId observability changes from #5275 (#5834)

Integrated into release/v3.8.43

* refactor(executors): deduplicate shared utilities and add comprehensive tests (#5720)

Integrated into release/v3.8.43

* Harden provider node URL validation (#5760)

Integrated into release/v3.8.43

* [codex] Tune adaptive stream readiness timeouts (#5767)

Integrated into release/v3.8.43

* fix: restore om-usage HTTP endpoint (#5859)

Integrated into release/v3.8.43

* fix(sse): strip zero-width markers from streamed responses (parity with non-streaming) (#5857)

Integrated into release/v3.8.43

* [codex] Protect long-running agent goal streams (#5772)

Integrated into release/v3.8.43

* refactor(oauth): remove dead legacy OAuth service classes (#5838)

The src/lib/oauth/services/ service-class hierarchy is superseded — the live OAuth
flow runs through src/lib/oauth/providers.ts + providers/. The old per-provider
'class *Service extends OAuthService' implementations and their barrel had zero
production or test references. Removed oauth/openai/github/claude/codex/antigravity/
qwen/qoder + the index barrel (-1559 LOC). Kept kiro.ts, cursor.ts, codexImport.ts
(routes import them directly by path, never via the deleted barrel).

Proven safe by typecheck:core staying green (a live reference would fail the build)
+ a filesystem guard test pinning the removal. Salvage of closed PR #5039.
gaps v3.8.42 - T10 (5.7).

* chore(docs): scope release-freeze to /generate-release only (Hard Rule #21) (#5839)

A freeze is authorized ONLY inside /generate-release (raised Phase 0a,
lifted Phase 12c). No campaign/session/agent may open a release-freeze
mid-development; if one is ever unavoidable outside the release flow it
must be requested from the operator in chat first with an explicit
"estou criando um freeze" alert. Also codifies: never lift an active
captain freeze to unblock campaign merges (it auto-lifts at 12c).

* fix(chat): preserve JSON default when stream is omitted (#5866)

* fix(chat): preserve JSON default when stream is omitted

* chore(chat): type route record guard

* fix(api): gate early SSE keepalive on explicit stream intent, keep body untouched

Remove the stream:false body normalization so the legacy streaming
default (resolveStreamFlag) and the per-key streamDefaultMode json
opt-in keep deciding the response framing; the keepalive wrapper is
only applied when stream:true is explicit or Accept forces SSE.

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

---------

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

* feat(usage): report usage command quotas as percentages + honor observed provider quota resets (#5874)

* feat: report usage command quotas as percentages

Convert @@om-usage and the HTTP usage endpoint to report personal API key quotas as remaining percentages while keeping USD amounts out of the command output. Scale provider quota remaining percentages by the configured quota cutoff so the protected reserve reads as 0% left. Restore provider USD cost drilldown in the quota dashboard.\n\nAlso sync the 3.8.43 i18n changelog mirrors so the docs-sync pre-commit gate remains green.\n\nTests: DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/internal-usage-command.test.ts; DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/api-key-usage-limits.test.ts; DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/provider-window-costs.test.ts; DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/api-manager-usage-command.test.ts tests/unit/apikeys-usage-command.test.ts; npx eslint <changed files>; npm run typecheck:core; npm run build; npm run check:migration-numbering; npm run check:docs-sync; docker build --target runner-base

(cherry picked from commit f66abd2028)

* fix: honor observed provider quota resets

Detect same-resetAt quota resets when provider usage drops back to the reset floor, and prefer that observed snapshot over stale recorded weekly events for provider USD windows and API-key USD quotas.\n\nTests: npx eslint changed files\nTests: npm run typecheck:core\nTests: DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/lib/quota-reset-events.test.ts tests/unit/provider-window-costs.test.ts tests/unit/api-key-usage-limits.test.ts\nTests: npm run build\nTests: docker build --target runner-base --build-arg OMNIROUTE_BUILD_MEMORY_MB=4096 -t omniroute:quota-reset-window-20260702002300 .

(cherry picked from commit 39c12a6f17)

* docs(changelog): credit usage quota percentages extraction from #5863

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

---------

Co-authored-by: Wital <wital@example.com>

* fix(github): keep Copilot access-token sessions active (#5875)

* fix(github): keep Copilot access-token sessions active

GitHub Copilot device-flow accounts may have a GitHub access token and short-lived Copilot token without a refresh token. The proactive health check was treating that as terminal no_refresh_token and marking the connection expired minutes after login. Keep those sessions active, clear stale no_refresh_token state, and refresh the Copilot sub-token when needed.\n\nTests:\n- npx eslint src/lib/tokenHealthCheck.ts tests/unit/token-health-no-refresh-token-expired-5326.test.ts\n- DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/token-health-no-refresh-token-expired-5326.test.ts tests/unit/token-health-check.test.ts tests/unit/token-health-check-circuit-breaker.test.ts tests/unit/token-refresh-service.test.ts tests/unit/token-refresh-route-service.test.ts tests/unit/executor-github.test.ts\n- npm run typecheck:core\n- npm run build

(cherry picked from commit 68095d4796)

* docs(changelog): credit Copilot token-health fix extraction from #5863

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

---------

Co-authored-by: Wital <wital@example.com>

* feat: add NEXT_PUBLIC_LIVE_WS_PUBLIC_URL for custom domain WebSocket support (#5878)

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

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

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

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

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

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

---------

Co-authored-by: Septianata Rizky Pratama <ian.rizkypratama@gmail.com>

* Add .editorconfig to improve repository standards (#5879)

* chore(ci): pass sonar.projectVersion to SonarQube scan so the new-code baseline advances per release (#5880)

* fix(dashboard): Modal — two-field auth (Token ID + Token Secret) (#5446) (#5881)

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

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

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

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

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

* docs(changelog): add v3.8.43 bullet for Modal two-field auth (#5446)

* fix(mcp): forwarded caller auth wins over OMNIROUTE_API_KEY env fallback (#5819) (#5882)

* fix(middleware): run operator hook code in hardened vm sandbox instead of new Function (#5872) (#5885)

* fix(providers): include custom compatible providers in auto/ routing (#5873) (#5886)

* fix(db): honor autoBackupEnabled setting for pre-write backups (#5871) (#5888)

* fix(dashboard): gate Token Expired badge on terminal testStatus, not raw token expiry (#5836) (#5883)

* docs: use pnpm --allow-build flag instead of unsupported approve-builds -g (#5554) (#5884)

* fix(dashboard): pre-fill Modal Validation Model Id with the server probe model (#5446) (#5892)

* fix(api): strip upstream x-middleware-* headers from proxied responses (#5849) (#5893)

* fix(providers): restore codex inference for unprefixed gpt-5.5 on codex-only setups (#5887) (#5895)

* test(autoCombo): stabilize model fitness source expectation (#5890)

* test(autoCombo): make fitness source test stable against model caps

* chore(ci): retrigger checks for PR 5890

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

---------

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

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

* routing: add router backend registry

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

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

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

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

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

---------

Co-authored-by: KooshaPari <kooshapari@gmail.com>

* fix(ci): re-green release/v3.8.43 fast-gates — db-rules stale allowlist + 4 more base-reds (#5798) (#5896)

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

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

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

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

* fix(docs): add required MDX frontmatter to ROUTER_BACKENDS ADR (build base-red, #5798)

* fix(image): keep bare gpt-5.5 codex mapping in image resolver (#5902)

* fix: preserve codex bare image model over combo shadowing

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

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

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>

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

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

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

* docs(changelog): keep #5842 bullet additive over release tip

* chore(release): v3.8.43 — 2026-07-02

* chore(release): allowlist 3 verified-legitimate test-assert reductions (#5805/#5856/#5855)

* chore(release): rebaseline file-size caps for base.ts + 2 aligned test files (v3.8.43 release-close)

* docs(changelog): add v3.8.43 Contributors section + sync i18n mirrors

---------

Co-authored-by: KooshaPari <42529354+KooshaPari@users.noreply.github.com>
Co-authored-by: Arthur Bodera <abodera@gmail.com>
Co-authored-by: Wahyu Hidayatulloh Pamungkas <87377496+Stazyu@users.noreply.github.com>
Co-authored-by: skyzea1 <161649495+skyzea1@users.noreply.github.com>
Co-authored-by: José Victor Ferreira <root@josevictor.me>
Co-authored-by: Choti Wongbussakorn <126886556+Chewji9875@users.noreply.github.com>
Co-authored-by: backryun <bakryun0718@proton.me>
Co-authored-by: Jan Leon <Jan.gaschler@gmail.com>
Co-authored-by: warelik <warelik@users.noreply.github.com>
Co-authored-by: WITALO ROCHA <witalo_rocha@hotmail.com>
Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
Co-authored-by: Alex <alexgild@gmail.com>
Co-authored-by: Chirag Singhal <76880977+chirag127@users.noreply.github.com>
Co-authored-by: Ardem2025 <ardemb22@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: KooshaPari <koosha@example.com>
Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com>
Co-authored-by: herjarsa <herjarsa@users.noreply.github.com>
Co-authored-by: PizzaV <103120356+pizzav-xyz@users.noreply.github.com>
Co-authored-by: OpenClaw Auto <openclaw-auto@example.invalid>
Co-authored-by: jleonar2 <92810914+jleonar2@users.noreply.github.com>
Co-authored-by: marcelpeterson <marcelpeterson@users.noreply.github.com>
Co-authored-by: Yuan Li <atom.long@outlook.com>
Co-authored-by: janeza2 <49841619+janeza2@users.noreply.github.com>
Co-authored-by: Aris <arissunandar399@gmail.com>
Co-authored-by: Isha Tiwari <156085572+ishatiwari21@users.noreply.github.com>
Co-authored-by: Markus Hartung <mail@hartmark.se>
Co-authored-by: Nguyen Minh <lop123thcs@gmail.com>
Co-authored-by: Denis Kotsyuba <kocubads96@gmail.com>
Co-authored-by: Wital <wital@example.com>
Co-authored-by: Septianata Rizky Pratama <ian.rizkypratama@gmail.com>
Co-authored-by: Shiva Vinodkumar <127319648+shiva24082@users.noreply.github.com>
Co-authored-by: kooshapari <kooshapari@users.noreply.github.com>
Co-authored-by: KooshaPari <kooshapari@gmail.com>
2026-07-02 10:47:13 -03:00
Diego Rodrigues de Sa e Souza
0adae00c7b Release v3.8.42 (#5459)
Release v3.8.42 — full CHANGELOG in CHANGELOG.md.

CI: 103 checks green incl. CodeQL (all languages), Semgrep, all 8 unit shards,
coverage, Node 24 compat, and integration tests. Full unit suite validated
locally: 19437 pass / 0 fail. The 3 red checks are advisory and do not gate
main (no required status checks): SonarCloud/SonarQube new-code coverage gate,
and PR Test Policy (test-masking detector flagging the legitimate dead-Phind
provider removal in #5530 — reviewed, correct).

Includes cycle-close reconciliation + repair of inherited base-red tests from
#5480/#5527/#5427/#5521 that the PR->release fast-path did not exercise.
2026-06-30 06:54:29 -03:00
KooshaPari
baa5ad5023 docs: add relay backend strategy guide (#5533) 2026-06-30 02:16:09 -03:00
Diego Rodrigues de Sa e Souza
78f09c8d9f Release v3.8.41 (#5327)
Release v3.8.41 — 52 commits since v3.8.40 (19 CHANGELOG bullets, 11 contributors).

All gating CI green: Unit×8, Coverage×8, Vitest, Package Artifact, Quality Ratchet, CodeQL, Lint, Docs Sync (Strict), Node 24/26 compat, E2E×9, Integration, Electron smoke.

Advisory checks overridden (main unprotected): PR Test Policy = test-masking heuristic on the cumulative 52-commit assert delta (legitimate dead-code-sweep removals + consolidations, reviewed per-PR); SonarCloud/SonarQube = new-code maintainability/coverage quality gate (CodeQL/Semgrep/Security/npm-audit/Dependabot all clean — not a security finding).
2026-06-29 16:51:03 -03:00
Diego Rodrigues de Sa e Souza
7c23dab64d Release v3.8.40
v3.8.40 cycle integration → main. All test gates green (Unit/Integration/Coverage/Node-compat/Quality-Ratchet). The only red check, 'PR Test Policy', is the test-masking heuristic firing on the cumulative ~57-commit release diff (legitimate assert consolidations already reviewed per-PR — Gemini CLI removal #5246, retired GPT models #5280, provider catalog refreshes); overridden with --admin per the documented release-PR convention. CodeQL/SonarQube advisory scans non-blocking; #5278's code already passed CodeQL on main. Homologated on VPS 192.168.0.15 (v3.8.40 healthy).
2026-06-29 08:40:06 -03:00
Arthur Bodera
1c18be4f8f fix: centralize public origin checks for proxied dashboards (#5278)
Centralizes browser-mutation origin validation into `src/server/origin/publicOrigin.ts` and wires it through the authz pipeline, replacing the per-route same-origin-only check that 403'd dashboard mutations when served behind a reverse proxy on a different public origin. The new module resolves the allowed public origin from configured base-URL env vars or trusted forwarded headers (only when OMNIROUTE_TRUST_PROXY is set AND the peer is loopback/LAN via peer-stamp), validates Sec-Fetch-Site metadata, and sanitizes Host/Forwarded inputs (rejects control chars, userinfo, path/query in Host).

Reviewed sound; validated locally: authz/public-origin + pipeline suites 27/27 green (incl. invalid-origin reject + configured-origin accept), typecheck clean. Maintainer fix-up: moved the new test from tests/unit/server/ (not collected by any runner — orphan-test gate fail) into tests/unit/authz/. Remaining red CI shards are the pre-existing #4076 Dockerfile heap base-red on `main` (unrelated; de-brittled in the v3.8.40 release line).

Co-authored-by: Thinkscape <Thinkscape@users.noreply.github.com>
2026-06-29 01:43:14 -03:00
Diego Rodrigues de Sa e Souza
7b139fdb5e Release v3.8.38 (#5078)
* chore(release): open v3.8.38 development cycle

* fix(executors): strip client_metadata for cerebras and mistral (#4727)

Integrated into release/v3.8.38 (leva 5)

* fix(codebuddy): only send reasoning params when client requests reasoning (#5019)

Integrated into release/v3.8.38 (leva 5)

* fix(sse): keep streaming for forceStream providers when client requests JSON (#5021)

Integrated into release/v3.8.38 (leva 5)

* fix(sse): guard non-JSON SSE lines and duplicate [DONE] (#4937)

Integrated into release/v3.8.38 (leva 5)

* feat(blackbox): refresh provider model catalog (#4935)

Integrated into release/v3.8.38 (leva 5)

* fix(sse): dedupe case-variant Anthropic version/beta headers (#4846)

Integrated into release/v3.8.38 (leva 5)

* feat(sse): Kiro inline <thinking> stream splitter (#4911)

Integrated into release/v3.8.38 (leva 5)

* feat(cursor): parse Composer DeepSeek-style inline tool calls (#4912)

Integrated into release/v3.8.38 (leva 5)

* feat(proxy): auth-less host:port batch import (#4938)

Integrated into release/v3.8.38 (leva 5)

* fix(oauth): support Kiro IDC (organization) token import (#4944)

Integrated into release/v3.8.38 (leva 5)

* fix(translator): preserve cache_control for DashScope OpenAI-compat providers (port from 9router#2069) (#5013)

Integrated into release/v3.8.38 (leva 5)

* fix(tts): resolve Gemini TTS models from catalog (#4934)

Integrated into release/v3.8.38 (leva 5)

* fix(sse): don't cool down the connection on a self-inflicted upstream timeout (504) (#5064)

Integrated into release/v3.8.38 (leva 5)

* fix(sse): robust Anthropic /v1/messages streaming — real ping keepalive + client-disconnect guard (#5063)

Integrated into release/v3.8.38 (leva 5)

* feat(video): add Alibaba DashScope (wan2.7-t2v) provider (#5051)

Integrated into release/v3.8.38 (leva 5)

* fix: preserve model hidden flags (isHidden) across model sync (#5086)

Integrated into release/v3.8.38 (leva 5)

* fix(models): derive model discovery config from registry modelsUrl (#5087)

Integrated into release/v3.8.38 (leva 5)

* fix(compression): replace fileURLToPath(import.meta.url) with runtime anchors for standalone bundle (#5089)

Integrated into release/v3.8.38 (leva 5)

* feat(cc): add summarized thinking display toggle (#5055)

Integrated into release/v3.8.38 (leva 5)

* Harden selected API error responses (#5032)

Integrated into release/v3.8.38 (leva 5)

* chore(quality): rebaseline file-size for leva 5 PR batch drift

6 frozen files grew from merged leva-5 PRs (cursor #4912, kiro #4911,
videoGeneration #5051, default #4727, base #4846, chat #5064); all covered
by per-PR tests. See _rebaseline_2026_06_26_leva5 in the baseline.

* feat(compression): compression playground (Play + Compare tabs) in the studio (#5080)

Integrated into release/v3.8.38

* fix(combo): fail over on empty-content 502 instead of exhausting the provider (#5085) (#5104)

* fix(dashboard): surface detailed credential-validation error in add-connection modal (#5088) (#5106)

* feat(providers): allow local/private provider URLs by default with scoped metadata-safe guard (#5066) (#5107)

* fix(diagnostics): treat non-streaming Claude messages shape as valid output (#5108) (#5116)

* fix(db): translate pt-BR SQLite driver-fallback log lines to English (#5103) (#5115)

* fix(sse): repair release base-reds — malformed-response false positives + header casing + stale tests (#5117)

Repairs the release/v3.8.38 base-reds; unblocks #5078.

* chore(quality): rebaseline file-size for responseSanitizer (#5117) + AddApiKeyModal drift

* fix(translator): forward image tool_result blocks as image_url (#5100)

Base-reds fixed (#5117); image tool_result→image_url. Integrated into release/v3.8.38.

* fix(responses): default text.format for openai-compatible responses providers (#5101)

Base-reds fixed (#5117); default text.format + file-size rebaseline. Integrated into release/v3.8.38.

* feat(dashboard): expose Fusion judgeModel + fusionTuning in the combo editor (#5074)

Base-reds fixed (#5117); Fusion editor + file-size rebaseline. Integrated into release/v3.8.38.

* feat(quota): add opt-in Codex/Claude auto-ping keepalive (#5102)

Base-reds fixed (#5117); auto-ping keepalive + file-size rebaseline. Integrated into release/v3.8.38.

* test(release): relocate 2 orphan test files into the collected flat tests/unit dir (#5120)

Unblocks Lint (test-discovery) on #5078. Integrated into release/v3.8.38.

* fix(translator): preserve reasoning-replay reasoning_content + repair 3 release-green test reds (#5122)

Repairs 3 release-green test reds + test-masking; unblocks #5078.

* test(golden): redact live Node version from provider translate-path snapshot (#5125)

Final golden unblock for #5078.

* test(golden): redact OmniRoute app version from translate-path snapshot (#5126)

Coverage shard golden unblock for #5078.

* Ignore disconnect races during in-band stream error handling (#5007)

Integrated into release/v3.8.38

* Track final connection IDs in failover logs (#5016)

Integrated into release/v3.8.38

* fix(sse): convert Gemini body to OpenAI format in antigravity MITM handler (#4845)

Integrated into release/v3.8.38 (rebased on tip, CHANGELOG re-injected)

* feat(providers): add ZenMux Free session-cookie provider (#5105)

Integrated into release/v3.8.38 (rebased on tip, CHANGELOG re-injected)

* feat(dashboard): click-to-edit model alias in provider page (#5119)

Integrated into release/v3.8.38 (rebased on tip, i18n scope verified, CHANGELOG re-injected)

* feat(mcp): web-session robustness — cookie dedup (PR6) + browser-pool observability (PR7) (#3368) (#5121)

Integrated into release/v3.8.38 (rebased on tip; cookie-dedup branch extracted to findExistingCookieConnection helper → complexity-neutral; CHANGELOG added)

* fix(usage): dedupe request-usage logging and debounce stats (#4940)

Integrated into release/v3.8.38 (rebased on tip; DB-handle hang was stale-base artifact — resetDbInstance already closes the handle, test green 5/5; file-size drift consolidated at release; CHANGELOG re-injected)

* fix(dashboard): key model visibility toggle on canonical providerId (#5091)

Integrated into release/v3.8.38 (retargeted main→release; .tsx visibility-key test green 2/2)

* chore(deps): bump actions/cache from 5.0.5 to 6.0.0 (#5112)

Integrated into release/v3.8.38 (retargeted main→release; workflow-only actions/cache bump — unit failures were stale main base-reds)

* fix(streaming): harden long OpenAI-compatible SSE streams (#5124)

Integrated into release/v3.8.38 (rebased on tip; streamHandler conflict with #5007 disconnect-guard resolved — both coexist, stream-handler 22/22 green)

* feat: Add Grok Build (xAI) provider with OAuth import-token flow (#5020)

Integrated into release/v3.8.38 (rebased on tip; Hard Rule #11 fix — Grok public client_id now via resolvePublicCred(grok_id), 3 literals removed; grok-oauth 7/7 + check:public-creds green)

* feat(providers): add Factory (factory.ai) as a subscription gateway provider (#5065)

Integrated into release/v3.8.38 (rebased on tip; added factory registry test for PR Test Policy + fixed check:env-doc-sync phantom FACTORY_API_KEY; factory loads in PROVIDERS, no Zod issue — that flag was a false positive)

* chore(test): reconcile golden snapshot + apikey count for new providers

#5020 (grok-cli), #5065 (factory), #5105 (zenmux-free) added providers but did
not regenerate tests/snapshots/provider/translate-path.json (now +3 entries) nor
bump the APIKEY_PROVIDERS count (159->160 for the factory gateway). Test-only
reconciliation; no production change.

* fix(resilience): harden quota and model lockout edge cases (#5093)

Integrated into release/v3.8.38 (rebased on tip). TRUST-BUT-VERIFY: dropped the PR's 0dd7df641 'fix unit gates' commit which reverted #5122 reasoning-replay (preserveReasoningContent) + re-introduced #4849 O(n^2) growth, and restored 5 tests it had realigned. Kept only the 3 declared resilience fixes (quota cutoff guard, gemini MIME, model-lockout maxCooldownMs); 23/23 green.

* Hydrate quota cache and scope auto combo candidates (#5015)

Integrated into release/v3.8.38 (rebased on tip). Kept core quota-cache hydration + auto-combo candidate scoping + combos UI; dropped out-of-scope toolCloaking refactor (conflicted with #4813 stripEnumDescriptions — took tip) and the unrelated sse-auth test split. Added quota-cache-hydrate-5015 regression test (Rule #18); combo-account-allowlist 8/8 + hydration 2/2 green.

* chore(quality): reconcile complexity + file-size baselines for v3.8.38 owner-PR batch

complexity 1972->1978 (+6) and file-size providers.ts 1093->1107 / usageHistory.ts
934->983 — drift from the /review-prs merge batch (#4845/#5105/#5020/#4940/#5093/
#5015 + #5121 cookie-dedup helper extraction). check:complexity/check:file-size do
not run on the PR->release fast-path, so the branch accrued unmeasured; all legit
feature/fix growth, not regression. See per-key justifications in each baseline.

* fix(security): exact-host Anthropic baseUrl check (CodeQL js/incomplete-url-substring-sanitization #674) (#5130)

The anthropic-compatible Bearer-fallback gate decided whether a configured baseUrl
targeted the official api.anthropic.com host via a substring `.includes("api.anthropic.com")`.
A look-alike upstream such as `https://api.anthropic.com.evil.test` or
`https://evil.test/?x=api.anthropic.com` matched the substring and was wrongly treated as
official, suppressing the Bearer fallback meant for third-party gateways
(CodeQL #674, js/incomplete-url-substring-sanitization, high).

Replace the substring test with an exported `isOfficialAnthropicBaseUrl()` helper that
parses the URL and compares the hostname for exact equality. Empty baseUrl stays official;
scheme-less hosts are parsed with an assumed https://; an unparseable baseUrl falls back to
third-party (Bearer emitted) as the safer default. Behavior for legitimate official/third-party
baseUrls is unchanged.

Adds tests/unit/anthropic-official-baseurl-host.test.ts covering official, look-alike,
scheme-less, and unparseable inputs plus a static guard that the substring pattern is gone.

* fix(proxy): repair one-click Deno & Cloudflare relay deployments (#5128) (#5132)

* fix(services): embed WS proxy honours LIVE_WS_HOST; reject empty messages early (#5110) (#5133)

* fix(api): resolve /v1/models/{id} case-insensitively (#5082) (#5135)

* fix(providers): add MiniMax M3 & Nemotron 3 Ultra to Cline catalog (#3321) (#5136)

* fix(proxy): make SOCKS5 handshake timeout tunable via SOCKS_HANDSHAKE_TIMEOUT_MS (#5109) (#5137)

* feat(sidebar): add support for colored menu icons (#3812)

Integrated into release/v3.8.38 (recreated on tip — fork had unrelated history; added getSidebarIconAccent regression test, Rule #18). Clean 2-file UI feature.

* fix(providers): complete grok-cli OAuth wiring + zenmux-free web-session metadata

Base-red repair for #5020 (grok-cli) and #5105 (zenmux-free), surfaced by the
full CI on the release PR (#5078) — the PR->release fast-path does not run the
oauth-providers-config / web-session-credentials / provider-consistency gates.

- grok-cli: register in OAUTH_PROVIDERS (providers.ts canonical list, fixes
  check:provider-consistency), add OAUTH_PROVIDER_IDS.GROK_CLI + GROK_CLI_CONFIG
  in oauth constants (provider config now sourced there, not a local literal),
  align oauth-providers-config.test.ts (EXPECTED_PROVIDER_KEYS + config map).
- zenmux-free: declare its web-session credential requirement (full Cookie header)
  in WEB_SESSION_CREDENTIAL_REQUIREMENTS.

Local: oauth-providers-config 27/27, web-session-credentials 4/4, grok-cli-oauth
7/7, check:provider-consistency OK, +115 OAUTH_PROVIDERS tests green.

* Fix resilience settings page response mapping (#5139)

Integrated into release/v3.8.38. Thanks @rdself for the fix and the regression test.

* fix(kiro): retire claude-sonnet-4.5 from catalog + pin 400 model-unavailable test (#5140)

Extracted the real change from #5140 (the bot PR regenerated the entire
freeModelCatalog.data.ts + touched package-lock.json; only the targeted
edits are kept here):
- remove claude-sonnet-4.5 from the Kiro registry entry
- remove the matching kiro free-model catalog row
- pin Kiro's verbatim 400 "Invalid model..." to isModelUnavailableError

Closes #4484

* fix(sidebar): drop orphan `settings` accent color (typecheck:core red) (#5142)

SIDEBAR_ICON_ACCENTS is typed Partial<Record<HideableSidebarItemId, string>>,
but `settings` is not a hideable item id (only `settings-general`,
`settings-appearance`, … and `context-settings` exist; there is no item with
`id: "settings"`), so the accent was unreachable. It broke `typecheck:core`
on the release tip ("'settings' does not exist in type …", introduced by
#3812 colored menu icons). Removing the orphan key restores a clean
typecheck:core (rc=0).

* feat: salvage batch 2 — diagnostics null-guard (#5096) + observed quota reset windows (#5025) (#5141)

* fix(diagnostics): null-guard content blocks in detectMalformedNonStream

A null (or non-object) entry in a Claude-native `content` array made the
non-stream classifier throw `TypeError: Cannot read properties of null
(reading 'type')`, crashing the malformed-response detection path. Guard
before type-asserting each block: a null/non-object block is simply skipped.

Two regression tests added (null block among valid blocks → null; only-null
blocks → empty_choices).

Salvaged from closed PR #5096 (base-stale; only the defensive guard — the
Claude-shape recognition it also carried already landed via #5108).

Co-authored-by: herjarsa <herjarsa@users.noreply.github.com>

* feat(quota): persist observed provider quota reset windows

Adds `provider_quota_reset_events` (migration 108) + `db/quotaResetEvents.ts`
to record real upstream weekly-quota window transitions whenever a quota
refresh shows the reset rolling to a new cycle (different day, later resetAt).
`apiKeyUsageLimits` now prefers the observed window start over the inferred
`resetAt − 7d`, falling back to snapshot inference when no event is recorded
yet. `quotaCache.setQuotaCache` records the transition opportunistically.

`recordProviderQuotaResetEventIfChanged` only fires for the primary weekly
window (not daily/sonnet), is idempotent (INSERT OR IGNORE on the unique
window key), and no-ops when the reset didn't actually roll. 4 unit tests
(tests/unit/lib/quota-reset-events.test.ts).

Salvaged from closed PR #5025 (which bundled this with two unrelated
features + a colliding migration 104). Renumbered to 108; module re-exported
from localDb (Rule #2).

Co-authored-by: Witroch4 <175152067+Witroch4@users.noreply.github.com>

---------

Co-authored-by: herjarsa <herjarsa@users.noreply.github.com>
Co-authored-by: Witroch4 <175152067+Witroch4@users.noreply.github.com>

* docs(i18n): sync 3.8.38 CHANGELOG section to 41 mirrors (unblock docs-accuracy) (#5144)

The root CHANGELOG [3.8.38] section grew with this cycle's merged PRs, but the
docs/i18n/<lang>/CHANGELOG.md mirrors were not re-synced — drifting >25% in body
size and failing check:docs-sync (the "Docs accuracy" fast-gate step) for every
open PR against the release.

Ran scripts/release/sync-changelog-i18n.mjs 3.8.38 3.8.37 to copy the root
[3.8.38] section into all 41 mirrors. check:docs-all now passes (exit 0).

Sections are copied verbatim; the per-language translation pass runs at release
time via i18n:run — this only restores the size-sync the gate enforces.

* feat(compression): pure per-step fidelity checker (4 invariants, fail-open)

* feat(compression): fidelityGate config + rejected breakdown fields

* feat(compression): wire per-step fidelity gate into stacked pipeline (opt-in)

* feat(compression): preview route accepts fidelityGate flag (playground)

* feat(compression): playground fidelity-gate toggle + lane rejection display

* docs(compression): note fidelityGate advanced thresholds are intentionally API-omitted

* refactor(compression): extract fidelity-gate step helpers to shrink strategySelector (file-size gate)

bodyToText and gateAdvance moved to fidelityGateStep.ts; StackAccumulator exported.
strategySelector: 889->854 (-35). Residual +6 vs pre-Milestone-B frozen 848 is the
irreducible StackOptions.fidelityGate field + two stacked-loop dispatch reads + import.
Baseline updated to 854 with justification. No cycle introduced (import type only).
940 compression tests pass; typecheck clean.

* test(usage): wire usageHistoryDedup under unit runner brace-list (#5145)

Integrated into release/v3.8.38.

* feat: salvage batch from closed stale PRs (#5038, #5057, #5076) (#5138)

Integrated into release/v3.8.38.

* test(combo): deterministic routing-decision matrix for all 17 strategies (#5146)

Integrated into release/v3.8.38.

* feat(compression): fuzzy near-duplicate dedup (session-dedup 2nd pass + playground toggle) (#5143)

Integrated into release/v3.8.38.

* chore(quality): rebaseline file-size for sidebarVisibility.ts + chat.ts drift (#5147)

Mid-cycle drift on release/v3.8.38 from already-merged PRs that the fast-path
(PR->release skips check:file-size) let accumulate without a bump:

- src/shared/constants/sidebarVisibility.ts 1100->1198 (#3812 colored menu
  icons, per-item accent map; #5142 dropped one orphan, net still above frozen)
- src/sse/handlers/chat.ts 1560->1575 (#5064 self-inflicted-timeout cooldown
  skip + #5124 long OpenAI-compatible SSE hardening + #5110 embed-WS
  LIVE_WS_HOST honour / early empty-message reject)

Each covered by its own PR tests; structural shrink of chat.ts tracked in #3501.
Unblocks the Fast Quality Gates for PRs targeting release/v3.8.38.

* chore(release): finalize v3.8.38 CHANGELOG + cycle reconciliation

- Reconcile [3.8.38]: +18 bullets (compression fidelity-gate/fuzzy-dedup #5143,
  quota keepalive #5102, web-session robustness #5121, MiniMax/Nemotron #5136,
  model-visibility #5091, failover logs #5016, disconnect races #5007, sidebar
  orphan #5142, SRE playbooks salvage #5138, new Security #5130 + Maintenance roll-up)
- Credit salvaged-PR authors (@JxnLexn / @KooshaPari / @herjarsa / @Witroch4)
- Remove phantom bullet for CLOSED-not-merged #5092 (setup aggregator never landed)
- Fix isHidden bullet PR citation #4389 -> #5086 (@herjarsa)
- Back-fill forgotten v3.8.36 bullet: #5026 crypto.randomUUID ID-gen (@hamsa0x7)
- Sync 41 i18n CHANGELOG mirrors; README What's New -> v3.8.38
- Rebaseline cycle drift: eslint 3987->4002, cognitive 833->841, dead-exports
  345->346, cyclomatic 1978->1980 (file-size handled by #5147)

* fix(i18n): add missing English UI labels (#5153)

Integrated into release/v3.8.38

* Preserve non-stream reasoning fields for compatible clients (#5155)

Integrated into release/v3.8.38

* feat(compression): ionizer engine — lossy JSON-array sampling reversible via CCR (#5148)

Integrated into release/v3.8.38

* test(combo): gated live smoke for combo strategies (in-process + VPS HTTP) (#5151)

Integrated into release/v3.8.38

* test: refresh release expectations to match current code (#5150)

Integrated into release/v3.8.38 (test-only base-red alignment extracted from #5150)

---------

Co-authored-by: Éder Costa <eder.almeida.costa@gmail.com>
Co-authored-by: José Victor Ferreira <root@josevictor.me>
Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com>
Co-authored-by: fulorgnas <46461624+fulorgnas@users.noreply.github.com>
Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
Co-authored-by: Jan Leon <Jan.gaschler@gmail.com>
Co-authored-by: R. Beltran <rbeltran8000@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: KooshaPari <42529354+KooshaPari@users.noreply.github.com>
Co-authored-by: Ramel Tecnologia - Rafa Martins <146174365+rafacpti23@users.noreply.github.com>
Co-authored-by: herjarsa <herjarsa@users.noreply.github.com>
Co-authored-by: Witroch4 <175152067+Witroch4@users.noreply.github.com>
2026-06-27 09:07:12 -03:00
Diego Rodrigues de Sa e Souza
555b21d296 Release v3.8.37 (#5053)
* chore(release): open v3.8.37 development cycle

* chore(ci): harden release flow — ratchet decoupling, fast-path drift gates, build-scope guard, heap default (#5054)

Implements improvements 1-4 from the v3.8.36 release benchmark (_tasks/release-bench/v3.8.36/PLANO-MELHORIA.md):

1. Quality Ratchet decoupled from flaky coverage (ci.yml): the shard→coverage→ratchet
   chain meant a single flaky Coverage Shard SKIPPED the whole Quality Ratchet on the
   release PR (v3.8.36 #4854), so cycle drift only surfaced post-merge in #5029. The job
   now runs on !cancelled(); coverage download is continue-on-error and the ratchet runs
   --allow-missing, so the DETERMINISTIC gates (eslint/complexity/cognitive/duplication/
   codeql) stay blocking even when coverage is unavailable.

2. Fast-path drift gates (quality.yml PR→release): added check:complexity,
   check:cognitive-complexity, and a new lightweight check:pack-policy (pack-artifact
   unexpected-files check WITHOUT a build, via --policy-only) so drift + stray-tarball-file
   regressions are caught/rebaselined PER-PR instead of cascading onto the release PR.

3. Build heap default 4096→8192 MB (build-next-isolated.mjs): the clean graph peaks
   ~3.9 GB and brushed the old 4 GB ceiling; 8 GB gives headroom. Comment notes heap is
   NOT the fix for a poisoned scope (run check:build-scope instead).

4. check:build-scope gate (new): fails if .ts/.tsx/.js/.jsx files in the tsconfig scope
   exceed a threshold — catches worktrees/cruft leaking into the build scope (the v3.8.36
   OOM root cause: 355,215 vs 4,547 files) BEFORE it detonates next build. Wired into the
   fast-path.

* fix(auth): only trust forwarding headers from loopback TCP peers (#4689)

Integrated into release/v3.8.37 — loopback-gated forwarding headers (IP spoofing fix). Cherry-picked onto current release tip; ipUtils.test.ts 9/9 green.

* fix(codex): treat OAuth 401 as unrecoverable refresh failure (#4686)

Integrated into release/v3.8.37 — codex OAuth 401 treated as unrecoverable refresh. Cherry-picked onto release tip; token-refresh-service.test.ts 38/38 green.

* fix(translator): preserve reasoning_effort for non-Copilot Responses clients (#4688)

Integrated into release/v3.8.37 — preserve reasoning_effort for non-Copilot Responses clients. Cherry-picked onto release tip; tests 47/47 green.

* fix(translator): coerce tool descriptions to strings in OpenAI normalization (#4675)

Integrated into release/v3.8.37 — coerce tool descriptions to strings in OpenAI normalization. Cherry-picked onto release tip; tests 3/3 green.

* feat(sse): x-omniroute-strip-reasoning header to drop reasoning_content (#4678)

Integrated into release/v3.8.37 — x-omniroute-strip-reasoning header. Cherry-picked onto release tip (resolved chatCore.ts/headers.ts adjacency conflict, kept resolveCompressionHeader + isStripReasoningRequested); tests 8/8 green.

* fix(combo): flatten Anthropic tool messages + tool history to prevent upstream 503 (#4648)

Integrated into release/v3.8.37 — flattenToolHistory helper (combo anti-503). Cherry-picked onto release tip; tests 9/9 green.

* feat(headroom): proxy lifecycle management + dashboard UI (Docker sidecar supported) (#4649)

Integrated into release/v3.8.37 — headroom proxy lifecycle (status/start/stop, local-only + spawn-capable per Rules #15/#17). Cherry-picked onto release tip; lifecycle 7/7 + route-guard 43/43 + check:cycles green.

* feat(cli): multi-model support for Factory Droid CLI (#4682)

Integrated into release/v3.8.37 — Factory Droid multi-model support. Cherry-picked onto release tip (kept readJsoncConfig + droidCustomModels imports); droid-custom-models 11/11 green.

* fix(providers): require Default Model in compatible-provider API-key setup (#4641)

Integrated into release/v3.8.37 — require Default Model in compatible-provider API-key setup. Cherry-picked fix + test-move onto release tip (kept release providerSpecificData + QuotaScrapingFields; fixed moved-test import path; baseline rebaseline unneeded, 865<866); UI test 2/2 green.

* fix(dashboard): stop double-masking already-masked API key in list (E2E 3/9 regression) (#4671)

Integrated into release/v3.8.37 — render server-masked key verbatim (drop redundant maskKey call). Note: release's maskKey already guards '****' (since v3.8.34), so this is a safe simplification; added a contract test pinning the **** passthrough invariant (2/2 green, would fail against the pre-guard maskKey = the historical double-mask bug).

* chore(quality): rebaseline file-size for rc17 PR batch drift

Own growth from the merged rc17 PRs (#4678/#4686/#4688) at existing chokepoints —
cohesive, not extractable:
- open-sse/handlers/responseSanitizer.ts 1103->1122 (SanitizeOpenAIResponseOptions + stripReasoning, #4678)
- open-sse/services/tokenRefresh.ts 2070->2090 (codex 401 unrecoverable-refresh guard, #4686)
- tests/unit/token-refresh-service.test.ts 1322->1353 (401 regression case, #4686)
- tests/unit/translator-openai-responses-req.test.ts 1047->1050 (reasoning_effort assertion, #4688)

* docs(env): document HEADROOM_URL in .env.example + ENVIRONMENT.md

The headroom proxy lifecycle (#4649) reads HEADROOM_URL (src/lib/headroom/detect.ts,
default http://localhost:8787) but it was missing from the env contract, tripping
check:env-doc-sync. Adds the var to both .env.example (commented, has a default) and
the Proxy Health table in ENVIRONMENT.md.

* fix(sse): stream writer mock abort() returns a Promise (#4788)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* fix(cli): fall back to default data dir when DATA_DIR is not writable (#4767)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* fix(oauth): verify Cursor installation on Linux before auto-import (#4770)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* fix(sse): track Ollama streaming usage from raw NDJSON chunks (#4754)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* fix(sse): strip enumDescriptions from antigravity tool schema (#4740)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* fix(sse): include low-level cause details in formatProviderError (#4741)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* fix(translator): strip x-anthropic-billing-header in claude-to-openai (#4728)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* fix(sse): gate Kiro image attachments behind a Claude-capability check (#4763)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* fix(sse): read Antigravity usage from the response.usageMetadata envelope (#4785)

Integrated into release/v3.8.37 — Antigravity response.usageMetadata envelope. Cherry-picked onto release tip (resolved test-tail adjacency with #4754 Ollama block); usage-extractor 23/23 green.

* fix(api): fall back to existing access token for any OAuth provider on refresh failure (#4786)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* fix(cli): verify launchd registration + skip self-SIGTERM in macOS autostart (#4765)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* fix(executors): anthropic-compatible-* gateways get Bearer alongside x-api-key (#4729)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* fix(sse): json_schema fallback for OpenAI-compatible providers (#4766)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* fix(sse): use workos auth token shape for cline (#4787)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* feat(sse): parse Gemini CLI 429 retryDelay from structured RetryInfo (#4738)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* fix(sse): finalize tool_calls finish_reason on early stream end in OpenAI Responses translator (#4764)

Integrated into release/v3.8.37 — computeFinishReason finalizes tool_calls on early stream end (Responses translator). Cherry-picked onto release tip; responses-translation-fixes 29/29 green.

* test(sse): golden-lock provider.ts translate-path across all providers (#4734)

Integrated into release/v3.8.37 — golden-lock for provider.ts translate-path. Cherry-picked onto release tip; snapshot regenerated against the current provider set (UPDATE_GOLDEN=1, 167 entries); golden test 3/3 deterministic.

* chore(quality): rebaseline file-size for rc17 leva2 PR batch drift

Own growth from the merged leva2 PRs (cohesive, not extractable):
- src/lib/usage/providerLimits.ts 950->955 (#4786)
- open-sse/executors/default.ts NEW frozen @828 (#4729 + #4766 + #4787 header branches)
- open-sse/translator/request/openai-to-kiro.ts 807->814 (#4763)
- open-sse/translator/response/openai-responses.ts 923->937 (#4764)
- tests/unit/executor-default-base.test.ts 1339->1440 (#4766)
- tests/unit/translator-openai-to-kiro.test.ts 918->980 (#4763)

* fix(dashboard): align Engine Combos editor engines with API schema (#4955) (#5062)

The named-combos pipeline dropdown offered four engines (headroom,
session-dedup, ccr, llmlingua) that stackedPipelineStepSchema rejects, so
selecting one made PUT /api/context/combos/[id] return HTTP 400 while
saveCombo swallowed the non-OK response (if (!res.ok) return). Editing the
default 'Standard Savings' combo and changing an engine reproduced the 400.

- Add canonical STACKED_PIPELINE_ENGINE_INTENSITIES next to the schema as the
  single source of truth; the client dropdown imports it so it can never drift
  from the discriminated union the API validates against.
- Surface save errors and empty-name/empty-pipeline validation in the editor
  instead of failing silently.
- Add a parity unit test asserting the UI engine map equals the schema union
  and that every (engine, intensity) the UI emits is accepted.

* fix(sse): filter nameless hosted tools when converting Responses API to Chat format (#4789)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(dashboard): keep desktop sidebar visible via explicit CSS class (#4812)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): strip enumDescriptions from Antigravity tool schemas (#4813)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(dashboard): resolve passthrough model aliases by providerId in ModelSelectModal (#4815)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(oauth): allow per-connection refresh lead-time override via providerSpecificData.refreshLeadMs (#4818)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): strip X-Stainless-* headers and normalize SDK User-Agent for OpenAI-compatible endpoints (#4820)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): strip Gemini built-in tools when functionDeclarations present in Antigravity envelope (#4821)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(api): surface a Docker-localhost hint on provider-node validation connection errors (#4822)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): resolve bare model names to connection defaultModel before upstream calls (#4825)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(build): trace-include sql.js sql-wasm.wasm in standalone bundle (#4839)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): strip Composer <|final|> sentinel markers leaking after Composer reasoning (#4842)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(config): sync full SiliconFlow model list into registry (#4844)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): close reasoning before message content in Responses stream (#4848)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): reject unsupported Kiro [1m] context suffix (#4816)

Integrated into release/v3.8.37 — cherry-picked onto release tip; test-tail conflict with #4763 resolved (kept both image + [1m] test blocks); CHANGELOG re-merged; 29/29 green.

* fix(db): validate HuggingFace tokens via whoami-v2 auth probe (#4819)

Integrated into release/v3.8.37 — defining commit re-homed onto the god-file-split validation module (validateHuggingFaceProvider in validation/openaiFormat.ts + map wiring); 115/115 green.

* fix(sse): make anthropic-version default-guard case-insensitive (#4823)

Integrated into release/v3.8.37 — conflict with #4729 Bearer-fallback resolved (kept both Bearer fallback + case-insensitive anthropic-version guard); 48/48 green.

* fix(sse): sanitize Kiro tool schemas to avoid 400 "Improperly formed request" (#4847)

Integrated into release/v3.8.37 — conflict in kiro-to-openai.ts resolved (kept release fallbackToolCallId + adopted #1375 toolNameMap remap); 7/7 green.

* feat(sse): add GPT-4 to the GitHub Copilot provider (#4798)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* feat(sse): add GPT-4o mini to GitHub Copilot provider (#4797)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* feat(api): add MiniMax-M3 pricing row (#4814)

Integrated into release/v3.8.37 — pricing row re-homed onto god-file-split pricing/regional.ts (pricing.ts is now a barrel); 4/4 green.

* fix(cli): save runtime deps with --save-exact so a sibling install can't prune them (#4841)

Integrated into release/v3.8.37 — trayRuntime conflict resolved (kept release SYSTRAY_SPEC + added --save-exact); 2/2 green.

* fix(sse): preserve required fields in antigravity tool schemas (#4843)

Integrated into release/v3.8.37 — conflict resolved (kept #4740/#4813 enumDescriptions strip + typed normalizeSchemaTypes, added required-preservation helpers; test-tail merged keeping both enumDescriptions + required tests); 7/7 green.

* chore(quality): rebaseline file-size for rc17b leva3 PR batch drift

* fix(sse): strip reasoning blobs from agentic context to prevent O(n^2) token growth (#4849)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): unwrap Qoder HTTP 200 SSE error envelope so fallback can trigger (#4850)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): strip temperature for Claude models with extended thinking (#4853)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): emit valid concatenable kiro tool_calls.arguments deltas (#4855)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* feat(sse): add toggleable tool-source diagnostics (#4856)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): redact api key from the AUTH debug log in the chat handler (#4858)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): forward AI SDK image parts in Responses translator (#4859)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): resolve custom combos by id and case-insensitive name (#4446) (#4869)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): exclude WS bridge controller-closed error from provider breaker (#4602) (#4870)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* feat(providers): add xAI Grok inbound translators and thinking patcher (#4910)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* feat(embeddings): add dimensions override field to embedding combos (#4913)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* feat(oauth): Codex bulk-import endpoint — POST /api/oauth/codex/import (#4914)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(antigravity): retry transient upstream failures (#4941)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): surface malformed HTTP-200 upstream responses (#4942)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): normalize Codex custom tools (apply_patch) to { input: string } schema (#4862)

Integrated into release/v3.8.37 — conflict in request/openai-responses.ts resolved (kept #4789 nameless-tool skip + added #1007 custom-tool {input:string} normalization); 48/48 green incl. #4789/#4859 regression.

* fix(sse): dense, deterministic output ordering in Responses API response.completed (#4906)

Integrated into release/v3.8.37 — manual integration with #4862 in response/openai-responses.ts (custom-tool funcItem + dense recordCompletedItem). Fixed a latent #4848 interaction: the close-reasoning-before-message guard force-closed <think>-tag reasoning prematurely, which dense output (#4906) then snapshotted as a partial buffer ("plan" vs "planning") — scoped the guard to native reasoning_content (!inThinking) in BOTH transformer + translator paths. Full Responses suite 203/203 green incl. #4848/#4862 regression.

* feat(sse): auto-promote successful combo model to position #1 (#4852)

Integrated into release/v3.8.37 — dropped the stale file-size-baseline.json hunk (re-derived against the rc17b rebaseline); code+test applied clean; 13/13 green.

* feat(providers): add Pioneer AI (Fastino Labs) provider (#4909)

Integrated into release/v3.8.37 — providers.ts apikey block re-homed onto god-file-split src/shared/constants/providers/apikey/frontier-labs.ts (inline APIKEY_PROVIDERS no longer exists); registry/pioneer + providers/index.ts applied clean; 6/6 green.

* add DGrid AI gateway provider (#4931)

Integrated into release/v3.8.37 — rebased the contributor's commit onto the release tip; providers.ts god-file-split conflict resolved by relocating the dgrid APIKEY_PROVIDERS entry into apikey/gateways.ts; CHANGELOG added. 7/7 green. Thanks @dgridOP!

* chore(quality): rebaseline file-size for rc17b leva4 PR batch drift

* docs(routing): sync combo strategy docs for Fusion (17 strategies) (#5067)

Fusion (16th strategy, panel fan-out + judge synthesis) and headroom
shipped but the strategy-count docs were stale (14/15) and omitted both.
Update every combo-strategy reference to the canonical 17, add fusion +
headroom to all strategy tables, and add a dedicated Fusion section to
AUTO-COMBO.md documenting judgeModel / fusionTuning config + an example.

- CLAUDE.md, README.md, FEATURES.md, RESILIENCE_GUIDE.md,
  ARCHITECTURE.md, OPEN_SSE_ARCHITECTURE.md, OMNIROUTE_VS_ALTERNATIVES.md,
  docs/README.md, request-pipeline.mmd: 14/15 -> 17, list fusion + headroom
- docs/routing/AUTO-COMBO.md: strategy table + new Fusion strategy section
- docs/openapi.yaml: add reset-window, headroom, fusion to the strategy enum

* fix(oauth): classify /api/oauth/cursor/auto-import as local-only (route-guard) (#5070)

The Cursor auto-import route runs execFile("which", ["cursor"]) to verify a
local Cursor install before importing credentials — a child-process spawn. The
check:route-guard-membership gate (Hard Rules #15/#17) flagged it as an
unclassified spawn-capable route: reachable past the loopback gate, an
RCE-via-tunnel surface (a leaked JWT over a tunnel could trigger the spawn).

Classify the specific path in LOCAL_ONLY_API_PREFIXES so loopback enforcement
runs unconditionally before any auth check. Scoped to the exact path — the rest
of /api/oauth/ (browser redirect/callback flows) stays remote-reachable.

TDD: added a failing-then-passing assertion in route-guard-local-prefix.test.ts
(classification + an over-broadening guard proving sibling OAuth paths stay
remote). check:route-guard-membership now reports 0 new gaps.

* chore(release): v3.8.37 — 2026-06-26

---------

Co-authored-by: dgridOP <dgrid_op@outlook.com>
2026-06-26 02:51:06 -03:00
Diego Rodrigues de Sa e Souza
a7ae9550bd Release v3.8.36 (#4854)
* chore(release): open v3.8.36 development cycle

* refactor(chatCore): extrai resolveCompressionSettings (#3501) (#4826)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 1/13)

* refactor(chatCore): extrai predicados puros de combo de compressão (#3501) (#4824)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 2/13)

* refactor(chatCore): extrai emitOutputStyleTelemetry (#3501) (#4811)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 3/13)

* refactor(chatCore): extrai writeCompressionAnalytics (bloco analytics completo, #3501) (#4817)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 4/13)

* refactor(chatCore): extrai runPluginOnRequestHook (#3501) (#4827)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 5/13)

* refactor(chatCore): extrai applyClientUsageBuffer (buffer/estimate de usage non-streaming, #3501) (#4832)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 6/13)

* refactor(chatCore): extrai buildPostCallGuardrailContext (contexto guardrail post-call, #3501) (#4831)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 7/13)

* refactor(chatCore): extrai storeSemanticCacheResponse (cache-store non-streaming, #3501) (#4828)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 8/13)

* refactor(chatCore): extrai buildNonStreamingResponseHeaders (headers de resposta non-streaming, #3501) (#4835)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 9/13)

* refactor(chatCore): extrai maybeConvertJsonBodyToSse (#3089 JSON→SSE streaming, #3501) (#4833)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 10/13)

* refactor(chatCore): extrai assembleStreamingResponseHeaders (headers de resposta streaming, #3501) (#4836)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 11/13)

* refactor(chatCore): extrai storeStreamingSemanticCacheResponse (cache-store streaming, #3501) (#4829)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 12/13)

* refactor(chatCore): extrai assembleStreamingPipeline (chain de transforms streaming, #3501) (#4837)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 13/13)

* ci(quality): shift heavy validations to the PR→release fast-path (release-acceleration) (#4857)

* feat(quality): add check:test-runner-api gate (vitest-only dirs must use vitest API)

* feat(release): reusable CHANGELOG i18n-mirror sync script

* chore(ops): add prune-stale-worktrees.sh (dry-run by default)

* ci(quality): run test-runner-api + docs-all + vitest + full unit suite on PR->release fast-path

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(quota): cota exclusiva lista qtSd/ no /v1/models (#4806) + limite EPSILON não bloqueia (#4830)

Integrated into release/v3.8.36 — quota-exclusive qtSd/ listing (#4806) + EPSILON placeholder no longer blocks; rebuilt from stale base (3 defining commits cherry-picked clean over release tip)

* feat(sse): add Google Flow video-generation provider (#4569) (#4769)

Integrated into release/v3.8.36 — Google Flow video-generation provider (#4569), release-green validated (typecheck + 21 tests + file-size)

* fix(api): auth on compression run-telemetry + document OMNIROUTE_EVAL_CREDENTIALS (#4694, #4720) (#4796)

Integrated into release/v3.8.36 — auth on compression run-telemetry + OMNIROUTE_EVAL_CREDENTIALS doc, release-green validated (typecheck + 3 tests + env-doc-sync)

* fix(translator): strip top-level client_metadata on the OpenAI passthrough (port from 9router#1157) (#4624)

Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)

* fix(translator): normalize `developer` role to `system` for OpenAI-format providers (#4625)

Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)

* fix(translator): emit </think> close marker for Anthropic thinking blocks (#4633)

Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)

* fix(translator): normalize tools to Anthropic-native shape for non-Anthropic providers (#4650)

Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)

* fix(gemini): preserve `pattern` in antigravity tool schema sanitizer (#4651)

Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)

* fix(perplexity): validate API keys via /v1/models endpoint (#4654)

Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)

* fix(image): prevent compatible nodes from shadowing provider aliases (#4656)

Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)

* fix(cli-tools): tolerate JSONC (comments, trailing commas) in tool settings (#4659)

Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)

* fix(security): validate kiro region to prevent SSRF (GHSA-6mwv-4mrm-5p3m) (#4629)

Integrated into release/v3.8.36 — kiro region SSRF guard (GHSA-6mwv-4mrm-5p3m), port rebuilt clean over release tip

* fix(cli): harden the systray2 tray runtime (port of 9router#1080) (#4628)

Integrated into release/v3.8.36 — port rebuilt clean over release tip, release-green validated

* fix(test): validate anthropic-compatible connections via POST /v1/messages (#4657)

Integrated into release/v3.8.36 — anthropic-compat validation via POST /v1/messages (port 584cf66a), rebuilt clean + baseline; release-green

* fix(executors): strip params unsupported by the target provider/model (#4658)

Integrated into release/v3.8.36 — port rebuilt clean over release tip, release-green validated

* fix(claude-oauth): respect 429 backoff on usage endpoint to reduce spam (#4655)

Integrated into release/v3.8.36 — port rebuilt clean over release tip, release-green validated

* feat(api/v1): include alias-backed models in /v1/models listing (#4630)

Integrated into release/v3.8.36 — port rebuilt clean over release tip, release-green validated

* chore(quality): rebaseline catalog.ts 1574->1577 (#4630 aliases sobre quota-exclusive da release) (#4879)

rebaseline

* feat(compression): Kiro/CodeWhisperer tool-result compression engine (#4635)

Integrated into release/v3.8.36 — port rebuilt clean, release-green

* fix(security): don't trust loopback socket as local when behind reverse proxy (#4632)

Integrated into release/v3.8.36 — port rebuilt clean, release-green

* fix(opencode): preserve DeepSeek reasoning content in streamed responses (#4631)

Integrated into release/v3.8.36 — DeepSeek reasoning_content injection (port #1099); release-green

* fix(copilot,antigravity): cap maxOutputTokens at 16384 to stop "Invalid Argument" 400 (#4636)

Integrated into release/v3.8.36 — cap maxOutputTokens 16384 antigravity (port #779); release-green

* fix(dashboard): show custom vision models in LLM selector (#4653)

Integrated into release/v3.8.36 — custom vision models in LLM selector (port 5e5e78d3); release-green

* fix(claude): omit adaptive thinking + output_config.effort for haiku (#4661)

Integrated into release/v3.8.36 — haiku adaptive-thinking omit (port); release-green

* feat(provider): CodeBuddy CN (copilot.tencent.com) — full stack (#4664)

Integrated into release/v3.8.36 — CodeBuddy CN provider (port efd20be8); usage.ts import + public-creds allowlist line reconciled; release-green

* feat(combo): Fusion strategy — parallel panel + judge synthesis (16th strategy) (#4652)

Integrated into release/v3.8.36 — Fusion combo strategy (16th, port 87e5c1c6); combo.ts baseline reconciled; release-green

* feat(proxy-pool): Deno Deploy relays + group action buttons (#4643)

Integrated into release/v3.8.36 — Deno Deploy relays (port #1437); proxies.ts baseline reconciled + env docs restored; release-green

* fix(security): pin image fetch DNS resolution to prevent SSRF rebinding (GHSA-cmhj-wh2f-9cgx) (#4634)

Integrated into release/v3.8.36 — pin DNS for image fetch SSRF rebinding guard (GHSA-cmhj-wh2f-9cgx, port c7d07448); caller DNS stubs + test-file baseline reconciled; release-green

* fix(github): route Copilot Codex models to /responses (port from 9router#102) (#4626)

Integrated into release/v3.8.36 — route Copilot Codex models to /responses (port #102); release-green

* fix(copilot): never route Gemini/Claude variants to /responses (chat-completions only) (#4627)

Integrated into release/v3.8.36 — never route Gemini/Claude to /responses (port #1536); fused with #4626 codex routing via supportsResponsesEndpoint gate; release-green

* docs(ops): add canonical incident response runbook (#4868)

Integrated into release/v3.8.36

* docs(perf): add per-endpoint p50/p95/p99 latency + cost budgets (#4867)

Integrated into release/v3.8.36

* fix(proxy): fan out direct dispatcher streams (#4803)

Integrated into release/v3.8.36

* fix(antigravity): exclude standard Gemini rate limit message from quota exhaustion keywords (#4810)

Integrated into release/v3.8.36

* fix(sse): skip third-party tool-name cloak for Anthropic server tools (#4808)

Integrated into release/v3.8.36

* fix(install): make transformers optional for CUDA-host installs (#4807)

Integrated into release/v3.8.36

* fix(combo): propagate selected connection ID to fallback error responses for correct model lockout (#4809)

Integrated into release/v3.8.36

* fix db storage tuning settings (#4834)

Integrated into release/v3.8.36

* fix(sse): drop ccp pin when pinned provider is durably unhealthy (failover + anti-flap) (#4864)

Integrated into release/v3.8.36

* fix(claude): skip mcp__ tool-name cloak + guard missing connectionId (#4861)

Integrated into release/v3.8.36

* chore(quality): reconcile env-doc + file-size base-reds in release/v3.8.36 (#4886)

- env-doc-sync: document PIN_DROP_BACKOFF_LEVEL / PIN_DROP_GRACE_MS (added by the
  ccp-pin health gate #4864) in .env.example + ENVIRONMENT.md.
- file-size: rebaseline image-generation-handler.test.ts 1996 -> 2019 to its actual
  size (pre-existing drift).

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(codex): drop non-standard codex.* events that break responses.stream (env-gated, #4602) (#4715)

Integrated into release/v3.8.36

* feat(routing): honor X-Route-Model header to override body.model (#4863)

Integrated into release/v3.8.36

* feat(live-ws): allow non-loopback clients via LIVE_WS_ALLOWED_HOSTS (closes #4873) (#4877)

Integrated into release/v3.8.36 (live-ws + combo-api commits; Tailscale CGNAT commit held pending opt-in/opt-out decision)

* chore(claude,codex): bump pinned CLI identity — Claude 2.1.158→2.1.187, Codex 0.132.0→0.142.0 (#4883)

Integrated into release/v3.8.36

* fix(security): SSRF allowlist bypass via x-relay-path nos relays Deno/Vercel (#4899)

Integrated into release/v3.8.36

* feat(quota): recuperação proativa de conexões em cooldown (cron heal) [Fase 3 #8] (#4900)

Integrated into release/v3.8.36

* fix(quota): policy inválida não vaza allow + guard connectionIds vazio [Fase 3 #10] (#4901)

Integrated into release/v3.8.36

* feat(quota): saturação real do Claude no fair-share via /api/oauth/usage (#4885)

Integrated into release/v3.8.36

* chore(dashboard): rename Qoder display label from "Qoder AI" to "Qoder" (#4733)

Integrated into release/v3.8.36

* fix(ci): include coverage/lcov.info in coverage-report artifact for SonarQube (#4670)

Integrated into release/v3.8.36

* fix(cli): bump better-sqlite3 runtime pin to 12.10.1 for Node 26 (#4685)

Integrated into release/v3.8.36

* docs: clarify Kiro is ~50 credits/month per account, not unlimited (#4690)

Integrated into release/v3.8.36

* docs(agentbridge): document Electron NODE_EXTRA_CA_CERTS, real model IDs, identity caveat (#4718)

Integrated into release/v3.8.36

* docs(ops): document the release-green family (green-prs, check:release-green, babysit, nightly) (#4679)

Integrated into release/v3.8.36

* fix(translator): replay reasoning_content on plain Xiaomi MiMo turns (port from 9router#1321) (#4639)

Integrated into release/v3.8.36

* feat(opencode-go): advertise glm-5.2 and kimi-k2.7-code (align with official Go endpoints) (#4711)

Integrated into release/v3.8.36

* feat(db): track API endpoint dimension on usage_history (#4676)

Integrated into release/v3.8.36 (migration renumbered 103→105; endpoint plumbed through extracted usage-stats helpers)

* fix(cli): SIGKILL systray child PID before IPC close to avoid macOS NSStatusItem orphan (#4732)

Integrated into release/v3.8.36

* feat(proxy-pool): Cloudflare Workers proxy deployer + pool integration (#4640)

Integrated into release/v3.8.36 (relay type added to RELAY_TYPES set; dropdown UX preserved + Cloudflare item added; proxies.ts file-size rebaselined 1057→1060)

* chore(quality): conserta base-red de release/v3.8.36 (gates + 7 testes + build MDX) (#4915)

A base tinha base-red sistêmica herdada de PRs de outras sessões, bloqueando
TODOS os PRs do ciclo (o TIA roda a suíte full em fail-safe p/ diffs hub).

4 Fast Quality Gates:
- test-discovery (#4877): live-server-allowlist.test.ts em tests/unit/server/
  (não-coletado) + vitest → nunca rodava. Convertido p/ node:test em tests/unit/security/.
- any-budget:t11 (#4664): 3 explicit-any em tokenRefresh.ts tipados (sem crescer file-size).
- docs-symbols (#4868): rotas inexistentes → /api/system/version e
  PUT /api/providers/{id} {isActive:false}.
- docs-all fabricated-claim (#4868 + #4718): 5 bin/*.sh reais criados (rollback,
  snapshot-data, restore-data, restore-policies, cold-start-bench) + _ops-common.sh
  (snapshot VACUUM INTO, guards de confirmação/TTY, testes de contrato); NODE_EXTRA_CA_CERTS
  (env de runtime Node) na allowlist do checker.

7 testes unit base-red (de features alheias à quota):
- oauth-providers-config (#4664): teste alinhado ao provider codebuddy-cn do registry.
- antigravity-model-aliases (#4636): maxOutputTokens esperado 32769→16384 (cap intencional).
- provider-request-capture #4091 (#4861): exemplo do teste trocado de mcp__ (que #4861
  isenta de cloak por causa dos 400s de assimetria de histórico) para um tool de terceiro
  cloakável — preserva o invariante de #4091 SEM reverter #4861.
- combo-error-response: convertido de vitest p/ node:test (era coletado pelo glob node:test
  e crashava); api/** e server/** removidos do vitest.config (config morta).

Build MDX (dast-smoke, #4679):
- docs/ops/RELEASE_GREEN.md não tinha frontmatter `title` → fumadocs-mdx rejeitava no
  webpack compile ("invalid frontmatter: title expected string"), quebrando o next build
  (e o deploy). Frontmatter title adicionado (único doc do collection sem ele).

17/17 Fast Quality Gates + suíte unit completa (17737 testes, 0 fail) + vitest verdes localmente.

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* feat(quota): saturação proativa por headers de tokens (universal) [Fase 3 #2] (#4907)

storeRateLimitHeaders só capturava os headers de REQUESTS (RPM/min), que não
refletem a pressão de TOKENS. Agora também parseia os headers de tokens (em toda
resposta, sucesso também) para throttle proativo antes do 429:
- Anthropic: anthropic-ratelimit-tokens-{limit,remaining,reset} (+ input/output), RFC3339.
- OpenAI: x-ratelimit-{limit,remaining,reset}-tokens, reset em duração (6m0s).

saturation = 1 − remaining/limit; resetAt normalizado a epoch (parse de duração
ReDoS-safe). getTokenHeaderSaturation por (provider, connectionId). fetchGeneric-
Saturation passa a usar esse sinal (complementa o oauth/usage do #1, que segue
primário p/ Claude). Fail-open, cache mantido, request-path inalterado.

16 testes novos + regressão (oauth/usage #1 8/8, signals 6/6) = 30/30;
typecheck:core + eslint limpos.

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* feat(quota): estratégia de combo "headroom" — seleção por folga de cota [Fase 3 #4] (#4908)

Nova estratégia de roteamento que escolhe a conexão com MAIS folga de plano:
headroom = 1 − max(util_5h, util_7d) (técnica do dario), via getSaturation
(melhorado p/ Claude no #1). Proativo em vez de só fill-first reativo.

- Helper PURO headroomRanking.ts (computeHeadroom + rankByHeadroom; saturação
  injetada, não-mutante, tie-break estável, fail-open).
- Orderer async em combo/quotaStrategies.ts (reusa a maquinaria reset-aware de
  expansão de conexões + concorrência limitada; seam injetável).
- Registrada como "headroom" em routingStrategies (combo-only); fill-first segue
  default — nenhuma estratégia existente tocada.
- baseline file-size combo.ts 3168->3180 (só +12L de dispatch; lógica fora do god-file).

16 testes novos + combo-strategies 15/15 = 31/31; typecheck:core + eslint +
file-size limpos.

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* feat(quota): cap per-(key,model) — quota_allocation_model_caps [Fase 3 #7] (#4927)

* feat(quota): cap per-(key,model) com tabela quota_allocation_model_caps [Fase 3 #7]

Fecha o buraco onde uma API key pode drenar o pool inteiro consumindo um único modelo.

Tabela nova: quota_allocation_model_caps(pool_id, api_key_id, model, cap_value, cap_unit)
PK composta (pool_id, api_key_id, model). cap_unit alinhado ao QuotaUnit existente.

Comportamento: keyA acima do cap para modelo M → bloqueada somente em M; ainda
permitida em qualquer outro modelo no mesmo pool. Cap <= EPSILON → ignorado (seed).

Consumo por-(key,model) usa bucket segregado no quota_consumption existente
(poolId mangled ':model:<model>') com window fixa 'hourly'; nenhuma nova tabela
ou método de store necessário.

Módulo novo: src/lib/db/quotaModelCaps.ts (getModelCap/setModelCap/deleteModelCap/listModelCaps)
enforce.ts ganha o pre-check em enforceQuotaShare + recording em recordConsumption.
EnforceInput e RecordConsumptionInput ganham model?: string (backward-compatible).
localDb.ts re-exporta os 4 helpers (Hard Rule #2).

TDD: tests/unit/quota-per-key-model.test.ts — 4 cenários (bloqueia em M, permite em M2,
sem cap → sem bloqueio, EPSILON → ignorado). Todos os gates de qualidade passam.

* feat(quota): plumba model resolvido no hot path para ativar o per-(key,model) cap [Fase 3 #7]

A tabela/enforce do commit anterior estavam INERTES: o hot path não passava `model`
ao enforce nem ao record, então nenhum model-cap disparava em produção.

Plumbagem (model resolvido = mesma var usada no log/roteamento, pós background-redirect/alias):
- chatCore.ts: enforceQuotaShare ganha `model`; scheduleQuotaShareConsumption recebe `model`.
- chatCore/quotaShareConsumption.ts: threade `model` no RecordConsumptionInput (non-streaming).
- spendRecorder.ts: recordStreamingConsumption já recebia `model` — agora o coloca no
  RecordConsumptionInput (streaming accrue por-modelo).
- embeddings.ts: enforce + record ganham `model`.

Namespace do cap = id do modelo RESOLVIDO (o mesmo de modelForScope/pendingScope/getUnsupportedParams),
não o requestedModel cru nem o finalModelToUpstream (sem prefixo de provider). Operador configura
o cap contra esse id. `model || undefined` em todos os pontos: vazio/null → check pulado (fail-safe,
zero latência — só um campo no objeto).

Teste de integração novo (tests/unit/quota-per-key-model-hotpath.test.ts): prova end-to-end que
N consumos via scheduleQuotaShareConsumption({model}) → enforceQuotaShare({model}) bloqueia, e que
outro modelo no mesmo pool ainda passa; + guard de que enforce SEM model nunca dispara model-cap.

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* feat(quota): session stickiness p/ integridade de prompt-cache [Fase 3 #5] (#4929)

* feat(quota): session stickiness p/ integridade de prompt-cache [Fase 3 #5]

Adiciona stickiness de sessão ao roteamento de combo: uma conversa multi-turno
é roteada para a MESMA conexão enquanto ela permanecer saudável, evitando a
perda do prompt-cache do provider (custo 5-10× sem stickiness, efeito conhecido
no dario/clewdr).

Implementação:
- `open-sse/services/combo/sessionStickiness.ts` (novo, <800 linhas):
  mapa em memória (messageHash → connectionId) com TTL 15 min + cap 500 entradas;
  `applySessionStickiness` promove a conexão sticky ao índice 0 dos targets
  ordenados pelo strategy, guardado por `computeHeadroom > 0.15` (threshold);
  quando saturada (headroom ≤ 0.15), o binding é limpo e a seleção normal reage.
  Hash da sessão = SHA-256 dos primeiros chars da 1ª mensagem user → 16 hex chars.
  Seam de teste: `__setStickinessHeadroomFetcherForTests`.
- `open-sse/services/combo.ts`: import + 2 pontos de integração (pré-eval-scores
  e pós-success), dentro do orçamento congelado de 3180 linhas.
- `tests/unit/combo-session-stickiness.test.ts`: 19 testes node:test + assert/strict,
  todos via injeção de fetcher (zero rede/DB).

Threshold 0.15: conexão a >85% de utilização está a um burst de rate-limit;
o benefício de cache não compensa manter-se numa conexão degradada. Valor
alinhado com a zona de soft-penalty do restante do engine de quota-share.

* test(combo): isola combo-strategies da session stickiness (#5)

selectedConnectionFor reusa o mesmo body, então o sticky map (#5) fixava a
connection após a 1ª chamada e quebrava o round-robin tie-break do teste
reset-aware. Limpa o sticky map no início da helper — a stickiness tem suíte
própria (combo-session-stickiness). Sem enfraquecer asserts.

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* feat(quota): buckets multi-janela por conexão (5h/7d/per-model) [Fase 3 #3] (#4928)

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* refactor(providers): decompõe catálogo providers.ts em módulos de dados (godfile sweep, #3501) (#4917)

Integrado em release/v3.8.36 (godfile sweep providers.ts, #3501)

* refactor(pricing): decompõe pricing.ts em shared-tiers + DEFAULT_PRICING particionado (godfile sweep, #3501) (#4918)

Integrado em release/v3.8.36 (godfile sweep pricing.ts, #3501)

* refactor(api): extrai camada-folha pura de validation.ts (URL/headers/transport) (#4921)

Integrado em release/v3.8.36 (validation.ts split fatia 1 — leaf layer)

* refactor(api): extrai validators web-cookie + Meta AI de validation.ts (#4922)

Integrado em release/v3.8.36 (validation.ts split fatia 2 — web-cookie + Meta AI)

* refactor(api): extrai validators enterprise-cloud + probe compartilhado de validation.ts (#4923)

Integrado em release/v3.8.36 (validation.ts split fatia 3 — enterprise-cloud + probe)

* refactor(api): extrai validators áudio/speech + misc apikey de validation.ts (#4930)

Integrado em release/v3.8.36 (validation.ts split fatia 4 — áudio/speech + misc apikey)

* feat(quota): estratégia dedicada de quota-share (DRR + P2C in-flight + gating per-model) [Fase 3 #9] (#4939)

* feat(quota): estratégia dedicada de quota-share (DRR + P2C in-flight + gating per-model) [Fase 3 #9]

Estratégia interna "quota-share" isolada num módulo dedicado — NÃO toca a seleção/
fair-share genérica (decisão do dono: não mexer no que já funciona). Os combos qtSd/
(quotaCombos.ts) passam de fill-first para essa strategy; combo.ts ganha só 1 branch
de dispatch que delega 100% ao módulo (nenhum case existente alterado).

- quotaShareStrategy.ts: gating per-model (isBucketSaturated do #3) + DRR (quantum
  proporcional ao weight) + P2C sobre carga in-flight.
- quotaShareInflight.ts: contador in-flight com TTL/lease de 120s — fallback do
  decrement-on-abort sem precisar instrumentar o combo genérico.
- "quota-share" registrada como strategy INTERNA (não exposta na UI).
- testes de síntese (quota-combo-balancing, quota-multiprovider) alinhados: a strategy
  esperada dos combos qtSd/ passa de "fill-first" para "quota-share" (alinhamento ao novo
  comportamento intencional, não mascaramento — os 73 testes de qtSd/ seguem verdes).

* test(quota-share): alinha 2 scope-guards ao godfile sweep (base-reds que bloqueavam o CI)

Dois testes de "arquivo contém X" quebraram por decomposições de godfile que outras
sessões mergearam no release DURANTE a validação de #9 — NÃO são regressão de #9
(que não toca validation/oauth). Alinhados ao novo layout, asserts preservados:
- proxy-bypass-scope-guard #3226: bypassProxyPatch foi extraído de validation.ts para
  validation/headers.ts (split #4921–#4930) → o teste lê a camada de validação.
- sse-error-passthrough #3324: a windsurf authHint foi extraída de providers.ts para
  providers/oauth.ts → o teste lê o novo local.

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* refactor(api): extrai validators search + embedding/rerank de validation.ts (#4932)

Integrated into release/v3.8.36

* refactor(api): extrai format-validators (OpenAI/Anthropic) de validation.ts (#4933)

Integrated into release/v3.8.36

* refactor(db): extrai model-permission matching de db/apiKeys.ts (#4936)

Integrated into release/v3.8.36

* refactor(db): extrai row-parsers + tipos compartilhados de db/apiKeys.ts (#4943)

Integrated into release/v3.8.36

* refactor(db): extrai column-mapping (snake↔camel) de db/core.ts (#4947)

Integrated into release/v3.8.36

* refactor(db): extrai schema-column reconciliation de db/core.ts (#4948)

Integrated into release/v3.8.36

* refactor(sse): extrai scalar/format helpers de services/usage.ts (#4949)

Integrated into release/v3.8.36

* refactor(sse): extrai quota-core (UsageQuota + builders) de services/usage.ts (#4950)

Integrated into release/v3.8.36

* fix(translator): regroup parallel tool results adjacent to their assistant (#4714) (#4882)

Integrated into release/v3.8.36 (fixes #4714)

* fix(qoder): exchange PAT for jt-* job token before Cosy chat (#4683) (#4884)

Integrated into release/v3.8.36 (fixes #4683)

* refactor(sse): dedup fallback tool_call id helper (#4736)

Integrated into release/v3.8.36

* refactor(open-sse): extract safeParseJSON util, dedup tryParseJSON (#4735)

Integrated into release/v3.8.36

* fix(compression): eliminate ReDoS in math_inline preservation pattern (#4795) (#4838)

Integrated into release/v3.8.36 (fixes #4795)

* fix(combo): fetch models dynamically from custom provider endpoints (#4860)

Integrated into release/v3.8.36

* feat(providers): update volcengine-ark model list with DeepSeek V4 (#4905)

Integrated into release/v3.8.36

* fix(translator): provider thinking compatibility (DeepSeek/Gemini) (#4946)

Integrated into release/v3.8.36

* feat(combo): task-aware routing strategy (#4945)

Integrated into release/v3.8.36

* refactor(sse): extrai a família MiniMax de services/usage.ts (#4952)

Integrated into release/v3.8.36

* refactor(sse): extrai a família GLM de services/usage.ts (#4953)

Integrated into release/v3.8.36

* refactor(sse): extrai a família Antigravity de services/usage.ts (#4956)

Integrated into release/v3.8.36

* fix(dashboard): show custom provider given-name instead of internal id across dashboard pages (#4603) (#4960)

Integrated into release/v3.8.36 (fixes #4603)

* fix(api): evict stale in-memory rate-limit windows to stop slow heap leak (#4041) (#4957)

Integrated into release/v3.8.36 (fixes #4041)

* fix(api): parse /v1/responses body once instead of 3-4x on the hot path (#4041) (#4958)

Integrated into release/v3.8.36 (fixes #4041)

* fix(translator): preserve legitimate empty-string tool arguments in openai-to-claude streaming (#4951) (#4959)

Integrated into release/v3.8.36 (fixes #4951)

* chore(quality): reconcile file-size baseline for #4960 provider-display-name (#4961)

Integrated into release/v3.8.36

* fix(dashboard): restore home provider-topology card hidden by #4596 default (#4963)

Integrated into release/v3.8.36 — restores home topology card (#4596 regression)

* fix(build): drop @omniroute/open-sse from optimizePackageImports (build OOM) (#4968)

Integrated into release/v3.8.36 — fixes build OOM (optimizePackageImports open-sse)

* fix(quota): migração 107 ativa estratégia quota-share nos combos qtSd/ existentes [Fase 3 #9] (#4962)

Integrated into release/v3.8.36

* feat(quota): respeita max_concurrent por conexão no roteamento (#4965)

Integrated into release/v3.8.36

* feat(quota): combo quota-share espera cooldown curto e re-despacha (Variante A) (#4967)

Integrated into release/v3.8.36

* fix(quality): resolve base-reds da release — db-rules allowlist + task-aware router precedence (#4973)

Dois base-reds pré-existentes que reprovavam o CI da release v3.8.36 (Fast
Quality Gates + Unit Tests fast-path), independentes de qualquer feature em voo:

1. check:db-rules / allowlist: os módulos db-internal caseMapping (#4947) e
   schemaColumns (#4948), extraídos de db/core.ts e importados só por ele, não
   estavam em INTENTIONALLY_INTERNAL. Registrados na allowlist (correção
   canônica — são internos legítimos, não re-exportados pelo localDb).

2. auto-strategy honra LKGP/cost (combo-routing-engine.test.ts, 2 testes): o
   task-aware reordering (#4945, reorderByTaskWeight) roda para strategy "auto" e
   era aplicado DEPOIS do router explícito (selectWithStrategy: lkgp/cost),
   sobrescrevendo o orderedTargets[0] que o operador escolheu. Instrumentação
   provou: post-filter [0]=claude (LKGP) → post-task [0]=gpt-oss. Correção: quando
   o auto usa router explícito, preserva o [0] dele e deixa o task-aware refinar
   só a cauda de fallback. gpt-oss-120b PERMANECE tool-capable (não é mudança de
   catálogo; o model-capabilities-registry test segue verde).

Validado: 121 testes (combo-routing-engine + combo-task-aware + registry) verdes,
red-check confirmado, db-rules/file-size/typecheck/lint/prettier OK.

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* feat(quota): serializa concorrência por conexão no caminho quota-share (FASE 2.1) (#4970)

O gating de quota-share em selectQuotaShareTarget é fail-open: uma conexão
at-cap só é despriorizada, nunca bloqueada. Com 1 conexão por conta de
assinatura (caso comum), chamadas concorrentes ainda floodam a conta (→ 429 +
cooldown) — provado live na .15: 3 chamadas concorrentes com max_concurrent=1
despacharam todas em 94ms.

Adiciona um semáforo POR CONEXÃO em torno do dispatch quota-share: chamadas
excedentes esperam na fila em vez de floodar (key qsconn:<connectionId>, cap =
max_concurrent da conexão). Fail-open em fila saturada/timeout para nunca
piorar disponibilidade. Gated por strategy===quota-share + kill-switch
resilienceSettings.quotaShareConcurrencyLimit (default on; UI no ResilienceTab).

Lógica extraível isolada no leaf puro combo/quotaShareConcurrency.ts
(unit-testado: estabilidade da key, no-op sem cap, serialização real,
fail-open). Settings + schema + UI espelham comboCooldownWait.

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* docs(resilience): document Quota-Share Concurrency Control (max_concurrent + serialization + cooldown-wait) (#4980)

Documents the v3.8.36 quota-share concurrency layers in RESILIENCE_GUIDE.md:
per-connection max_concurrent cap, the quota-share request serialization semaphore
(FASE 2.1, qsconn:<connectionId>, fail-open, kill-switch), and the combo
cooldown-aware retry — so operators know how to cap a subscription account's
concurrency and why the routing gate alone cannot contain a single-connection flood.

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(dashboard): proxy-pool success gating, sync timestamp, opt-in Redis (#4878) (#4988)

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(sse): fail over on 400 responses carrying rate-limit text (#4976) (#4986)

* fix(sse): fail over on 400 responses carrying rate-limit text (#4976)

* chore(quality): rebaseline accountFallback.ts file-size for #4976 fix

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(compression): stop RTK over-truncating file-read tool results (#4559) (#4987)

* fix(compression): stop RTK over-truncating file-read tool results (#4559)

* chore(quality): trim #4559 comment to keep rtk/index.ts within size cap

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(sse): honor per-account proxies and fingerprint rotation in opencode executor (#4954) (#4989)

* fix(sse): honor per-account proxies and fingerprint rotation in opencode executor (#4954)

* chore(quality): rebaseline auth.ts file-size for #4954 (+39: synthetic no-auth providerSpecificData hydration of fingerprints/accountProxies; irreducible credential-path wiring, covered by opencode-proxy-rotation-4954.test.ts + 159 auth/noauth regression)

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(sse): soft-penalize exhausted providers in auto-combo scoring (#4540) (#4990)

* fix(sse): soft-penalize exhausted providers in auto-combo scoring (#4540)

* chore(quality): document STATUS_SOFT_DEPRIORITIZE_FACTOR + rebaseline combo.ts for #4540

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(dashboard): switch to visible filter after auto-hiding failed models in test-all (#4887) (#4991)

* fix(dashboard): switch to visible filter after auto-hiding failed models in OAuth provider test-all (#4887)

* test(dashboard): move #4887 test into tests/unit/ui so a CI runner collects it

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(pollinations): only enable jsonMode when JSON output is requested (#3981) (#5009)

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(antigravity): default safetySettings to all-OFF for parity with native Gemini paths (#5003) (#5008)

* fix(antigravity): default safetySettings to all-OFF for parity with native Gemini paths (#5003)

* docs(changelog): restore #3981 pollinations entry eaten by merge

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(chatgpt-web): map advertised gpt-5.5/5.4-pro/5.2-pro slugs to prevent silent model substitution (#4665) (#5010)

* fix(chatgpt-web): map advertised gpt-5.5/5.4-pro/5.2-pro slugs to prevent silent model substitution (#4665)

MODEL_MAP was missing the advertised catalog ids gpt-5.5, gpt-5.5-pro,
gpt-5.4-pro and gpt-5.2-pro, so MODEL_MAP[model] ?? model sent the dot-form
id verbatim to the ChatGPT backend-api, which silently rejected it and served
the default Plus model. Map each to its dash-form slug. gpt-4-5 is already
dash-form and falls through correctly, so it is intentionally left unmapped.

Extends the executor MODEL_MAP test with the four ids and adds a drift guard
asserting every advertised dot-form catalog id reaches the backend in dash-form
(never verbatim), guarding future catalog<->map drift.

file-size: tests/unit/chatgpt-web.test.ts frozen baseline 2809->2855 (+46) for
the added test cases and drift-guard test; executor source unchanged in baseline.

* docs(changelog): restore #3981/#5003 entries eaten by merge

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* feat(combos): add editable per-combo description field persisted via /api/combos (#5005) (#5011)

* feat(combos): add editable per-combo description field persisted via /api/combos (#5005)

* docs(changelog): restore #3981/#5003/#4665 entries eaten by merge

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* Fix Ollama Cloud max reasoning effort (#4993)

Integrated into release/v3.8.36

* fix(copilot): replace execSync with execFile to prevent command injection (#5024)

Integrated into release/v3.8.36

* fix(plugin): auth.json dual-key fallback for auto-prefix migration (#5027)

Integrated into release/v3.8.36

* feat(endpoint): per-endpoint custom system prompt injection (#5022)

Integrated into release/v3.8.36

* fix(headroom): translate openai-responses input through OpenAI for compression (#5023)

Integrated into release/v3.8.36

* docs(changelog): add entries for #4993, #5024, #5027 (release notes credit)

* fix(api): stop /api/system/env/repair 500 on packaged install (#5006) (#5028)

* fix(api): stop /api/system/env/repair 500 on packaged install — lazy createRequire in sync-env.mjs (#5006)

scripts/dev/sync-env.mjs ran createRequire(import.meta.url) at module
top-level. When webpack bundles it into the standalone env-repair route,
import.meta.url is frozen to the build-machine path (file:///home/runner/...)
and createRequire throws during module evaluation, so the whole route
module fails to load and every GET returns HTTP 500 — breaking the
onboarding wizard on packaged/global installs.

- Move createRequire into the guarded better-sqlite3 block (only place
  that needs it); a bad import.meta.url now returns the safe default.
- resolveRootDir() falls back to process.cwd() when fileURLToPath throws.
- route.ts passes an explicit rootDir (process.cwd()) so the helper never
  derives the root from the frozen import.meta.url, matching the .env
  target used by createEnvBackup().
- Regression guard: assert sync-env.mjs has no top-level createRequire +
  getEnvSyncPlan(oauth) works with explicit rootDir without throwing.

* docs(changelog): restore #4993/#5023/#5024/#5027 + custom-system-prompt/headroom entries eaten by release merge

* chore(quality): rebaseline 3 inherited base-reds from release merge

Files NOT touched by this PR — grew on release/v3.8.36 via --admin merges and
inherited here through 'git merge origin/release':
- open-sse/executors/base.ts 1414->1416 (#4993 Ollama Cloud max-effort)
- src/lib/db/settings.ts 1149->1151 (#5023 custom system prompt)
- src/app/(dashboard)/.../endpoint/EndpointPageClient.tsx 2570->2612 (custom system prompt UI)

* chore(release): finalize v3.8.36 CHANGELOG + docs (2026-06-25)

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>
Co-authored-by: KooshaPari <42529354+KooshaPari@users.noreply.github.com>
Co-authored-by: Makcim Ivanov <makcimbx@gmail.com>
Co-authored-by: Chewji <126886556+Chewji9875@users.noreply.github.com>
Co-authored-by: Anton <39598727+NomenAK@users.noreply.github.com>
Co-authored-by: Demiurge The Single <megamen932@gmail.com>
Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
Co-authored-by: Éder Costa <eder.almeida.costa@gmail.com>
Co-authored-by: Jefferson Felizardo <jeffer1312@gmail.com>
Co-authored-by: Arthur Bodera <abodera@gmail.com>
Co-authored-by: Hamsa_M <116961508+hamsa0x7@users.noreply.github.com>
Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com>
2026-06-25 13:17:40 -03:00