Skip to content

feat: add new feature on combos - falloverBeforeRetry - #2417

Merged
diegosouzapw merged 1 commit into
cheaper-inference:release/v3.8.0from
hartmark:feature/fallbackCombo
May 20, 2026
Merged

diegosouzapw merged 1 commit into
cheaper-inference:release/v3.8.0from
hartmark:feature/fallbackCombo

Conversation

@hartmark

Copy link
Copy Markdown
Contributor

Summary

I'm mostly using gemini provider and they have two models. Gemma-4 31B and Gemma-4 24B, they have a 10 RPM limit so when I get 429 on one model it should go over to the next.

I have digged through the code and with help of AI identified that it seems that combos are handling fail over to another model on the same provider.

This PR should address this. All tests and added tests are in place.

@hartmark
hartmark requested a review from diegosouzapw as a code owner May 19, 2026 20:45

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces advanced failover and retry configurations for model combos, including a failoverBeforeRetry option to bypass intra-URL retries and a maxSetRetries feature for retrying the entire target set. The implementation involves updates to the base executor, combo services, Zod schemas, and the dashboard UI, supported by a new system-level failover test suite. Feedback identifies a critical issue with a .ts extension in a dynamic import that may break production builds, a logic error where request metrics are reset during set-level retries, and a potential listener leak in the delay logic used for retries.


// Also clear in-memory model lockouts (per-model quota cooldowns)
const { clearAllModelLockouts } =
await import("@omniroute/open-sse/services/accountFallback.ts");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The dynamic import() call includes a .ts extension. In Next.js and standard Node.js ESM resolution, extensions should generally be omitted (or use .js if required by specific configurations). Including .ts can cause module resolution failures in production builds where the source files are compiled.

Suggested change
await import("@omniroute/open-sse/services/accountFallback.ts");
await import("@omniroute/open-sse/services/accountFallback");

Comment on lines +1984 to +1986
const startTime = Date.now();
let fallbackCount = 0;
let recordedAttempts = 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The variables startTime, fallbackCount, and recordedAttempts are reset at the beginning of each set retry (inside the setTry loop). This causes metrics like total latency and cumulative fallback count to only reflect the final set attempt, which is misleading for requests that required multiple set-level retries.

Additionally, the block at line 2284 that records a failure when recordedAttempts === 0 will fire for every set attempt that finds no available models, potentially cluttering logs and metrics with redundant failure entries.

Consider moving these variables and the final failure recording logic outside the setTry loop to track the entire request lifecycle correctly.

Comment on lines +1964 to +1974
await new Promise((resolve) => {
const timer = setTimeout(resolve, setRetryDelayMs);
signal?.addEventListener(
"abort",
() => {
clearTimeout(timer);
resolve(undefined);
},
{ once: true }
);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The Promise wrapper used for the delay does not remove the abort event listener if the timer completes normally. While request-scoped signals are eventually garbage collected, this pattern can lead to a build-up of listeners in high-concurrency scenarios.

Consider using a cleanup mechanism to remove the listener when the timer fires, or use AbortSignal.timeout or setTimeout from node:timers/promises if supported in your environment.

upstreamExtraHeaders: buildUpstreamHeadersForExecute(retryModelId),
clientHeaders: buildExecutorClientHeaders(clientRawRequest?.headers, userAgent),
onCredentialsRefreshed,
skipUpstreamRetry: isCombo,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: skipUpstreamRetry: isCombo disables executor-level retries for ALL combo requests, not just those with failoverBeforeRetry: true.

This means combos without failoverBeforeRetry will also skip the executor's intra-URL retry logic (BaseExecutor.RETRY_CONFIG). For example, if an upstream provider returns a transient 500, the executor won't retry that URL even if the combo config would normally allow retries.

If the combo layer is intended to fully own retry behavior, this is fine. But if non-fobr combos should still benefit from executor-level retries as a safety net, this should be:

Suggested change
skipUpstreamRetry: isCombo,
skipUpstreamRetry: isCombo && skipUpstreamRetry,

This way, executor retries are only skipped when both conditions are met: it's a combo request AND skipUpstreamRetry was explicitly passed as true (i.e., failoverBeforeRetry is enabled).

@kilo-code-bot

kilo-code-bot Bot commented May 19, 2026 •

Copy link
Copy Markdown

Code Review Summary

Status: 1 Warning Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
open-sse/handlers/chatCore.ts 3666 skipUpstreamRetry: isCombo disables executor retries for ALL combos, not just those with failoverBeforeRetry: true. Non-fobr combos lose their executor-level retry safety net.
Other Observations (not in diff)

Issues found in unchanged code that cannot receive inline comments:

File Line Issue
tests/e2e/system-failover.test.ts 634-649 Test "single provider, modelA 500 with fobr" — both models use the same TOKEN_A, so the mock server doesn't distinguish between them. After modelA's queued 429 is consumed, modelB hits the default 200. The test name says "modelA retry" but the assertion hits: 2 is ambiguous — it could be 1 modelA + 1 modelB. Consider using distinct tokens per model for clarity.
tests/e2e/system-failover.test.ts 586-601 Test "single provider, modelA 500" — same issue: both models share TOKEN_A. The combo routes modelA first (gets 500), then tries modelB (same token, gets default 200). The test asserts result.json.model === "sys-a/test-model" which passes because the mock always returns that model name. This means the test can't actually verify which combo target succeeded.
tests/e2e/system-failover.test.ts 610-625 Test "single provider, modelA 503 with fobr" — same shared token issue. The assertion hits: 2 could mean either 2 retries on modelA, or 1 hit on modelA + 1 on modelB. Using distinct tokens would make the intent verifiable.
Files Reviewed (17 files)
  • open-sse/executors/base.ts — skipUpstreamRetry parameter added correctly
  • open-sse/handlers/chatCore.ts — 1 warning (skipUpstreamRetry: isCombo)
  • open-sse/services/accountFallback.ts — clearAllModelLockouts() helper added
  • open-sse/services/combo.ts — major refactor: set-level retry loop, restructured error handling
  • open-sse/services/comboConfig.ts — defaults for new fields
  • package.json — test:system script added
  • src/app/(dashboard)/dashboard/combos/page.tsx — UI for new config fields
  • src/app/api/resilience/reset/route.ts — calls clearAllModelLockouts()
  • src/i18n/messages/en.json — i18n strings for new fields
  • src/shared/validation/schemas.ts — Zod schemas for new fields
  • src/sse/handlers/chat.ts — failoverBeforeRouting propagated through handler chain
  • src/sse/handlers/chatHelpers.ts — skipUpstreamRetry passthrough
  • src/sse/services/auth.ts — lockout reason extended to status >= 500
  • tests/e2e/combos-flow.spec.ts — UI test for new fields
  • tests/e2e/helpers/mockUpstreamServer.ts — new test helper (175 lines)
  • tests/e2e/system-failover.test.ts — new e2e test file (699 lines)
  • tests/unit/combo-config.test.ts — unit tests for new schema fields

Reviewed by qwen3.6-plus · 759,384 tokens

@diegosouzapw diegosouzapw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — falloverBeforeRetry correctly solves per-model quota skipping within a combo without touching the provider-level circuit breaker. Tests in place.

@diegosouzapw
diegosouzapw merged commit ae94b26 into cheaper-inference:release/v3.8.0 May 20, 2026
3 checks passed
@diegosouzapw

Copy link
Copy Markdown
Collaborator

🎉 Thank you @hartmark for the falloverBeforeRetry feature! This cleanly solves the per-model Gemini RPM quota issue by having combos skip to the next target instead of retrying the same rate-limited model. The feature has been integrated into the v3.8.0 release and will be available in the next build. 🚀

diegosouzapw added a commit that referenced this pull request May 20, 2026
diegosouzapw added a commit that referenced this pull request May 20, 2026
Integrated into release/v3.8.0 — batch of 10 feature requests: llama.cpp local provider, upstream error exposure, Termux detection, providers rotate CLI, t3.chat web skeleton, Zed Docker integration, Kiro multi-account OAuth isolation, auto-combo cost blending, auto-combo context filter, combo provider-level exhaustion tracking (#1731). Conflicts with #2417 (falloverBeforeRetry) resolved.
@diegosouzapw

Copy link
Copy Markdown
Collaborator

Thank you @hartmark for this combo routing improvement! The falloverBeforeRetry feature is now part of OmniRoute v3.8.0. This elegantly solves the tail-latency problem where a failing endpoint would exhaust all per-model retries before moving to the next target — now combo routing falls over immediately. The setTry outer loop structure was a clean approach to coordinate this.

diegosouzapw added a commit that referenced this pull request May 20, 2026
- feat(batch): T3-Chat-Web executor, exhaustedProviders set (#1731), Zed Docker
- feat(combos): falloverBeforeRetry + setTry loop (#2417 — @hartmark)
- fix(gamification): SQL SELECT bug, federation auth, pagination, z-score (#2421 — @oyi77)
- docs: AgentRouter setup guide (#2422 — @leninejunior)
diegosouzapw added a commit that referenced this pull request May 20, 2026
* fix(cli-tools): guard modelId type before calling indexOf

E2E shakedown v3.8.0: cli-tools quebrava com TypeError quando dynamicModels
continha entradas sem .id (objeto retornado diretamente em vez de string).

* fix(offline): avoid SSR/CSR hydration mismatch on navigator.onLine

Replace useState+lazy-initializer with useSyncExternalStore so the server
snapshot (() => false) and client snapshot (() => navigator.onLine) are
declared separately. React hydrates with the server value and switches to
the real online status client-side without a mismatch.

* chore(i18n): add missing en.json keys for translator, cli-tools, memory, onboarding

Adds 58 missing keys identified by the new dashboard audit script:
- cliTools: 18 custom CLI builder keys (CustomCliCard)
- translator: 24 keys covering stream transformer, live monitor, test bench
- memory: 12 health/pagination/dialog keys
- onboarding.tier: 8 keys for the tier tour walkthrough

Also adds scripts/i18n/audit-dashboard-pages.mjs which scans all dashboard
pages, reports t() calls referencing missing en.json keys, and flags
candidate hardcoded JSX/attribute strings.

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 1)

Subagents refactored 8 high-impact dashboard pages, replacing 81 of the
407 hardcoded English/PT strings flagged by the audit with proper
useTranslations() lookups. Added 73 corresponding keys to en.json across
the home, apiManager, providers, settings, and usage namespaces.

Pages affected:
- BudgetTab (27 → 0)
- HomePageClient (2 → 0)
- RoutingTab (25 → 7)
- ResilienceTab (38 → 18)
- SystemStorageTab (42 → 21)
- providers/[id] (17 → 15)
- ApiManagerPageClient (14 → 13)
- OneproxyTab (13 → 10)

Also adds two helper scripts:
- scripts/i18n/extract-keys-from-diff.mjs — extracts new keys from git diff
- scripts/i18n/merge-keys.mjs — merges a pending-keys JSON into en.json

Remaining hardcoded strings will be addressed in follow-up rounds.

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 2)

Continues round 1 (commit 8d34f4c). Round-2 subagents refactored
additional dashboard pages, replacing 77 more hardcoded strings with
useTranslations() lookups. Added 79 corresponding keys to en.json
across the a2aDashboard, agents, analytics, apiManager, cliTools,
common, and settings namespaces.

Pages affected:
- a2a/page (new useTranslations + 6 keys)
- agent-skills/page (new useTranslations + 9 keys)
- AutoRoutingAnalyticsTab (new useTranslations + 6 keys)
- AppearanceTab (8 → 6 remaining)
- OneproxyTab (10 → 0)
- ResilienceTab (18 → 0 missing key)
- RoutingTab (7 → 0 missing key)
- VisionBridgeSettingsTab (new useTranslations + 6 keys)
- CopilotToolCard (7 → 0 missing key)
- ApiManagerPageClient (13 → 0 missing key)
- gamification/admin (new useTranslations + 7 keys)

Hardcoded total: 326 → 249. Real missing keys: 0 (the 6 still flagged
are false positives in exampleTemplates.tsx where t is passed as a
parameter — keys exist at translator.templatePayloads.*).

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 3)

Round-3 subagents and manual edits refactored 9 more dashboard pages
(plus 2 small extras), replacing ~80 hardcoded strings with
useTranslations() lookups. Added 79 corresponding keys to en.json
across analytics, cloudAgents, combos, common, health, settings, and
usage namespaces.

Pages affected:
- analytics/ComboHealthTab (new useTranslations + 15 keys)
- analytics/CompressionAnalyticsTab (new useTranslations + 11 keys)
- settings/SystemStorageTab (21 → 0 missing key)
- tokens/page (new useTranslations + 13 keys)
- usage/BudgetTab (9 missing fixed)
- health/page (manual: 6 keys)
- cloud-agents/page (manual: 3 keys)
- combos/page (manual: 1 key)

Hardcoded total: 249 → 164. Real missing keys: 0 (6 remaining are
exampleTemplates.tsx false positives).

Also adds scripts/i18n/build-pending-from-missing.mjs which reads
_audit.json and locates English values from HEAD to rebuild
_pending-keys.json after race-condition resets between subagent edits.

* chore(i18n): localize remaining dashboard settings labels

Replace hardcoded labels in compression and resilience settings with
translation lookups to continue the dashboard i18n cleanup.

Add the v3.8.0 dashboard shakedown runbook to document the manual
smoke-test process and known dev environment pitfalls.

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 4)

Round-4 subagent + manual key-resolution refactored remaining strings in
3 high-traffic settings/API tabs, plus extracted English values for
keys that were already added as t() calls but lost during the previous
en.json race-condition resets.

Pages affected:
- api-manager/ApiManagerPageClient (7 → 0 missing key)
- settings/CompressionSettingsTab (8 → 0 missing key)
- settings/MemorySkillsTab (8 → 0 missing key)
- settings/ResilienceTab (4 more keys recovered)

Hardcoded total: 164 → 140. Real missing keys: 0 (6 remaining are the
exampleTemplates.tsx false positives — t passed as parameter).

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 5)

Round-5 agent began processing the remaining smaller dashboard files.
Added 5 more keys to en.json for providers/[id]/page.tsx OAuth flow
labels and the cross-OS auto-detection hint.

Pages affected:
- providers/[id]/page.tsx (5 keys)

Hardcoded total: 140 → 136. Real missing keys: 0.

* chore(i18n): resolve last 2 missing providers/[id] keys

Adds providerDetailMyClaudeAccountPlaceholder and
providerDetailPathAutoDetected — the final user-visible labels in the
providers/[id] page that the round-5 subagent rewrote to t() calls
without yet adding to en.json.

Real missing keys: 0 (6 remaining are exampleTemplates.tsx false
positives — t is passed as a parameter so the audit cannot resolve the
namespace; keys do exist at translator.templatePayloads.*).

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 6 — 10 parallel agents)

Round-6 dispatched 10 parallel subagents covering all 57 remaining
dashboard files. Each agent worked on a disjoint file set to avoid
en.json race conditions. Added ~60 new i18n keys across 9 namespaces
covering small UI labels, table headers, search placeholders, and
empty-state messages.

Major changes:
- analytics: SearchAnalyticsTab, ProviderUtilizationTab, DiversityScoreCard, CompressionAnalyticsTab (new useTranslations + keys)
- batch: BatchDetailModal, BatchListTab, FileDetailModal, FilesListTab (new useTranslations + keys)
- settings: CliproxyapiSettingsTab, PayloadRulesTab, ModelCooldownsCard, AppearanceTab, PricingTab (mostly new useTranslations)
- endpoint: TokenSaverCard, ApiEndpointsTab, EndpointPageClient
- cache: CachePerformance, IdempotencyLayer, ReasoningCacheTab, MediaPageClient, page
- combos: IntelligentComboPanel, page
- playground: ChatPlayground, SearchPlayground
- providers: ProviderCard
- onboarding: TierFlowDiagram
- changelog: ChangelogViewer
- home: ProviderTopology, TierCoverageWidget, BootstrapBanner, BadgeToast
- usage: BudgetTab, BudgetTelemetryCards, QuotaTable
- quotaShare: QuotaSharePageClient
- profile: page
- leaderboard: page
- skills: page

Hardcoded total: 131 → 60. Real missing keys: 0 plus 1 false-positive
for combos.modePack (lookup via prop-passed t).

* chore(i18n): finalize round-6 keys for batch/cache/endpoint/usage

Adds the remaining keys produced by parallel agents A4, A6, A8, A9:
- common: batch-related labels (BatchDetailModal, BatchListTab,
  FileDetailModal, FilesListTab, page) + profile/leaderboard
- cache: hit rate, latency, retry, avg chars
- endpoint: token saver, API endpoints, copy URL, cloud/local labels
- usage: noSpend, activeSessions, quotaAlerts, budget timing
- skills: install/marketplace/filter
- proxyRegistry/quotaShare/mcpDashboard: misc labels

Hardcoded total: 60 → 48. Real missing keys: 0 (modePack remaining is a
false positive — combos.modePack exists but the audit can't resolve it
since IntelligentComboPanel receives t as a prop).

* fix(playground): dedupe filteredModels to avoid duplicate React key warning

The /v1/models endpoint can return the same model id twice (e.g., when a
model is listed by both an alias and its canonical provider), which made
the <Select> emit two <option> elements with the same key — triggering
"Encountered two children with the same key, codex/gpt-5.5".

Replace the chained filter + map with a single pass that skips ids
already added.

* fix(playground): guard against non-string model ids before .split/.startsWith

The /v1/models endpoint can include synthetic entries (combos, locals,
in-progress imports) with a null/undefined id. The playground used to
call m.id.split("/") in the provider-discovery loop, which threw on the
first non-string entry; the surrounding .catch(() => {}) silently
swallowed the error, so the provider/model/account dropdowns ended up
empty even though /v1/models returned thousands of valid entries.

- Skip entries without a string id before split/startsWith.
- Log the rejection in the .catch handler so future regressions are
  visible in DevTools instead of silently emptying the UI.

* fix(playground): guard ChatPlayground filteredModels for non-string ids

Same root cause as commit 49fe356: ChatPlayground filtered models
with m.id.startsWith(...) which crashed on null/undefined ids returned
by /v1/models (synthetic combo entries). Apply the same defensive guard
and dedupe used in the parent page.

* fix(claude): drop orphan tool_result after fixToolAdjacency strip (discussion #2410)

Discussion #2410 reports Claude returning 400 for sequences like:
  assistant: tool_use(id=X)
  user: <plain text>           ← breaks adjacency
  user: tool_result(id=X)

The previous round added `fixToolAdjacency` (commit 44d9aba) which
correctly strips the orphan tool_use from the assistant message. But
that left the now-unmatched tool_result intact, so the upstream
rejected the request with:

  messages.N.content.M: unexpected `tool_use_id` found in `tool_result`
  blocks: X. Each tool_result block must have a corresponding tool_use
  block in the previous message.

Fix: after running `fixToolAdjacency`, re-run `fixToolPairs` to drop
the orphaned tool_result blocks. All three call sites updated:
  - contextManager.purifyHistory (both inside the binary-search loop
    and the final pass)
  - BaseExecutor message-prep (Claude path)
  - claudeCodeCompatible request signer

Also tightens an unrelated dynamic-key access in
readNestedString (claudeCodeCompatible) to satisfy the prototype-
pollution scanner triggered by the post-tool semgrep hook.

* fix(mitm): point runtime manager re-export to js entrypoint

Use the emitted `.js` path for the runtime manager re-export so dynamic
runtime loading resolves correctly outside the Turbopack alias handling.

* docs: add AgentRouter setup guide (#2422)

Integrated into release/v3.8.0 — AgentRouter setup guide docs.

* feat: add new feature on combos - falloverBeforeRetry (#2417)

Integrated into release/v3.8.0 — falloverBeforeRetry for per-model quota skipping in combos.

* feat(batch): implement 10 feature requests harvested  (#2414)

Integrated into release/v3.8.0 — batch of 10 feature requests: llama.cpp local provider, upstream error exposure, Termux detection, providers rotate CLI, t3.chat web skeleton, Zed Docker integration, Kiro multi-account OAuth isolation, auto-combo cost blending, auto-combo context filter, combo provider-level exhaustion tracking (#1731). Conflicts with #2417 (falloverBeforeRetry) resolved.

* fix(gamification): resolve SQL bug, auth gap, pagination, and anomaly scoring (#2421)

Integrated into release/v3.8.0 — 6 critical gamification bug fixes: SQL SELECT in checkActionCountBadges, federation auth enforcement, leaderboard pagination offset, real z-score computation, addXp level calculation, and barrel index.ts

* docs(changelog): add post-release entries for #2414 #2417 #2421 #2422

- feat(batch): T3-Chat-Web executor, exhaustedProviders set (#1731), Zed Docker
- feat(combos): falloverBeforeRetry + setTry loop (#2417 — @hartmark)
- fix(gamification): SQL SELECT bug, federation auth, pagination, z-score (#2421 — @oyi77)
- docs: AgentRouter setup guide (#2422 — @leninejunior)

* fix(security): resolve CodeQL random/password-hash alerts and sync docs & tests

---------

Co-authored-by: diegosouzapw <diego.souza.pw@gmail.com>
Co-authored-by: Lenine Júnior <lenine@engrene.com.br>
Co-authored-by: Markus Hartung <mail@hartmark.se>
Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com>
@hartmark

Copy link
Copy Markdown
Contributor Author

Thank you @hartmark for this combo routing improvement! The falloverBeforeRetry feature is now part of OmniRoute v3.8.0. This elegantly solves the tail-latency problem where a failing endpoint would exhaust all per-model retries before moving to the next target — now combo routing falls over immediately. The setTry outer loop structure was a clean approach to coordinate this.

Thanks mate. I have some batchjob fixes coming up in the next few days. I've noticed some issues

@hartmark
hartmark deleted the feature/fallbackCombo branch May 20, 2026 10:09
diegosouzapw added a commit that referenced this pull request May 20, 2026
* fix(cli-tools): guard modelId type before calling indexOf

E2E shakedown v3.8.0: cli-tools quebrava com TypeError quando dynamicModels
continha entradas sem .id (objeto retornado diretamente em vez de string).

* fix(offline): avoid SSR/CSR hydration mismatch on navigator.onLine

Replace useState+lazy-initializer with useSyncExternalStore so the server
snapshot (() => false) and client snapshot (() => navigator.onLine) are
declared separately. React hydrates with the server value and switches to
the real online status client-side without a mismatch.

* chore(i18n): add missing en.json keys for translator, cli-tools, memory, onboarding

Adds 58 missing keys identified by the new dashboard audit script:
- cliTools: 18 custom CLI builder keys (CustomCliCard)
- translator: 24 keys covering stream transformer, live monitor, test bench
- memory: 12 health/pagination/dialog keys
- onboarding.tier: 8 keys for the tier tour walkthrough

Also adds scripts/i18n/audit-dashboard-pages.mjs which scans all dashboard
pages, reports t() calls referencing missing en.json keys, and flags
candidate hardcoded JSX/attribute strings.

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 1)

Subagents refactored 8 high-impact dashboard pages, replacing 81 of the
407 hardcoded English/PT strings flagged by the audit with proper
useTranslations() lookups. Added 73 corresponding keys to en.json across
the home, apiManager, providers, settings, and usage namespaces.

Pages affected:
- BudgetTab (27 → 0)
- HomePageClient (2 → 0)
- RoutingTab (25 → 7)
- ResilienceTab (38 → 18)
- SystemStorageTab (42 → 21)
- providers/[id] (17 → 15)
- ApiManagerPageClient (14 → 13)
- OneproxyTab (13 → 10)

Also adds two helper scripts:
- scripts/i18n/extract-keys-from-diff.mjs — extracts new keys from git diff
- scripts/i18n/merge-keys.mjs — merges a pending-keys JSON into en.json

Remaining hardcoded strings will be addressed in follow-up rounds.

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 2)

Continues round 1 (commit 8d34f4c). Round-2 subagents refactored
additional dashboard pages, replacing 77 more hardcoded strings with
useTranslations() lookups. Added 79 corresponding keys to en.json
across the a2aDashboard, agents, analytics, apiManager, cliTools,
common, and settings namespaces.

Pages affected:
- a2a/page (new useTranslations + 6 keys)
- agent-skills/page (new useTranslations + 9 keys)
- AutoRoutingAnalyticsTab (new useTranslations + 6 keys)
- AppearanceTab (8 → 6 remaining)
- OneproxyTab (10 → 0)
- ResilienceTab (18 → 0 missing key)
- RoutingTab (7 → 0 missing key)
- VisionBridgeSettingsTab (new useTranslations + 6 keys)
- CopilotToolCard (7 → 0 missing key)
- ApiManagerPageClient (13 → 0 missing key)
- gamification/admin (new useTranslations + 7 keys)

Hardcoded total: 326 → 249. Real missing keys: 0 (the 6 still flagged
are false positives in exampleTemplates.tsx where t is passed as a
parameter — keys exist at translator.templatePayloads.*).

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 3)

Round-3 subagents and manual edits refactored 9 more dashboard pages
(plus 2 small extras), replacing ~80 hardcoded strings with
useTranslations() lookups. Added 79 corresponding keys to en.json
across analytics, cloudAgents, combos, common, health, settings, and
usage namespaces.

Pages affected:
- analytics/ComboHealthTab (new useTranslations + 15 keys)
- analytics/CompressionAnalyticsTab (new useTranslations + 11 keys)
- settings/SystemStorageTab (21 → 0 missing key)
- tokens/page (new useTranslations + 13 keys)
- usage/BudgetTab (9 missing fixed)
- health/page (manual: 6 keys)
- cloud-agents/page (manual: 3 keys)
- combos/page (manual: 1 key)

Hardcoded total: 249 → 164. Real missing keys: 0 (6 remaining are
exampleTemplates.tsx false positives).

Also adds scripts/i18n/build-pending-from-missing.mjs which reads
_audit.json and locates English values from HEAD to rebuild
_pending-keys.json after race-condition resets between subagent edits.

* chore(i18n): localize remaining dashboard settings labels

Replace hardcoded labels in compression and resilience settings with
translation lookups to continue the dashboard i18n cleanup.

Add the v3.8.0 dashboard shakedown runbook to document the manual
smoke-test process and known dev environment pitfalls.

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 4)

Round-4 subagent + manual key-resolution refactored remaining strings in
3 high-traffic settings/API tabs, plus extracted English values for
keys that were already added as t() calls but lost during the previous
en.json race-condition resets.

Pages affected:
- api-manager/ApiManagerPageClient (7 → 0 missing key)
- settings/CompressionSettingsTab (8 → 0 missing key)
- settings/MemorySkillsTab (8 → 0 missing key)
- settings/ResilienceTab (4 more keys recovered)

Hardcoded total: 164 → 140. Real missing keys: 0 (6 remaining are the
exampleTemplates.tsx false positives — t passed as parameter).

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 5)

Round-5 agent began processing the remaining smaller dashboard files.
Added 5 more keys to en.json for providers/[id]/page.tsx OAuth flow
labels and the cross-OS auto-detection hint.

Pages affected:
- providers/[id]/page.tsx (5 keys)

Hardcoded total: 140 → 136. Real missing keys: 0.

* chore(i18n): resolve last 2 missing providers/[id] keys

Adds providerDetailMyClaudeAccountPlaceholder and
providerDetailPathAutoDetected — the final user-visible labels in the
providers/[id] page that the round-5 subagent rewrote to t() calls
without yet adding to en.json.

Real missing keys: 0 (6 remaining are exampleTemplates.tsx false
positives — t is passed as a parameter so the audit cannot resolve the
namespace; keys do exist at translator.templatePayloads.*).

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 6 — 10 parallel agents)

Round-6 dispatched 10 parallel subagents covering all 57 remaining
dashboard files. Each agent worked on a disjoint file set to avoid
en.json race conditions. Added ~60 new i18n keys across 9 namespaces
covering small UI labels, table headers, search placeholders, and
empty-state messages.

Major changes:
- analytics: SearchAnalyticsTab, ProviderUtilizationTab, DiversityScoreCard, CompressionAnalyticsTab (new useTranslations + keys)
- batch: BatchDetailModal, BatchListTab, FileDetailModal, FilesListTab (new useTranslations + keys)
- settings: CliproxyapiSettingsTab, PayloadRulesTab, ModelCooldownsCard, AppearanceTab, PricingTab (mostly new useTranslations)
- endpoint: TokenSaverCard, ApiEndpointsTab, EndpointPageClient
- cache: CachePerformance, IdempotencyLayer, ReasoningCacheTab, MediaPageClient, page
- combos: IntelligentComboPanel, page
- playground: ChatPlayground, SearchPlayground
- providers: ProviderCard
- onboarding: TierFlowDiagram
- changelog: ChangelogViewer
- home: ProviderTopology, TierCoverageWidget, BootstrapBanner, BadgeToast
- usage: BudgetTab, BudgetTelemetryCards, QuotaTable
- quotaShare: QuotaSharePageClient
- profile: page
- leaderboard: page
- skills: page

Hardcoded total: 131 → 60. Real missing keys: 0 plus 1 false-positive
for combos.modePack (lookup via prop-passed t).

* chore(i18n): finalize round-6 keys for batch/cache/endpoint/usage

Adds the remaining keys produced by parallel agents A4, A6, A8, A9:
- common: batch-related labels (BatchDetailModal, BatchListTab,
  FileDetailModal, FilesListTab, page) + profile/leaderboard
- cache: hit rate, latency, retry, avg chars
- endpoint: token saver, API endpoints, copy URL, cloud/local labels
- usage: noSpend, activeSessions, quotaAlerts, budget timing
- skills: install/marketplace/filter
- proxyRegistry/quotaShare/mcpDashboard: misc labels

Hardcoded total: 60 → 48. Real missing keys: 0 (modePack remaining is a
false positive — combos.modePack exists but the audit can't resolve it
since IntelligentComboPanel receives t as a prop).

* fix(playground): dedupe filteredModels to avoid duplicate React key warning

The /v1/models endpoint can return the same model id twice (e.g., when a
model is listed by both an alias and its canonical provider), which made
the <Select> emit two <option> elements with the same key — triggering
"Encountered two children with the same key, codex/gpt-5.5".

Replace the chained filter + map with a single pass that skips ids
already added.

* fix(playground): guard against non-string model ids before .split/.startsWith

The /v1/models endpoint can include synthetic entries (combos, locals,
in-progress imports) with a null/undefined id. The playground used to
call m.id.split("/") in the provider-discovery loop, which threw on the
first non-string entry; the surrounding .catch(() => {}) silently
swallowed the error, so the provider/model/account dropdowns ended up
empty even though /v1/models returned thousands of valid entries.

- Skip entries without a string id before split/startsWith.
- Log the rejection in the .catch handler so future regressions are
  visible in DevTools instead of silently emptying the UI.

* fix(playground): guard ChatPlayground filteredModels for non-string ids

Same root cause as commit 49fe356: ChatPlayground filtered models
with m.id.startsWith(...) which crashed on null/undefined ids returned
by /v1/models (synthetic combo entries). Apply the same defensive guard
and dedupe used in the parent page.

* fix(claude): drop orphan tool_result after fixToolAdjacency strip (discussion #2410)

Discussion #2410 reports Claude returning 400 for sequences like:
  assistant: tool_use(id=X)
  user: <plain text>           ← breaks adjacency
  user: tool_result(id=X)

The previous round added `fixToolAdjacency` (commit 44d9aba) which
correctly strips the orphan tool_use from the assistant message. But
that left the now-unmatched tool_result intact, so the upstream
rejected the request with:

  messages.N.content.M: unexpected `tool_use_id` found in `tool_result`
  blocks: X. Each tool_result block must have a corresponding tool_use
  block in the previous message.

Fix: after running `fixToolAdjacency`, re-run `fixToolPairs` to drop
the orphaned tool_result blocks. All three call sites updated:
  - contextManager.purifyHistory (both inside the binary-search loop
    and the final pass)
  - BaseExecutor message-prep (Claude path)
  - claudeCodeCompatible request signer

Also tightens an unrelated dynamic-key access in
readNestedString (claudeCodeCompatible) to satisfy the prototype-
pollution scanner triggered by the post-tool semgrep hook.

* fix(mitm): point runtime manager re-export to js entrypoint

Use the emitted `.js` path for the runtime manager re-export so dynamic
runtime loading resolves correctly outside the Turbopack alias handling.

* docs: add AgentRouter setup guide (#2422)

Integrated into release/v3.8.0 — AgentRouter setup guide docs.

* feat: add new feature on combos - falloverBeforeRetry (#2417)

Integrated into release/v3.8.0 — falloverBeforeRetry for per-model quota skipping in combos.

* feat(batch): implement 10 feature requests harvested  (#2414)

Integrated into release/v3.8.0 — batch of 10 feature requests: llama.cpp local provider, upstream error exposure, Termux detection, providers rotate CLI, t3.chat web skeleton, Zed Docker integration, Kiro multi-account OAuth isolation, auto-combo cost blending, auto-combo context filter, combo provider-level exhaustion tracking (#1731). Conflicts with #2417 (falloverBeforeRetry) resolved.

* fix(gamification): resolve SQL bug, auth gap, pagination, and anomaly scoring (#2421)

Integrated into release/v3.8.0 — 6 critical gamification bug fixes: SQL SELECT in checkActionCountBadges, federation auth enforcement, leaderboard pagination offset, real z-score computation, addXp level calculation, and barrel index.ts

* docs(changelog): add post-release entries for #2414 #2417 #2421 #2422

- feat(batch): T3-Chat-Web executor, exhaustedProviders set (#1731), Zed Docker
- feat(combos): falloverBeforeRetry + setTry loop (#2417 — @hartmark)
- fix(gamification): SQL SELECT bug, federation auth, pagination, z-score (#2421 — @oyi77)
- docs: AgentRouter setup guide (#2422 — @leninejunior)

* fix(security): resolve CodeQL random/password-hash alerts and sync docs & tests

* feat/fix: integrate PRs #2423, #2425, #2427, #2428 with test & security fixes

* docs(changelog): credit contributors for PRs #2423, #2425, #2427, #2428

* fix(mitm): drop .js extension on manager.runtime re-export for webpack build (#2425)

Merged into release/v3.8.0

* fix: persist STORAGE_ENCRYPTION_KEY across upgrades (closes #1622) (#2428)

Merged into release/v3.8.0

* fix: auto-reset apiKeyHealth on successful connection test (#2427)

Merged into release/v3.8.0

* fix: support Antigravity image generation, Add Gemini 3.5 Flash (#2423)

Merged into release/v3.8.0

---------

Co-authored-by: diegosouzapw <diego.souza.pw@gmail.com>
Co-authored-by: Lenine Júnior <lenine@engrene.com.br>
Co-authored-by: Markus Hartung <mail@hartmark.se>
Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com>
Co-authored-by: Anton <39598727+NomenAK@users.noreply.github.com>
Co-authored-by: Chewji <126886556+Chewji9875@users.noreply.github.com>
Co-authored-by: clousky2020 <33016567+clousky2020@users.noreply.github.com>
Co-authored-by: backryun <bakryun0718@proton.me>
diegosouzapw added a commit that referenced this pull request Jul 6, 2026
…#6373)

add DigitalOcean AI OpenAI-compatible provider (port PR #2417). Resolved registry conflict with hcnsec #6410; count-guard 169→170. Tests green. Integrated into release/v3.8.46.
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
…2419)

* fix(cli-tools): guard modelId type before calling indexOf

E2E shakedown v3.8.0: cli-tools quebrava com TypeError quando dynamicModels
continha entradas sem .id (objeto retornado diretamente em vez de string).

* fix(offline): avoid SSR/CSR hydration mismatch on navigator.onLine

Replace useState+lazy-initializer with useSyncExternalStore so the server
snapshot (() => false) and client snapshot (() => navigator.onLine) are
declared separately. React hydrates with the server value and switches to
the real online status client-side without a mismatch.

* chore(i18n): add missing en.json keys for translator, cli-tools, memory, onboarding

Adds 58 missing keys identified by the new dashboard audit script:
- cliTools: 18 custom CLI builder keys (CustomCliCard)
- translator: 24 keys covering stream transformer, live monitor, test bench
- memory: 12 health/pagination/dialog keys
- onboarding.tier: 8 keys for the tier tour walkthrough

Also adds scripts/i18n/audit-dashboard-pages.mjs which scans all dashboard
pages, reports t() calls referencing missing en.json keys, and flags
candidate hardcoded JSX/attribute strings.

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 1)

Subagents refactored 8 high-impact dashboard pages, replacing 81 of the
407 hardcoded English/PT strings flagged by the audit with proper
useTranslations() lookups. Added 73 corresponding keys to en.json across
the home, apiManager, providers, settings, and usage namespaces.

Pages affected:
- BudgetTab (27 → 0)
- HomePageClient (2 → 0)
- RoutingTab (25 → 7)
- ResilienceTab (38 → 18)
- SystemStorageTab (42 → 21)
- providers/[id] (17 → 15)
- ApiManagerPageClient (14 → 13)
- OneproxyTab (13 → 10)

Also adds two helper scripts:
- scripts/i18n/extract-keys-from-diff.mjs — extracts new keys from git diff
- scripts/i18n/merge-keys.mjs — merges a pending-keys JSON into en.json

Remaining hardcoded strings will be addressed in follow-up rounds.

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 2)

Continues round 1 (commit 8d34f4c). Round-2 subagents refactored
additional dashboard pages, replacing 77 more hardcoded strings with
useTranslations() lookups. Added 79 corresponding keys to en.json
across the a2aDashboard, agents, analytics, apiManager, cliTools,
common, and settings namespaces.

Pages affected:
- a2a/page (new useTranslations + 6 keys)
- agent-skills/page (new useTranslations + 9 keys)
- AutoRoutingAnalyticsTab (new useTranslations + 6 keys)
- AppearanceTab (8 → 6 remaining)
- OneproxyTab (10 → 0)
- ResilienceTab (18 → 0 missing key)
- RoutingTab (7 → 0 missing key)
- VisionBridgeSettingsTab (new useTranslations + 6 keys)
- CopilotToolCard (7 → 0 missing key)
- ApiManagerPageClient (13 → 0 missing key)
- gamification/admin (new useTranslations + 7 keys)

Hardcoded total: 326 → 249. Real missing keys: 0 (the 6 still flagged
are false positives in exampleTemplates.tsx where t is passed as a
parameter — keys exist at translator.templatePayloads.*).

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 3)

Round-3 subagents and manual edits refactored 9 more dashboard pages
(plus 2 small extras), replacing ~80 hardcoded strings with
useTranslations() lookups. Added 79 corresponding keys to en.json
across analytics, cloudAgents, combos, common, health, settings, and
usage namespaces.

Pages affected:
- analytics/ComboHealthTab (new useTranslations + 15 keys)
- analytics/CompressionAnalyticsTab (new useTranslations + 11 keys)
- settings/SystemStorageTab (21 → 0 missing key)
- tokens/page (new useTranslations + 13 keys)
- usage/BudgetTab (9 missing fixed)
- health/page (manual: 6 keys)
- cloud-agents/page (manual: 3 keys)
- combos/page (manual: 1 key)

Hardcoded total: 249 → 164. Real missing keys: 0 (6 remaining are
exampleTemplates.tsx false positives).

Also adds scripts/i18n/build-pending-from-missing.mjs which reads
_audit.json and locates English values from HEAD to rebuild
_pending-keys.json after race-condition resets between subagent edits.

* chore(i18n): localize remaining dashboard settings labels

Replace hardcoded labels in compression and resilience settings with
translation lookups to continue the dashboard i18n cleanup.

Add the v3.8.0 dashboard shakedown runbook to document the manual
smoke-test process and known dev environment pitfalls.

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 4)

Round-4 subagent + manual key-resolution refactored remaining strings in
3 high-traffic settings/API tabs, plus extracted English values for
keys that were already added as t() calls but lost during the previous
en.json race-condition resets.

Pages affected:
- api-manager/ApiManagerPageClient (7 → 0 missing key)
- settings/CompressionSettingsTab (8 → 0 missing key)
- settings/MemorySkillsTab (8 → 0 missing key)
- settings/ResilienceTab (4 more keys recovered)

Hardcoded total: 164 → 140. Real missing keys: 0 (6 remaining are the
exampleTemplates.tsx false positives — t passed as parameter).

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 5)

Round-5 agent began processing the remaining smaller dashboard files.
Added 5 more keys to en.json for providers/[id]/page.tsx OAuth flow
labels and the cross-OS auto-detection hint.

Pages affected:
- providers/[id]/page.tsx (5 keys)

Hardcoded total: 140 → 136. Real missing keys: 0.

* chore(i18n): resolve last 2 missing providers/[id] keys

Adds providerDetailMyClaudeAccountPlaceholder and
providerDetailPathAutoDetected — the final user-visible labels in the
providers/[id] page that the round-5 subagent rewrote to t() calls
without yet adding to en.json.

Real missing keys: 0 (6 remaining are exampleTemplates.tsx false
positives — t is passed as a parameter so the audit cannot resolve the
namespace; keys do exist at translator.templatePayloads.*).

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 6 — 10 parallel agents)

Round-6 dispatched 10 parallel subagents covering all 57 remaining
dashboard files. Each agent worked on a disjoint file set to avoid
en.json race conditions. Added ~60 new i18n keys across 9 namespaces
covering small UI labels, table headers, search placeholders, and
empty-state messages.

Major changes:
- analytics: SearchAnalyticsTab, ProviderUtilizationTab, DiversityScoreCard, CompressionAnalyticsTab (new useTranslations + keys)
- batch: BatchDetailModal, BatchListTab, FileDetailModal, FilesListTab (new useTranslations + keys)
- settings: CliproxyapiSettingsTab, PayloadRulesTab, ModelCooldownsCard, AppearanceTab, PricingTab (mostly new useTranslations)
- endpoint: TokenSaverCard, ApiEndpointsTab, EndpointPageClient
- cache: CachePerformance, IdempotencyLayer, ReasoningCacheTab, MediaPageClient, page
- combos: IntelligentComboPanel, page
- playground: ChatPlayground, SearchPlayground
- providers: ProviderCard
- onboarding: TierFlowDiagram
- changelog: ChangelogViewer
- home: ProviderTopology, TierCoverageWidget, BootstrapBanner, BadgeToast
- usage: BudgetTab, BudgetTelemetryCards, QuotaTable
- quotaShare: QuotaSharePageClient
- profile: page
- leaderboard: page
- skills: page

Hardcoded total: 131 → 60. Real missing keys: 0 plus 1 false-positive
for combos.modePack (lookup via prop-passed t).

* chore(i18n): finalize round-6 keys for batch/cache/endpoint/usage

Adds the remaining keys produced by parallel agents A4, A6, A8, A9:
- common: batch-related labels (BatchDetailModal, BatchListTab,
  FileDetailModal, FilesListTab, page) + profile/leaderboard
- cache: hit rate, latency, retry, avg chars
- endpoint: token saver, API endpoints, copy URL, cloud/local labels
- usage: noSpend, activeSessions, quotaAlerts, budget timing
- skills: install/marketplace/filter
- proxyRegistry/quotaShare/mcpDashboard: misc labels

Hardcoded total: 60 → 48. Real missing keys: 0 (modePack remaining is a
false positive — combos.modePack exists but the audit can't resolve it
since IntelligentComboPanel receives t as a prop).

* fix(playground): dedupe filteredModels to avoid duplicate React key warning

The /v1/models endpoint can return the same model id twice (e.g., when a
model is listed by both an alias and its canonical provider), which made
the <Select> emit two <option> elements with the same key — triggering
"Encountered two children with the same key, codex/gpt-5.5".

Replace the chained filter + map with a single pass that skips ids
already added.

* fix(playground): guard against non-string model ids before .split/.startsWith

The /v1/models endpoint can include synthetic entries (combos, locals,
in-progress imports) with a null/undefined id. The playground used to
call m.id.split("/") in the provider-discovery loop, which threw on the
first non-string entry; the surrounding .catch(() => {}) silently
swallowed the error, so the provider/model/account dropdowns ended up
empty even though /v1/models returned thousands of valid entries.

- Skip entries without a string id before split/startsWith.
- Log the rejection in the .catch handler so future regressions are
  visible in DevTools instead of silently emptying the UI.

* fix(playground): guard ChatPlayground filteredModels for non-string ids

Same root cause as commit 49fe356: ChatPlayground filtered models
with m.id.startsWith(...) which crashed on null/undefined ids returned
by /v1/models (synthetic combo entries). Apply the same defensive guard
and dedupe used in the parent page.

* fix(claude): drop orphan tool_result after fixToolAdjacency strip (discussion cheaper-inference#2410)

Discussion cheaper-inference#2410 reports Claude returning 400 for sequences like:
  assistant: tool_use(id=X)
  user: <plain text>           ← breaks adjacency
  user: tool_result(id=X)

The previous round added `fixToolAdjacency` (commit 51b2327) which
correctly strips the orphan tool_use from the assistant message. But
that left the now-unmatched tool_result intact, so the upstream
rejected the request with:

  messages.N.content.M: unexpected `tool_use_id` found in `tool_result`
  blocks: X. Each tool_result block must have a corresponding tool_use
  block in the previous message.

Fix: after running `fixToolAdjacency`, re-run `fixToolPairs` to drop
the orphaned tool_result blocks. All three call sites updated:
  - contextManager.purifyHistory (both inside the binary-search loop
    and the final pass)
  - BaseExecutor message-prep (Claude path)
  - claudeCodeCompatible request signer

Also tightens an unrelated dynamic-key access in
readNestedString (claudeCodeCompatible) to satisfy the prototype-
pollution scanner triggered by the post-tool semgrep hook.

* fix(mitm): point runtime manager re-export to js entrypoint

Use the emitted `.js` path for the runtime manager re-export so dynamic
runtime loading resolves correctly outside the Turbopack alias handling.

* docs: add AgentRouter setup guide (cheaper-inference#2422)

Integrated into release/v3.8.0 — AgentRouter setup guide docs.

* feat: add new feature on combos - falloverBeforeRetry (cheaper-inference#2417)

Integrated into release/v3.8.0 — falloverBeforeRetry for per-model quota skipping in combos.

* feat(batch): implement 10 feature requests harvested  (cheaper-inference#2414)

Integrated into release/v3.8.0 — batch of 10 feature requests: llama.cpp local provider, upstream error exposure, Termux detection, providers rotate CLI, t3.chat web skeleton, Zed Docker integration, Kiro multi-account OAuth isolation, auto-combo cost blending, auto-combo context filter, combo provider-level exhaustion tracking (cheaper-inference#1731). Conflicts with cheaper-inference#2417 (falloverBeforeRetry) resolved.

* fix(gamification): resolve SQL bug, auth gap, pagination, and anomaly scoring (cheaper-inference#2421)

Integrated into release/v3.8.0 — 6 critical gamification bug fixes: SQL SELECT in checkActionCountBadges, federation auth enforcement, leaderboard pagination offset, real z-score computation, addXp level calculation, and barrel index.ts

* docs(changelog): add post-release entries for cheaper-inference#2414 cheaper-inference#2417 cheaper-inference#2421 cheaper-inference#2422

- feat(batch): T3-Chat-Web executor, exhaustedProviders set (cheaper-inference#1731), Zed Docker
- feat(combos): falloverBeforeRetry + setTry loop (cheaper-inference#2417 — @hartmark)
- fix(gamification): SQL SELECT bug, federation auth, pagination, z-score (cheaper-inference#2421 — @oyi77)
- docs: AgentRouter setup guide (cheaper-inference#2422 — @leninejunior)

* fix(security): resolve CodeQL random/password-hash alerts and sync docs & tests

---------

Co-authored-by: diegosouzapw <diego.souza.pw@gmail.com>
Co-authored-by: Lenine Júnior <lenine@engrene.com.br>
Co-authored-by: Markus Hartung <mail@hartmark.se>
Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com>
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
* fix(cli-tools): guard modelId type before calling indexOf

E2E shakedown v3.8.0: cli-tools quebrava com TypeError quando dynamicModels
continha entradas sem .id (objeto retornado diretamente em vez de string).

* fix(offline): avoid SSR/CSR hydration mismatch on navigator.onLine

Replace useState+lazy-initializer with useSyncExternalStore so the server
snapshot (() => false) and client snapshot (() => navigator.onLine) are
declared separately. React hydrates with the server value and switches to
the real online status client-side without a mismatch.

* chore(i18n): add missing en.json keys for translator, cli-tools, memory, onboarding

Adds 58 missing keys identified by the new dashboard audit script:
- cliTools: 18 custom CLI builder keys (CustomCliCard)
- translator: 24 keys covering stream transformer, live monitor, test bench
- memory: 12 health/pagination/dialog keys
- onboarding.tier: 8 keys for the tier tour walkthrough

Also adds scripts/i18n/audit-dashboard-pages.mjs which scans all dashboard
pages, reports t() calls referencing missing en.json keys, and flags
candidate hardcoded JSX/attribute strings.

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 1)

Subagents refactored 8 high-impact dashboard pages, replacing 81 of the
407 hardcoded English/PT strings flagged by the audit with proper
useTranslations() lookups. Added 73 corresponding keys to en.json across
the home, apiManager, providers, settings, and usage namespaces.

Pages affected:
- BudgetTab (27 → 0)
- HomePageClient (2 → 0)
- RoutingTab (25 → 7)
- ResilienceTab (38 → 18)
- SystemStorageTab (42 → 21)
- providers/[id] (17 → 15)
- ApiManagerPageClient (14 → 13)
- OneproxyTab (13 → 10)

Also adds two helper scripts:
- scripts/i18n/extract-keys-from-diff.mjs — extracts new keys from git diff
- scripts/i18n/merge-keys.mjs — merges a pending-keys JSON into en.json

Remaining hardcoded strings will be addressed in follow-up rounds.

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 2)

Continues round 1 (commit 8d34f4c). Round-2 subagents refactored
additional dashboard pages, replacing 77 more hardcoded strings with
useTranslations() lookups. Added 79 corresponding keys to en.json
across the a2aDashboard, agents, analytics, apiManager, cliTools,
common, and settings namespaces.

Pages affected:
- a2a/page (new useTranslations + 6 keys)
- agent-skills/page (new useTranslations + 9 keys)
- AutoRoutingAnalyticsTab (new useTranslations + 6 keys)
- AppearanceTab (8 → 6 remaining)
- OneproxyTab (10 → 0)
- ResilienceTab (18 → 0 missing key)
- RoutingTab (7 → 0 missing key)
- VisionBridgeSettingsTab (new useTranslations + 6 keys)
- CopilotToolCard (7 → 0 missing key)
- ApiManagerPageClient (13 → 0 missing key)
- gamification/admin (new useTranslations + 7 keys)

Hardcoded total: 326 → 249. Real missing keys: 0 (the 6 still flagged
are false positives in exampleTemplates.tsx where t is passed as a
parameter — keys exist at translator.templatePayloads.*).

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 3)

Round-3 subagents and manual edits refactored 9 more dashboard pages
(plus 2 small extras), replacing ~80 hardcoded strings with
useTranslations() lookups. Added 79 corresponding keys to en.json
across analytics, cloudAgents, combos, common, health, settings, and
usage namespaces.

Pages affected:
- analytics/ComboHealthTab (new useTranslations + 15 keys)
- analytics/CompressionAnalyticsTab (new useTranslations + 11 keys)
- settings/SystemStorageTab (21 → 0 missing key)
- tokens/page (new useTranslations + 13 keys)
- usage/BudgetTab (9 missing fixed)
- health/page (manual: 6 keys)
- cloud-agents/page (manual: 3 keys)
- combos/page (manual: 1 key)

Hardcoded total: 249 → 164. Real missing keys: 0 (6 remaining are
exampleTemplates.tsx false positives).

Also adds scripts/i18n/build-pending-from-missing.mjs which reads
_audit.json and locates English values from HEAD to rebuild
_pending-keys.json after race-condition resets between subagent edits.

* chore(i18n): localize remaining dashboard settings labels

Replace hardcoded labels in compression and resilience settings with
translation lookups to continue the dashboard i18n cleanup.

Add the v3.8.0 dashboard shakedown runbook to document the manual
smoke-test process and known dev environment pitfalls.

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 4)

Round-4 subagent + manual key-resolution refactored remaining strings in
3 high-traffic settings/API tabs, plus extracted English values for
keys that were already added as t() calls but lost during the previous
en.json race-condition resets.

Pages affected:
- api-manager/ApiManagerPageClient (7 → 0 missing key)
- settings/CompressionSettingsTab (8 → 0 missing key)
- settings/MemorySkillsTab (8 → 0 missing key)
- settings/ResilienceTab (4 more keys recovered)

Hardcoded total: 164 → 140. Real missing keys: 0 (6 remaining are the
exampleTemplates.tsx false positives — t passed as parameter).

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 5)

Round-5 agent began processing the remaining smaller dashboard files.
Added 5 more keys to en.json for providers/[id]/page.tsx OAuth flow
labels and the cross-OS auto-detection hint.

Pages affected:
- providers/[id]/page.tsx (5 keys)

Hardcoded total: 140 → 136. Real missing keys: 0.

* chore(i18n): resolve last 2 missing providers/[id] keys

Adds providerDetailMyClaudeAccountPlaceholder and
providerDetailPathAutoDetected — the final user-visible labels in the
providers/[id] page that the round-5 subagent rewrote to t() calls
without yet adding to en.json.

Real missing keys: 0 (6 remaining are exampleTemplates.tsx false
positives — t is passed as a parameter so the audit cannot resolve the
namespace; keys do exist at translator.templatePayloads.*).

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 6 — 10 parallel agents)

Round-6 dispatched 10 parallel subagents covering all 57 remaining
dashboard files. Each agent worked on a disjoint file set to avoid
en.json race conditions. Added ~60 new i18n keys across 9 namespaces
covering small UI labels, table headers, search placeholders, and
empty-state messages.

Major changes:
- analytics: SearchAnalyticsTab, ProviderUtilizationTab, DiversityScoreCard, CompressionAnalyticsTab (new useTranslations + keys)
- batch: BatchDetailModal, BatchListTab, FileDetailModal, FilesListTab (new useTranslations + keys)
- settings: CliproxyapiSettingsTab, PayloadRulesTab, ModelCooldownsCard, AppearanceTab, PricingTab (mostly new useTranslations)
- endpoint: TokenSaverCard, ApiEndpointsTab, EndpointPageClient
- cache: CachePerformance, IdempotencyLayer, ReasoningCacheTab, MediaPageClient, page
- combos: IntelligentComboPanel, page
- playground: ChatPlayground, SearchPlayground
- providers: ProviderCard
- onboarding: TierFlowDiagram
- changelog: ChangelogViewer
- home: ProviderTopology, TierCoverageWidget, BootstrapBanner, BadgeToast
- usage: BudgetTab, BudgetTelemetryCards, QuotaTable
- quotaShare: QuotaSharePageClient
- profile: page
- leaderboard: page
- skills: page

Hardcoded total: 131 → 60. Real missing keys: 0 plus 1 false-positive
for combos.modePack (lookup via prop-passed t).

* chore(i18n): finalize round-6 keys for batch/cache/endpoint/usage

Adds the remaining keys produced by parallel agents A4, A6, A8, A9:
- common: batch-related labels (BatchDetailModal, BatchListTab,
  FileDetailModal, FilesListTab, page) + profile/leaderboard
- cache: hit rate, latency, retry, avg chars
- endpoint: token saver, API endpoints, copy URL, cloud/local labels
- usage: noSpend, activeSessions, quotaAlerts, budget timing
- skills: install/marketplace/filter
- proxyRegistry/quotaShare/mcpDashboard: misc labels

Hardcoded total: 60 → 48. Real missing keys: 0 (modePack remaining is a
false positive — combos.modePack exists but the audit can't resolve it
since IntelligentComboPanel receives t as a prop).

* fix(playground): dedupe filteredModels to avoid duplicate React key warning

The /v1/models endpoint can return the same model id twice (e.g., when a
model is listed by both an alias and its canonical provider), which made
the <Select> emit two <option> elements with the same key — triggering
"Encountered two children with the same key, codex/gpt-5.5".

Replace the chained filter + map with a single pass that skips ids
already added.

* fix(playground): guard against non-string model ids before .split/.startsWith

The /v1/models endpoint can include synthetic entries (combos, locals,
in-progress imports) with a null/undefined id. The playground used to
call m.id.split("/") in the provider-discovery loop, which threw on the
first non-string entry; the surrounding .catch(() => {}) silently
swallowed the error, so the provider/model/account dropdowns ended up
empty even though /v1/models returned thousands of valid entries.

- Skip entries without a string id before split/startsWith.
- Log the rejection in the .catch handler so future regressions are
  visible in DevTools instead of silently emptying the UI.

* fix(playground): guard ChatPlayground filteredModels for non-string ids

Same root cause as commit 49fe356: ChatPlayground filtered models
with m.id.startsWith(...) which crashed on null/undefined ids returned
by /v1/models (synthetic combo entries). Apply the same defensive guard
and dedupe used in the parent page.

* fix(claude): drop orphan tool_result after fixToolAdjacency strip (discussion cheaper-inference#2410)

Discussion cheaper-inference#2410 reports Claude returning 400 for sequences like:
  assistant: tool_use(id=X)
  user: <plain text>           ← breaks adjacency
  user: tool_result(id=X)

The previous round added `fixToolAdjacency` (commit 51b2327) which
correctly strips the orphan tool_use from the assistant message. But
that left the now-unmatched tool_result intact, so the upstream
rejected the request with:

  messages.N.content.M: unexpected `tool_use_id` found in `tool_result`
  blocks: X. Each tool_result block must have a corresponding tool_use
  block in the previous message.

Fix: after running `fixToolAdjacency`, re-run `fixToolPairs` to drop
the orphaned tool_result blocks. All three call sites updated:
  - contextManager.purifyHistory (both inside the binary-search loop
    and the final pass)
  - BaseExecutor message-prep (Claude path)
  - claudeCodeCompatible request signer

Also tightens an unrelated dynamic-key access in
readNestedString (claudeCodeCompatible) to satisfy the prototype-
pollution scanner triggered by the post-tool semgrep hook.

* fix(mitm): point runtime manager re-export to js entrypoint

Use the emitted `.js` path for the runtime manager re-export so dynamic
runtime loading resolves correctly outside the Turbopack alias handling.

* docs: add AgentRouter setup guide (cheaper-inference#2422)

Integrated into release/v3.8.0 — AgentRouter setup guide docs.

* feat: add new feature on combos - falloverBeforeRetry (cheaper-inference#2417)

Integrated into release/v3.8.0 — falloverBeforeRetry for per-model quota skipping in combos.

* feat(batch): implement 10 feature requests harvested  (cheaper-inference#2414)

Integrated into release/v3.8.0 — batch of 10 feature requests: llama.cpp local provider, upstream error exposure, Termux detection, providers rotate CLI, t3.chat web skeleton, Zed Docker integration, Kiro multi-account OAuth isolation, auto-combo cost blending, auto-combo context filter, combo provider-level exhaustion tracking (cheaper-inference#1731). Conflicts with cheaper-inference#2417 (falloverBeforeRetry) resolved.

* fix(gamification): resolve SQL bug, auth gap, pagination, and anomaly scoring (cheaper-inference#2421)

Integrated into release/v3.8.0 — 6 critical gamification bug fixes: SQL SELECT in checkActionCountBadges, federation auth enforcement, leaderboard pagination offset, real z-score computation, addXp level calculation, and barrel index.ts

* docs(changelog): add post-release entries for cheaper-inference#2414 cheaper-inference#2417 cheaper-inference#2421 cheaper-inference#2422

- feat(batch): T3-Chat-Web executor, exhaustedProviders set (cheaper-inference#1731), Zed Docker
- feat(combos): falloverBeforeRetry + setTry loop (cheaper-inference#2417 — @hartmark)
- fix(gamification): SQL SELECT bug, federation auth, pagination, z-score (cheaper-inference#2421 — @oyi77)
- docs: AgentRouter setup guide (cheaper-inference#2422 — @leninejunior)

* fix(security): resolve CodeQL random/password-hash alerts and sync docs & tests

* feat/fix: integrate PRs cheaper-inference#2423, cheaper-inference#2425, cheaper-inference#2427, cheaper-inference#2428 with test & security fixes

* docs(changelog): credit contributors for PRs cheaper-inference#2423, cheaper-inference#2425, cheaper-inference#2427, cheaper-inference#2428

* fix(mitm): drop .js extension on manager.runtime re-export for webpack build (cheaper-inference#2425)

Merged into release/v3.8.0

* fix: persist STORAGE_ENCRYPTION_KEY across upgrades (closes cheaper-inference#1622) (cheaper-inference#2428)

Merged into release/v3.8.0

* fix: auto-reset apiKeyHealth on successful connection test (cheaper-inference#2427)

Merged into release/v3.8.0

* fix: support Antigravity image generation, Add Gemini 3.5 Flash (cheaper-inference#2423)

Merged into release/v3.8.0

---------

Co-authored-by: diegosouzapw <diego.souza.pw@gmail.com>
Co-authored-by: Lenine Júnior <lenine@engrene.com.br>
Co-authored-by: Markus Hartung <mail@hartmark.se>
Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com>
Co-authored-by: Anton <39598727+NomenAK@users.noreply.github.com>
Co-authored-by: Chewji <126886556+Chewji9875@users.noreply.github.com>
Co-authored-by: clousky2020 <33016567+clousky2020@users.noreply.github.com>
Co-authored-by: backryun <bakryun0718@proton.me>
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
…nce#2417)

Integrated into release/v3.8.0 — falloverBeforeRetry for per-model quota skipping in combos.
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
…ce#2414)

Integrated into release/v3.8.0 — batch of 10 feature requests: llama.cpp local provider, upstream error exposure, Termux detection, providers rotate CLI, t3.chat web skeleton, Zed Docker integration, Kiro multi-account OAuth isolation, auto-combo cost blending, auto-combo context filter, combo provider-level exhaustion tracking (cheaper-inference#1731). Conflicts with cheaper-inference#2417 (falloverBeforeRetry) resolved.
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
…heaper-inference#2417 cheaper-inference#2421 cheaper-inference#2422

- feat(batch): T3-Chat-Web executor, exhaustedProviders set (cheaper-inference#1731), Zed Docker
- feat(combos): falloverBeforeRetry + setTry loop (cheaper-inference#2417 — @hartmark)
- fix(gamification): SQL SELECT bug, federation auth, pagination, z-score (cheaper-inference#2421 — @oyi77)
- docs: AgentRouter setup guide (cheaper-inference#2422 — @leninejunior)
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
…cheaper-inference#6373)

add DigitalOcean AI OpenAI-compatible provider (port PR cheaper-inference#2417). Resolved registry conflict with hcnsec cheaper-inference#6410; count-guard 169→170. Tests green. Integrated into release/v3.8.46.
Poid-ZA pushed a commit to Poid-ZA/OmniRoute that referenced this pull request Aug 5, 2026
…2419)

* fix(cli-tools): guard modelId type before calling indexOf

E2E shakedown v3.8.0: cli-tools quebrava com TypeError quando dynamicModels
continha entradas sem .id (objeto retornado diretamente em vez de string).

* fix(offline): avoid SSR/CSR hydration mismatch on navigator.onLine

Replace useState+lazy-initializer with useSyncExternalStore so the server
snapshot (() => false) and client snapshot (() => navigator.onLine) are
declared separately. React hydrates with the server value and switches to
the real online status client-side without a mismatch.

* chore(i18n): add missing en.json keys for translator, cli-tools, memory, onboarding

Adds 58 missing keys identified by the new dashboard audit script:
- cliTools: 18 custom CLI builder keys (CustomCliCard)
- translator: 24 keys covering stream transformer, live monitor, test bench
- memory: 12 health/pagination/dialog keys
- onboarding.tier: 8 keys for the tier tour walkthrough

Also adds scripts/i18n/audit-dashboard-pages.mjs which scans all dashboard
pages, reports t() calls referencing missing en.json keys, and flags
candidate hardcoded JSX/attribute strings.

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 1)

Subagents refactored 8 high-impact dashboard pages, replacing 81 of the
407 hardcoded English/PT strings flagged by the audit with proper
useTranslations() lookups. Added 73 corresponding keys to en.json across
the home, apiManager, providers, settings, and usage namespaces.

Pages affected:
- BudgetTab (27 → 0)
- HomePageClient (2 → 0)
- RoutingTab (25 → 7)
- ResilienceTab (38 → 18)
- SystemStorageTab (42 → 21)
- providers/[id] (17 → 15)
- ApiManagerPageClient (14 → 13)
- OneproxyTab (13 → 10)

Also adds two helper scripts:
- scripts/i18n/extract-keys-from-diff.mjs — extracts new keys from git diff
- scripts/i18n/merge-keys.mjs — merges a pending-keys JSON into en.json

Remaining hardcoded strings will be addressed in follow-up rounds.

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 2)

Continues round 1 (commit 8d34f4c). Round-2 subagents refactored
additional dashboard pages, replacing 77 more hardcoded strings with
useTranslations() lookups. Added 79 corresponding keys to en.json
across the a2aDashboard, agents, analytics, apiManager, cliTools,
common, and settings namespaces.

Pages affected:
- a2a/page (new useTranslations + 6 keys)
- agent-skills/page (new useTranslations + 9 keys)
- AutoRoutingAnalyticsTab (new useTranslations + 6 keys)
- AppearanceTab (8 → 6 remaining)
- OneproxyTab (10 → 0)
- ResilienceTab (18 → 0 missing key)
- RoutingTab (7 → 0 missing key)
- VisionBridgeSettingsTab (new useTranslations + 6 keys)
- CopilotToolCard (7 → 0 missing key)
- ApiManagerPageClient (13 → 0 missing key)
- gamification/admin (new useTranslations + 7 keys)

Hardcoded total: 326 → 249. Real missing keys: 0 (the 6 still flagged
are false positives in exampleTemplates.tsx where t is passed as a
parameter — keys exist at translator.templatePayloads.*).

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 3)

Round-3 subagents and manual edits refactored 9 more dashboard pages
(plus 2 small extras), replacing ~80 hardcoded strings with
useTranslations() lookups. Added 79 corresponding keys to en.json
across analytics, cloudAgents, combos, common, health, settings, and
usage namespaces.

Pages affected:
- analytics/ComboHealthTab (new useTranslations + 15 keys)
- analytics/CompressionAnalyticsTab (new useTranslations + 11 keys)
- settings/SystemStorageTab (21 → 0 missing key)
- tokens/page (new useTranslations + 13 keys)
- usage/BudgetTab (9 missing fixed)
- health/page (manual: 6 keys)
- cloud-agents/page (manual: 3 keys)
- combos/page (manual: 1 key)

Hardcoded total: 249 → 164. Real missing keys: 0 (6 remaining are
exampleTemplates.tsx false positives).

Also adds scripts/i18n/build-pending-from-missing.mjs which reads
_audit.json and locates English values from HEAD to rebuild
_pending-keys.json after race-condition resets between subagent edits.

* chore(i18n): localize remaining dashboard settings labels

Replace hardcoded labels in compression and resilience settings with
translation lookups to continue the dashboard i18n cleanup.

Add the v3.8.0 dashboard shakedown runbook to document the manual
smoke-test process and known dev environment pitfalls.

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 4)

Round-4 subagent + manual key-resolution refactored remaining strings in
3 high-traffic settings/API tabs, plus extracted English values for
keys that were already added as t() calls but lost during the previous
en.json race-condition resets.

Pages affected:
- api-manager/ApiManagerPageClient (7 → 0 missing key)
- settings/CompressionSettingsTab (8 → 0 missing key)
- settings/MemorySkillsTab (8 → 0 missing key)
- settings/ResilienceTab (4 more keys recovered)

Hardcoded total: 164 → 140. Real missing keys: 0 (6 remaining are the
exampleTemplates.tsx false positives — t passed as parameter).

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 5)

Round-5 agent began processing the remaining smaller dashboard files.
Added 5 more keys to en.json for providers/[id]/page.tsx OAuth flow
labels and the cross-OS auto-detection hint.

Pages affected:
- providers/[id]/page.tsx (5 keys)

Hardcoded total: 140 → 136. Real missing keys: 0.

* chore(i18n): resolve last 2 missing providers/[id] keys

Adds providerDetailMyClaudeAccountPlaceholder and
providerDetailPathAutoDetected — the final user-visible labels in the
providers/[id] page that the round-5 subagent rewrote to t() calls
without yet adding to en.json.

Real missing keys: 0 (6 remaining are exampleTemplates.tsx false
positives — t is passed as a parameter so the audit cannot resolve the
namespace; keys do exist at translator.templatePayloads.*).

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 6 — 10 parallel agents)

Round-6 dispatched 10 parallel subagents covering all 57 remaining
dashboard files. Each agent worked on a disjoint file set to avoid
en.json race conditions. Added ~60 new i18n keys across 9 namespaces
covering small UI labels, table headers, search placeholders, and
empty-state messages.

Major changes:
- analytics: SearchAnalyticsTab, ProviderUtilizationTab, DiversityScoreCard, CompressionAnalyticsTab (new useTranslations + keys)
- batch: BatchDetailModal, BatchListTab, FileDetailModal, FilesListTab (new useTranslations + keys)
- settings: CliproxyapiSettingsTab, PayloadRulesTab, ModelCooldownsCard, AppearanceTab, PricingTab (mostly new useTranslations)
- endpoint: TokenSaverCard, ApiEndpointsTab, EndpointPageClient
- cache: CachePerformance, IdempotencyLayer, ReasoningCacheTab, MediaPageClient, page
- combos: IntelligentComboPanel, page
- playground: ChatPlayground, SearchPlayground
- providers: ProviderCard
- onboarding: TierFlowDiagram
- changelog: ChangelogViewer
- home: ProviderTopology, TierCoverageWidget, BootstrapBanner, BadgeToast
- usage: BudgetTab, BudgetTelemetryCards, QuotaTable
- quotaShare: QuotaSharePageClient
- profile: page
- leaderboard: page
- skills: page

Hardcoded total: 131 → 60. Real missing keys: 0 plus 1 false-positive
for combos.modePack (lookup via prop-passed t).

* chore(i18n): finalize round-6 keys for batch/cache/endpoint/usage

Adds the remaining keys produced by parallel agents A4, A6, A8, A9:
- common: batch-related labels (BatchDetailModal, BatchListTab,
  FileDetailModal, FilesListTab, page) + profile/leaderboard
- cache: hit rate, latency, retry, avg chars
- endpoint: token saver, API endpoints, copy URL, cloud/local labels
- usage: noSpend, activeSessions, quotaAlerts, budget timing
- skills: install/marketplace/filter
- proxyRegistry/quotaShare/mcpDashboard: misc labels

Hardcoded total: 60 → 48. Real missing keys: 0 (modePack remaining is a
false positive — combos.modePack exists but the audit can't resolve it
since IntelligentComboPanel receives t as a prop).

* fix(playground): dedupe filteredModels to avoid duplicate React key warning

The /v1/models endpoint can return the same model id twice (e.g., when a
model is listed by both an alias and its canonical provider), which made
the <Select> emit two <option> elements with the same key — triggering
"Encountered two children with the same key, codex/gpt-5.5".

Replace the chained filter + map with a single pass that skips ids
already added.

* fix(playground): guard against non-string model ids before .split/.startsWith

The /v1/models endpoint can include synthetic entries (combos, locals,
in-progress imports) with a null/undefined id. The playground used to
call m.id.split("/") in the provider-discovery loop, which threw on the
first non-string entry; the surrounding .catch(() => {}) silently
swallowed the error, so the provider/model/account dropdowns ended up
empty even though /v1/models returned thousands of valid entries.

- Skip entries without a string id before split/startsWith.
- Log the rejection in the .catch handler so future regressions are
  visible in DevTools instead of silently emptying the UI.

* fix(playground): guard ChatPlayground filteredModels for non-string ids

Same root cause as commit 49fe356: ChatPlayground filtered models
with m.id.startsWith(...) which crashed on null/undefined ids returned
by /v1/models (synthetic combo entries). Apply the same defensive guard
and dedupe used in the parent page.

* fix(claude): drop orphan tool_result after fixToolAdjacency strip (discussion cheaper-inference#2410)

Discussion cheaper-inference#2410 reports Claude returning 400 for sequences like:
  assistant: tool_use(id=X)
  user: <plain text>           ← breaks adjacency
  user: tool_result(id=X)

The previous round added `fixToolAdjacency` (commit 44d9aba) which
correctly strips the orphan tool_use from the assistant message. But
that left the now-unmatched tool_result intact, so the upstream
rejected the request with:

  messages.N.content.M: unexpected `tool_use_id` found in `tool_result`
  blocks: X. Each tool_result block must have a corresponding tool_use
  block in the previous message.

Fix: after running `fixToolAdjacency`, re-run `fixToolPairs` to drop
the orphaned tool_result blocks. All three call sites updated:
  - contextManager.purifyHistory (both inside the binary-search loop
    and the final pass)
  - BaseExecutor message-prep (Claude path)
  - claudeCodeCompatible request signer

Also tightens an unrelated dynamic-key access in
readNestedString (claudeCodeCompatible) to satisfy the prototype-
pollution scanner triggered by the post-tool semgrep hook.

* fix(mitm): point runtime manager re-export to js entrypoint

Use the emitted `.js` path for the runtime manager re-export so dynamic
runtime loading resolves correctly outside the Turbopack alias handling.

* docs: add AgentRouter setup guide (cheaper-inference#2422)

Integrated into release/v3.8.0 — AgentRouter setup guide docs.

* feat: add new feature on combos - falloverBeforeRetry (cheaper-inference#2417)

Integrated into release/v3.8.0 — falloverBeforeRetry for per-model quota skipping in combos.

* feat(batch): implement 10 feature requests harvested  (cheaper-inference#2414)

Integrated into release/v3.8.0 — batch of 10 feature requests: llama.cpp local provider, upstream error exposure, Termux detection, providers rotate CLI, t3.chat web skeleton, Zed Docker integration, Kiro multi-account OAuth isolation, auto-combo cost blending, auto-combo context filter, combo provider-level exhaustion tracking (cheaper-inference#1731). Conflicts with cheaper-inference#2417 (falloverBeforeRetry) resolved.

* fix(gamification): resolve SQL bug, auth gap, pagination, and anomaly scoring (cheaper-inference#2421)

Integrated into release/v3.8.0 — 6 critical gamification bug fixes: SQL SELECT in checkActionCountBadges, federation auth enforcement, leaderboard pagination offset, real z-score computation, addXp level calculation, and barrel index.ts

* docs(changelog): add post-release entries for cheaper-inference#2414 cheaper-inference#2417 cheaper-inference#2421 cheaper-inference#2422

- feat(batch): T3-Chat-Web executor, exhaustedProviders set (cheaper-inference#1731), Zed Docker
- feat(combos): falloverBeforeRetry + setTry loop (cheaper-inference#2417 — @hartmark)
- fix(gamification): SQL SELECT bug, federation auth, pagination, z-score (cheaper-inference#2421 — @oyi77)
- docs: AgentRouter setup guide (cheaper-inference#2422 — @leninejunior)

* fix(security): resolve CodeQL random/password-hash alerts and sync docs & tests

---------

Co-authored-by: diegosouzapw <diego.souza.pw@gmail.com>
Co-authored-by: Lenine Júnior <lenine@engrene.com.br>
Co-authored-by: Markus Hartung <mail@hartmark.se>
Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com>
Poid-ZA pushed a commit to Poid-ZA/OmniRoute that referenced this pull request Aug 5, 2026
* fix(cli-tools): guard modelId type before calling indexOf

E2E shakedown v3.8.0: cli-tools quebrava com TypeError quando dynamicModels
continha entradas sem .id (objeto retornado diretamente em vez de string).

* fix(offline): avoid SSR/CSR hydration mismatch on navigator.onLine

Replace useState+lazy-initializer with useSyncExternalStore so the server
snapshot (() => false) and client snapshot (() => navigator.onLine) are
declared separately. React hydrates with the server value and switches to
the real online status client-side without a mismatch.

* chore(i18n): add missing en.json keys for translator, cli-tools, memory, onboarding

Adds 58 missing keys identified by the new dashboard audit script:
- cliTools: 18 custom CLI builder keys (CustomCliCard)
- translator: 24 keys covering stream transformer, live monitor, test bench
- memory: 12 health/pagination/dialog keys
- onboarding.tier: 8 keys for the tier tour walkthrough

Also adds scripts/i18n/audit-dashboard-pages.mjs which scans all dashboard
pages, reports t() calls referencing missing en.json keys, and flags
candidate hardcoded JSX/attribute strings.

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 1)

Subagents refactored 8 high-impact dashboard pages, replacing 81 of the
407 hardcoded English/PT strings flagged by the audit with proper
useTranslations() lookups. Added 73 corresponding keys to en.json across
the home, apiManager, providers, settings, and usage namespaces.

Pages affected:
- BudgetTab (27 → 0)
- HomePageClient (2 → 0)
- RoutingTab (25 → 7)
- ResilienceTab (38 → 18)
- SystemStorageTab (42 → 21)
- providers/[id] (17 → 15)
- ApiManagerPageClient (14 → 13)
- OneproxyTab (13 → 10)

Also adds two helper scripts:
- scripts/i18n/extract-keys-from-diff.mjs — extracts new keys from git diff
- scripts/i18n/merge-keys.mjs — merges a pending-keys JSON into en.json

Remaining hardcoded strings will be addressed in follow-up rounds.

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 2)

Continues round 1 (commit 8d34f4c). Round-2 subagents refactored
additional dashboard pages, replacing 77 more hardcoded strings with
useTranslations() lookups. Added 79 corresponding keys to en.json
across the a2aDashboard, agents, analytics, apiManager, cliTools,
common, and settings namespaces.

Pages affected:
- a2a/page (new useTranslations + 6 keys)
- agent-skills/page (new useTranslations + 9 keys)
- AutoRoutingAnalyticsTab (new useTranslations + 6 keys)
- AppearanceTab (8 → 6 remaining)
- OneproxyTab (10 → 0)
- ResilienceTab (18 → 0 missing key)
- RoutingTab (7 → 0 missing key)
- VisionBridgeSettingsTab (new useTranslations + 6 keys)
- CopilotToolCard (7 → 0 missing key)
- ApiManagerPageClient (13 → 0 missing key)
- gamification/admin (new useTranslations + 7 keys)

Hardcoded total: 326 → 249. Real missing keys: 0 (the 6 still flagged
are false positives in exampleTemplates.tsx where t is passed as a
parameter — keys exist at translator.templatePayloads.*).

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 3)

Round-3 subagents and manual edits refactored 9 more dashboard pages
(plus 2 small extras), replacing ~80 hardcoded strings with
useTranslations() lookups. Added 79 corresponding keys to en.json
across analytics, cloudAgents, combos, common, health, settings, and
usage namespaces.

Pages affected:
- analytics/ComboHealthTab (new useTranslations + 15 keys)
- analytics/CompressionAnalyticsTab (new useTranslations + 11 keys)
- settings/SystemStorageTab (21 → 0 missing key)
- tokens/page (new useTranslations + 13 keys)
- usage/BudgetTab (9 missing fixed)
- health/page (manual: 6 keys)
- cloud-agents/page (manual: 3 keys)
- combos/page (manual: 1 key)

Hardcoded total: 249 → 164. Real missing keys: 0 (6 remaining are
exampleTemplates.tsx false positives).

Also adds scripts/i18n/build-pending-from-missing.mjs which reads
_audit.json and locates English values from HEAD to rebuild
_pending-keys.json after race-condition resets between subagent edits.

* chore(i18n): localize remaining dashboard settings labels

Replace hardcoded labels in compression and resilience settings with
translation lookups to continue the dashboard i18n cleanup.

Add the v3.8.0 dashboard shakedown runbook to document the manual
smoke-test process and known dev environment pitfalls.

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 4)

Round-4 subagent + manual key-resolution refactored remaining strings in
3 high-traffic settings/API tabs, plus extracted English values for
keys that were already added as t() calls but lost during the previous
en.json race-condition resets.

Pages affected:
- api-manager/ApiManagerPageClient (7 → 0 missing key)
- settings/CompressionSettingsTab (8 → 0 missing key)
- settings/MemorySkillsTab (8 → 0 missing key)
- settings/ResilienceTab (4 more keys recovered)

Hardcoded total: 164 → 140. Real missing keys: 0 (6 remaining are the
exampleTemplates.tsx false positives — t passed as parameter).

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 5)

Round-5 agent began processing the remaining smaller dashboard files.
Added 5 more keys to en.json for providers/[id]/page.tsx OAuth flow
labels and the cross-OS auto-detection hint.

Pages affected:
- providers/[id]/page.tsx (5 keys)

Hardcoded total: 140 → 136. Real missing keys: 0.

* chore(i18n): resolve last 2 missing providers/[id] keys

Adds providerDetailMyClaudeAccountPlaceholder and
providerDetailPathAutoDetected — the final user-visible labels in the
providers/[id] page that the round-5 subagent rewrote to t() calls
without yet adding to en.json.

Real missing keys: 0 (6 remaining are exampleTemplates.tsx false
positives — t is passed as a parameter so the audit cannot resolve the
namespace; keys do exist at translator.templatePayloads.*).

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 6 — 10 parallel agents)

Round-6 dispatched 10 parallel subagents covering all 57 remaining
dashboard files. Each agent worked on a disjoint file set to avoid
en.json race conditions. Added ~60 new i18n keys across 9 namespaces
covering small UI labels, table headers, search placeholders, and
empty-state messages.

Major changes:
- analytics: SearchAnalyticsTab, ProviderUtilizationTab, DiversityScoreCard, CompressionAnalyticsTab (new useTranslations + keys)
- batch: BatchDetailModal, BatchListTab, FileDetailModal, FilesListTab (new useTranslations + keys)
- settings: CliproxyapiSettingsTab, PayloadRulesTab, ModelCooldownsCard, AppearanceTab, PricingTab (mostly new useTranslations)
- endpoint: TokenSaverCard, ApiEndpointsTab, EndpointPageClient
- cache: CachePerformance, IdempotencyLayer, ReasoningCacheTab, MediaPageClient, page
- combos: IntelligentComboPanel, page
- playground: ChatPlayground, SearchPlayground
- providers: ProviderCard
- onboarding: TierFlowDiagram
- changelog: ChangelogViewer
- home: ProviderTopology, TierCoverageWidget, BootstrapBanner, BadgeToast
- usage: BudgetTab, BudgetTelemetryCards, QuotaTable
- quotaShare: QuotaSharePageClient
- profile: page
- leaderboard: page
- skills: page

Hardcoded total: 131 → 60. Real missing keys: 0 plus 1 false-positive
for combos.modePack (lookup via prop-passed t).

* chore(i18n): finalize round-6 keys for batch/cache/endpoint/usage

Adds the remaining keys produced by parallel agents A4, A6, A8, A9:
- common: batch-related labels (BatchDetailModal, BatchListTab,
  FileDetailModal, FilesListTab, page) + profile/leaderboard
- cache: hit rate, latency, retry, avg chars
- endpoint: token saver, API endpoints, copy URL, cloud/local labels
- usage: noSpend, activeSessions, quotaAlerts, budget timing
- skills: install/marketplace/filter
- proxyRegistry/quotaShare/mcpDashboard: misc labels

Hardcoded total: 60 → 48. Real missing keys: 0 (modePack remaining is a
false positive — combos.modePack exists but the audit can't resolve it
since IntelligentComboPanel receives t as a prop).

* fix(playground): dedupe filteredModels to avoid duplicate React key warning

The /v1/models endpoint can return the same model id twice (e.g., when a
model is listed by both an alias and its canonical provider), which made
the <Select> emit two <option> elements with the same key — triggering
"Encountered two children with the same key, codex/gpt-5.5".

Replace the chained filter + map with a single pass that skips ids
already added.

* fix(playground): guard against non-string model ids before .split/.startsWith

The /v1/models endpoint can include synthetic entries (combos, locals,
in-progress imports) with a null/undefined id. The playground used to
call m.id.split("/") in the provider-discovery loop, which threw on the
first non-string entry; the surrounding .catch(() => {}) silently
swallowed the error, so the provider/model/account dropdowns ended up
empty even though /v1/models returned thousands of valid entries.

- Skip entries without a string id before split/startsWith.
- Log the rejection in the .catch handler so future regressions are
  visible in DevTools instead of silently emptying the UI.

* fix(playground): guard ChatPlayground filteredModels for non-string ids

Same root cause as commit 49fe356: ChatPlayground filtered models
with m.id.startsWith(...) which crashed on null/undefined ids returned
by /v1/models (synthetic combo entries). Apply the same defensive guard
and dedupe used in the parent page.

* fix(claude): drop orphan tool_result after fixToolAdjacency strip (discussion cheaper-inference#2410)

Discussion cheaper-inference#2410 reports Claude returning 400 for sequences like:
  assistant: tool_use(id=X)
  user: <plain text>           ← breaks adjacency
  user: tool_result(id=X)

The previous round added `fixToolAdjacency` (commit 44d9aba) which
correctly strips the orphan tool_use from the assistant message. But
that left the now-unmatched tool_result intact, so the upstream
rejected the request with:

  messages.N.content.M: unexpected `tool_use_id` found in `tool_result`
  blocks: X. Each tool_result block must have a corresponding tool_use
  block in the previous message.

Fix: after running `fixToolAdjacency`, re-run `fixToolPairs` to drop
the orphaned tool_result blocks. All three call sites updated:
  - contextManager.purifyHistory (both inside the binary-search loop
    and the final pass)
  - BaseExecutor message-prep (Claude path)
  - claudeCodeCompatible request signer

Also tightens an unrelated dynamic-key access in
readNestedString (claudeCodeCompatible) to satisfy the prototype-
pollution scanner triggered by the post-tool semgrep hook.

* fix(mitm): point runtime manager re-export to js entrypoint

Use the emitted `.js` path for the runtime manager re-export so dynamic
runtime loading resolves correctly outside the Turbopack alias handling.

* docs: add AgentRouter setup guide (cheaper-inference#2422)

Integrated into release/v3.8.0 — AgentRouter setup guide docs.

* feat: add new feature on combos - falloverBeforeRetry (cheaper-inference#2417)

Integrated into release/v3.8.0 — falloverBeforeRetry for per-model quota skipping in combos.

* feat(batch): implement 10 feature requests harvested  (cheaper-inference#2414)

Integrated into release/v3.8.0 — batch of 10 feature requests: llama.cpp local provider, upstream error exposure, Termux detection, providers rotate CLI, t3.chat web skeleton, Zed Docker integration, Kiro multi-account OAuth isolation, auto-combo cost blending, auto-combo context filter, combo provider-level exhaustion tracking (cheaper-inference#1731). Conflicts with cheaper-inference#2417 (falloverBeforeRetry) resolved.

* fix(gamification): resolve SQL bug, auth gap, pagination, and anomaly scoring (cheaper-inference#2421)

Integrated into release/v3.8.0 — 6 critical gamification bug fixes: SQL SELECT in checkActionCountBadges, federation auth enforcement, leaderboard pagination offset, real z-score computation, addXp level calculation, and barrel index.ts

* docs(changelog): add post-release entries for cheaper-inference#2414 cheaper-inference#2417 cheaper-inference#2421 cheaper-inference#2422

- feat(batch): T3-Chat-Web executor, exhaustedProviders set (cheaper-inference#1731), Zed Docker
- feat(combos): falloverBeforeRetry + setTry loop (cheaper-inference#2417 — @hartmark)
- fix(gamification): SQL SELECT bug, federation auth, pagination, z-score (cheaper-inference#2421 — @oyi77)
- docs: AgentRouter setup guide (cheaper-inference#2422 — @leninejunior)

* fix(security): resolve CodeQL random/password-hash alerts and sync docs & tests

* feat/fix: integrate PRs cheaper-inference#2423, cheaper-inference#2425, cheaper-inference#2427, cheaper-inference#2428 with test & security fixes

* docs(changelog): credit contributors for PRs cheaper-inference#2423, cheaper-inference#2425, cheaper-inference#2427, cheaper-inference#2428

* fix(mitm): drop .js extension on manager.runtime re-export for webpack build (cheaper-inference#2425)

Merged into release/v3.8.0

* fix: persist STORAGE_ENCRYPTION_KEY across upgrades (closes cheaper-inference#1622) (cheaper-inference#2428)

Merged into release/v3.8.0

* fix: auto-reset apiKeyHealth on successful connection test (cheaper-inference#2427)

Merged into release/v3.8.0

* fix: support Antigravity image generation, Add Gemini 3.5 Flash (cheaper-inference#2423)

Merged into release/v3.8.0

---------

Co-authored-by: diegosouzapw <diego.souza.pw@gmail.com>
Co-authored-by: Lenine Júnior <lenine@engrene.com.br>
Co-authored-by: Markus Hartung <mail@hartmark.se>
Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com>
Co-authored-by: Anton <39598727+NomenAK@users.noreply.github.com>
Co-authored-by: Chewji <126886556+Chewji9875@users.noreply.github.com>
Co-authored-by: clousky2020 <33016567+clousky2020@users.noreply.github.com>
Co-authored-by: backryun <bakryun0718@proton.me>
Poid-ZA pushed a commit to Poid-ZA/OmniRoute that referenced this pull request Aug 5, 2026
…nce#2417)

Integrated into release/v3.8.0 — falloverBeforeRetry for per-model quota skipping in combos.
Poid-ZA pushed a commit to Poid-ZA/OmniRoute that referenced this pull request Aug 5, 2026
Poid-ZA pushed a commit to Poid-ZA/OmniRoute that referenced this pull request Aug 5, 2026
…ce#2414)

Integrated into release/v3.8.0 — batch of 10 feature requests: llama.cpp local provider, upstream error exposure, Termux detection, providers rotate CLI, t3.chat web skeleton, Zed Docker integration, Kiro multi-account OAuth isolation, auto-combo cost blending, auto-combo context filter, combo provider-level exhaustion tracking (cheaper-inference#1731). Conflicts with cheaper-inference#2417 (falloverBeforeRetry) resolved.
Poid-ZA pushed a commit to Poid-ZA/OmniRoute that referenced this pull request Aug 5, 2026
…heaper-inference#2417 cheaper-inference#2421 cheaper-inference#2422

- feat(batch): T3-Chat-Web executor, exhaustedProviders set (cheaper-inference#1731), Zed Docker
- feat(combos): falloverBeforeRetry + setTry loop (cheaper-inference#2417 — @hartmark)
- fix(gamification): SQL SELECT bug, federation auth, pagination, z-score (cheaper-inference#2421 — @oyi77)
- docs: AgentRouter setup guide (cheaper-inference#2422 — @leninejunior)
diegosouzapw added a commit that referenced this pull request Aug 13, 2026
…ry (#10217)

* fix(deps): bump nanoid, dompurify for 2 new Dependabot alerts (#189, #190)

Bumps: nanoid ^3.3.17 (was transitive, now overridden), dompurify ^3.4.13
(with monaco-editor scoped override). Closes Dependabot #189, #190.

Remaining #182-#188 (js-yaml + mermaid) already closed by #9651 merge —
awaiting Dependabot re-scan.

npm audit → 0 vulnerabilities.

* fix(repo): harden .gitignore to also ignore a _tasks symlink (/_tasks)

_tasks is a SEPARATE nested git repo (gitignored). The pattern _tasks/ (trailing
slash) ignores only a directory, not a SYMLINK named _tasks. A self-referential
_tasks symlink can slip in via git add -A and, once pulled, checkout materializes
it over the real _tasks repo (destroying plans/specs/hands-off). Anchored /_tasks
ignores the symlink too, preventing re-capture.

* fix(combo): make failoverBeforeRetry actually skip the same-model retry

Both same-target retry loops (priority/auto and round-robin) checked
isTransient/maxRetries/providerExhausted but never consulted
config.failoverBeforeRetry, so a rate-limited model still got
maxRetries+1 back-to-back attempts on itself before falling back to a
sibling — the config option (#2417) was only ever wired into
skipUpstreamRetry, a separate lower-level mechanism. Now the same-model
retry is skipped when failoverBeforeRetry is set AND a sibling target
is actually available; with no sibling left, it still retries same-model
since skipping would just burn the last attempt for nothing.

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@outlook.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
diegosouzapw added a commit that referenced this pull request Aug 13, 2026
…ipUpstreamRetry default

Audit found that DEFAULT_COMBO_CONFIG.failoverBeforeRetry has defaulted to
true since before #10217 (predates #2417), and that value also feeds the
independent skipUpstreamRetry mechanism (src/sse/handlers/chat.ts:859,1126).
The previous commit on this branch flipped that default to false to fix the
#10217 same-model retry guard, which silently disabled skipUpstreamRetry's
own default-on behavior for every combo without an opt-in — a regression in
the opposite direction (executor-level retries before the loop's own
failover, changing latency/failure behavior).

Revert the default back to true and decouple the two mechanisms instead:
resolveComboConfig/resolveComboSetupConfig now also compute
failoverBeforeRetryExplicit, true only when a cascade layer (combo/provider/
global) literally sets failoverBeforeRetry to true — not merely inherited
from the default. The #10217 same-model retry guards in combo.ts (priority/
auto and round-robin loops) now read failoverBeforeRetryExplicit instead of
config.failoverBeforeRetry, restoring opt-in-only behavior for that guard
while the skipUpstreamRetry pass-through (config.failoverBeforeRetry at
combo.ts:1297,2865) is untouched and keeps its historical default-on.
diegosouzapw added a commit that referenced this pull request Aug 14, 2026
* fix(ci): clear base-reds on release/v3.8.50 (round 4)

Drains the HARD failures reported by Release-Green run 31693210948 on issue #9985
(ESLint errors: 2) plus the merge-integrity red every open PR is inheriting.

- ESLint error 1: @omniroute/opencode-plugin/src/index.ts had a stray extra
  '});' (introduced by #9316) that broke parsing with 'unexpected file in NFT list'
  on the build path.
- ESLint error 2: cli-env-inline-comment-10100.test.ts used new Function to extract
  parseEnvValue from the bin entrypoint (no-new-func, Hard Rule #3). Extracted the
  helper to bin/cli/utils/parseEnvValue.mjs and import it from both the entrypoint
  and the test (same behavior, no eval).
- open-sse-typecheck (Fast Quality Gates): open-sse/utils/stream.ts imported
  sseCommentsEnabled twice (#9378) causing TS2300 Duplicate identifier; removed the
  duplicate import.
- Merge integrity (changelog + generated skills): skills/omni-settings/SKILL.md was
  edited manually by #10169 without updating the generator source, so
  check:agent-skills-sync failed on every PR (Generated: 1). Moved the curated
  thinking-budget content into a <!-- skill:custom-start --> block (the documented
  preservation mechanism), which the generator now keeps in sync.

Refs #9985

* fix(tests): align wave1-a poolside test with #10216 probed catalog

#10216 published Poolside's two authenticated-probe models
(poolside/laguna-xs-2.1, poolside/laguna-s-2.1) as static seeds, but the
wave1-a free-tier test still asserted 'no invented static model ids'
(entry.models === []), failing every open PR. Separate poolside from the
empty-models assertion and pin its probed catalog explicitly so a future
catalog change is a deliberate update, not a silent drift.

* fix(pack): register parseEnvValue.mjs in PACK_ARTIFACT_REQUIRED_PATHS

The extract of parseEnvValue to bin/cli/utils/parseEnvValue.mjs added a new
direct import to bin/omniroute.mjs, which pack-artifact-entrypoint-closures
enforces against PACK_ARTIFACT_REQUIRED_PATHS. Register the module so a future
tarball omission fails loudly.

* fix(combo): restore default same-model retry semantics after #10217

#10217 wired config.failoverBeforeRetry into the same-model retry guard in
both the priority/auto and round-robin loops, but DEFAULT_COMBO_CONFIG
defaulted the flag to true — flipping same-model retry off for every combo
that never touched the setting, not just the opt-in case. Round-4 bisect
(06f41cd vs d2fd88d) reproduced this against
tests/unit/combo-499-abort.test.ts, tests/unit/combo-quota-exhaustion-only-fallback.test.ts
and tests/unit/combo-stream-readiness-fallback.test.ts. Flip the default to
false so the historical retry-before-failover behavior returns for combos
that never set the flag, while explicit opt-in (the two new tests #10217
added to combo-routing-engine.test.ts) still works.

* fix(quality): register visionBridge-responses-9597 in stryker tap.testFiles

check-mutation-test-coverage.mjs flagged tests/unit/guardrails/visionBridge-responses-9597.test.ts
as covering open-sse/services/combo/comboStructure.ts without being listed
in stryker.conf.json's tap.testFiles array. Add it so mutation coverage
attribution stays accurate.

* test(pack): expect parseEnvValue.mjs in the missing-artifact-paths fixture

The prior commit on this branch registered bin/cli/utils/parseEnvValue.mjs
in PACK_ARTIFACT_REQUIRED_PATHS but the "findMissingArtifactPaths flags
missing root runtime files in the tarball" test still hardcoded the old
expected list, so it never accounted for the new required path being
absent from the simulated tarball. Add it in its alphabetical slot.

* chore(lint): prune stale no-explicit-any suppression for call-log-file-rotation

--prune-suppressions found tests/unit/call-log-file-rotation.test.ts no
longer produces the 5 suppressed @typescript-eslint/no-explicit-any
warnings recorded in config/quality/eslint-suppressions.json. Remove the
dead entry so a regression would be caught again. Full-tree run with
--max-warnings 0 is clean: 0 errors, 0 warnings.

* fix(combo): decouple failoverBeforeRetry same-model guard from the skipUpstreamRetry default

Audit found that DEFAULT_COMBO_CONFIG.failoverBeforeRetry has defaulted to
true since before #10217 (predates #2417), and that value also feeds the
independent skipUpstreamRetry mechanism (src/sse/handlers/chat.ts:859,1126).
The previous commit on this branch flipped that default to false to fix the
#10217 same-model retry guard, which silently disabled skipUpstreamRetry's
own default-on behavior for every combo without an opt-in — a regression in
the opposite direction (executor-level retries before the loop's own
failover, changing latency/failure behavior).

Revert the default back to true and decouple the two mechanisms instead:
resolveComboConfig/resolveComboSetupConfig now also compute
failoverBeforeRetryExplicit, true only when a cascade layer (combo/provider/
global) literally sets failoverBeforeRetry to true — not merely inherited
from the default. The #10217 same-model retry guards in combo.ts (priority/
auto and round-robin loops) now read failoverBeforeRetryExplicit instead of
config.failoverBeforeRetry, restoring opt-in-only behavior for that guard
while the skipUpstreamRetry pass-through (config.failoverBeforeRetry at
combo.ts:1297,2865) is untouched and keeps its historical default-on.

* fix(combo,i18n): align getDefaultComboConfig with 10217 explicit flag; pt denoRelay entities

Two round-4 follow-ups exposed by the combinated base-red PR run:

1. comboConfig.ts: #10217 round-4 fix (104afed) added
   failoverBeforeRetryExplicit to resolveComboConfig/resolveComboSetupConfig
   but getDefaultComboConfig() returned only DEFAULT_COMBO_CONFIG, so the
   combo-config.test.ts deepEqual (resolveComboConfig(null) ===
   getDefaultComboConfig()) failed on the extra field. Mirror the opt-in flag
   as false in the default.

2. pt.json: denoRelayOrgDomainHint still carried raw <app-name>/<org-slug>
   (the UNCLOSED_TAG RSC regression) — encode as &lt;...&gt; like the other
   42 locales, greening i18n-deno-relay-unclosed-tag.test.ts.

* chore(lint): disable @next/next/no-location-assign-relative-destination pending per-case review (#10292)

The eslint-config-next bump in #10043 shipped this new rule, flagging 6
pre-existing window.location.href navigations — several are deliberate
full-page reloads (login/logout state reset). Off with tracking issue
rather than a blanket router.push rewrite.

* fix(i18n): fill 439 missing UI keys (thinkingMode ×39 locales + pt catch-up) to restore 100% coverage

The #10169 Thinking Budget keys existed only in en/pt-BR/vi and the pt (PT-PT)
catalog from #10250 lagged 88 recent keys, dropping i18nUiCoverage to 99.3%
vs the frozen 100% ratchet baseline. Translated via the i18n:sync-ui marker
pipeline; glossary + ICU placeholder post-pass clean.

* fix(i18n): zh-TW glossary — replace retired 默認 with canonical 預設 in new thinkingMode keys

* chore(quality): rebase dead-code baseline 248 -> 409 for knip 6.32 bump (#10043)

dependabot #10043 upgraded knip 6.27 -> 6.32, which detects 162 MORE
genuinely-unused exports (331 vs 169) that 6.27 missed; DEAD_FILES
unchanged (78). Reproduced identically on the clean release/v3.8.50 tip
266e39d with a fresh 6.32 node_modules, so every PR is born red until
the tool change is absorbed. Owner authorized rebaseline (2026-08-13 via
PR #10260). Structural cleanup of the newly-surfaced dead exports remains
separate debt.

---------

Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
Co-authored-by: backryun <bakryun0718@proton.me>
fenix007 pushed a commit to fenix007/OmniRoute that referenced this pull request Aug 20, 2026
…ry (cheaper-inference#10217)

* fix(deps): bump nanoid, dompurify for 2 new Dependabot alerts (cheaper-inference#189, cheaper-inference#190)

Bumps: nanoid ^3.3.17 (was transitive, now overridden), dompurify ^3.4.13
(with monaco-editor scoped override). Closes Dependabot cheaper-inference#189, cheaper-inference#190.

Remaining cheaper-inference#182-cheaper-inference#188 (js-yaml + mermaid) already closed by cheaper-inference#9651 merge —
awaiting Dependabot re-scan.

npm audit → 0 vulnerabilities.

* fix(repo): harden .gitignore to also ignore a _tasks symlink (/_tasks)

_tasks is a SEPARATE nested git repo (gitignored). The pattern _tasks/ (trailing
slash) ignores only a directory, not a SYMLINK named _tasks. A self-referential
_tasks symlink can slip in via git add -A and, once pulled, checkout materializes
it over the real _tasks repo (destroying plans/specs/hands-off). Anchored /_tasks
ignores the symlink too, preventing re-capture.

* fix(combo): make failoverBeforeRetry actually skip the same-model retry

Both same-target retry loops (priority/auto and round-robin) checked
isTransient/maxRetries/providerExhausted but never consulted
config.failoverBeforeRetry, so a rate-limited model still got
maxRetries+1 back-to-back attempts on itself before falling back to a
sibling — the config option (cheaper-inference#2417) was only ever wired into
skipUpstreamRetry, a separate lower-level mechanism. Now the same-model
retry is skipped when failoverBeforeRetry is set AND a sibling target
is actually available; with no sibling left, it still retries same-model
since skipping would just burn the last attempt for nothing.

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@outlook.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
(cherry picked from commit d2fd88d)
tkgo11 pushed a commit to tkgo11/OmniRoute that referenced this pull request Sep 23, 2026
…ipUpstreamRetry default

Audit found that DEFAULT_COMBO_CONFIG.failoverBeforeRetry has defaulted to
true since before cheaper-inference#10217 (predates cheaper-inference#2417), and that value also feeds the
independent skipUpstreamRetry mechanism (src/sse/handlers/chat.ts:859,1126).
The previous commit on this branch flipped that default to false to fix the
cheaper-inference#10217 same-model retry guard, which silently disabled skipUpstreamRetry's
own default-on behavior for every combo without an opt-in — a regression in
the opposite direction (executor-level retries before the loop's own
failover, changing latency/failure behavior).

Revert the default back to true and decouple the two mechanisms instead:
resolveComboConfig/resolveComboSetupConfig now also compute
failoverBeforeRetryExplicit, true only when a cascade layer (combo/provider/
global) literally sets failoverBeforeRetry to true — not merely inherited
from the default. The cheaper-inference#10217 same-model retry guards in combo.ts (priority/
auto and round-robin loops) now read failoverBeforeRetryExplicit instead of
config.failoverBeforeRetry, restoring opt-in-only behavior for that guard
while the skipUpstreamRetry pass-through (config.failoverBeforeRetry at
combo.ts:1297,2865) is untouched and keeps its historical default-on.
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…2419)

* fix(cli-tools): guard modelId type before calling indexOf

E2E shakedown v3.8.0: cli-tools quebrava com TypeError quando dynamicModels
continha entradas sem .id (objeto retornado diretamente em vez de string).

* fix(offline): avoid SSR/CSR hydration mismatch on navigator.onLine

Replace useState+lazy-initializer with useSyncExternalStore so the server
snapshot (() => false) and client snapshot (() => navigator.onLine) are
declared separately. React hydrates with the server value and switches to
the real online status client-side without a mismatch.

* chore(i18n): add missing en.json keys for translator, cli-tools, memory, onboarding

Adds 58 missing keys identified by the new dashboard audit script:
- cliTools: 18 custom CLI builder keys (CustomCliCard)
- translator: 24 keys covering stream transformer, live monitor, test bench
- memory: 12 health/pagination/dialog keys
- onboarding.tier: 8 keys for the tier tour walkthrough

Also adds scripts/i18n/audit-dashboard-pages.mjs which scans all dashboard
pages, reports t() calls referencing missing en.json keys, and flags
candidate hardcoded JSX/attribute strings.

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 1)

Subagents refactored 8 high-impact dashboard pages, replacing 81 of the
407 hardcoded English/PT strings flagged by the audit with proper
useTranslations() lookups. Added 73 corresponding keys to en.json across
the home, apiManager, providers, settings, and usage namespaces.

Pages affected:
- BudgetTab (27 → 0)
- HomePageClient (2 → 0)
- RoutingTab (25 → 7)
- ResilienceTab (38 → 18)
- SystemStorageTab (42 → 21)
- providers/[id] (17 → 15)
- ApiManagerPageClient (14 → 13)
- OneproxyTab (13 → 10)

Also adds two helper scripts:
- scripts/i18n/extract-keys-from-diff.mjs — extracts new keys from git diff
- scripts/i18n/merge-keys.mjs — merges a pending-keys JSON into en.json

Remaining hardcoded strings will be addressed in follow-up rounds.

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 2)

Continues round 1 (commit 8d34f4c). Round-2 subagents refactored
additional dashboard pages, replacing 77 more hardcoded strings with
useTranslations() lookups. Added 79 corresponding keys to en.json
across the a2aDashboard, agents, analytics, apiManager, cliTools,
common, and settings namespaces.

Pages affected:
- a2a/page (new useTranslations + 6 keys)
- agent-skills/page (new useTranslations + 9 keys)
- AutoRoutingAnalyticsTab (new useTranslations + 6 keys)
- AppearanceTab (8 → 6 remaining)
- OneproxyTab (10 → 0)
- ResilienceTab (18 → 0 missing key)
- RoutingTab (7 → 0 missing key)
- VisionBridgeSettingsTab (new useTranslations + 6 keys)
- CopilotToolCard (7 → 0 missing key)
- ApiManagerPageClient (13 → 0 missing key)
- gamification/admin (new useTranslations + 7 keys)

Hardcoded total: 326 → 249. Real missing keys: 0 (the 6 still flagged
are false positives in exampleTemplates.tsx where t is passed as a
parameter — keys exist at translator.templatePayloads.*).

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 3)

Round-3 subagents and manual edits refactored 9 more dashboard pages
(plus 2 small extras), replacing ~80 hardcoded strings with
useTranslations() lookups. Added 79 corresponding keys to en.json
across analytics, cloudAgents, combos, common, health, settings, and
usage namespaces.

Pages affected:
- analytics/ComboHealthTab (new useTranslations + 15 keys)
- analytics/CompressionAnalyticsTab (new useTranslations + 11 keys)
- settings/SystemStorageTab (21 → 0 missing key)
- tokens/page (new useTranslations + 13 keys)
- usage/BudgetTab (9 missing fixed)
- health/page (manual: 6 keys)
- cloud-agents/page (manual: 3 keys)
- combos/page (manual: 1 key)

Hardcoded total: 249 → 164. Real missing keys: 0 (6 remaining are
exampleTemplates.tsx false positives).

Also adds scripts/i18n/build-pending-from-missing.mjs which reads
_audit.json and locates English values from HEAD to rebuild
_pending-keys.json after race-condition resets between subagent edits.

* chore(i18n): localize remaining dashboard settings labels

Replace hardcoded labels in compression and resilience settings with
translation lookups to continue the dashboard i18n cleanup.

Add the v3.8.0 dashboard shakedown runbook to document the manual
smoke-test process and known dev environment pitfalls.

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 4)

Round-4 subagent + manual key-resolution refactored remaining strings in
3 high-traffic settings/API tabs, plus extracted English values for
keys that were already added as t() calls but lost during the previous
en.json race-condition resets.

Pages affected:
- api-manager/ApiManagerPageClient (7 → 0 missing key)
- settings/CompressionSettingsTab (8 → 0 missing key)
- settings/MemorySkillsTab (8 → 0 missing key)
- settings/ResilienceTab (4 more keys recovered)

Hardcoded total: 164 → 140. Real missing keys: 0 (6 remaining are the
exampleTemplates.tsx false positives — t passed as parameter).

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 5)

Round-5 agent began processing the remaining smaller dashboard files.
Added 5 more keys to en.json for providers/[id]/page.tsx OAuth flow
labels and the cross-OS auto-detection hint.

Pages affected:
- providers/[id]/page.tsx (5 keys)

Hardcoded total: 140 → 136. Real missing keys: 0.

* chore(i18n): resolve last 2 missing providers/[id] keys

Adds providerDetailMyClaudeAccountPlaceholder and
providerDetailPathAutoDetected — the final user-visible labels in the
providers/[id] page that the round-5 subagent rewrote to t() calls
without yet adding to en.json.

Real missing keys: 0 (6 remaining are exampleTemplates.tsx false
positives — t is passed as a parameter so the audit cannot resolve the
namespace; keys do exist at translator.templatePayloads.*).

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 6 — 10 parallel agents)

Round-6 dispatched 10 parallel subagents covering all 57 remaining
dashboard files. Each agent worked on a disjoint file set to avoid
en.json race conditions. Added ~60 new i18n keys across 9 namespaces
covering small UI labels, table headers, search placeholders, and
empty-state messages.

Major changes:
- analytics: SearchAnalyticsTab, ProviderUtilizationTab, DiversityScoreCard, CompressionAnalyticsTab (new useTranslations + keys)
- batch: BatchDetailModal, BatchListTab, FileDetailModal, FilesListTab (new useTranslations + keys)
- settings: CliproxyapiSettingsTab, PayloadRulesTab, ModelCooldownsCard, AppearanceTab, PricingTab (mostly new useTranslations)
- endpoint: TokenSaverCard, ApiEndpointsTab, EndpointPageClient
- cache: CachePerformance, IdempotencyLayer, ReasoningCacheTab, MediaPageClient, page
- combos: IntelligentComboPanel, page
- playground: ChatPlayground, SearchPlayground
- providers: ProviderCard
- onboarding: TierFlowDiagram
- changelog: ChangelogViewer
- home: ProviderTopology, TierCoverageWidget, BootstrapBanner, BadgeToast
- usage: BudgetTab, BudgetTelemetryCards, QuotaTable
- quotaShare: QuotaSharePageClient
- profile: page
- leaderboard: page
- skills: page

Hardcoded total: 131 → 60. Real missing keys: 0 plus 1 false-positive
for combos.modePack (lookup via prop-passed t).

* chore(i18n): finalize round-6 keys for batch/cache/endpoint/usage

Adds the remaining keys produced by parallel agents A4, A6, A8, A9:
- common: batch-related labels (BatchDetailModal, BatchListTab,
  FileDetailModal, FilesListTab, page) + profile/leaderboard
- cache: hit rate, latency, retry, avg chars
- endpoint: token saver, API endpoints, copy URL, cloud/local labels
- usage: noSpend, activeSessions, quotaAlerts, budget timing
- skills: install/marketplace/filter
- proxyRegistry/quotaShare/mcpDashboard: misc labels

Hardcoded total: 60 → 48. Real missing keys: 0 (modePack remaining is a
false positive — combos.modePack exists but the audit can't resolve it
since IntelligentComboPanel receives t as a prop).

* fix(playground): dedupe filteredModels to avoid duplicate React key warning

The /v1/models endpoint can return the same model id twice (e.g., when a
model is listed by both an alias and its canonical provider), which made
the <Select> emit two <option> elements with the same key — triggering
"Encountered two children with the same key, codex/gpt-5.5".

Replace the chained filter + map with a single pass that skips ids
already added.

* fix(playground): guard against non-string model ids before .split/.startsWith

The /v1/models endpoint can include synthetic entries (combos, locals,
in-progress imports) with a null/undefined id. The playground used to
call m.id.split("/") in the provider-discovery loop, which threw on the
first non-string entry; the surrounding .catch(() => {}) silently
swallowed the error, so the provider/model/account dropdowns ended up
empty even though /v1/models returned thousands of valid entries.

- Skip entries without a string id before split/startsWith.
- Log the rejection in the .catch handler so future regressions are
  visible in DevTools instead of silently emptying the UI.

* fix(playground): guard ChatPlayground filteredModels for non-string ids

Same root cause as commit 49fe356: ChatPlayground filtered models
with m.id.startsWith(...) which crashed on null/undefined ids returned
by /v1/models (synthetic combo entries). Apply the same defensive guard
and dedupe used in the parent page.

* fix(claude): drop orphan tool_result after fixToolAdjacency strip (discussion cheaper-inference#2410)

Discussion cheaper-inference#2410 reports Claude returning 400 for sequences like:
  assistant: tool_use(id=X)
  user: <plain text>           ← breaks adjacency
  user: tool_result(id=X)

The previous round added `fixToolAdjacency` (commit 09653ef) which
correctly strips the orphan tool_use from the assistant message. But
that left the now-unmatched tool_result intact, so the upstream
rejected the request with:

  messages.N.content.M: unexpected `tool_use_id` found in `tool_result`
  blocks: X. Each tool_result block must have a corresponding tool_use
  block in the previous message.

Fix: after running `fixToolAdjacency`, re-run `fixToolPairs` to drop
the orphaned tool_result blocks. All three call sites updated:
  - contextManager.purifyHistory (both inside the binary-search loop
    and the final pass)
  - BaseExecutor message-prep (Claude path)
  - claudeCodeCompatible request signer

Also tightens an unrelated dynamic-key access in
readNestedString (claudeCodeCompatible) to satisfy the prototype-
pollution scanner triggered by the post-tool semgrep hook.

* fix(mitm): point runtime manager re-export to js entrypoint

Use the emitted `.js` path for the runtime manager re-export so dynamic
runtime loading resolves correctly outside the Turbopack alias handling.

* docs: add AgentRouter setup guide (cheaper-inference#2422)

Integrated into release/v3.8.0 — AgentRouter setup guide docs.

* feat: add new feature on combos - falloverBeforeRetry (cheaper-inference#2417)

Integrated into release/v3.8.0 — falloverBeforeRetry for per-model quota skipping in combos.

* feat(batch): implement 10 feature requests harvested  (cheaper-inference#2414)

Integrated into release/v3.8.0 — batch of 10 feature requests: llama.cpp local provider, upstream error exposure, Termux detection, providers rotate CLI, t3.chat web skeleton, Zed Docker integration, Kiro multi-account OAuth isolation, auto-combo cost blending, auto-combo context filter, combo provider-level exhaustion tracking (cheaper-inference#1731). Conflicts with cheaper-inference#2417 (falloverBeforeRetry) resolved.

* fix(gamification): resolve SQL bug, auth gap, pagination, and anomaly scoring (cheaper-inference#2421)

Integrated into release/v3.8.0 — 6 critical gamification bug fixes: SQL SELECT in checkActionCountBadges, federation auth enforcement, leaderboard pagination offset, real z-score computation, addXp level calculation, and barrel index.ts

* docs(changelog): add post-release entries for cheaper-inference#2414 cheaper-inference#2417 cheaper-inference#2421 cheaper-inference#2422

- feat(batch): T3-Chat-Web executor, exhaustedProviders set (cheaper-inference#1731), Zed Docker
- feat(combos): falloverBeforeRetry + setTry loop (cheaper-inference#2417 — @hartmark)
- fix(gamification): SQL SELECT bug, federation auth, pagination, z-score (cheaper-inference#2421 — @oyi77)
- docs: AgentRouter setup guide (cheaper-inference#2422 — @leninejunior)

* fix(security): resolve CodeQL random/password-hash alerts and sync docs & tests

---------

Co-authored-by: diegosouzapw <diego.souza.pw@gmail.com>
Co-authored-by: Lenine Júnior <lenine@engrene.com.br>
Co-authored-by: Markus Hartung <mail@hartmark.se>
Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com>
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
* fix(cli-tools): guard modelId type before calling indexOf

E2E shakedown v3.8.0: cli-tools quebrava com TypeError quando dynamicModels
continha entradas sem .id (objeto retornado diretamente em vez de string).

* fix(offline): avoid SSR/CSR hydration mismatch on navigator.onLine

Replace useState+lazy-initializer with useSyncExternalStore so the server
snapshot (() => false) and client snapshot (() => navigator.onLine) are
declared separately. React hydrates with the server value and switches to
the real online status client-side without a mismatch.

* chore(i18n): add missing en.json keys for translator, cli-tools, memory, onboarding

Adds 58 missing keys identified by the new dashboard audit script:
- cliTools: 18 custom CLI builder keys (CustomCliCard)
- translator: 24 keys covering stream transformer, live monitor, test bench
- memory: 12 health/pagination/dialog keys
- onboarding.tier: 8 keys for the tier tour walkthrough

Also adds scripts/i18n/audit-dashboard-pages.mjs which scans all dashboard
pages, reports t() calls referencing missing en.json keys, and flags
candidate hardcoded JSX/attribute strings.

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 1)

Subagents refactored 8 high-impact dashboard pages, replacing 81 of the
407 hardcoded English/PT strings flagged by the audit with proper
useTranslations() lookups. Added 73 corresponding keys to en.json across
the home, apiManager, providers, settings, and usage namespaces.

Pages affected:
- BudgetTab (27 → 0)
- HomePageClient (2 → 0)
- RoutingTab (25 → 7)
- ResilienceTab (38 → 18)
- SystemStorageTab (42 → 21)
- providers/[id] (17 → 15)
- ApiManagerPageClient (14 → 13)
- OneproxyTab (13 → 10)

Also adds two helper scripts:
- scripts/i18n/extract-keys-from-diff.mjs — extracts new keys from git diff
- scripts/i18n/merge-keys.mjs — merges a pending-keys JSON into en.json

Remaining hardcoded strings will be addressed in follow-up rounds.

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 2)

Continues round 1 (commit 8d34f4c). Round-2 subagents refactored
additional dashboard pages, replacing 77 more hardcoded strings with
useTranslations() lookups. Added 79 corresponding keys to en.json
across the a2aDashboard, agents, analytics, apiManager, cliTools,
common, and settings namespaces.

Pages affected:
- a2a/page (new useTranslations + 6 keys)
- agent-skills/page (new useTranslations + 9 keys)
- AutoRoutingAnalyticsTab (new useTranslations + 6 keys)
- AppearanceTab (8 → 6 remaining)
- OneproxyTab (10 → 0)
- ResilienceTab (18 → 0 missing key)
- RoutingTab (7 → 0 missing key)
- VisionBridgeSettingsTab (new useTranslations + 6 keys)
- CopilotToolCard (7 → 0 missing key)
- ApiManagerPageClient (13 → 0 missing key)
- gamification/admin (new useTranslations + 7 keys)

Hardcoded total: 326 → 249. Real missing keys: 0 (the 6 still flagged
are false positives in exampleTemplates.tsx where t is passed as a
parameter — keys exist at translator.templatePayloads.*).

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 3)

Round-3 subagents and manual edits refactored 9 more dashboard pages
(plus 2 small extras), replacing ~80 hardcoded strings with
useTranslations() lookups. Added 79 corresponding keys to en.json
across analytics, cloudAgents, combos, common, health, settings, and
usage namespaces.

Pages affected:
- analytics/ComboHealthTab (new useTranslations + 15 keys)
- analytics/CompressionAnalyticsTab (new useTranslations + 11 keys)
- settings/SystemStorageTab (21 → 0 missing key)
- tokens/page (new useTranslations + 13 keys)
- usage/BudgetTab (9 missing fixed)
- health/page (manual: 6 keys)
- cloud-agents/page (manual: 3 keys)
- combos/page (manual: 1 key)

Hardcoded total: 249 → 164. Real missing keys: 0 (6 remaining are
exampleTemplates.tsx false positives).

Also adds scripts/i18n/build-pending-from-missing.mjs which reads
_audit.json and locates English values from HEAD to rebuild
_pending-keys.json after race-condition resets between subagent edits.

* chore(i18n): localize remaining dashboard settings labels

Replace hardcoded labels in compression and resilience settings with
translation lookups to continue the dashboard i18n cleanup.

Add the v3.8.0 dashboard shakedown runbook to document the manual
smoke-test process and known dev environment pitfalls.

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 4)

Round-4 subagent + manual key-resolution refactored remaining strings in
3 high-traffic settings/API tabs, plus extracted English values for
keys that were already added as t() calls but lost during the previous
en.json race-condition resets.

Pages affected:
- api-manager/ApiManagerPageClient (7 → 0 missing key)
- settings/CompressionSettingsTab (8 → 0 missing key)
- settings/MemorySkillsTab (8 → 0 missing key)
- settings/ResilienceTab (4 more keys recovered)

Hardcoded total: 164 → 140. Real missing keys: 0 (6 remaining are the
exampleTemplates.tsx false positives — t passed as parameter).

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 5)

Round-5 agent began processing the remaining smaller dashboard files.
Added 5 more keys to en.json for providers/[id]/page.tsx OAuth flow
labels and the cross-OS auto-detection hint.

Pages affected:
- providers/[id]/page.tsx (5 keys)

Hardcoded total: 140 → 136. Real missing keys: 0.

* chore(i18n): resolve last 2 missing providers/[id] keys

Adds providerDetailMyClaudeAccountPlaceholder and
providerDetailPathAutoDetected — the final user-visible labels in the
providers/[id] page that the round-5 subagent rewrote to t() calls
without yet adding to en.json.

Real missing keys: 0 (6 remaining are exampleTemplates.tsx false
positives — t is passed as a parameter so the audit cannot resolve the
namespace; keys do exist at translator.templatePayloads.*).

* chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 6 — 10 parallel agents)

Round-6 dispatched 10 parallel subagents covering all 57 remaining
dashboard files. Each agent worked on a disjoint file set to avoid
en.json race conditions. Added ~60 new i18n keys across 9 namespaces
covering small UI labels, table headers, search placeholders, and
empty-state messages.

Major changes:
- analytics: SearchAnalyticsTab, ProviderUtilizationTab, DiversityScoreCard, CompressionAnalyticsTab (new useTranslations + keys)
- batch: BatchDetailModal, BatchListTab, FileDetailModal, FilesListTab (new useTranslations + keys)
- settings: CliproxyapiSettingsTab, PayloadRulesTab, ModelCooldownsCard, AppearanceTab, PricingTab (mostly new useTranslations)
- endpoint: TokenSaverCard, ApiEndpointsTab, EndpointPageClient
- cache: CachePerformance, IdempotencyLayer, ReasoningCacheTab, MediaPageClient, page
- combos: IntelligentComboPanel, page
- playground: ChatPlayground, SearchPlayground
- providers: ProviderCard
- onboarding: TierFlowDiagram
- changelog: ChangelogViewer
- home: ProviderTopology, TierCoverageWidget, BootstrapBanner, BadgeToast
- usage: BudgetTab, BudgetTelemetryCards, QuotaTable
- quotaShare: QuotaSharePageClient
- profile: page
- leaderboard: page
- skills: page

Hardcoded total: 131 → 60. Real missing keys: 0 plus 1 false-positive
for combos.modePack (lookup via prop-passed t).

* chore(i18n): finalize round-6 keys for batch/cache/endpoint/usage

Adds the remaining keys produced by parallel agents A4, A6, A8, A9:
- common: batch-related labels (BatchDetailModal, BatchListTab,
  FileDetailModal, FilesListTab, page) + profile/leaderboard
- cache: hit rate, latency, retry, avg chars
- endpoint: token saver, API endpoints, copy URL, cloud/local labels
- usage: noSpend, activeSessions, quotaAlerts, budget timing
- skills: install/marketplace/filter
- proxyRegistry/quotaShare/mcpDashboard: misc labels

Hardcoded total: 60 → 48. Real missing keys: 0 (modePack remaining is a
false positive — combos.modePack exists but the audit can't resolve it
since IntelligentComboPanel receives t as a prop).

* fix(playground): dedupe filteredModels to avoid duplicate React key warning

The /v1/models endpoint can return the same model id twice (e.g., when a
model is listed by both an alias and its canonical provider), which made
the <Select> emit two <option> elements with the same key — triggering
"Encountered two children with the same key, codex/gpt-5.5".

Replace the chained filter + map with a single pass that skips ids
already added.

* fix(playground): guard against non-string model ids before .split/.startsWith

The /v1/models endpoint can include synthetic entries (combos, locals,
in-progress imports) with a null/undefined id. The playground used to
call m.id.split("/") in the provider-discovery loop, which threw on the
first non-string entry; the surrounding .catch(() => {}) silently
swallowed the error, so the provider/model/account dropdowns ended up
empty even though /v1/models returned thousands of valid entries.

- Skip entries without a string id before split/startsWith.
- Log the rejection in the .catch handler so future regressions are
  visible in DevTools instead of silently emptying the UI.

* fix(playground): guard ChatPlayground filteredModels for non-string ids

Same root cause as commit 49fe356: ChatPlayground filtered models
with m.id.startsWith(...) which crashed on null/undefined ids returned
by /v1/models (synthetic combo entries). Apply the same defensive guard
and dedupe used in the parent page.

* fix(claude): drop orphan tool_result after fixToolAdjacency strip (discussion cheaper-inference#2410)

Discussion cheaper-inference#2410 reports Claude returning 400 for sequences like:
  assistant: tool_use(id=X)
  user: <plain text>           ← breaks adjacency
  user: tool_result(id=X)

The previous round added `fixToolAdjacency` (commit 09653ef) which
correctly strips the orphan tool_use from the assistant message. But
that left the now-unmatched tool_result intact, so the upstream
rejected the request with:

  messages.N.content.M: unexpected `tool_use_id` found in `tool_result`
  blocks: X. Each tool_result block must have a corresponding tool_use
  block in the previous message.

Fix: after running `fixToolAdjacency`, re-run `fixToolPairs` to drop
the orphaned tool_result blocks. All three call sites updated:
  - contextManager.purifyHistory (both inside the binary-search loop
    and the final pass)
  - BaseExecutor message-prep (Claude path)
  - claudeCodeCompatible request signer

Also tightens an unrelated dynamic-key access in
readNestedString (claudeCodeCompatible) to satisfy the prototype-
pollution scanner triggered by the post-tool semgrep hook.

* fix(mitm): point runtime manager re-export to js entrypoint

Use the emitted `.js` path for the runtime manager re-export so dynamic
runtime loading resolves correctly outside the Turbopack alias handling.

* docs: add AgentRouter setup guide (cheaper-inference#2422)

Integrated into release/v3.8.0 — AgentRouter setup guide docs.

* feat: add new feature on combos - falloverBeforeRetry (cheaper-inference#2417)

Integrated into release/v3.8.0 — falloverBeforeRetry for per-model quota skipping in combos.

* feat(batch): implement 10 feature requests harvested  (cheaper-inference#2414)

Integrated into release/v3.8.0 — batch of 10 feature requests: llama.cpp local provider, upstream error exposure, Termux detection, providers rotate CLI, t3.chat web skeleton, Zed Docker integration, Kiro multi-account OAuth isolation, auto-combo cost blending, auto-combo context filter, combo provider-level exhaustion tracking (cheaper-inference#1731). Conflicts with cheaper-inference#2417 (falloverBeforeRetry) resolved.

* fix(gamification): resolve SQL bug, auth gap, pagination, and anomaly scoring (cheaper-inference#2421)

Integrated into release/v3.8.0 — 6 critical gamification bug fixes: SQL SELECT in checkActionCountBadges, federation auth enforcement, leaderboard pagination offset, real z-score computation, addXp level calculation, and barrel index.ts

* docs(changelog): add post-release entries for cheaper-inference#2414 cheaper-inference#2417 cheaper-inference#2421 cheaper-inference#2422

- feat(batch): T3-Chat-Web executor, exhaustedProviders set (cheaper-inference#1731), Zed Docker
- feat(combos): falloverBeforeRetry + setTry loop (cheaper-inference#2417 — @hartmark)
- fix(gamification): SQL SELECT bug, federation auth, pagination, z-score (cheaper-inference#2421 — @oyi77)
- docs: AgentRouter setup guide (cheaper-inference#2422 — @leninejunior)

* fix(security): resolve CodeQL random/password-hash alerts and sync docs & tests

* feat/fix: integrate PRs cheaper-inference#2423, cheaper-inference#2425, cheaper-inference#2427, cheaper-inference#2428 with test & security fixes

* docs(changelog): credit contributors for PRs cheaper-inference#2423, cheaper-inference#2425, cheaper-inference#2427, cheaper-inference#2428

* fix(mitm): drop .js extension on manager.runtime re-export for webpack build (cheaper-inference#2425)

Merged into release/v3.8.0

* fix: persist STORAGE_ENCRYPTION_KEY across upgrades (closes cheaper-inference#1622) (cheaper-inference#2428)

Merged into release/v3.8.0

* fix: auto-reset apiKeyHealth on successful connection test (cheaper-inference#2427)

Merged into release/v3.8.0

* fix: support Antigravity image generation, Add Gemini 3.5 Flash (cheaper-inference#2423)

Merged into release/v3.8.0

---------

Co-authored-by: diegosouzapw <diego.souza.pw@gmail.com>
Co-authored-by: Lenine Júnior <lenine@engrene.com.br>
Co-authored-by: Markus Hartung <mail@hartmark.se>
Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com>
Co-authored-by: Anton <39598727+NomenAK@users.noreply.github.com>
Co-authored-by: Chewji <126886556+Chewji9875@users.noreply.github.com>
Co-authored-by: clousky2020 <33016567+clousky2020@users.noreply.github.com>
Co-authored-by: backryun <bakryun0718@proton.me>
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…nce#2417)

Integrated into release/v3.8.0 — falloverBeforeRetry for per-model quota skipping in combos.
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…ce#2414)

Integrated into release/v3.8.0 — batch of 10 feature requests: llama.cpp local provider, upstream error exposure, Termux detection, providers rotate CLI, t3.chat web skeleton, Zed Docker integration, Kiro multi-account OAuth isolation, auto-combo cost blending, auto-combo context filter, combo provider-level exhaustion tracking (cheaper-inference#1731). Conflicts with cheaper-inference#2417 (falloverBeforeRetry) resolved.
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…heaper-inference#2417 cheaper-inference#2421 cheaper-inference#2422

- feat(batch): T3-Chat-Web executor, exhaustedProviders set (cheaper-inference#1731), Zed Docker
- feat(combos): falloverBeforeRetry + setTry loop (cheaper-inference#2417 — @hartmark)
- fix(gamification): SQL SELECT bug, federation auth, pagination, z-score (cheaper-inference#2421 — @oyi77)
- docs: AgentRouter setup guide (cheaper-inference#2422 — @leninejunior)
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…cheaper-inference#6373)

add DigitalOcean AI OpenAI-compatible provider (port PR cheaper-inference#2417). Resolved registry conflict with hcnsec cheaper-inference#6410; count-guard 169→170. Tests green. Integrated into release/v3.8.46.
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…ry (cheaper-inference#10217)

* fix(deps): bump nanoid, dompurify for 2 new Dependabot alerts (cheaper-inference#189, cheaper-inference#190)

Bumps: nanoid ^3.3.17 (was transitive, now overridden), dompurify ^3.4.13
(with monaco-editor scoped override). Closes Dependabot cheaper-inference#189, cheaper-inference#190.

Remaining cheaper-inference#182-cheaper-inference#188 (js-yaml + mermaid) already closed by cheaper-inference#9651 merge —
awaiting Dependabot re-scan.

npm audit → 0 vulnerabilities.

* fix(repo): harden .gitignore to also ignore a _tasks symlink (/_tasks)

_tasks is a SEPARATE nested git repo (gitignored). The pattern _tasks/ (trailing
slash) ignores only a directory, not a SYMLINK named _tasks. A self-referential
_tasks symlink can slip in via git add -A and, once pulled, checkout materializes
it over the real _tasks repo (destroying plans/specs/hands-off). Anchored /_tasks
ignores the symlink too, preventing re-capture.

* fix(combo): make failoverBeforeRetry actually skip the same-model retry

Both same-target retry loops (priority/auto and round-robin) checked
isTransient/maxRetries/providerExhausted but never consulted
config.failoverBeforeRetry, so a rate-limited model still got
maxRetries+1 back-to-back attempts on itself before falling back to a
sibling — the config option (cheaper-inference#2417) was only ever wired into
skipUpstreamRetry, a separate lower-level mechanism. Now the same-model
retry is skipped when failoverBeforeRetry is set AND a sibling target
is actually available; with no sibling left, it still retries same-model
since skipping would just burn the last attempt for nothing.

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@outlook.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…nce#10260)

* fix(ci): clear base-reds on release/v3.8.50 (round 4)

Drains the HARD failures reported by Release-Green run 31693210948 on issue cheaper-inference#9985
(ESLint errors: 2) plus the merge-integrity red every open PR is inheriting.

- ESLint error 1: @omniroute/opencode-plugin/src/index.ts had a stray extra
  '});' (introduced by cheaper-inference#9316) that broke parsing with 'unexpected file in NFT list'
  on the build path.
- ESLint error 2: cli-env-inline-comment-10100.test.ts used new Function to extract
  parseEnvValue from the bin entrypoint (no-new-func, Hard Rule cheaper-inference#3). Extracted the
  helper to bin/cli/utils/parseEnvValue.mjs and import it from both the entrypoint
  and the test (same behavior, no eval).
- open-sse-typecheck (Fast Quality Gates): open-sse/utils/stream.ts imported
  sseCommentsEnabled twice (cheaper-inference#9378) causing TS2300 Duplicate identifier; removed the
  duplicate import.
- Merge integrity (changelog + generated skills): skills/omni-settings/SKILL.md was
  edited manually by cheaper-inference#10169 without updating the generator source, so
  check:agent-skills-sync failed on every PR (Generated: 1). Moved the curated
  thinking-budget content into a <!-- skill:custom-start --> block (the documented
  preservation mechanism), which the generator now keeps in sync.

Refs cheaper-inference#9985

* fix(tests): align wave1-a poolside test with cheaper-inference#10216 probed catalog

cheaper-inference#10216 published Poolside's two authenticated-probe models
(poolside/laguna-xs-2.1, poolside/laguna-s-2.1) as static seeds, but the
wave1-a free-tier test still asserted 'no invented static model ids'
(entry.models === []), failing every open PR. Separate poolside from the
empty-models assertion and pin its probed catalog explicitly so a future
catalog change is a deliberate update, not a silent drift.

* fix(pack): register parseEnvValue.mjs in PACK_ARTIFACT_REQUIRED_PATHS

The extract of parseEnvValue to bin/cli/utils/parseEnvValue.mjs added a new
direct import to bin/omniroute.mjs, which pack-artifact-entrypoint-closures
enforces against PACK_ARTIFACT_REQUIRED_PATHS. Register the module so a future
tarball omission fails loudly.

* fix(combo): restore default same-model retry semantics after cheaper-inference#10217

cheaper-inference#10217 wired config.failoverBeforeRetry into the same-model retry guard in
both the priority/auto and round-robin loops, but DEFAULT_COMBO_CONFIG
defaulted the flag to true — flipping same-model retry off for every combo
that never touched the setting, not just the opt-in case. Round-4 bisect
(dec88b5 vs 3c3b434) reproduced this against
tests/unit/combo-499-abort.test.ts, tests/unit/combo-quota-exhaustion-only-fallback.test.ts
and tests/unit/combo-stream-readiness-fallback.test.ts. Flip the default to
false so the historical retry-before-failover behavior returns for combos
that never set the flag, while explicit opt-in (the two new tests cheaper-inference#10217
added to combo-routing-engine.test.ts) still works.

* fix(quality): register visionBridge-responses-9597 in stryker tap.testFiles

check-mutation-test-coverage.mjs flagged tests/unit/guardrails/visionBridge-responses-9597.test.ts
as covering open-sse/services/combo/comboStructure.ts without being listed
in stryker.conf.json's tap.testFiles array. Add it so mutation coverage
attribution stays accurate.

* test(pack): expect parseEnvValue.mjs in the missing-artifact-paths fixture

The prior commit on this branch registered bin/cli/utils/parseEnvValue.mjs
in PACK_ARTIFACT_REQUIRED_PATHS but the "findMissingArtifactPaths flags
missing root runtime files in the tarball" test still hardcoded the old
expected list, so it never accounted for the new required path being
absent from the simulated tarball. Add it in its alphabetical slot.

* chore(lint): prune stale no-explicit-any suppression for call-log-file-rotation

--prune-suppressions found tests/unit/call-log-file-rotation.test.ts no
longer produces the 5 suppressed @typescript-eslint/no-explicit-any
warnings recorded in config/quality/eslint-suppressions.json. Remove the
dead entry so a regression would be caught again. Full-tree run with
--max-warnings 0 is clean: 0 errors, 0 warnings.

* fix(combo): decouple failoverBeforeRetry same-model guard from the skipUpstreamRetry default

Audit found that DEFAULT_COMBO_CONFIG.failoverBeforeRetry has defaulted to
true since before cheaper-inference#10217 (predates cheaper-inference#2417), and that value also feeds the
independent skipUpstreamRetry mechanism (src/sse/handlers/chat.ts:859,1126).
The previous commit on this branch flipped that default to false to fix the
cheaper-inference#10217 same-model retry guard, which silently disabled skipUpstreamRetry's
own default-on behavior for every combo without an opt-in — a regression in
the opposite direction (executor-level retries before the loop's own
failover, changing latency/failure behavior).

Revert the default back to true and decouple the two mechanisms instead:
resolveComboConfig/resolveComboSetupConfig now also compute
failoverBeforeRetryExplicit, true only when a cascade layer (combo/provider/
global) literally sets failoverBeforeRetry to true — not merely inherited
from the default. The cheaper-inference#10217 same-model retry guards in combo.ts (priority/
auto and round-robin loops) now read failoverBeforeRetryExplicit instead of
config.failoverBeforeRetry, restoring opt-in-only behavior for that guard
while the skipUpstreamRetry pass-through (config.failoverBeforeRetry at
combo.ts:1297,2865) is untouched and keeps its historical default-on.

* fix(combo,i18n): align getDefaultComboConfig with 10217 explicit flag; pt denoRelay entities

Two round-4 follow-ups exposed by the combinated base-red PR run:

1. comboConfig.ts: cheaper-inference#10217 round-4 fix (104afed) added
   failoverBeforeRetryExplicit to resolveComboConfig/resolveComboSetupConfig
   but getDefaultComboConfig() returned only DEFAULT_COMBO_CONFIG, so the
   combo-config.test.ts deepEqual (resolveComboConfig(null) ===
   getDefaultComboConfig()) failed on the extra field. Mirror the opt-in flag
   as false in the default.

2. pt.json: denoRelayOrgDomainHint still carried raw <app-name>/<org-slug>
   (the UNCLOSED_TAG RSC regression) — encode as &lt;...&gt; like the other
   42 locales, greening i18n-deno-relay-unclosed-tag.test.ts.

* chore(lint): disable @next/next/no-location-assign-relative-destination pending per-case review (cheaper-inference#10292)

The eslint-config-next bump in cheaper-inference#10043 shipped this new rule, flagging 6
pre-existing window.location.href navigations — several are deliberate
full-page reloads (login/logout state reset). Off with tracking issue
rather than a blanket router.push rewrite.

* fix(i18n): fill 439 missing UI keys (thinkingMode ×39 locales + pt catch-up) to restore 100% coverage

The cheaper-inference#10169 Thinking Budget keys existed only in en/pt-BR/vi and the pt (PT-PT)
catalog from cheaper-inference#10250 lagged 88 recent keys, dropping i18nUiCoverage to 99.3%
vs the frozen 100% ratchet baseline. Translated via the i18n:sync-ui marker
pipeline; glossary + ICU placeholder post-pass clean.

* fix(i18n): zh-TW glossary — replace retired 默認 with canonical 預設 in new thinkingMode keys

* chore(quality): rebase dead-code baseline 248 -> 409 for knip 6.32 bump (cheaper-inference#10043)

dependabot cheaper-inference#10043 upgraded knip 6.27 -> 6.32, which detects 162 MORE
genuinely-unused exports (331 vs 169) that 6.27 missed; DEAD_FILES
unchanged (78). Reproduced identically on the clean release/v3.8.50 tip
6e1e80e with a fresh 6.32 node_modules, so every PR is born red until
the tool change is absorbed. Owner authorized rebaseline (2026-08-13 via
PR cheaper-inference#10260). Structural cleanup of the newly-surfaced dead exports remains
separate debt.

---------

Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
Co-authored-by: backryun <bakryun0718@proton.me>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants