Commit Graph

2152 Commits

Author SHA1 Message Date
Diego Rodrigues de Sa e Souza
ef777d77e1 feat: editable ComfyUI base-URL field + per-connection override for image/video/music generation (#6928) (#7232)
* feat(providers): editable ComfyUI base-URL field + per-connection override for image/video/music generation (#6928)

Adds a shared resolveComfyUiBaseUrl() helper (open-sse/utils/comfyuiClient.ts) that
prefers a per-connection providerSpecificData.baseUrl override over the registry
default, wired through the comfyui dispatch branch in the image, video, and music
generation handlers plus a best-effort authType:"none" credential lookup in all
three /v1/{images,videos,music}/generations routes (never hard-fails when no
connection exists, so zero-config localhost users are unaffected).

Surfaces an editable base-URL field on the ComfyUI connection form by adding it to
CONFIGURABLE_BASE_URL_PROVIDERS / DEFAULT_PROVIDER_BASE_URLS /
getProviderBaseUrlPlaceholder in providerPageHelpers.ts, so Docker-network setups
(e.g. http://comfyui:8188) can be configured the same way self-hosted chat
providers are.

Closes #6928

* refactor(6928): extract local-override credential lookup in media routes

The inline per-connection override block nested if>if inside the music and
videos POST handlers, taking each to cognitive complexity 16 (>15) — two NEW
violations that broke check:complexity-ratchets (892 > baseline 890).
Extracted resolveLocalOverrideCredentials() in both routes; behaviour is
unchanged. cognitive-complexity back to 890 = baseline.
2026-07-17 10:40:22 -03:00
Diego Rodrigues de Sa e Souza
8143d8e3a6 feat: replace free-text model inputs with hidePaid-aware Selects (#6540) (#7229)
* feat(dashboard): replace free-text model inputs with hidePaid-aware Selects (#6540)

Swap RoutingTab.webSearchRouteModel, ComboDefaultsTab.handoffModel, and
BackgroundDegradationTab's from/to fields from free-text inputs to a new
shared ModelSelectField fed by the already hidePaidModels-aware
GET /api/models, with an off-catalog "(custom)" fallback so an existing
saved value is never silently dropped. ModelRoutingSection's glob pattern
field gets a fail-open "matches only paid models" warning instead, since
it's a wildcard matcher rather than a single model id.

Adds save-time paid-target rejection (PAID_MODEL_TARGET_BLOCKED, 400) on
PATCH /api/settings, PATCH /api/settings/combo-defaults, and PUT
/api/settings/background-degradation when hidePaidModels is on, failing
open for aliases/combo names/unrecognized providers.

globToRegex is extracted from lib/db/modelComboMappings.ts into a new
dependency-free shared/utils/globPattern.ts so both the DB module and the
new client-side pattern heuristic reuse the same regex-building logic.

* refactor(6540): extract paid-target guard in background-degradation PUT

The inline hidePaid check nested if>if>for>if inside the PUT handler,
pushing its cognitive complexity to 16 (>15) — a NEW violation that broke
the check:complexity-ratchets gate (891 > baseline 890). Extracted the
check into a module-local hasBlockedPaidTarget() helper; behaviour and
response body are unchanged. cognitive-complexity back to 890 = baseline.

* fix(i18n): mirror paidModelPatternWarning into pt-BR.json (#6540)

The new key landed only in en.json; the i18n pt-BR integrity test
(no drift, #6695) requires pt-BR.json to carry every en.json key.
2026-07-17 10:40:18 -03:00
Diego Rodrigues de Sa e Souza
ca03d619a4 feat(mitm): add Antigravity reasoning-effort overrides (#7228)
* feat(mitm): add Antigravity reasoning-effort overrides

The Antigravity MITM alias mapping only ever swapped the destination model;
there was no way to override the reasoning effort Antigravity's own
thinkingConfig requested. Alias entries are now `{ model?, reasoningEffort? }`
(a legacy plain-string mapping still normalizes to `{ model }`, so no DB
migration is required). The standalone proxy (server.cjs) forwards the chosen
tier as a top-level `reasoningEffortOverride` on the intercepted request; the
antigravity->openai translator honors it ahead of its thinkingConfig-derived
guess, and an explicit "none" suppresses reasoning_effort entirely even when
Antigravity's own request asked for thinking. Reuses the existing canonical
5-tier reasoning vocabulary (`@/shared/reasoning/effortStandardization.ts`,
with max/extra aliasing to xhigh) instead of introducing a new one. The API
route validates the reasoning-effort value at the boundary and the Antigravity
tool card UI now exposes a per-model reasoning-effort selector alongside the
existing model-mapping input.

Co-authored-by: Truong Fiu <gnourtf@gmail.com>
Inspired-by: https://github.com/decolua/9router/pull/2584

* chore(changelog): fragment for #7228

---------

Co-authored-by: Truong Fiu <gnourtf@gmail.com>
2026-07-17 10:40:14 -03:00
Diego Rodrigues de Sa e Souza
0f10225f1d feat(dashboard): add compression-mode selector to Context & Cache combos page (#6760) (#7219)
Extracts the routing-combo compression-mode dropdown (Default/Off/Lite/
Standard/Aggressive/Ultra) from the combo card into a shared
ComboCompressionModeSelect component, reused on both the combo card
(compact) and the Compression Combos page's "Assign to routing" list
under Context & Cache. Both surfaces persist through the existing
PUT /api/combos/{id} route -- no backend or schema change.
2026-07-17 10:40:05 -03:00
Diego Rodrigues de Sa e Souza
69c778eb45 feat(api): expose GET /api/usage/model-latency-stats (#6873) (#7218) 2026-07-17 10:40:01 -03:00
Diego Rodrigues de Sa e Souza
57ac712772 feat(api): add Vary: Accept-Encoding to token-authenticated /v1* responses (#6737) (#7217) 2026-07-17 10:39:58 -03:00
Diego Rodrigues de Sa e Souza
a2df195d5e fix: honor PROVIDER_LIMITS_SYNC_SPACING_MS for local/API-key connections (#6916) (#7214) 2026-07-17 10:39:54 -03:00
Diego Rodrigues de Sa e Souza
eb529cfa12 feat(dashboard): add reorder connections by availability button (#7211)
* feat(dashboard): add reorder-by-availability button to provider connections

Adds a "Reorder" action to the provider detail Connections toolbar that
sorts a provider's connections so available ones float to the top and
unavailable ones sink to the bottom, then persists the new order via the
existing per-connection priority PUT endpoint (same pattern already used
by handleSwapPriority).

Availability is computed with OmniRoute's own resilience model rather than
upstream's `modelLock_*` convention: a connection counts as available when
its effective status (testStatus, adjusted for the lazy connection-cooldown
window via rateLimitedUntil) is active/success — mirroring the exact logic
ConnectionRow already uses for its status badge, so the button and the row
badges never disagree. The sort is a stable Array.prototype.sort, so
connections keep their relative order within each availability group.

New pure helpers (sortConnectionsByAvailability, isConnectionAvailable,
getConnectionEffectiveStatus) live in connectionRowHelpers.ts and are
covered by a dedicated unit test, including the cooldown-lazy-recovery
edge case. i18n keys added to all 43 locales.

Co-authored-by: Fazril Syaveral Hillaby <fazriloke18@gmail.com>
Inspired-by: https://github.com/decolua/9router/pull/2558

* chore(changelog): fragment for #7211

* fix(dashboard): extract reorder-by-availability into its own hook (file-size ratchet)

The reorder-by-availability feature pushed useProviderConnections.ts to 974
lines, past its frozen file-size cap (954). Extract the handler + its state
into a dedicated useReorderByAvailability hook, following the same pattern
already used for useModelVisibilityHandlers/useModelImportHandlers — no
behavior change, same tests still cover the sort logic in
connectionRowHelpers.ts.

* fix(dashboard): type the reorder hook's notifier explicitly (dashboard-typecheck TS2339)

ReturnType<typeof useNotificationStore> resolves to unknown under the
dashboard-scoped tsconfig gate (#7203), so notify.error tripped TS2339.
The hook only needs error(), so declare that minimal surface directly.

---------

Co-authored-by: Fazril Syaveral Hillaby <fazriloke18@gmail.com>
2026-07-17 10:39:50 -03:00
Diego Rodrigues de Sa e Souza
97f993013d feat(dashboard): show Codex plan label in provider and quota views (#7210)
* feat(dashboard): show Codex plan label in provider and quota views

ConnectionRow on the provider-detail page never surfaced the Codex
subscription plan captured at OAuth import time
(providerSpecificData.chatgptPlanType, src/lib/oauth/services/codexImport.ts)
anywhere in the row UI. Added a small pure helper, getCodexPlanLabel, and a
Badge in ConnectionRow gated on isCodex.

Separately, the quota view's plan-badge machinery (resolvePlanValue /
tierByConnection / QuotaCardHeader) already existed for all providers, but
its persisted-metadata fallback list omitted chatgptPlanType. When the live
Codex usage endpoint has no plan_type/planType field, the usage service
reports the literal string "unknown" (open-sse/services/usage/codex.ts),
which resolvePlanValue's normalizePlanCandidate() filters out — so the quota
badge fell through to "Unknown" instead of the plan captured at login.
Added chatgptPlanType to the persisted candidate list.

Co-authored-by: Carmelo Campos <carmelogunsroses@gmail.com>
Inspired-by: https://github.com/decolua/9router/pull/2570

* chore(changelog): fragment for #7210

* fix(dashboard): extract getCodexPlanLabel to unfreeze providerPageHelpers.ts

The Fast Quality Gates file-size ratchet froze
providerPageHelpers.ts at 1053 lines; adding getCodexPlanLabel
inline pushed it to 1067. Move the self-contained helper into its
own codexPlanLabel.ts module instead of growing the frozen file,
and repoint ConnectionRow.tsx + the regression test at the new
location. No behavior change.

---------

Co-authored-by: Carmelo Campos <carmelogunsroses@gmail.com>
2026-07-17 10:39:46 -03:00
Diego Rodrigues de Sa e Souza
2dc4a92be7 feat(kiro): register GPT-5.6 Sol/Terra/Luna model family (#7209)
* feat(kiro): register GPT-5.6 Sol/Terra/Luna model family

Kiro announced its first OpenAI-family models on 2026-07-14
(kiro.dev/changelog/models): GPT-5.6 Sol (flagship), Terra (balanced
mid-tier) and Luna (fastest/cheapest), all sharing a 272k context
window. Registers the three base model ids in the kiro provider
registry with contextLength/maxOutputTokens so getResolvedModelCapabilities()
resolves the real 272k window instead of falling back to the generic
default. OmniRoute derives the thinking/agentic synthetic variants and
per-account rate multipliers dynamically at discovery time
(open-sse/services/kiroModels.ts), so only the three base entries need
static registration here.

Co-authored-by: Edison42 <gn00742754@gmail.com>
Inspired-by: https://github.com/decolua/9router/pull/2596

* chore(changelog): fragment for #7209

* fix(kiro): add GPT-5.6 Sol/Terra/Luna pricing rows

The registry additions in this PR exposed three new Kiro model ids
without matching pricing rows, tripping the catalog invariant that
every Kiro registry model must resolve a non-zero pricing row
(tests/unit/catalog-updates-v3x.test.ts) — the models would have
billed at $0.00.

Reuses the shared GPT_5_6_{SOL,TERRA,LUNA}_PRICING tiers already used
by the codex and openai aliases.

---------

Co-authored-by: Edison42 <gn00742754@gmail.com>
2026-07-17 10:39:42 -03:00
Diego Rodrigues de Sa e Souza
2b24448bd6 fix(oauth): resolve Kiro AWS SSO cache client credentials by clientId match (port from 9router#1253) (#7122)
tryAwsSsoCache() only resolved clientId/clientSecret via data.clientIdHash -> <hash>.json. Newer kiro-auth-token.json files instead carry a top-level clientId directly, so that lookup silently failed and left clientId/clientSecret null, sending the dashboard's Import Token POST down the non-IDC path. That path (KiroService.validateImportToken -> readCachedClientCredentials) picked a client registration by region + latest-expiry across ALL cached SSO client registrations, ignoring the token's actual clientId — on a machine with multiple stale registrations this returned a mismatched clientId/clientSecret pair, producing 'Bad credentials' on refresh.

Fix: resolve clientId/clientSecret by scanning the cache for a registration file whose own clientId matches the token's clientId (falling back to clientIdHash first, then a direct-match scan), and thread an optional clientId hint into readCachedClientCredentials()/validateImportToken() so an exact match always wins over the region/latest-expiry heuristic.

Reported-by: Asher (@XCrag) (https://github.com/decolua/9router/issues/1253)
2026-07-17 10:39:29 -03:00
Diego Rodrigues de Sa e Souza
b914eb1b0f feat(providers): curated OpenRouter embeddings catalog + specialty merge in live discovery (#6976) (#6994)
* feat(providers): curated OpenRouter embeddings catalog + specialty merge in live discovery (#6976)

OpenRouter serves embeddings via a dedicated OpenAI-compatible
/api/v1/embeddings endpoint that is omitted from /v1/models, and the
embeddingRegistry entry for it was stale (3 legacy ids). Meanwhile
providerModelsConfig gives openrouter a live discovery config, so
buildApiDiscoveryResponse's success path returned only the live chat
catalog verbatim — the specialty (embeddings/rerank) static catalog was
only ever merged in on the no-config local_catalog fallback, so OpenRouter
embeddings never surfaced through model discovery.

Refreshed the curated openrouter embeddingRegistry lineup (ids verified
against https://openrouter.ai/docs/api/reference/embeddings and the
collections page) and added a scoped, additive merge
(mergeSpecialtyCatalogIntoLiveModels, allowlisted to openrouter) that folds
embeddings/rerank entries from getStaticModelsForProvider() into the live
discovery response, deduped by id. Scoped as an allowlist rather than a
blanket merge because some providers (e.g. Gemini) already return
embedding models directly from their live /v1/models endpoint, where a
blind merge would risk stale/duplicate entries.

* test(providers): type the models discovery payload instead of any (#6976)

no-explicit-any is an error under tests/ (#6218), so the 4 `any`
usages in the new discovery assertions failed the max-warnings-0
lint gate. Replace them with an explicit ModelsResponseBody shape —
type-only change, all 13 assertions unchanged and still passing.

* test(providers): type the openrouter merge assertion callback (#6976)

The new #6976 assertion added a 56th explicit `any` to this file,
one over the 55 frozen in config/quality/eslint-suppressions.json,
tripping the max-warnings-0 lint gate. Type the callback param
instead of raising the frozen count — the debt ratchet only decreases.
All 59 tests still pass.
2026-07-17 10:39:21 -03:00
Diego Rodrigues de Sa e Souza
c46d35bcb4 fix(dashboard): hide disabled provider connections from combo builder (#6984)
* fix(dashboard): hide disabled provider connections from combo builder

The combos page's fetchData() only filtered available connections by
testStatus ("active"/"success"), so a connection the user had
explicitly disabled (isActive: false) could still show up in the
combo builder if it carried a stale testStatus from before it was
disabled.

Add filterActiveConnections() in src/shared/utils/connectionStatus.ts
and apply it ahead of the existing testStatus filter.

Co-authored-by: itolstov <attid0@gmail.com>
Inspired-by: https://github.com/decolua/9router/pull/2526

* chore(changelog): fragment for #6984

* fix(combos): keep combos page within frozen size cap

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

* fix(combos): extract filterUsableConnections to shrink the combos god-file

The combos page only filtered provider connections on testStatus, so a
connection the user had explicitly disabled survived with a stale
"active"/"success" status. The isActive + testStatus gate now lives in
the shared connectionStatus util as filterUsableConnections(), which the
page calls in a single line.

This keeps src/app/(dashboard)/dashboard/combos/page.tsx BELOW its frozen
file-size cap (4653 vs 4655 congelado — the file shrinks by 2 lines vs the
release tip) without touching config/quality/file-size-baseline.json, as
the gate asks ("modularize/extraia (DRY) para encolher").

The regression test now exercises filterUsableConnections directly instead
of hand-mirroring the page's filter chain, so it guards the real code path.

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

* fix(combos): drop nullish entries in filterActiveConnections

`connection?.isActive !== false` evaluated to true for null/undefined
entries, so nullish elements survived the filter. Callers read properties
off the result — filterUsableConnections() reads `connection.testStatus`
— which would throw "TypeError: Cannot read properties of null".

Guard with an explicit truthiness check. Covered by a test that fails
against the previous predicate.

Reported-by: gemini-code-assist
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

---------

Co-authored-by: itolstov <attid0@gmail.com>
2026-07-17 10:39:18 -03:00
Diego Rodrigues de Sa e Souza
2013103765 fix(providers): derive static model catalogs for search providers from searchTypes (#7589)
getStaticModelsForProvider() only defined literal catalogs for
linkup-search, ollama-search, and searchapi-search out of the 12 ids in
SEARCH_PROVIDERS. The other 9 (serper-search, brave-search,
perplexity-search, exa-search, tavily-search, google-pse-search,
youcom-search, searxng-search, zai-search) returned undefined and hit
the 400 "does not support models listing" tail in the models route
during the "Import Models" step.

Instead of adding 9 more one-off literal entries, generalize the class:
when a provider has no dedicated STATIC_MODEL_PROVIDERS entry, fall
back to a catalog derived from SEARCH_PROVIDERS[id].searchTypes (every
search-registry entry already declares this). Future search providers
added to searchRegistry.ts automatically get a usable catalog with zero
extra code.

Closes #7529
2026-07-17 07:12:38 -03:00
Diego Rodrigues de Sa e Souza
265d00c0e1 fix(sse): honor per-model targetFormat override for zai/glm-coding-apikey (#7364) (#7584)
DefaultExecutor.buildUrl()'s "zai"/"glm-coding-apikey" case always returned
the Anthropic Messages URL, ignoring a per-model targetFormat override
(custom-model dropdown, #2905) that resolves to "openai" — e.g. for a vision
model like glm-4.6v. chatCore/executionCredentials.ts now threads the
resolved override onto providerSpecificData.targetFormat so buildUrl (via
the new default/zaiFormatOverride.ts helper, extracted to respect the
file-size ratchet) can route to the OpenAI-compatible endpoint instead.

Separately, custom-model id lookup (lookupCustomModelMeta in
src/sse/services/model.ts, getCustomModelRow in src/lib/db/models.ts) did an
exact, case-sensitive match, so a model saved as "glm-4.6v" was invisible
when looked up as "glm-4.6V". Both now fall back to a case-insensitive match
after the exact match fails.

Regression tests: tests/unit/zai-glm-target-format-override.test.ts (reused
from the triage plan-file's RED probe) and
tests/unit/zai-execution-credentials-target-format-7364.test.ts (production
wiring in executionCredentials.ts).

Gates run: check-file-size, check-complexity, check-cognitive-complexity,
typecheck:core, eslint (suppressions), tests/unit/zai-glm-target-format-override.test.ts,
tests/unit/zai-execution-credentials-target-format-7364.test.ts,
tests/unit/executor-default-base.test.ts, tests/unit/custom-model-target-format.test.ts,
tests/unit/chatcore-execution-credentials.test.ts, tests/unit/chatcore-target-format.test.ts,
tests/unit/model-resolver.test.ts, tests/unit/model-alias-provider-resolution.test.ts,
tests/unit/combo-custom-provider-resolution.test.ts — all green.

Refs #7364
2026-07-17 06:46:50 -03:00
Diego Rodrigues de Sa e Souza
6459dde35c fix(cli): reuse win32-aware locateCommand in tool-detector (#7279) (#7569)
detectBinary() in tool-detector.ts never checked process.platform and never
passed shell:true, so on native Windows an installed CLI (npm installs
claude/codex/opencode as .cmd shims) was reported as NOT installed:
1. execFileImpl(binary, ["--version"]) fails without shell:true for .cmd
   shims (Node's CVE-2024-27980 hardening).
2. the `which` fallback doesn't exist on native Windows (no WSL/git-bash).

Both threw, both were swallowed by empty catches, detectBinary returned
{installed: false}. cliRuntime.ts::locateCommand already solved this for the
runtime-spawn path (#968) but never propagated here — re-drift, per the
issue title.

Exports locateCommand from cliRuntime.ts and reuses it (+ shouldUseShellForCommand,
+ getLookupEnv) for the win32 existence/path probe in tool-detector.ts, keeping
the --version probe local but shell-gated. Also routes the which fallback through
the injectable execFileImpl hook (it previously called the raw execFileAsync,
making it unmockable and prone to false-positives from a real system which).
2026-07-17 06:36:04 -03:00
Diego Rodrigues de Sa e Souza
6b0c295b95 fix(sse): stop per-byte enumeration of binary image bytes in log redaction (#7297) (#7576)
captureCurrentProviderRequest mirrors every Bedrock Converse request into the
pending-request log tracker right after openAIToBedrockConverse() builds it,
including the decoded image.source.bytes Uint8Array. sanitizePayloadPII() and
redactPayload() in src/lib/logPayloads.ts both gate their recursive walk on
Array.isArray(), which is false for typed arrays, so each image fell into the
generic-object branch and got enumerated one JS key per decoded byte (twice,
once per function) before any truncation bound applied. For 3x ~1MB images
this took ~4s of synchronous, event-loop-blocking work, matching the
reporter's "1-2 images OK, 3+ fails" threshold and their --stack-size
observation (data-width pressure, not call-depth).

Add an opaque-binary short-circuit (ArrayBuffer.isView) ahead of the
Array.isArray branch in both functions, returning a fixed-size placeholder
instead of recursing. Apply the same guard to cloneBoundedForLog() in
open-sse/utils/requestLogger.ts for defense-in-depth (same blind spot, only
accidentally safe today via its own key-count slice).

Regression test reproduces the exact reporter shape (3x 1MB images) through
the real openAIToBedrockConverse() converter and protectPayloadForLog(),
asserting completion well under the previous ~4s and that binary bytes are
never expanded into per-byte object keys.
2026-07-17 06:12:07 -03:00
Diego Rodrigues de Sa e Souza
eb92e626d2 fix(api): resolve provider display name and dedup byModel on normalized key (#7534, #7535) (#7573)
- byProvider now resolves the internal provider id to its configured
  display name via getProviderById() (fallback: raw id for providers
  not in the static registry). Fixes the Usage page showing "codex"
  instead of "OpenAI Codex".
- byModel's in-memory dedup key now uses the normalized model name
  instead of the raw one, so the same logical model recorded under a
  bare and a provider-prefixed spelling (e.g. "glm-5.2" vs
  "z-ai/glm-5.2") merges into a single aggregated row instead of
  appearing twice with the same displayed name.
- Introduces a local UsageRows type alias in route.ts to shrink the
  repeated "as Array<Record<string, unknown>>" casts back under the
  frozen file-size baseline once the file was touched.
2026-07-17 06:11:43 -03:00
Diego Rodrigues de Sa e Souza
63c85ea76d fix(dashboard): resolve costs page 500 from out-of-scope t() in TopListCard (#7272) (#7564)
* fix(dashboard): resolve costs page 500 from out-of-scope t() in TopListCard (#7272)

TopListCard (CostOverviewTab.tsx) referenced the bare identifier `t`
from an outer component's scope when rendering the zero-cost /
!hasCostData branch, throwing "ReferenceError: t is not defined" and
crashing /dashboard/costs?range=all&apiKeyIds=...&groupBy=model
whenever a filtered slice landed only $0-cost rows.

Extracted TopListCard into its own component file and threaded the
resolved legacyFreeLabel string in as a prop, mirroring the existing
CostBreakdownTable pattern in the same file. Also fixes a case of the
typecheck:core dashboard .tsx coverage gap tracked in #7033.

* test(dashboard): move TopListCard #7272 regression test to vitest UI project

The node:test unit runner cannot load TopListCard's import chain
(@/shared/components -> ProviderIcon -> @lobehub/icons ESM), which made
the "Impacted unit tests (TIA subset; blocking)" CI job red with
"Unexpected token 'export'" on tests/unit/costs-toplistcard-legacy-free-label-7272.test.ts.

Moved the regression test to tests/unit/ui/ and rewrote it against the
vitest UI project (test:vitest:ui, blocking in the test-vitest CI job),
which handles the ESM import chain natively. Verified the test still
fails with "ReferenceError: t is not defined" against the pre-#7272
TopListCard body and passes against the fixed component.
2026-07-17 06:11:35 -03:00
Diego Rodrigues de Sa e Souza
ff15646f9b fix(db): pre-init sql.js WASM ahead of any getDbInstance() consumer (#7288) (#7562)
* fix(db): pre-init sql.js WASM ahead of any getDbInstance() consumer (#7288)

* fix(db): close sqljs preinit ordering gap without top-level await (#7288)

The previous fix added a top-level await barrier at the bottom of
src/lib/db/core.ts to guarantee sql.js pre-init before any consumer
reached getDbInstance(). That made core.ts an async ES module, which
broke esbuild's CJS require() bundling for every test file that does
require("../../src/lib/db/core.ts") (tsx's CJS require hook rejects
requiring a transitive dependency with a top-level await), and caused
unrelated tests running in the same node:test process to fail with
"Promise resolution is still pending but the event loop has already
resolved".

Move the fix to the real startup entrypoint instead: registerNodejs()
(src/instrumentation-node.ts) now awaits ensureDbReadyForBoot() before
ensureSecrets()/clearStaleCrashCooldowns()/getSettings()/initAuditLog(),
all of which reach getDbInstance() transitively. ensureDbInitialized()
is idempotent, so later getDbInstance() calls are free cache reads.

The driverFactory.ts error-surfacing improvements from the original
#7288 fix (logging swallowed sync-driver errors, surfacing the real
sql.js pre-init failure instead of the generic "not pre-initialized
yet" message) are unchanged.

Updated tests/unit/db-sqljs-preinit-ordering-gap-7288.test.ts to prove:
no top-level await in core.ts, the corrected call order in
registerNodejs() (source-order assertion), and the original
getDbInstance()-no-longer-throws-the-misleading-message behavior driven
via the same warm-up path ensureDbReadyForBoot() now guarantees ahead
of every other startup step.

* refactor(db): drop the orphaned preInitSqlJsIfSyncDriversUnavailable helper (#7288)

Moving the ordering guarantee to registerNodejs() left this exported
helper with zero production callers — its own docblock still claimed it
was 'Chamada no top level de core.ts', describing an architecture the
hotfix removed. It was also redundant: ensureDbInitialized() already does
tryOpenSync-then-preInitSqlJs on the real boot path (core.ts:1358).

Its two tests exercised the helper as a stand-in for the real warm-up
('Simulates the fixed ordering'), so they proved a simulation rather than
production behaviour. They now drive ensureDbReadyForBoot()/tryOpenSync()
directly. The ordering guard still fails against the unfixed
instrumentation-node.ts (verified) and the whole file is 4/4 green.

The live parts of the driverFactory change (logSwallowedDriverError,
getSqlJsPreInitError) are untouched — both have real callers.
2026-07-17 06:11:27 -03:00
Diego Rodrigues de Sa e Souza
b9847bf791 fix(dashboard): surface rate-limit warning on 429 chat-probe (#7284) (#7565)
Root cause: validateOpenAILikeProvider's chat-probe status handling
(src/lib/providers/validation/openaiFormat.ts) only special-cased
401/403, 404/405, and >=500 — every other status, including 429, fell
through to the unqualified return { valid: true, error: null }. For
permanently-throttled free tiers (e.g. opencode-zen, classified
'avoid' in freeTierCatalog.ts), the dashboard connection Test reported
green forever while real traffic hit 429 on every request, with no
signal to the user.

Fix mirrors the existing validateBedrockProvider 429 precedent in the
same file: keep valid:true (the key is accepted) but add a warning
field describing the rate limit, instead of an indistinguishable pass.

Regression test: tests/unit/issue-7284-connection-test-masks-429.test.ts
mocks a 404 /models probe followed by a 429 chat probe and asserts the
result now carries { valid: true, error: null, warning: <rate-limit string> }.

Gates run (all green):
- node --import tsx/esm --test tests/unit/issue-7284-connection-test-masks-429.test.ts
- node --import tsx/esm --test tests/unit/provider-validation-firepass-403.test.ts tests/unit/validation-format-validators-split.test.ts (existing tests of the touched area, unaffected)
- node scripts/check/check-file-size.mjs
- node scripts/check/check-complexity.mjs (2054/2056 baseline, unchanged)
- node scripts/check/check-cognitive-complexity.mjs (889/890 baseline, unchanged)
- npm run typecheck:core
- npx eslint --suppressions-location config/quality/eslint-suppressions.json <changed files>
2026-07-17 05:32:25 -03:00
Diego Rodrigues de Sa e Souza
851582a88a fix(dashboard): providers model-name filter matches live/synced catalog (#7250) (#7561)
Root cause: the Providers page model-name filter
(filterConfiguredProviderEntries) matched only against
getModelsByProviderId(...), the static curated model registry, never
the live/synced catalog for the connection. Aggregator providers
(openrouter, kilocode, theoldllm...) declare a single-entry static
placeholder (e.g. openrouter's {id:'auto',name:'Auto (Best Available)'}),
so searching for any real upstream model name could never match and the
whole provider silently disappeared from the list.

Fix: source the live/synced catalog (already persisted per-connection via
GET /api/synced-available-models, the same store the combo builder's model
picker already relies on) via a new useSyncedModelsByProvider hook, and
union it with the static registry inside the filter. An empty/never-synced
catalog falls back to the static-only match so already-correct static
providers are unaffected.

Regression test: tests/unit/provider-model-filter-live-catalog-7250.test.ts
reproduces the original bug (static-only match returns 0 results for a
real model name) and proves the fix (live catalog match returns 1),
plus non-regression coverage for the static-only fast path and unrelated
providers.
2026-07-17 05:01:23 -03:00
Diego Rodrigues de Sa e Souza
0bd60f3fb2 fix(cli): Windows cert check/uninstall key off the real CA identity, not a hardcoded legacy host (#7275) (#7557)
* fix(cli): Windows cert check/uninstall key off the real CA identity, not a hardcoded legacy host (#7275)

checkCertInstalledWindows()/uninstallCertWindows() queried the Windows Root
store by the literal legacy hostname daily-cloudcode-pa.googleapis.com
regardless of the certPath passed in (the check's param was even
underscore-prefixed/unused). It only worked because that hostname happens to
be the generated CA's own commonName today (generate.ts derives it from
ANTIGRAVITY_TARGET.hosts[0]) -- a coincidence, not a derivation, with no
shared symbol coupling the two. installCertWindows() was already correct.

Both functions now derive a SHA-1 thumbprint straight from the certPath file
via the new exported certutilThumbprint() helper (reusing the existing
getCertFingerprint() logic checkCertInstalledMac() already keys off), so the
Windows store lookup/delete always matches the real generated CA regardless
of any future rename/reorder of ANTIGRAVITY_TARGET.hosts in generate.ts. Same
anti-pattern class as #6338 (DNS side).

* test(cli): assert certutil argv precisely instead of scanning for the legacy host (#7275)

The two negative substring checks tripped CodeQL's
js/incomplete-url-substring-sanitization (high) by looking like URL
sanitization while actually asserting a certutil argv. Replaced with
assertions on the exact argv / extracted certId, which are strictly
stronger: pinning the value proves no other identity can be passed.
Both still fail against the unfixed install.ts (5/5 RED) and pass with
the fix (5/5 GREEN).
2026-07-17 05:01:16 -03:00
Diego Rodrigues de Sa e Souza
12b25d5e0d fix(i18n): treat __MISSING__ sync placeholders as absent in EN fallback (#7258) (#7556)
deepMergeFallback in src/i18n/request.ts only substituted the English
fallback value when a key was entirely undefined. Keys backfilled by
scripts/i18n/sync-ui-keys.mjs with the __MISSING__:<english> sentinel
exist on the target object, so they passed through untouched and were
rendered raw to the user (395 zh-TW keys, 337 pt-BR keys, systemic
across locales).

Now any target leaf that still carries the __MISSING__: prefix is
treated the same as an absent key, so the clean EN value wins.

Does not touch the underlying translation content (395/337 strings) -
that is a separate content workstream.
2026-07-17 04:23:30 -03:00
Diego Rodrigues de Sa e Souza
197f726c62 fix(oauth): surface tunnel hint when Codex OAuth runs on a remote host (#7523) (#7527)
The PKCE callback server binds the SERVER's loopback (localhost:PORT). When
the operator drives the OAuth flow from a different machine (OmniRoute on a
remote host/VPS), the provider redirects the browser to the operator's OWN
localhost:PORT — the confirmation screen hangs forever with no explanation.

start-callback-server now inspects the request Host: on a non-loopback host it
returns { remoteHost, tunnelCommand, message } so the UI can show the
'ssh -L PORT:127.0.0.1:PORT' instruction (or steer to the paste/import flow)
instead of a silent hang. Loopback access is unaffected. The Host header is
spoofable, so this drives only a UI hint — never an auth decision.

Logic extracted to remoteOAuthHint.ts (keeps the god-route under its size
budget and makes it unit-testable). TDD: 4 tests covering loopback (no hint),
null host (fail-open), and remote host (correct tunnel command for both the
fixed 1455 and OS-assigned ports).

Closes #7523
2026-07-17 02:39:30 -03:00
Diego Rodrigues de Sa e Souza
ab01d74306 fix(codex): validate refresh_token on import before persisting (#7522) (#7525)
POST /api/oauth/codex/import accepted a payload with an already-invalidated
refresh_token and persisted it as an 'active' connection that could never
work — the failure surfaced confusingly only on first real use, long after
the import looked successful.

Validate each record's refresh_token against OpenAI's OAuth endpoint before
persisting, reusing refreshCodexToken() (free exchange, no quota). On an
unrecoverable refresh error the record is rejected with a clear re-auth
message; a valid token imports as before, with any rotated tokens applied.
Bulk import still processes each record independently — one dead token no
longer blocks the valid ones.

Reproduced live 2026-07-16: a 2026-07-10 auth.json imported clean but its
refresh_token returned 401 refresh_token_invalidated. TDD: 3 tests RED against
the old route, 5/5 GREEN with the fix.

Closes #7522
2026-07-17 02:39:11 -03:00
Diego Rodrigues de Sa e Souza
7974d03b9d fix(codex): Test probe uses a ChatGPT-account-supported model (#7521) (#7524)
The connection Test button always reported success for a Codex connection
backed by a ChatGPT account: the probe sent `gpt-5.3-codex`, a codex-only
model the ChatGPT-account backend rejects outright with a 400 — the same
status the probe treats as 'auth accepted, body invalid'. A bad token and a
good token both came back 400, so Test could never fail on a bad token.

Probe with `gpt-5.5` (confirmed served for ChatGPT-account sessions via live
VPS test 2026-07-16) instead; `input: []` still yields the intended 400 for a
good token, 401/403 for a bad one.

Live verification (VPS): gpt-5.3-codex, gpt-5.6-sol and the gpt-5*-codex ids all
return 'not supported when using Codex with a ChatGPT account'; gpt-5.5 and
gpt-5.6-terra answer normally on the same account.

Closes #7521
2026-07-17 02:39:02 -03:00
Wibias
88507a6edc [needs-vps] fix(dashboard): align onboarding tier content (#7125)
* fix(dashboard): align onboarding welcome feature cards vertically

* fix(dashboard): align onboarding tier content

* chore: scope onboarding PR to UI fix

* i18n(pt-BR): add onboarding.tier.flowCaption + afterSetup keys

The two new tier keys added to en.json were missing from pt-BR.json, tripping
the i18n-pt-br no-drift test (#6695). Add their pt-BR translations.

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-17 01:01:29 -03:00
Diego Rodrigues de Sa e Souza
3f8acbf835 [needs-vps] fix(dashboard): add vision-capability toggle for custom OpenAI-compatible models (#7124)
* fix(dashboard): add vision-capability toggle for custom OpenAI-compatible models (port from 9router#1904)

detectVisionInput()/getCustomVisionCapabilityFields() already honoured an explicit
supportsVision flag on a custom-model record, but there was no way to set it: the
POST/PUT /api/provider-models Zod schema and updateCustomModel()/addCustomModel()
silently dropped the field, and the 'Custom Models' add/edit UI had no checkbox at
all. Self-hosted/local backends that don't self-report an image input modality
(OpenRouter-style architecture.input_modalities) therefore had no way to be flagged
vision-capable, so the vision tag never appeared and image inputs were rejected.

Reported-by: nguyenphi37 (https://github.com/decolua/9router/issues/1904)

* refactor(dashboard): extract providerCredentialText from providerPageHelpers to respect the file-size gate

providerPageHelpers.ts is a frozen god-file (cap 1053, split(\n).length metric)
and this PR's own +3 lines (the #1904 supportsVision field) pushed it to 1054,
failing check:file-size. Extract the cohesive providerText utility + the 4
web-session-credential label/hint/title helpers into a new leaf module
(providerCredentialText.ts), re-exported from providerPageHelpers.ts for
backward compatibility so all existing import sites keep working unchanged.
File now sits at 946 lines, well under the frozen cap.

* refactor(db): extract tri-state override helper to keep the complexity ratchet at baseline

The #1904 supportsVision override added a second copy of the "absent keeps /
null clears / else coerce" block already used by preserveOpenAIDeveloperRole,
pushing updateCustomModel to 84 lines and check:complexity to 2057 > 2056.

The file-size failure was masking this one: the gate exits on its first red,
so complexity never ran until providerPageHelpers was back under its cap.

Fold both blocks into applyTriStateBooleanOverride(). Behavior is unchanged —
updateCustomModel is back under max-lines-per-function and the global count
returns to the 2056 baseline (cognitive-complexity stays at 890).
2026-07-17 01:01:18 -03:00
KooshaPari
fd468b5ef1 Use OpenAI chunks for early chat keepalives (#7136)
* Use OpenAI chunks for early chat keepalives

* Update keepalive assertion to match chat completion chunk format

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>
2026-07-16 14:14:27 -03:00
Diego Rodrigues de Sa e Souza
db5ee5995b fix(combos): reject oversized fusion panels before fan-out (port from 9router#1905) (#7120)
A fusion combo fans every panel model out in parallel and buffers each
model's full response text in memory simultaneously. With the runtime heap
capped by Dockerfile's OMNIROUTE_MEMORY_MB (default 1024MB), a large panel
(reported: ~73 models via an 'auto' combo with strategy: fusion) with
sizable concurrent responses can exceed the heap ceiling and OOM-crash the
whole container instead of failing one request.

handleFusionChat now rejects panels above a configurable hard cap
(FUSION_DEFAULTS.maxPanel = 40, overridable per-combo via
fusionTuning.maxPanel) with a clean 400 before fan-out begins.

Reported-by: Phong Vu (@fontvu) (https://github.com/decolua/9router/issues/1905)
2026-07-16 14:14:05 -03:00
Diego Rodrigues de Sa e Souza
86b293d3a3 fix(api): check Vercel SSO-protection PATCH response on relay deploy (#7119)
* fix(api): check Vercel SSO-protection PATCH response on relay deploy (port from 9router#1037)

The Vercel relay deploy route disabled Deployment Protection (SSO) by firing a PATCH request with .catch(() => {}) and never checking res.ok. When Vercel rejects or no-ops the PATCH (plan doesn't allow disabling protection, an under-scoped token, etc.), the relay was still saved and activated as a healthy proxy pool, and later requests routed through it failed with an undiagnosed 403 Access denied from Vercel's own deployment protection — indistinguishable from an upstream-provider rejection (e.g. Codex/ChatGPT edge-IP blocking).

Extract disableSsoProtection() to check the PATCH response and surface an ssoProtectionWarning in the deploy response when it fails, so the failure source can be diagnosed instead of silently masked.

Reported-by: Rico Aditya (@ricatix) (https://github.com/decolua/9router/issues/1037)

* refactor(api): extract vercel-deploy POST helpers to keep the cognitive-complexity ratchet at baseline

The SSO-protection check added to POST pushed its cognitive complexity from
15 to 21, regressing the cognitive-complexity ratchet (891 > baseline 890).

Extract two pure helpers with identical behavior:
- buildDeployErrorResponse(): the sanitized non-ok Vercel deploy response
- resolveSsoProtectionWarning(): the SSO PATCH check + warning string

POST now reads as a flat sequence of guards. No behavior change.
2026-07-16 14:13:57 -03:00
Diego Rodrigues de Sa e Souza
c48e54604f fix(cli): remove MITM DNS spoof entries before killing server process (#7117)
* fix(cli): remove MITM DNS spoof entries before killing server process (port from 9router#1809)

stopMitm() killed the spawned MITM server process first and only removed the /etc/hosts DNS-spoof entries afterward. During that window any client whose DNS still resolved a target host to 127.0.0.1 but whose MITM listener was already dead got connect ECONNREFUSED 127.0.0.1:443 — exactly the community-confirmed workaround (stop DNS before stopping the server) proves. Swap the two steps so DNS is always cleared first, mirroring the ordering already used by repairMitm() and handleExitCleanup().

Reported-by: dionisius95 (https://github.com/decolua/9router/issues/1809)

* refactor(mitm): extract repair planning out of manager to respect the file-size cap

The #1809 DNS-before-kill ordering fix pushed src/mitm/manager.ts to 813
lines, over the 800-line cap check:file-size enforces for non-frozen files.

Move the pure repair-planning pieces (collectManagedHosts, the RepairPlan
shape and its filesystem/cert/DNS sweep) into a sibling src/mitm/repair.ts.
The in-memory session bookkeeping repairMitm() owns — cached sudo password,
orphaned flag, PID file — deliberately stays in manager.ts, so the seam is
"plan the repair" vs "own the session".

manager.ts is now 731 lines; behavior is unchanged. The DNS-first ordering
fix and its regression guard (tests/unit/mitm-stop-dns-before-kill-1809.ts)
are untouched and still pass.

* fix(mitm): split stopMitm() DNS/kill steps to fix complexity ratchet regression

stopMitm()'s new DNS-before-kill ordering (#1809) pushed its cyclomatic
complexity to 18 (max 15), regressing the complexity ratchet from 2056 to
2057. Extract the DNS-removal step and the process-kill step (in-memory +
PID-file fallback) into two private helpers, mirroring the existing
performRepairSteps() extraction pattern in repair.ts. Behavior unchanged;
complexity back at 2056 (cognitive-complexity drops to 889, one under
baseline).
2026-07-16 14:13:50 -03:00
Diego Rodrigues de Sa e Souza
39222525ea fix(providers): surface a warning on 404 model_not_found in OpenAI-compatible Check (port from 9router#2032) (#7103)
Root cause: validateOpenAICompatibleProvider's chat-completions probe fallback treated ANY 4xx other than 401/403/429/400 as a silent 'credentials valid' pass with no warning, so a bogus/non-standard model id (e.g. Featherless/OpenRouter vendor/model typos) went undetected at Check time. The first real request then hit the upstream 404 model_not_found and the per-model lockout, holding the model unavailable for the configured reset window with no prior indication anything was wrong.

User-visible effect: 'Check' now returns valid:true with an explicit warning (including the upstream error message when parseable) whenever the chat probe answers 404, so a bad model id is caught before it reaches production traffic and the lockout.

Reported-by: advane204f (https://github.com/decolua/9router/issues/2032)
2026-07-16 14:13:18 -03:00
KooshaPari
ac61e28f44 fix(relay): bound Bifrost stream lifetime (#7093)
Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>
2026-07-16 14:12:49 -03:00
Xiangzhe
7724b31c99 fix(models): preserve chat-capable image model rows (#7004)
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>
2026-07-16 14:12:25 -03:00
Rafael Dias Zendron
994f1c78a0 fix(6980): classify Cloudflare AI neuron exhaustion as quota_exhausted (#6983)
Cloudflare Workers AI free tier (10k Neurons/day, account-wide) returns
429 with body 'you have used up your daily free allocation of 10,000
neurons' which matched no QUOTA_PATTERNS keyword — falling through to
rate_limit (~60s cooldown) instead of quota_exhausted.

Two layers:
1. Provider-specific rule for 'cloudflare-ai' in providerRuleRegistry
   (scope: connection — budget is account-wide, not per-model)
2. Defense-in-depth: /daily free allocation/i in classify429 QUOTA_PATTERNS

Tests: 11/11 pass (provider rule + classify429 paths covered).

Closes #6980

Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>
2026-07-16 14:12:18 -03:00
Diego Rodrigues de Sa e Souza
13e312b311 fix(skills): register cli-skill-collector in the agent-skills catalog (Integration 2/2 base-red) (#7310)
* fix(skills): register cli-skill-collector in the agent-skills catalog (#6294 shipped the dir only)

* chore(skills): regenerate cli-skill-collector SKILL.md via the generator, preserving the #6294 authored workflow in the custom block

* fix(skills): derive coverage totals from the id lists + align remaining count assertions (45 catalog / 21 cli)

* fix(skills): SkillCoverage totals are number, not stale literals
2026-07-15 20:05:56 -03:00
Diego Rodrigues de Sa e Souza
af0c72fba5 fix: raise main server keepAliveTimeout/headersTimeout above Node's 5s default (#7003) (#7191)
* fix: raise main server keepAliveTimeout/headersTimeout above Node's 5s default (#7003)

JetBrains AI Assistant's pooled java.net.http.HttpClient reuses a
keep-alive connection past Node's unconfigured 5_000ms keepAliveTimeout,
hitting a socket the server already tore down and getting 0 response
bytes back ("HTTP/1.1 header parser received no bytes"). Wire a new
getMainServerTimeoutConfig() (mirroring apiBridgeServer's pattern) into
run-next.mjs so the main dashboard/API server raises keepAliveTimeout
to 65s and headersTimeout to 66s by default, both env-overridable.

* fix: wire main-server keepAlive timeouts into standalone/production server path (#7003)

getMainServerTimeoutConfig() was only wired into scripts/dev/run-next.mjs,
the dev-only entry point for `npm run dev`/`npm start`. The server real
end users run — `omniroute serve` (npm-installed CLI), Docker, and
Electron — spawns the standalone Next build's server.js via
run-standalone.mjs, which prefers server-ws.mjs (built verbatim from
scripts/dev/standalone-server-ws.mjs by assembleStandalone.mjs) over the
bare server.js precisely because it wraps http.createServer with
production behavior the bare server lacks. That wrapper never configured
keepAliveTimeout/headersTimeout, so the JetBrains AI Assistant reconnect
bug this issue reports still hit the production entry point after the
first pass of this fix. Wire the same helper into the wrapped server
object there too.
2026-07-14 23:25:09 -03:00
Diego Rodrigues de Sa e Souza
d6df9314b4 fix: filter hidden custom models out of legacy combo model picker (#7156) (#7199)
* fix: filter hidden custom models out of legacy combo model picker (#7156)

* chore(test): move model-select-modal-hidden-models-7156 test into tests/unit/ui (collector coverage) (#7156)
2026-07-14 21:45:25 -03:00
Diego Rodrigues de Sa e Souza
aa8b7c3086 fix: wire adaptive context-budget dial into settings schema and DB (#7005) (#7183)
* fix: wire adaptive context-budget dial into settings schema and DB (#7005)

* chore(db): re-export compressionContextBudget from localDb.ts per db-rules gate (#7005)

* chore(db): keep localDb.ts line-neutral after compressionContextBudget re-export (#7005)
2026-07-14 21:45:17 -03:00
Diego Rodrigues de Sa e Souza
3a92236d7a fix: wire modelAliases fetch into HermesAgentToolCard (#7151) (#7195) 2026-07-14 21:21:52 -03:00
Diego Rodrigues de Sa e Souza
8b38a21779 fix: honor combo-level proxy assignments from the registry (#7149) (#7201) 2026-07-14 21:21:47 -03:00
Diego Rodrigues de Sa e Souza
d84ccbc67c fix(dashboard): implement missing handleToggleSource on Free Pool tab (#7161) (#7200) 2026-07-14 21:15:54 -03:00
Diego Rodrigues de Sa e Souza
cdb4998ea4 fix(dashboard): agent bridge dns toggle uses POST, not PUT (#7157) (#7197)
The dns toggle button called fetch(..., { method: "PUT" }) but
src/app/api/tools/agent-bridge/agents/[id]/dns/route.ts only exports
POST, so Next.js auto-returned 405 on every Start/Stop DNS click.
Fixes the frontend caller to match the documented POST contract
(docs/frameworks/AGENTBRIDGE.md:490) already covered by
tests/unit/agent-bridge-dns-route-validation.test.ts.

Adds a regression test asserting the fetch call uses method: POST.
2026-07-14 21:14:04 -03:00
Diego Rodrigues de Sa e Souza
de0db5a777 fix: include proxyId when testing a saved registry proxy (#7080) (#7189) 2026-07-14 21:13:58 -03:00
Diego Rodrigues de Sa e Souza
ed1120efd5 fix: restore mobile grid-cols-1 fallback on quota page card grid (#7072) (#7194) 2026-07-14 21:13:56 -03:00
Diego Rodrigues de Sa e Souza
3729967cf6 fix: route zai-web (and other registry-entry web-cookie providers) connection-test cookie probe through the configured proxy (#7058) (#7192) 2026-07-14 21:13:53 -03:00
Diego Rodrigues de Sa e Souza
005199ceb3 fix(db): cap OOM probe-failure cycle in getDbInstance() (#6835) (#7186)
When better-sqlite3/node:sqlite are unavailable and the sql.js WASM
fallback OOMs while probing storage.sqlite, getDbInstance() rethrew
an identical 'Out of memory while probing' error on every call,
forever — unlike the generic-corruption probe-failure path (#6632),
which correctly caps at 3 attempts via the restore-count cycle
breaker. Because the OOM path never renames the file away
(intentional — OOM is not corruption), the existing cap is
structurally unreachable for this branch, so every independent
background poller (BATCH, ProviderLimitsSync, HealthCheck,
ModelSync) kept re-triggering the same failure with no terminal
diagnostic, hanging the app forever.

Adds an independent __omnirouteDbOomFailureCount cycle-breaker
mirroring the existing threshold of 3, throwing a distinct terminal
'Aborting startup' diagnostic after repeated OOM failures instead of
looping. Does not touch the rename/backup safety mechanism.

Reported-by: xHmeyer, mostafa-binesh
2026-07-14 21:13:51 -03:00
Diego Rodrigues de Sa e Souza
a798b4d5d9 fix: preserve relayAuth for pool-referenced relay proxies (#5716) (#7182) 2026-07-14 21:13:45 -03:00