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.
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.
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.
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).
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.
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.
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.
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.
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).
#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).
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.
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.
#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.
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.
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.
- #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).
- #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.
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.
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.
* 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>
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(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>
* 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>
* 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(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>
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).
* 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).
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)
* 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.