Commit Graph

3162 Commits

Author SHA1 Message Date
Dizzle
a86b9019a8 feat(auto-combo): declare observed reliability as a scoring factor (#12317)
The weight table said stability accounts for "low latency stdDev / error rate". Grep errorRate in scoring.ts and you find it declared on ProviderCandidate and read nowhere — while combo.ts pulls 24 hours of usage history behind a ten-sample floor, falls back to real-time metrics, and hands every candidate an errorRate the scorer ignores. Two candidates, one failing 1% of calls and one failing 99%, scored identically at 0.459486.

This declares reliability as a sixteenth factor: 1 - failureRate, using the same formula, field precedence and rate-bounding speedRanking.ts already applies, so a corrupt reading means "nothing observed" rather than "fails every call". It ships at weight 0, leaving the ranking unchanged to the digit — the honest default, since which weight this deserves is a product call backed by traffic the author does not have. Two declared-but-silent factors already ship (cacheAffinity, resetWindowAffinity), so the pattern is not new. The stability row now describes what that factor actually computes: latency variance.

The rest is the mechanical 15 → 16 across nineteen documents and the forty-two llm.txt mirrors — sourced from check:docs-counts rather than a grep, the first real use of the gate #12316 extended.

Protected-surface note: this PR touches AGENTS.md, llm.txt and its 42 mirrors, and skills/omni-combos-routing/SKILL.md. Every changed line in those 45 files is a digit substitution and nothing else — masking all digits makes the removed and added lines identical, with no sentence added, removed or reworded. Reviewed and approved on that basis before merging.

Verified on the author's rebased head: check:docs-counts green (the gate that now enforces the count this PR moves), typecheck:core clean, and 71/71 focused tests across scoring-reliability-factor, combo-scoring-weights-schema-coverage, check-docs-counts-sync, lkgp-enabled-context, intelligent-routing-options and the combo-matrix auto integration suite.

Thanks @maxmad64bis — shipping the factor at weight 0 and saying plainly that the weight is someone else's call is the right way to land this.
2026-09-01 15:36:39 -03:00
Patryk Kopyciński
438db55c46 feat(providers): manual "Clear cooldown" action in the cooling panel (#12224)
* feat(providers): manual "Clear cooldown" action in the cooling panel

The persisted 429 cooldown (provider_connections.rate_limited_until) is
OmniRoute's local lesson, not upstream truth. When a quota has already
refreshed upstream (daily/weekly reset, provider-side fix), the only
automatic clear paths — Test-button success or Edit-modal key
re-validation — still require an upstream round-trip, so the user waits
out a bench that is already stale.

Adds a per-row "Clear cooldown" button to CoolingConnectionsPanel that
PUTs rateLimitedUntil: null (the route applies backoff reset defaults),
optimistically drops the bench, and refetches. The next request becomes
the real test of the key.

- useProviderConnections: handleClearCooldown + clearingCooldownId
  (in-flight guard mirrors the retestingId pattern)
- CoolingConnectionsPanel: optional onClearCooldown/clearingCooldownId
  props; button hidden for id-less rows, disabled per-row while clearing
- ProviderDetailPageClient: wires the new handler through
- i18n: en.json keys (clearCooldown, cooldownCleared,
  failedClearCooldown, ...) with providerText fallbacks

Tests: CoolingConnectionsPanel.test.tsx — click fires handler with the
row id, disabled + silent while in flight, per-row independence,
read-only when handler omitted, no button without connection id,
renders nothing when empty. Pre-existing
tests/unit/ui/CoolingConnectionsPanel.test.tsx stays green.

* fix(dashboard): dedupe clear-cooldown i18n keys and extract the row button

The providers namespace already carried an (orphaned) clearCooldown /
cooldownCleared / failedClearCooldown key trio, so the new feature keys
re-declared them as duplicate JSON keys ~1200 lines apart. JSON.parse is
last-wins, which silently shadowed the older values and broke ICU
placeholder parity in every locale (EN lost {model} while all 42
translations still carry it). Rename the feature's five keys to a
connection-scoped family instead:

  clearConnectionCooldown / clearConnectionCooldownInProgress /
  clearConnectionCooldownTitle / connectionCooldownCleared /
  failedClearConnectionCooldown

Also extract the per-row action into ClearCooldownButton so the panel
body stays inside the max-lines-per-function ratchet (was 83/80).

* feat(dashboard): mirror the clear-cooldown keys into all 42 locales

Adds the five connection-cooldown keys to every non-EN catalog with the
English value as the runtime fallback (fill-missing-from-en semantics),
and real translations for pt-BR and vi so their strict parity suites
stay meaningful:

  pt-BR: Limpar cooldown / Limpando… / Cooldown limpo — a conexão voltou
         ao roteamento / Falha ao limpar cooldown
  vi:    Xóa thời gian chờ / Đang xóa… / Đã xóa thời gian chờ — kết nối
         đã tham gia lại định tuyến / Không thể xóa thời gian chờ

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-09-01 12:08:31 -03:00
Bob.Hou
6dd82b77de fix(guardrails): pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) (#12169)
* fix(guardrails): pass providerId to getResolvedModelCapabilities in checkComboVision (#12112)

Signed-off-by: Minxi Hou <houminxi@gmail.com>

* chore(quality): register combo-vision providerId test in the stryker tap set

---------

Signed-off-by: Minxi Hou <houminxi@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-09-01 12:08:04 -03:00
Nguyen Thanh Dat
5ff6513ca5 fix(combos): send null to clear an agent feature instead of omitting it (#12177)
* fix(combos): send null to clear an agent feature instead of omitting it

PUT /api/combos/[id] merges its body over the stored record, so an omitted
field means "leave unchanged". The combos editor deleted a cleared agent
feature from the payload, so unchecking context cache protection -- or
emptying the system message or the tool filter -- never persisted: the old
value survived the merge and the editor reopened with the toggle still on.

updateCombo already deletes any key explicitly set to null, which is how
description and context_length are cleared in the same save handler. Use the
same shape for the three agent fields, and make them nullable in
updateComboSchema so the null survives validation.

The clearing logic moves into comboAgentFeatures.ts so it can be tested
directly, matching comboQuotaOnlyFallback.ts next to it.

Fixes #12158

* chore(changelog): point the fragment at the real PR number
2026-09-01 12:07:22 -03:00
Syed Raheemuddin
8fc6834372 fix(system): propagate abort signal to stream reader in HTTP version checks (#12232)
When fetching version metadata from npm registry or GitHub APIs, if the remote connection stalls during stream reading, the 10-second AbortController timer aborts the request signal but the underlying body reader stream was not listening to the abort signal. This caused readBoundedJson reader.read() loop to hang until external socket close.

Now readBoundedJson listens to AbortSignal abort events, triggers reader.cancel(), and releases locks immediately on abort.
2026-09-01 12:07:02 -03:00
Dizzle
ad4b67d631 feat(radar): show the rate limits and training disclosure the feed already sends (#12320)
The Radar feed reports per-model rate limits and whether a provider says it may train on your prompts. Both fields are on RadarMergedEntry and the catalog table rendered neither — grep them in RadarCatalogTable.tsx and the only hits were the type declaration. The training flag is the one that stings: freeModelCatalog.ts documents it as "Surfaced in the UI", a promise the UI did not keep, and thirteen catalog entries carry it today.

Adds a Rate limits column and a badge in the ToS cell when a provider discloses training. No new data, no request, no API change.

Two judgement calls worth keeping: a limit of zero renders as 0/min rather than formatTokens' "rate-only" (right for a monthly budget, nonsense for a ceiling where zero is a real and alarming fact), and the badge condition is === true, since an absent training statement is not a guarantee.

Validation note — read before trusting the green: this PR's own suite (tests/unit/dashboard/radar-catalog-table-limits-training.test.tsx, 14 cases) could NOT be run locally. Vitest fails to resolve react18-json-view, which is declared in package.json and package-lock.json but is not present in this machine's node_modules; two pre-existing .test.tsx files in the same directory fail identically, so the cause is environmental and not this PR. The blocking test-vitest CI job runs npm ci and will execute it.

What was verified locally, in a combined batch worktree with all 11 PRs of this batch: 174/174 node-runner focused tests, typecheck:core clean, complexity 2706/3218, cognitive-complexity 1221/1437, check:cycles and check:docs-counts green, plus both CI i18n gates for the 42-locale pass — check-ui-keys-coverage PASS (all 42 locales at or above 65%) and check-ui-value-drift PASS against the release tip.

Thanks @maxmad64bis.
2026-09-01 11:58:50 -03:00
Dizzle
5253b93b89 feat(rankings): order free providers by measured reliability (#12218)
Free Provider Rankings sorted by the top model's Arena score, so a provider stayed first even if it failed every call — the reliability column from #11546 already showed what each one actually served, but ordering ignored it.

Adds an opt-in ?sortBy=reliability (API) and a "Most reliable first" toggle (page) sharing one comparator in freeProviderRankingsUsage.ts: measured providers first by successRate desc with ELO on ties, then unmeasured in their incoming order. The default is unchanged and locked by tests. successRate is null below MIN_USAGE_REQUESTS = 5 (existing, never zero), and ordering runs before slice(0, limit) so limit counts in the requested order. The toggle composes with the existing sortTypeFirst/groupByType grouping — a stable sort keeps reliability order within each group.

Opt-in is the right default here: the page is for discovery, including providers never called.

13 tests across three files (6 new for the comparator in isolation, plus filter and route coverage including unknown sortBy → 400 and the default path staying off call_logs).

Verified in a combined batch worktree with all 11 PRs of this batch: 174/174 focused tests, typecheck:core clean, complexity 2706/3218, cognitive-complexity 1221/1437, check:cycles and check:docs-counts green. The 42-locale i18n pass was checked with the CI gates: check-ui-keys-coverage PASS (all 42 locales at or above 65%) and check-ui-value-drift PASS against the release tip.

Thanks @maxmad64bis.
2026-09-01 11:58:15 -03:00
Nads
bdf218387b fix(resilience): per-model 402 on a passthrough gateway no longer terminalizes the whole connection (#12266)
* fix(resilience): per-model 402 on a passthrough gateway no longer terminalizes the whole connection

402 variant of #3027. Passthrough/gateway providers that multiplex many
models behind one credential (kilo-gateway, ollama-cloud, etc.) can 402
on a single PAID model while free models on the same key remain
perfectly usable. Previously any 402 unconditionally set the connection
to a terminal `credits_exhausted` status, which is never auto-recovered
without an operator reset — taking out every remaining model on that
provider, amplified further inside combo routing (measured: one 402
removed 9 of 14 fallback targets in a real combo, dropping success rate
from 98.3% to 74.2% on a fixed load test per the issue report).

Root cause (matches the issue's own analysis):
1. resolveTerminalConnectionStatus() returned "credits_exhausted" for
   ANY status === 402, with no per-model/passthrough check.
2. The generic per-model lockout gate (404/429/>=500) excluded 402.
3. The #3027 403-branch is gated on `!terminalStatus` — since (1) already
   resolves a terminal status for any 402 before that branch runs, simply
   adding 402 to its condition alone would not have fired.

Fix:
- resolveTerminalConnectionStatus() now takes isPerModelQuotaProvider and
  skips the connection-wide terminal path for a bare `status === 402`
  when true, letting it fall through to the per-model lockout branch
  instead. An explicit result.creditsExhausted (a provider's own
  classification, independent of HTTP status) is untouched and remains
  unconditionally terminal.
- Extended the existing #3027 per-model lockout branch to also handle
  402 (reason "credits" vs "forbidden" for 403), reusing the same
  cooldown/lockout machinery and log format.
- Single-credential (non-passthrough) providers are unaffected:
  isPerModelQuotaProvider is false there, so a 402 still terminalizes
  the connection as before — that behavior is deliberate for prepaid
  API keys (#5239 / #10616).

Also checked the issue's 4th root cause (terminal statuses never
auto-recovering) against the current codebase: connectionRecovery.ts
already has a 30-minute credits_exhausted reprobe
(isCreditsExhaustedReprobeCandidate) that the issue's report — filed
against v3.8.49 — didn't account for. The other two files it names
(rateLimit.ts's clearStaleCrashCooldowns, tokenHealthCheck.ts's
OAuth-refresh skip) legitimately exclude credits_exhausted for
unrelated reasons and are not bugs. Moot regardless: this fix prevents
credits_exhausted from being set at all for the passthrough case, so no
recovery wait is needed in the first place.

Tests: tests/unit/auth-passthrough-per-model-402-12242.test.ts, modeled
on the existing #3027 precedent test (real DB-backed integration test
via auth.markAccountUnavailable). Covers: paid-model-only lockout with
free model unaffected, a subsequent free-model request succeeding after
a sibling paid-model 402, single-credential 402 still fully terminal,
and no connection-wide backoff escalation on repeated 402s.

Verified:
- node --import tsx/esm --test tests/unit/auth-passthrough-per-model-402-12242.test.ts: 4/4 pass
- All related pre-existing tests (auth-ollama-cloud-per-model-403-3027,
  auth-terminal-status, openrouter-free-model-credits-exhausted,
  vertex-passthrough-model-lockout, 10347-embed-402-cooldown): 27/27
  pass, no regressions
- npm run typecheck:core: 0 errors
- npm run check:cycles: no cycles
- eslint (auth.ts + new test file, with project suppressions): 0 errors

Fixes #12242

* chore(quality): register 402 per-model test in stryker tap and de-ratchet auth.ts

- stryker.conf.json: add tests/unit/auth-passthrough-per-model-402-12242.test.ts
  to tap.testFiles in its alphabetical slot
- auth.ts: extract the #12242 connection-wide 402 decision into the pure helper
  isConnectionWideCreditsExhausted() so resolveTerminalConnectionStatus stays
  within the cyclomatic ratchet (file back to the base's 11 violations)

---------

Co-authored-by: OmniRoute Dev <dev@local>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-09-01 11:52:21 -03:00
Dizzle
3b82d85081 docs(auto-combo): complete the mode pack table and gate what it claims (#12316)
docs/routing/AUTO-COMBO.md documented four mode packs; six ship. It printed 0.14 where modePacks.ts says 0.1333. And nothing was watching: four documents stated a scoring-factor count and check:docs-counts covered none of them. Wiring them up turned the gate red on seven real drifts — ARCHITECTURE.md and REPOSITORY_MAP.md at "9-factor" (code: 15) and "4 mode packs" (code: 6), RESILIENCE_GUIDE.md and SKILL.md at 13, AUTO-COMBO-GUIDE.md at both 5 and 13. ARCHITECTURE.md did not merely have the wrong number: it named nine factors that are not the engine's, and its four "mode packs" were the auto/* request prefixes.

A product fact fell out of writing the table: no pack sets quality, and applying a pack replaces the weight map wholesale (weights = pack in engine.ts, not a merge), so quality carries 0.03 by default and normalizes to 0 under any pack — pick a mode pack and the observed-quality signal stops voting. Documented, not changed.

The gate reads pack names from the module through the tsx subprocess that already reads every other code-derived count, matching the three spellings the docs actually use; on the reference document a missing claim now fails rather than passing. The dashboard was behind too (four of six packs offered); the count is dropped from the strategy label rather than corrected, since nothing reads selector labels and a right-today number goes stale unnoticed.

Verified in a combined batch worktree: 174/174 focused tests across all 11 PRs of this batch, typecheck:core clean, and check:docs-counts green with the four newly-wired documents.

Thanks @maxmad64bis — finding the quality-under-a-pack behaviour while writing a docs table is the kind of thing a table is for.
2026-09-01 11:50:57 -03:00
Dizzle
d19572fb95 fix(combo): expose every scoring weight the engine actually uses (#12314)
Two of the fifteen factors calculateScore applies could not be set by anyone. scoringWeightsSchema is a plain z.object, so zod strips what it does not name: PUT a combo with connectionDensity and you get a 200 back with nothing saved, and normalizeScoringWeights then reads the gap as a deliberate zero — switching off anti-concentration and the quality signal. DEFAULT_INTELLIGENT_WEIGHTS, the dashboard's own copy, missed the same two and every non-zero value differed from the engine's; summing to 1.05, validateWeights rejected them outright.

This adds the two keys to both lists and takes the dashboard defaults from DEFAULT_WEIGHTS. The scorer is not touched.

One behaviour change, and it is the point: a combo whose stored weights omitted the two keys was running with them at zero and the other thirteen renormalized upward. It now uses the engine's distribution (quota 0.1549 → 0.1429, health 0.1740 → 0.1605) and a test pins those numbers.

Left alone and documented rather than widened: rounded percentages now total 101% (six factors at 4.76% each render as 5%), and five stale .default() values in the schema that only bite when a config omits the key.

Verified in a combined batch worktree: 174/174 focused tests across all 11 PRs of this batch, typecheck:core clean, complexity 2706/3218, cognitive-complexity 1221/1437, check:cycles and check:docs-counts green.

Thanks @maxmad64bis — the red-before-green note (7 of 8 failing, and naming the one that passes on purpose) is exactly the evidence that makes a behaviour change reviewable.
2026-09-01 11:50:33 -03:00
Dizzle
33bdc386bc fix(free-tier): never serve a Radar overlay older than the shipped catalog (#12215)
GET /api/free-tier/summary could answer from a Radar overlay built 2026-08-02 while the release ships a catalog curated 2026-08-30 (FREE_CATALOG_CURATED_AT) — totals computed from older data, still tagged catalogSource: radar-overlay. The route now refuses any overlay built before the shipped catalog and falls back to that catalog through the operator's local state.

Tightens #11550 using the generatedAt persisted by #11435.

Verified in a combined batch worktree: 174/174 focused tests across all 11 PRs of this batch (this PR's free-tier-summary-radar-overlay suite included), typecheck:core clean, check-file-size, check-changelog-integrity, check:cycles and check:docs-counts green.

Thanks @maxmad64bis.
2026-09-01 11:49:49 -03:00
Diego Rodrigues de Sa e Souza
accdfa9f33 fix(usage): console-aware Token Plan guidance + subscription hint on bailian 401 (#12288)
* fix(usage): console-aware Token Plan guidance and subscription hint on bailian 401

The personal Token Plan is sold through two consoles with different portals,
gateway hosts and login tickets. Two operator-facing messages ignored the split:

- The quota guidance always said 'get the cookie at home.qwencloud.com', even
  for connections served by the Alibaba Model Studio console — following it
  verbatim produces a cookie the gateway rejects (console mismatch →
  BailianGateway.Login.NotLogined). The guidance now derives the console from
  the provider via resolveConsoleSite, matching what the fetcher will do with
  the pasted cookie.
- Key validation mapped upstream 401 to a bare 'Invalid API key'. An expired
  Token Plan subscription produces the exact same upstream 401 (observed live
  2026-09-01: subscription ended 08-23, the working key started failing), so
  the message now names the subscription as a cause worth checking.

* test(providers): align the remaining bailian 401/403 message pins to prefix match

search-provider-validation.test.ts pinned the exact 'Invalid API key' string for
the bailian validator; the message now also names an expired Token Plan
subscription. Same property asserted (401/403 => invalid), prefix match.
2026-09-01 11:40:40 -03:00
Diego Rodrigues de Sa e Souza
abbbca216d feat(sse): full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) (#12289)
* feat(sse): add it/ru/zh caveman output instructions

* fix(sse): expose the dormant terse-prose translations through the catalog

* feat(sse): translate less-code to es/de/fr/it/ru/zh

* feat(sse): translate ponytail to es/de/fr/it/ru/zh

* feat(sse): translate i-have-adhd to es/de/fr/it/ru/zh

* test(sse): anchor wave-2 output-style translations to their own language

* feat(dashboard): offer every output-style language in the default-language selector

* feat(sse): let autoDetect pick the output-style instruction language

* docs(compression): consolidate the output-style tables and record full language parity
2026-09-01 11:13:26 -03:00
backryun
fe5f4b0ef9 chore(quality): remove unreachable code and restore test discovery (#11950) 2026-09-01 11:06:04 -03:00
backryun
f301d34ce5 fix(dev): qualify Turbopack runtime boundaries (#12258)
Co-authored-by: backryun <backryun@daonlab.local>
2026-09-01 11:01:56 -03:00
Diego Rodrigues de Sa e Souza
ad54249c54 fix(security): strip Qwen/Alibaba console-session cookies from provider API responses (#12287)
The Token Plan console cookie is a browser credential for the operator's
cloud-console account — same class as the ollama/opencode cookies that
sanitizeProviderSpecificDataForResponse already strips — but the four
qwen/alibaba fields (qwenCloudCookie, qwenCloudSecToken, alibabaConsoleCookie,
alibabaConsoleSecToken) were missing from the strip list, so GET /api/providers
returned the operator's console session in the clear to any dashboard session.

The edit modal depended on that leak: it initialized the cookie fields from the
round-tripped response. It now starts them empty, matching the ollama pattern —
the quota-scraping assign skips empty fields and the PUT handler's partial merge
preserves keys the payload does not carry, so 'leave blank to keep the stored
cookie' (already what the field hints promise) holds for real.

Found in the 2026-09-01 audit of the Token Plan quota feature.
2026-09-01 09:22:56 -03:00
Diego Rodrigues de Sa e Souza
412298b624 chore(proxy): purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) (#12290)
The 1proxy marketplace integration was decommissioned in v3.8.4; the code
survived only through the localDb barrel, deleted in #12055. Everything below
had zero consumers (grep-proven across src/, open-sse/, bin/, electron/,
scripts/ and tests/):

- src/lib/oneproxySync.ts and src/lib/oneproxyRotator.ts deleted.
- src/lib/db/oneproxy.ts: upsertOneproxyProxy, getOneproxyProxyById,
  getOneproxyProxyForRotation and markOneproxyProxyFailed removed (their only
  consumers were the two deleted modules); listOneproxyProxies and the record
  interface stay — open-sse/utils/proxyFallback.ts still uses them.
- src/shared/validation/oneproxySchemas.ts and the unmounted
  settings/components/OneproxyTab.tsx deleted (no importer anywhere; the live
  UI is the FreePool* tabs over /api/settings/free-proxies).
- ONEPROXY_ENABLED feature flag removed (readerless since oneproxySync died —
  the toggle no longer controlled anything); flag-count contract test aligned
  54 → 53.
- Docs: PROXY_GUIDE (component rows, env rows, the three omniroute/
  oneproxyRotator snippet sections), CODEBASE_DOCUMENTATION, REPOSITORY_MAP,
  ENVIRONMENT (ONEPROXY_* rows), FEATURE_FLAGS, .env.example — canonical +
  pl/zh-CN/zh-TW mirrors.
- The 308 compat redirects under /api/settings/oneproxy/ stay (deliberate API
  compat), as do the live free-proxy provider and proxy_registry rows.

check:dead-code drops 424 → 417 (baseline kept at the velocity-phase 500 —
banking shrinks is paused until v4.0, headroom grows to 16.6%).
check:docs-all, check:env-doc-sync, typecheck:core and the 8 free-proxy/
proxy-fallback test files are green.

Closes #12091
2026-09-01 09:22:26 -03:00
Diego Rodrigues de Sa e Souza
073b98462d feat(dashboard): orchestration canvas — /dashboard/orchestration with Agents/Routing/Overview tabs (part 2/2) (#12261)
UI completa do Orchestration Canvas sobre o modelo da parte 1: página /dashboard/orchestration com abas em URL (Agents=grafo vivo via FlowCanvas, Routing=ComboLiveStudio intocado, Overview=contadores+kanban com totais reais sob cap), drawer de detalhe com approve/cancel (unwrap por fonte verificado contra as rotas reais, erros client-safe, prUrl https-only), i18n com traduções REAIS em 43 locales, entrada no sidebar. Ciclo SDD: 9 tasks TDD com review por task (Task 15 com fix round: Critical A2A unwrap + rewrite de lint + guard XSS), review final whole-branch (Ready to merge, 0 Critical/Important, refactor de complexity provado behavior-preserving), 3 fixes de CI validados RED→GREEN. CI: tudo verde. Crédito do conceito visual: design da PR #11815.
2026-09-01 05:12:54 -03:00
Diego Rodrigues de Sa e Souza
7f9195cd29 chore(lint): batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved (#12244)
Closes the src/ side of the campaign (no eslint-disable, no new suppressions;
the 37 matching react-hooks/* entries are removed from
config/quality/eslint-suppressions.json — only the 5 CI-divergent entries in
tests/unit/ui remain, frozen by design, see #12144):

- set-state-in-effect (30×): fetch-on-mount and sync-setter effects wrapped in
  the async-continuation pattern (await Promise.resolve() for pure-sync
  bodies), preserving semantics exactly.
- refs/purity (ResilienceConnectionsClient): render now reads stopReason state
  instead of stoppedRef; the receivedAt fallback Date.now() in JSX was dead
  (every setData stamps receivedAt) and became 0.
- exhaustive-deps (ApiTab, SessionInfoCard, useLiveDashboard): clearResults
  wrapped in useCallback; missing t dep added; channels array stabilized via
  channelsKey + useMemo so connect deps are statically checkable.
- global-error: locale/messages load moved into one async continuation (also
  renames the import binding to mod per @next/next/no-assign-module-variable).

Refs #12146
2026-09-01 01:07:46 -03:00
Diego Rodrigues de Sa e Souza
a249a9dc28 docs(api): document every implemented route in openapi.yaml (276 → 692 paths) (#12212)
* docs(api): document every implemented route in openapi.yaml (276 -> 692 paths)

Follow-up nº 3 of the 2026-08-31 docs audit: 416 implemented routes had no
OpenAPI entry (gamification, radar, skills, webhooks, mcp, a2a, tunnels,
version-manager and plugins were absent entirely). Adds a minimal, honest
entry for each — real methods parsed from every route.ts's exports, a group
tag and a neutral path-derived summary; no invented semantics. Rich schemas
remain hand-curated in the existing entries.

Generated by scripts/ad-hoc/gen-openapi-missing-paths.mjs, which enumerates
routes with the same lib check:api-docs-refs uses — the spec now covers
692/692 real routes and the gate verifies every spec path has a real route.

* docs(api): security tiers on generated paths, regenerated API skills, size baseline

The first CI round caught three real contract gaps in the generated coverage:

- Generated operations on LOCAL_ONLY routes now carry x-loopback-only (and
  x-always-protected for ALWAYS_PROTECTED_API_PATHS), resolved through the real
  src/server/authz/routeGuard.ts at generation time. The
  openapi-security-tiers guard now also accepts LOCAL_ONLY_API_PATTERNS —
  param-shaped routes (/api/providers/{id}/login) are classified by regex in
  the runtime and were invisible to the prefix-only check.
- The API agent skills are generated FROM the spec: 18 SKILL.md files
  regenerated via generate-agent-skills --apply so the generator stays 46/46.
- src/app/docs/lib/openapi.generated.ts grew with the spec (171 -> 1347 lines,
  emitted by gen-openapi-module): frozen in file-size-baseline.json with a
  _rebaseline justification — shrink by slimming the spec, never by editing
  the generated module.
2026-09-01 00:53:25 -03:00
Davide Baraldo
e7a65d28db fix(oauth): keep Claude personal and Team organizations apart (#12222)
* fix(oauth): keep Claude personal and Team organizations apart

One Anthropic identity reaches its personal workspace and every Team
organization it belongs to with the same email AND the same accountUUID,
each with its own tokens, plan and rate limits. The OAuth dedup matched on
email alone for every provider except Codex, so authenticating the second
organization overwrote the first connection instead of adding one: only the
most recent organization stayed usable. organizationUUID is the field that
separates them (cliUserID cannot be used, it changes on every login).

Disambiguate on organizationUUID, mirroring how Codex uses
workspaceId/chatgptUserId (#7737):

- findExistingOAuthConnectionMatch routes claude through a new
  isSameClaudeAccount helper, so a login only merges into an existing row
  when the organization agrees;
- isMatchingOauthIdentity gains organizationUUID as a third optional
  disambiguator, compared strictly two-sided;
- createProviderConnection passes the incoming organizationUUID, closing the
  same hole on the create path.

Rows stored before Claude returned organizationUUID keep the bare-email
match, so re-authenticating an existing connection still updates it in place
instead of forking a duplicate. No behaviour change for other providers.

* docs(oauth): changelog fragment for #12222
2026-09-01 00:51:38 -03:00
Bob.Hou
05490304bf fix(oauth): mark empty Antigravity projectId as degraded and clear stale errors (#11284) (#12205)
* fix(oauth): mark empty Antigravity projectId as degraded (#11284)

The #11284 gate only fired when projectDiscoveryOutcome was set. Paste
credentials, persistOAuthConnection, and agy CLI import could persist
projectId="" as testStatus=active, so the dashboard showed Connected
while fetchAvailableModels returned 403.

Degrade on empty projectId itself. Keep the refresh token stored so
request-time bootstrap can still self-heal.

Signed-off-by: Minxi Hou <houminxi@gmail.com>

* fix(oauth): clear stale degrade fields and bind CLI imports to builtin client

persistOAuthConnection left errorCode/lastError on the row when a later
connect discovered a Cloud Code projectId. agy CLI import also kept a
leftover custom: oauthClient marker from dashboard OAuth, so the next
refresh hit the operator web client instead of the public desktop client.

Signed-off-by: Minxi Hou <houminxi@gmail.com>

* fix(oauth): null error fields on healthy create paths too

Update already cleared errorCode/lastError* when a projectId appeared.
Create payloads still omitted the keys; match the update shape so a
fresh row cannot keep a leftover degrade marker.

Signed-off-by: Minxi Hou <houminxi@gmail.com>

* test(oauth): pin healthy create/upsert nulling of degrade fields

Forge flagged create payloads omitting errorCode/lastError* when a
projectId is present. Production already writes explicit nulls; the
reader strips them via cleanNulls, so pin both the payload shape and
the upsert path that must overwrite a leftover degrade marker.

Signed-off-by: Minxi Hou <houminxi@gmail.com>

* fix(oauth): type create payload from AntigravityDegradedProjectState

The persistence helper duplicated a subset of the degrade type and
dropped warning. Align the parameter so the HTTP-only warning field
cannot drift from the exported type.

Signed-off-by: Minxi Hou <houminxi@gmail.com>

* fix(oauth): persist degrade status through a single override helper

OAuth exchange/poll-callback spread the whole degrade object, which
wrote warning into the SQLite row and left healthy updates as {}.
Centralize testStatus/errorCode/lastError* so they always win over a
spread tokenData payload.

Signed-off-by: Minxi Hou <houminxi@gmail.com>

---------

Signed-off-by: Minxi Hou <houminxi@gmail.com>
2026-09-01 00:51:18 -03:00
backryun
dc6daf27b6 fix(dev): silence webpack runtime module warnings (#12228)
Co-authored-by: backryun <backryun@daonlab.local>
2026-09-01 00:50:58 -03:00
Syed Raheemuddin
903d1e0c52 fix(db): use module.require for CommonJS runtime driver loading (#12230) 2026-09-01 00:50:35 -03:00
backryun
8d388912a7 feat(providers): refresh vendored ChatGPT Web connector to v4.0.7 (#12181)
Refresh the existing MIT-licensed miuuyy/codex-chatgpt-web vendor snapshot and its OmniRoute integration as one reviewable change.

Co-authored-by: backryun <backryun@daonlab.local>
2026-09-01 00:50:15 -03:00
killer30001000
debb82bdd7 feat(usage): add Kilo Code balance and Kilo Pass quotas (#12178)
* feat(usage): add Kilo Code balance and Kilo Pass quotas

* feat(usage): add Kilo Pass dashboard meter

* test(usage): cover Kilo Code quota integration

* docs(usage): document Kilo API endpoint override
2026-09-01 00:49:54 -03:00
b3nw
c49ee53bc1 feat(providers): add RPD to rate limit overrides (#12147) 2026-09-01 00:49:33 -03:00
Syed Raheemuddin
ae37413aff fix(resilience): isolate local host execution errors from provider circuit breakers (#12233)
Local process execution failures (ENOENT spawn errors, binary missing, EPIPE, exit codes) were incorrectly treated as upstream provider failures, opening provider circuit breakers and cooling down valid connections. Added `isLocalExecutionError` guard to skip circuit breaker trips and connection disables when local host execution fails.
2026-09-01 00:47:21 -03:00
Syed Raheemuddin
26eeead268 fix(memory): honest probe-driven FTS5 keyword status + memory_id rowid sync (#12231)
* fix(memory): honest probe-driven FTS5 keyword status + memory_id rowid sync

The "no such module: fts5" complaint on FTS5-less runtime builds (sql.js/WASM
under a global install) was masked by a hardcoded keyword.available=true in
engineStatus and an unsanitized FTS5 MATCH path. Address root cause:

- engineStatus(): probe runtime via supportsFts5(db) instead of hardcoding
  available=true; keywordEngineStatus() reports the true backend (FTS5 vs
  none) with a reason. Schema, OpenAPI, dashboard chip updated to match.
- store.ts: sync memory_id to the SQLite rowid on insert (+ self-heal legacy
  NULL rows). Migration 023 keys the FTS5 external-content trigger off
  memory_id, but plain INSERT left it NULL so the JOIN returned 0 rows —
  keyword/hybrid search silently returned nothing on FTS5-capable builds.
- retrieval.ts: apply sanitizeFts5Query() to the preview MATCH path.

Tests updated/added across memory-engine-status, memory-retrieve-preview,
memory-schemas-roundtrip, memory-store, and the integration engine-status
test (dropping the hardcoded "always available" assertion). 66 unit tests
pass; lint and typecheck clean.

* fix(memory): sanitize FTS5 queries for memory retrieval

Prevent SQLite FTS5 syntax errors by sanitizing query terms and replacing FTS control operators with double-quoted tokens.
2026-09-01 00:47:16 -03:00
Dizzle
1b64372316 test(free-tier): counting vs deciding regimes (#12226)
Declare the two answers to "is it free?": counting may use the
Radar-overlaid catalog, deciding reads only the shipped FREE_MODEL_BUDGETS
plus :free suffix / zero pricing / grantsFreeAccess. No production behavior
change. A static-import guard discovers every non-client consumer of
freeModels.ts and asserts none reaches getRadarCatalog / getRadarCache,
mirroring client-bundle-no-server-only-10692 on the server arc.

Co-authored-by: Max <maxmad64@gmail.com>
2026-09-01 00:47:12 -03:00
KeelTrace
ba200b8d2b fix(chat-admission): clarify local 503 source (#12223) 2026-09-01 00:47:07 -03:00
Tobias Andersen
7ba5b7a74e Change hasFree from true to false for featherless.ai (#12216) 2026-09-01 00:46:59 -03:00
Bob.Hou
2bd3023e09 fix(combos): prioritize SQLite row id over inner JSON id and notify delete errors (#12213)
When a combo is duplicated or imported, its inner data JSON blob may
retain a stale id from the template. withRowId previously kept the inner
string id instead of prioritizing the database primary key (row.id),
causing GET /api/combos to return mismatched ids and breaking subsequent
DELETE / PUT operations with 404.

Also add an error notification branch to handleDelete in the combos page
so failed delete requests surface actionable feedback instead of failing
silently.

Signed-off-by: Minxi Hou <houminxi@gmail.com>
2026-09-01 00:46:54 -03:00
Abhishek Sharma
d0529c0365 fix(providers): gate the Codex auto-ping usage read on the shared quota throttle (#12209)
Every Codex quota read goes through throttleQuotaFetch() — the #6009/#6058
gate that spaces genuine upstream calls so many accounts behind one IP do not
fire in the same second, which is the pattern documented to have got a Codex
OAuth token revoked. The auto-ping scheduler called getCodexUsage() directly,
so the one Codex path that runs unattended every 60s per connection was the
one skipping the mitigation written for Codex.

The tick walks connections sequentially but without spacing, so N enabled
connections still produce N upstream usage requests within a few hundred ms.

Gate the read on the same throttle, injected through deps like every other
effect in this module. Placed after the skip checks so a connection filtered
out by the circuit breaker, a cooldown or the failure cache does not consume
a slot and delay the connections that do reach the network.

This does not change the polling cadence. Codex sets pingWhenResetAtSlides
because its resetAt slides forward while the window is idle, so the per-tick
re-fetch is deliberate and is left alone.

Closes #11904
2026-09-01 00:46:50 -03:00
Diego Rodrigues de Sa e Souza
63e4afa321 feat(dashboard): orchestration canvas — unified model + snapshot hook (part 1/2) (#12156)
Modelo puro do Orchestration Canvas (tipos, 3 mappers, mergeSnapshot com dedupe/staleness/cap, projeções flow+overview) + hook de polling com gatilho WS. Ciclo completo: 9 tasks TDD com review por task, review final whole-branch + fixes verificados 6/6, refactor de complexity re-validado (comportamento preservado). CI: 18 pass. Testes: 31 node:test + 2 vitest. Parte 2/2 (UI /dashboard/orchestration) na sequência.
2026-08-31 14:42:20 -03:00
Diego Rodrigues de Sa e Souza
7ca5e1c671 chore(lint): batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved (#12208)
45 violations across 27 files fixed at the source (no eslint-disable, no new
suppressions; the 45 matching react-hooks/* entries are removed from
config/quality/eslint-suppressions.json):

- set-state-in-effect (fetch-on-mount effects): async continuation wrapper.
- Prop/state sync effects (EditMemoryModal, radar/setup, EvalsTab): adjust
  during render with prev tracking.
- purity/refs (ActivityFeedClient, ProviderQuotaWidget, ReasoningCacheTab):
  Date.now() snapshots moved to state set from the fetch path; rendered refs
  converted to state.
- immutability (useCodexResetCreditRedemption): ref-store writes extracted to
  module-level helpers.
- exhaustive-deps (RequestLoggerV2, HomePageClient): COLUMN_SORT_MAP hoisted to
  module scope; openDetail/closeDetail wrapped in useCallback and added to the
  dependent hooks; versionInfo destructured to locals; baseUrl now reads
  location.origin via useSyncExternalStore (hydration-safe, no effect).

Refs #12146
2026-08-31 14:37:26 -03:00
Rahil Mavani
73db936f98 fix(api): keep registry width and type on embedding models (#11761)
* fix(api): keep registry width and type on embedding models

* docs: changelog fragment for embedding registry fix

* test(api): cover embedding width and type merge

Exercises /v1/models rather than the registry in isolation: a synced
model colliding with an embeddingRegistry entry must keep the width the
registry states, and a synced model the registry names must be typed as
an embedding model.

Fails on catalog.ts before e7fbb62 (2 failures), passes after.

Refs #11759
2026-08-31 14:16:41 -03:00
backryun
4b5266d3f8 fix(dev): isolate batch dispatch from instrumentation (#12081)
Co-authored-by: backryun <backryun@daonlab.local>
2026-08-31 14:14:02 -03:00
backryun
f8b01c966e fix(dev): make logging resources HMR-singleton (#12079)
Co-authored-by: backryun <backryun@daonlab.local>
2026-08-31 14:13:53 -03:00
backryun
e12fb110f9 [URGENT] fix(dev): reduce instrumentation executor fan-out (phase 3) (#12078)
* fix(dev): reduce instrumentation executor fan-out

* fix(ci): reduce credential refresh complexity

---------

Co-authored-by: backryun <backryun@daonlab.local>
2026-08-31 14:13:46 -03:00
backryun
2fbd0f5c25 fix(dev): isolate root layout settings reads (#12076)
Co-authored-by: backryun <backryun@daonlab.local>
2026-08-31 14:13:37 -03:00
Jacob Stoner
18c71b91dc feat(auto-combo): add weighted score router strategy (#12155)
Add a direct low-level mode for users who require explicit control over provider selection. score selects the highest configured weighted score directly while reusing the existing exploration rate.

Exact ties preserve configured candidate order. rules and all other strategies remain unchanged.
2026-08-31 14:10:50 -03:00
Alvin T. Veroy
668beed5b8 fix(sse): absorb AbortError/request_signal_aborted in the client-abort crash guard (#12165)
OmniRoute's SSE teardown aborts in-flight legs with
`Error [AbortError]: request_signal_aborted` on client disconnects
(open-sse/utils/streamHandler.ts getClientAbortReason), and fetch/DOM
cancellation surfaces as AbortError with an abort-flavoured message.
isClientAbortError() only matched message 'aborted'/'Aborted' plus errno
codes, so these shapes fell through shouldSwallowUncaught() and were
re-thrown from the process-level uncaughtException/unhandledRejection
handlers — killing the whole server on a routine client disconnect
(observed as repeated exit-code-7 crashes with
'uncaughtException: Error [AbortError]: request_signal_aborted').

Match AbortError by name when the message is abort-flavoured; genuine
errors that merely mention 'abort' (e.g. TypeError) still crash loudly.

Tests: new unit cases for the SSE/DOM AbortError shapes, a child-process
regression proving the process survives both benign emissions with the
production no-logger install shape, and a child-process test proving
genuine errors keep crash semantics.
2026-08-31 14:10:33 -03:00
Diego Rodrigues de Sa e Souza
6706c382d8 docs(audit): align every published number with the code and harden check:docs-counts (#12200)
Fase 2 da auditoria código×docs 2026-08-31: ~90 divergências corrigidas em README/llm.txt(+42 espelhos)/SVGs/AGENTS.md/25+ docs; correções semânticas (breaker 8/12/2 + DEGRADED, webhooks sem eventos fantasma, ROUTE_GUARD_TIERS completo, API_REFERENCE sem fantasmas, reasoning 200); gate check:docs-counts endurecido (versão em prosa, patterns anti-evasão, superfície +llm.txt/mcp-server/omni-mcp/tier-flow) +5 testes; fonte do gerador de agent-skills corrigida (107/32 → 110/33) e mesma família varrida do Copilot prompt, 39 locales, skills/README, CONTRIBUTING e 2 guias.
2026-08-31 13:39:21 -03:00
Diego Rodrigues de Sa e Souza
b7a0c54139 chore(lint): batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved (#12174)
* chore(lint): batch 5 of #12146 — resolve the react-hooks compiler violations in combos, endpoint, provider-stats, api-manager and costs

40 violations across 14 files, all real refactors (no eslint-disable, no new
suppressions; the areas' react-hooks entries are deleted from the freeze):

- set-state-in-effect (30): fetch-on-mount effects moved behind an async
  continuation (usePools, usePoolUsage, useApiKeyUsageLimits, Notion/Obsidian
  source cards, A2A/MCP dashboards, ComboControlCenterClient, provider-stats,
  combos modal loaders, ApiManager initial load); prop/state sync converted to
  state adjustment during render with prev tracking (ApiKeyUsageLimitCard,
  PoolWizard dimensions/reset/group snap, combos sortMethod, builder reset,
  builder stage guard, single-provider default, stale intelligent selection);
  localStorage reads became lazy useState initializers (combos usage guide).
- immutability / TDZ (8): effects that scheduled fetchers declared below them
  moved after the declarations (EndpointPageClient, ApiManagerPageClient,
  combos mount load); fetchData relocated below the per-key fetchers it calls.
- static-components (7): provider-stats SortIcon hoisted to module level.
- preserve-manual-memoization (2): ApiManager blockedModels dep destructured to
  a local; provider-scope derivation memoized so downstream memos see a stable
  dependency.

Validation: eslint (CI command with suppressions, --max-warnings 0) clean on the
14 files; dashboard typecheck within baseline; mutation gate no drift; area
tests 208/208 (node) + 29/29 (vitest).

Refs #12146

* chore(lint): batch 5 follow-up — hoist the render-adjustment predicates so the new-code complexity gates stay flat

The render adjustments added one cyclomatic branch to combos/page.tsx and one
cognitive point to PoolWizard (caught by the new-code gate on the committed
work); the compound conditions now live in pure module-level predicates.

* chore(lint): batch 5 follow-up 2 — PoolWizard render adjustments live in two small hooks

One consolidated hook tripped max-lines-per-function (>80) and the cognitive
budget; the dimensions and open/close adjustments now live in two focused hooks
with a shared WizardSetters type, and the group snap stays inline (one branch).
complexityNewCode=0, cognitiveComplexityNewCode=0.

* test(quota): repoint the two PoolWizard structural pins at the render-adjustment hook

quota-edit-opens-wizard anchored the pre-fill block on the old '} else if (editPool)'
effect literal and quota-pool-wizard-edit expected a bare 'if (editPool)' that only
existed there; both now anchor on the batch-5 structure (submit still branches via
if (!editPool)).
2026-08-31 03:10:49 -03:00
Diego Rodrigues de Sa e Souza
78fd3504dd chore(lint): batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers (#12163)
Resolves all 28 react-hooks/* compiler violations (24 set-state-in-effect,
4 refs) across the 18 dashboard/providers files of batch 2 and removes their
suppression entries — no eslint-disable, no new suppressions.

Techniques per file:
- Fetch-on-mount loaders (CustomModelsSection, ProviderCcAliasSection,
  ProviderInterceptionSection, ProviderParamFilterSection, page.tsx,
  useProviderConnections, useProviderSettings, CliproxyAccountHealthCard,
  DarioAccountPanel, NinerouterModelList): network/parse/error concerns
  extracted to module-level helpers returning error-as-value; the async glue is
  defined INSIDE each effect with every setState after the await. Loaders that
  handlers still need (refresh/retry buttons, exposed hook API) remain as
  callbacks; spinner flags moved into the button handlers.
- Loading flags for provider-keyed sections derived from a loadedProviderId
  marker instead of synchronous setLoading(true) resets.
- Modal init/reset effects (EditConnectionModal, EditCompatibleNodeModal,
  AddCompatibleProviderModal, VolcengineConnectModal state reset,
  useProviderUrlFilters hydration, page.tsx display-mode fallback,
  useProviderSettings per-provider flag reset): converted to render-phase
  adjustments guarded by the previously-seen prop/marker (react.dev "adjusting
  state when a prop changes").
- VolcengineConnectModal: phone prefill via localStorage lazy initializer;
  server-side session cancel + poll stop moved to the cleanup of an
  open-scoped effect reading a session ref mirror.
- ModelCompatPopover refs: render-time ref mirrors removed — headerRowsRef is
  maintained by an applyHeaderRows writer used by all handlers, paramTargetRef
  is mirrored in an effect, and blockText/allowText mirrors were already kept
  in sync by their single writer (applyParamFields).
- ModelCompatPopover state: header-row loading and value-visibility resets
  moved from [open, protocol] effects into the open/protocol/outside-click
  gesture handlers; the closed-popover rect reset was dropped (render is gated
  on open and the rect is recomputed pre-paint on reopen).
- useRiskAcknowledged: localStorage mirrored via useSyncExternalStore with a
  module-level listener set notified by acknowledgeProviderRisk.
- useProviderModels: loading for the empty-providerId case derived at the
  return site instead of a synchronous setLoading in the effect.

Validation: scoped eslint with the suppressions file passes with 0 problems;
check-dashboard-typecheck.mjs OK; node --test batch (14 files) and vitest
batch (9 files, 47 tests) green.

Refs #12146
2026-08-31 01:07:03 -03:00
Diego Rodrigues de Sa e Souza
c664505db3 chore(lint): batch 3 of #12146 — dashboard/settings react-hooks violations resolved (#12162)
* chore(lint): batch 3 of #12146 — resolve the react-hooks compiler violations in dashboard/settings

Resolves the 25 react-hooks/* React Compiler violations frozen in
config/quality/eslint-suppressions.json for the dashboard/settings area
(24 set-state-in-effect, 1 immutability), plus the adjacent
react-hooks/exhaustive-deps in ProviderAccountRoutingCard, and removes
their suppression entries. No eslint-disable added anywhere; one
pre-existing eslint-disable-line (AccessTokensTab) removed.

Techniques used:

- ResilienceTab (8×): the "sync draft state from prop via useEffect"
  cards now use the documented adjust-state-during-render pattern
  (prevValue state + conditional setState in render) instead of an
  effect.
- PricingTab visibleCount reset: same render-adjustment pattern keyed
  on the filters tuple, replacing the reset effect.
- Fetch-on-mount loaders only used by the effect (IPFilterSection,
  ModelCapabilityOverridesTab, PayloadRulesTab*, RoutingStrategyCard):
  loader inlined into the effect as an async IIFE with a cancelled
  flag; every setState now happens after the first await.
- Loaders reused by handlers/intervals (AccessTokensTab, AuthzSection,
  FallbackChainsEditor, MitmProxyTab, ModelsDevSyncTab, OneproxyTab,
  PayloadRulesTab, PoliciesPanel, PricingTab,
  ProviderAccountRoutingCard, SystemStorageTab, GlobalConfigTab,
  SubscriptionTab): split into a module-level pure fetcher + a
  useCallback applier; the effect awaits the fetcher and applies after
  the await (cancellation-guarded), while handlers keep the original
  named loader (sync setState is fine there) built from the same
  fetcher/applier — no logic duplication, identical error-message and
  loading semantics.
- OneproxyTab keeps the spinner-on-filter-change behavior via the same
  render-adjustment pattern (filtersKey → setLoading(true)).
- AccessTokensTab: the L() fallback helper is now memoized with
  useCallback([t]), which also let the old
  eslint-disable-line react-hooks/exhaustive-deps be removed.
- ProviderAccountRoutingCard: save's dependency array now includes
  load (the frozen exhaustive-deps violation).

Suppressions: all react-hooks/* entries for the 17 batch files removed
(19 rule entries, 26 violation counts). Entries for other rules/files
untouched.

Refs #12146

* chore(lint): batch 3 follow-up — extract useOneproxyData so the cyclomatic gate stays flat

The first pass grew OneproxyTab past the complexity threshold (caught by the
new-code gate on the PR); the data-loading state now lives in a dedicated
useOneproxyData hook. Also registers search-432-plan-limit-cooldown in
stryker tap.testFiles (base drift the gate flagged on every batch).
2026-08-31 01:06:53 -03:00
Diego Rodrigues de Sa e Souza
ef2a89bd69 chore(lint): batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved (#12160)
* chore(lint): batch 1 of #12146 — resolve the react-hooks compiler violations in dashboard/cli-code

Real refactors, no suppressions — the 42 frozen react-hooks/* entries for the
12 dashboard/cli-code files (plus Antigravity's exhaustive-deps one) are
removed from config/quality/eslint-suppressions.json and the files now lint
clean under the React Compiler rules.

Techniques, per pattern:

- set-state-in-effect ("default API key" effects — Antigravity, Claude, Cline,
  Codex, Droid, GrokBuild, Kilo, OpenClaw): the setState-in-effect that copied
  apiKeys[0].id into the selection state is deleted; an `effective*` value is
  derived during render (`selected || apiKeys[0]?.id`) and used by the select
  and the submit handlers. Behavior identical, one less render pass.

- immutability ("accessed before declared") + set-state-in-effect on the
  expand-time loaders (all tool cards): the fetchers (checkXStatus,
  fetchModelAliases, fetchBackups, fetchProfiles, loadSavedMappings) are
  hoisted above the effect as useCallback with correct deps, listed in the
  effect deps, and invoked through an async continuation
  (`void (async () => { await Promise.all([...]) })()`) so no setState runs
  synchronously in the effect body.

- set-state-in-effect ("init form from fetched status" effects — Claude,
  Cline, Codex, Droid, OpenClaw): the status-parsing effects are deleted and
  their logic now runs inside checkXStatus right after the fetch resolves
  (setState after await), keeping the same one-time ref guards. Codex's config
  parser became syncFormFromStatus(), called on both success and error paths.

- HermesAgentToolCard: Date.now() in render (purity) is snapshotted once via a
  lazy useState initializer; the batchStatus seeding effect is replaced by a
  derived `displayRoles` (useMemo over batchStatus with currentRoles taking
  precedence); the collapse-reset effect moved into the header toggle handler.

- ClaudeClassifierCompatToggle / CliProfileAutoSyncToggles / Cliproxyapi /
  GrokBuild: mount/expand loads wrapped in the same async continuation.

- DroidToolCard's isOmniRouteEntry helper hoisted to module scope (pure).

Validation: eslint with suppressions --max-warnings 0 on the 12 files (clean),
scripts/check/check-dashboard-typecheck.mjs (OK, within frozen baseline),
vitest UI suites for the touched cards (15 files / 57 tests green, plus the 3
quarantined #8618 files run explicitly: 27 tests green), and the node-native
cli-code tests (61 tests green).

Refs #12146

* chore(lint): batch 1 follow-up — hoist the settings-init helpers so the cognitive gate stays flat

The first pass folded the one-time form init into the status fetchers, which pushed
sonarjs/cognitive-complexity to 1 in Claude/Cline/OpenClaw tool cards (caught by the
new-code gate on the PR). The init logic now lives in module-level helpers
(initXFormFromSettings + defaultKeyId); complexityNewCode=-1, cognitiveComplexityNewCode=0.
2026-08-31 01:06:43 -03:00
Diego Rodrigues de Sa e Souza
bbbcc79384 chore(lint): batch 4 of #12146 — shared/components react-hooks violations resolved (#12159)
* chore(lint): batch 4 of #12146 — resolve the react-hooks compiler violations in shared/components

Real refactors (no suppressions, no eslint-disable) for the 21 react-hooks/*
violations across the 11 src/shared/components files of this batch:

- set-state-in-effect (prop/state mirror or modal open/close reset):
  replaced with guarded render-time adjustments (react.dev "You Might Not
  Need an Effect" prev-tracking pattern) — KiroAuthModal,
  ModelSelectModal, ProxyConfigModal, OAuthModal (provider-change, close
  and open resets; ref invalidation split into ref-only effects),
  RequestLoggerDetail.sections (liveDetail mirror),
  ComboCompressionModeSelect (initialCompressionMode mirror).
- set-state-in-effect (fetch+set effects calling component-scope
  functions): moved the async loader inside the effect (ModelSelectModal
  fetchCombos/fetchProviderNodes/fetchCustomModels, PricingModal
  loadPricing, useProviderDailyUsage fetchRows — now with a cancelled
  guard) or wrapped the call in an effect-local async runner
  (ReasoningRoutingRules load, UsageStats fetchStats, OAuthModal
  startOAuthFlow) with every setState on the async path.
- OAuthModal device-code countdown: deviceCodeSecondsRemaining state
  deleted and derived from deviceCodeExpiresAt plus a `now` tick state
  updated by the interval (re-anchored when polling starts).
- Sidebar localStorage hydration: reads moved into useSyncExternalStore
  snapshots (server snapshot null) applied via render-time adjustment;
  skipInitialActiveExpansion ref converted to state; the active-section
  expansion effect became a render-time adjustment keyed on the old
  effect deps; persistence consolidated into one saveToStorage effect
  (removes the saves that ran inside setState updaters and drops a
  pre-existing eslint-disable for exhaustive-deps).
- immutability (use-before-declare): PricingModal loadPricing inlined
  into its effect; ProxyConfigModal resetFields hoisted above the load
  effect as a dependency-free useCallback.
- exhaustive-deps (ProxyConfigModal): effect now depends on the stable
  resetFields and on hoisted translated strings (socks5HiddenError,
  levelGlobalLabel) instead of the `t` identity.
- preserve-manual-memoization (UsageStats sortedAccounts): optional
  chains destructured into locals so the memo deps match the usage.

config/quality/eslint-suppressions.json: removed every react-hooks/*
entry for the 11 files (other-rule entries preserved).

Validation: eslint gate (--suppressions-location, --max-warnings 0) green
on all 11 files; typecheck:core clean; node unit sweep 373/373; vitest
sweep 547/550 with the 3 fails being 5s-timeout flakes under parallel
load (all pass isolated 8/8, one in an untouched file).

Refs #12146

* test(mutation): register search-432-plan-limit-cooldown in tap.testFiles

The test (merged with the DuckDuckGo cooldown fix) covers accountFallback.ts and
auth.ts but was not listed, so check:mutation-test-coverage --strict reds any PR
whose merge ref includes it. Base also merged in.
2026-08-30 23:06:27 -03:00
Abhishek Divekar
8a1d9bf910 feat(resilience): default the credential health check sweep to 60 minutes (#12138)
Validado em worktree combinada com typecheck limpo, testes focados verdes, gates de file-size/complexity/cognitive-complexity/cycles OK. Obrigado!
2026-08-30 19:39:42 -03:00