Skip to content

Release/v3.8.0 — full changelog with 660+ commits - #2419

Merged
diegosouzapw merged 24 commits into
mainfrom
release/v3.8.0
May 20, 2026
Merged

diegosouzapw merged 24 commits into
mainfrom
release/v3.8.0

Conversation

@diegosouzapw

@diegosouzapw diegosouzapw commented May 20, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Reorganizes the [Unreleased] and [3.8.0] sections of CHANGELOG.md by date, covering 2026-05-06 → 2026-05-20 (660+ commits since v3.7.9)
  • Adds 7 missing entries that were in the git log but not in the CHANGELOG
  • Preserves every existing entry — nothing removed, only reorganized and supplemented

Missing entries added (Tarefa 1 findings)

The following commits were confirmed in git log v3.7.9..HEAD but missing from the CHANGELOG:

  1. feat(dashboard): add Claude Code auth import/export UI + i18n — three-PR series (libs PR1, API routes PR2, dashboard UI PR3) landed 2026-05-18
  2. feat(dashboard): add Gemini CLI auth import/export UI + i18n — same three-PR series, 2026-05-18
  3. fix(routing): implement embedding combos, local provider validation bypass, and resolve migration collisions — commit 5ed96f507, 2026-05-18
  4. fix(build): import Monaco ESM API to fix webpack nls.messages-loader error — commit 1c4d85044, 2026-05-18
  5. fix(providers): Fix Providers empty state blocking first provider setup — commit b191173ae by @slider23 (Michael), 2026-05-17
  6. feat(i18n): add Azerbaijani (az) language support — commit 6e392932c, 2026-05-15
  7. feat(workflow): pre-flight triage script — correctly placed in the 2026-05-19 section (was in [Unreleased] header without a date)

Items confirmed already in CHANGELOG

All items from the checklist were confirmed present:

Changelog structure (Tarefa 2)

The [3.8.0] section now has two layers:

  1. Chronological summary (#### 2026-05-20, #### 2026-05-19, ..., #### 2026-05-06 a 2026-05-07) — easy to scan day-by-day
  2. Detailed sub-sections (original verbose entries) preserved intact below

External contributors in this release (@mention)

Thank you to the 60+ community contributors:
@oyi77 @backryun @thepigdestroyer @mrmm @dhaern @hartmark @gleber @herjarsa @congvc-dev @terence71-glitch @TF0rd @slider23 @t-way666 @payne0420 @Rikonorus @8mbe @InkshadeWoods @clousky2020 @benzntech @NomenAK @Brkic-Nikola @Gioxaa @ddarkr @nickwizard @rafacpti23 @Tentoxa @wauputr4 @bypanghu @eleata @Tr0sT @AveryanAlex @rodrigogbbr-stack @NekoMonci12 @tatsster @xssdem @wucm667 @tces1 @guanbear @Gi99lin @ivan-mezentsev @JxnLexn @yoviarpauzi @rilham97 @boa-z @rdself @abhinavjnu @HoaPham98 @christlau @flyingmongoose @05dunski @DavyMassoneto @Zhaba1337228 @faisalill @Yosee11 @hachimed @JohnDoe-oss @brucevoin @kang-heewon @one-vs @josephvoxone @Flexible78 @yamansin @slider23 @Mrinal-Joshi @app/dependabot

Validation

  • git log v3.7.9..HEAD cross-referenced against all CHANGELOG entries
  • All 31 checklist items confirmed present or added
  • No entries removed — only added and reorganized
  • Contributor credits verified against git log --pretty="%an <%ae>"

🤖 Generated with Claude Code

E2E shakedown v3.8.0: cli-tools quebrava com TypeError quando dynamicModels
continha entradas sem .id (objeto retornado diretamente em vez de string).
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.
…ry, 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.
…d (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.
@kilo-code-bot

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

Copy link
Copy Markdown

Code Review Summary

Status: 12 Issues Found (3 NEW, 9 Carried Forward) | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 5
SUGGESTION 7

Incremental Review Notes

This review covers 7 files changed since 78c344b3a2643cc88061be8fa048da6e821933ed.

NEW issues found:

  • 3× SUGGESTION: Hardcoded PBKDF2 salt duplicated across 3 federation files (leaderboard/route.ts, score/route.ts, servers.ts). Should be centralized to a shared constant.

Changes reviewed (no new issues):

  • docs/architecture/ARCHITECTURE.md — Executor count update 31→38, correct
  • docs/architecture/CODEBASE_DOCUMENTATION.md — Same count update, correct
  • src/lib/gamification/invites.ts — randomInt replaces randomBytes + modulo, fixes modulo bias, correct
  • tests/unit/autocombo-unification.test.ts — Test updated for sidebar section rename (primary→omni-proxy), correct

No issues found with: PBKDF2 replacing SHA256 (correct direction), static crypto import replacing dynamic import, all-except invite code generation improvement.

Issue Details (click to expand)

WARNING

File Line Issue Status
src/lib/db/gamification.ts 169 ON CONFLICT clause does not update current_level — level becomes stale after first XP award. Relies on events.ts compensating separately. Carried Forward
open-sse/executors/base.ts 979 When skipUpstreamRetry is true and a fetch error occurs, code throws immediately without trying remaining fallback URLs. Carried Forward
open-sse/services/combo.ts 1732 estimateTokens(body?.messages ?? []) passes entire messages array to estimateTokens, which JSON.stringifies including role/metadata overhead. Overestimates token count. Carried Forward
src/app/offline/page.tsx 1 Medium priority issue from previous review. Carried Forward
src/app/offline/page.tsx 19 Medium priority issue from previous review. Carried Forward

SUGGESTION

File Line Issue Status
src/app/api/gamification/federation/leaderboard/route.ts 27 Hardcoded PBKDF2 salt duplicated across 3 files. Should be centralized. NEW
src/app/api/gamification/federation/score/route.ts 25 Hardcoded PBKDF2 salt duplicated across 3 files. Should be centralized. NEW
src/lib/gamification/servers.ts 28 Hardcoded PBKDF2 salt duplicated across 3 files. Should be centralized. NEW
src/app/api/gamification/federation/leaderboard/route.ts 44 No validation on limit query parameter — could be 0, negative, or extremely large. Carried Forward
open-sse/services/combo.ts 2370 latencyMs calculated from startTime reset inside setTry loop. When recordedAttempts === 0, only last set try's duration captured. Carried Forward
open-sse/services/combo.ts 2023 earliestRetryAfter scoped to each set try loop. Retry-after hints from earlier set tries lost when continue jumps. Carried Forward
open-sse/services/combo.ts 1232 OUTPUT_TOKEN_RATIO = 0.4 hardcoded and applied uniformly. Models with different input/output ratios get inaccurate cost estimates. Carried Forward
Files Reviewed (7 files)
  • docs/architecture/ARCHITECTURE.md — Executor count update, no issues
  • docs/architecture/CODEBASE_DOCUMENTATION.md — Executor count update, no issues
  • src/app/api/gamification/federation/leaderboard/route.ts — 1 SUGGESTION (hardcoded salt)
  • src/app/api/gamification/federation/score/route.ts — 1 SUGGESTION (hardcoded salt)
  • src/lib/gamification/servers.ts — 1 SUGGESTION (hardcoded salt)
  • src/lib/gamification/invites.ts — Modulo bias fix, no issues
  • tests/unit/autocombo-unification.test.ts — Test update, no issues

Reviewed by qwen3.6-plus · 400,696 tokens

@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 improves the robustness of model ID extraction in the CLI tools page and refactors the offline status detection logic to use the useSyncExternalStore hook. Review feedback highlights a violation of the repository style guide regarding missing tests for production code changes. Additionally, the implementation of useSyncExternalStore requires updates to ensure SSR safety when accessing the navigator object and to prevent UI flickering during hydration by providing a more optimistic server snapshot.

Comment thread src/app/offline/page.tsx
@@ -1,25 +1,24 @@
"use client";

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

According to the Repository Style Guide (Rule 29), production code changes should include tests. The PR description indicates no tests were added or updated for these changes in src/app/offline/page.tsx and src/app/(dashboard)/dashboard/cli-tools/CLIToolsPageClient.tsx.

References
  1. Always include tests when changing production code (src/, open-sse/, electron/, bin/). (link)

Comment thread src/app/offline/page.tsx
Comment on lines +18 to +19
() => navigator.onLine,
() => false

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 getSnapshot function directly accesses navigator.onLine without checking if navigator is defined, which can lead to errors in non-browser environments (e.g., during hydration or testing).

Additionally, the getServerSnapshot now returns false, changing the default initial state from true (as seen in the previous implementation). This will cause a flash of the 'Offline' UI state during hydration for users who are online. It's recommended to use true as the default for a more optimistic and consistent UX.

Suggested change
() => navigator.onLine,
() => false
() => (typeof navigator !== "undefined" ? navigator.onLine : true),
() => true

…d (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.*).
@github-actions

github-actions Bot commented May 20, 2026 •

Copy link
Copy Markdown
Contributor

CI Coverage Report

  • Coverage job: skipped
  • PR test policy: success

Coverage artifact was not available for this run.

diegosouzapw and others added 14 commits May 19, 2026 22:26
…d (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.
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.
…d (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).
…d (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.
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.*).
…d (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).
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).
…arning

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.
…artsWith

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.
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.
…scussion #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.
Use the emitted `.js` path for the runtime manager re-export so dynamic
runtime loading resolves correctly outside the Turbopack alias handling.
Integrated into release/v3.8.0 — AgentRouter setup guide docs.
Integrated into release/v3.8.0 — falloverBeforeRetry for per-model quota skipping in combos.
}
lastError = err;
if (urlIndex + 1 < fallbackCount) {
if (!skipUpstreamRetry && urlIndex + 1 < fallbackCount) {

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: When skipUpstreamRetry is true and a fetch error (network timeout, DNS failure, etc.) occurs, the code throws immediately at line 980 without trying remaining fallback URLs. This means if the executor has multiple URLs configured and the first one has a transient network issue, it throws instead of trying the next URL. The combo layer can handle HTTP error responses (429, 500, etc.) but cannot catch thrown errors from the executor — they propagate up the call stack. This asymmetry (error responses skip retry at line 962, but fetch errors skip fallback URL progression here) could cause premature failures when skipUpstreamRetry: true is set.


break; // Move to next model
// All models failed in this set try
const latencyMs = Date.now() - startTime;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: latencyMs is calculated from startTime which is reset at the start of each set try (line ~1984). When recordedAttempts === 0 on a subsequent set try (all targets skipped due to cooldown), the recorded latency only reflects that single set try's duration, not the total time across all set tries. Consider tracking a totalStartTime outside the set try loop for accurate total latency reporting.

return quality.clonedResponse ?? result;
}
// Track earliest retryAfter
if (

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: earliestRetryAfter is scoped to each set try (declared at line ~1982). If the first set try collects a retryAfter from an upstream response, but the second set try fails without collecting any retryAfter (e.g., all targets skipped due to cooldown), the final error response at line 2308-2312 will not include the retryAfter from the first attempt. Consider tracking globalEarliestRetryAfter outside the set try loop so the client gets the best retry-after hint from any attempt.

@diegosouzapw diegosouzapw changed the title Release/v3.8.0 Release/v3.8.0 — full changelog with 660+ commits 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.
// Estimate input tokens once; exclude candidates whose known context limit is too small.
// Uses the same 4-chars-per-token heuristic as contextManager.ts::compressContext().
// Null/unknown limits are treated as "include" to avoid incorrectly dropping valid targets.
const estimatedInputTokens = estimateTokens(body?.messages ?? []);

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: estimateTokens(body?.messages ?? []) passes a messages array to estimateTokens, which uses JSON.stringify() on the entire array including role/content metadata overhead (e.g., {"role":"user","content":"..."}). This overestimates token count compared to actual content tokens, potentially causing valid targets with borderline context limits to be incorrectly filtered out. Consider extracting only message content strings before estimation, or document the overestimation risk.

if (Number.isFinite(inputPrice) && inputPrice >= 0) {
costPer1MTokens = inputPrice;
if (Number.isFinite(outputPrice) && outputPrice >= 0) {
costPer1MTokens =

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: OUTPUT_TOKEN_RATIO = 0.4 (40% output, 60% input) is hardcoded and applied uniformly to all models in cost blending. Models with very different input/output ratios (e.g., reasoning-heavy models with large output vs summarization with large input) will have inaccurate cost estimates. Consider making this configurable or provider-specific.

oyi77 and others added 2 commits May 20, 2026 01:36
… 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
VALUES (?, ?, 1, datetime('now'))
VALUES (?, ?, ?, datetime('now'))
ON CONFLICT(api_key_id)
DO UPDATE SET total_xp = total_xp + excluded.total_xp, updated_at = datetime('now')`

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: ON CONFLICT clause does not update current_level — level becomes stale after first XP award

The ON CONFLICT updates total_xp but leaves current_level unchanged:

DO UPDATE SET total_xp = total_xp + excluded.total_xp, updated_at = datetime('now')

This means user_levels.current_level is only correct on the initial INSERT. Subsequent addXp calls via this function leave the level stale. The events.ts code path compensates by reading totalXp, calling calculateLevel, and updateLevel separately — but any direct caller of addXp (scripts, migrations, new code paths) will get incorrect level data.

Consider either:

  1. Documenting that addXp must always be followed by a level check/update
  2. Having addXp return the new total_xp so callers can decide if level needs updating


const url = new URL(request.url);
const scope: LeaderboardScope = (url.searchParams.get("scope") || "global") as LeaderboardScope;
const limit = Number(url.searchParams.get("limit") || 100);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: No validation on limit query parameter

The limit is parsed with Number() and passed directly to SQL without bounds checking:

const limit = Number(url.searchParams.get('limit') || 100);

This could result in limit=0, negative values, or extremely large numbers (e.g., limit=999999999) causing performance issues. Consider adding min/max validation:

const limit = Math.min(Math.max(Number(url.searchParams.get('limit') || 100), 1), 1000);

- 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)
Comment on lines +87 to +88
const stripped = removed
.replace(/<[^<>]+>/g, "")

const token = authHeader.slice(7);
const tokenHash = crypto
.pbkdf2Sync(token, "omniroute-federation-salt", 1000, 32, "sha256")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: Hardcoded PBKDF2 salt "omniroute-federation-salt" — should be extracted to a shared constant or configurable via environment variable. Same salt is duplicated across 3 files (leaderboard/route.ts, score/route.ts, servers.ts). If rotation is needed later, changing a string literal in multiple files is error-prone.

const crypto = await import("crypto");
const tokenHash = crypto.createHash("sha256").update(token).digest("hex");
const tokenHash = crypto
.pbkdf2Sync(token, "omniroute-federation-salt", 1000, 32, "sha256")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: Hardcoded PBKDF2 salt "omniroute-federation-salt" — same duplication as leaderboard/route.ts. Consider a shared constant in a config file (e.g., src/lib/gamification/constants.ts) so all federation hashing uses the same source of truth.

const id = crypto.randomUUID();
const apiKeyHash = crypto.createHash("sha256").update(apiKey).digest("hex");
const apiKeyHash = crypto
.pbkdf2Sync(apiKey, "omniroute-federation-salt", 1000, 32, "sha256")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: Hardcoded PBKDF2 salt "omniroute-federation-salt" — third occurrence of the same salt string. Centralize this to a single constant so it can be rotated or made configurable without touching multiple files.

@diegosouzapw
diegosouzapw merged commit 6248699 into main May 20, 2026
6 of 7 checks passed
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>
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>
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>
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.

5 participants