Skip to content

fix(api): point /api/cache/stats at the real semantic cache - #9446

Closed
Poid-ZA wants to merge 35 commits into
diegosouzapw:release/v3.8.50from
Poid-ZA:fix/dashboard-cache-stats-wrong-source
Closed

Poid-ZA wants to merge 35 commits into
diegosouzapw:release/v3.8.50from
Poid-ZA:fix/dashboard-cache-stats-wrong-source

Conversation

@Poid-ZA

@Poid-ZA Poid-ZA commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

  • /api/cache/stats read cacheLayer.getPromptCache(), a singleton with zero callers anywhere in the request pipeline — it always reported 0 hits/misses regardless of real cache activity.
  • The dashboard /dashboard/health and /dashboard/usage (BudgetTelemetryCards) both fetch this endpoint directly, so they were always displaying a dead cache instance instead of the real one.
  • The cache actually wired into chatCore.ts on every request is semanticCache.ts. Added getMemoryLayerStats() there, matching the exact field shape (size/maxSize/bytes/maxBytes/hits/misses/evictions/hitRate as a number) both existing consumers already depend on, and repointed the route + its DELETE handler at it.
  • Removed the now-unused getPromptCache() singleton from cacheLayer.ts. The LRUCache class it wrapped is unaffected and still used directly by semanticCache.ts.

Test plan

  • node --import tsx/esm --test tests/unit/semantic-cache.test.ts — new tests for getMemoryLayerStats() written first (failed with the missing export), then implemented and passing (23/23 total in file)
  • npm run typecheck:core — clean
  • npx eslint on all four changed files — clean
  • Confirmed via grep that nothing else in src/, open-sse/, or tests/ referenced cacheLayer.getPromptCache before removing it

diegosouzapw and others added 30 commits June 20, 2026 07:36
…itive) (diegosouzapw#4386)

getMitmToolHosts returns string[], so .includes(host) is Array.prototype.includes
(exact membership). CodeQL's js/incomplete-url-substring-sanitization heuristic
misreads it as a String.includes() URL-substring sanitization check and raises a
HIGH alert. Switch to .some(h => h === host) — identical semantics, explicit intent,
no flagged pattern. Surfaced post-v3.8.30 (diegosouzapw#4325) once the test landed on main.

Test-only change (no runtime behavior); the suite still passes and the CodeQL
re-scan on merge clears the alert.
Adds reasoning.summary=auto + reasoning.encrypted_content include for Codex. Thanks @xz-dev.
…dels (diegosouzapw#4341)

NVIDIA NIM asymmetric embedding models (e.g. nvidia/nv-embedqa-e5-v5) reject
requests without an `input_type` ("query" | "passage") with 400 "'input_type'
parameter is required". The embedding registry now carries a model-level
default param for the asymmetric NVIDIA model, and the embeddings handler
injects a model's default params into the upstream body only when the client
omitted them, leaving a client-supplied value untouched.

Reported-by: hydraromania (decolua/9router#1378)

Co-authored-by: hydraromania <252583922+hydraromania@users.noreply.github.com>
…s].hooks (diegosouzapw#4342)

Codex renamed the `codex_hooks` feature flag to `hooks`; recent Codex CLI
versions ignore the old key and warn. When OmniRoute rewrites an existing
config.toml (configure/reset Codex provider) it now renames
[features].codex_hooks -> [features].hooks, preserving the value and never
clobbering an already-present `hooks`, then drops the deprecated key. The
migration is a no-op when the flag is absent and runs on both the POST and
DELETE config paths.

Reported-by: Bian-Sh (decolua/9router#1327)

Co-authored-by: Bian-Sh <24520547+Bian-Sh@users.noreply.github.com>
…diegosouzapw#4344)

The streaming response translator's same-format fast path returned
`[chunk]` unconditionally, so the end-of-stream null/flush signal
(chunk === null) propagated as a literal `[null]`. Downstream this surfaced
as an empty `data: null` SSE event between chunks and crashed strict clients
(e.g. Factory Droid BYOK on /v1/responses). The fast path now returns `[]`
for the null flush while still passing real chunks through unchanged.

Reported-by: thaitryhand (decolua/9router#1052)

Co-authored-by: thaitryhand <248103256+thaitryhand@users.noreply.github.com>
…h (Mistral 422) (diegosouzapw#4350)

Strict OpenAI-compatible upstreams (e.g. mistral/codestral-latest) reject
client-only assistant echo fields sent back as input with 422
extra_forbidden (the report hit messages[].assistant.reasoning_content via
Codex /responses). Only reasoning_content was stripped on the OpenAI target
path; the sibling fields reasoning / refusal / annotations / cache_control
leaked through. They are now all dropped on the non-reasoner OpenAI target
path. `audio` is intentionally preserved (OpenAI audio models reference a
prior assistant audio response by id; Mistral never emits audio).

Reported-by: xxy9468615 (decolua/9router#1649)

Co-authored-by: xxy9468615 <63351664+xxy9468615@users.noreply.github.com>
…diegosouzapw#4343)

* fix(cli): honor TAILSCALE_AUTHKEY for non-interactive tailscale login (port from 9router#1263)

startTailscaleLogin built `tailscale up` without ever reading
process.env.TAILSCALE_AUTHKEY, so a pre-authenticated / headless daemon
waited for an interactive auth URL and timed out (~15s). When
TAILSCALE_AUTHKEY is set it is now passed via `--auth-key=` (an argv element
to spawn(binary, args) — no shell interpolation, Hard Rule diegosouzapw#13); when unset,
behavior is unchanged. The arg builder is extracted into a pure exported
`tailscaleUpArgs()` for testing.

Reported-by: ipeterpetrus (decolua/9router#1263)
Co-authored-by: ipeterpetrus <93033698+ipeterpetrus@users.noreply.github.com>

* chore(quality): rebaseline tailscaleTunnel.ts file-size to 1202 (diegosouzapw#1263 +13)

---------

Co-authored-by: ipeterpetrus <93033698+ipeterpetrus@users.noreply.github.com>
…ponse (diegosouzapw#4351)

* fix(dashboard): OAuth modal surfaces real error on non-JSON responses (port from 9router#1318)

The OAuth connect/reauth modal called `await res.json()` unconditionally, so
a non-JSON error response (e.g. a plain-text 500 page from a build/OAuth
endpoint) threw `Unexpected token 'I'...` and hid the real failure. New
shared helpers parseResponseBody / getErrorMessage (src/shared/utils/api.ts)
read the body safely (JSON when JSON, raw text otherwise) and produce a clean
message either way; every modal fetch site now uses them.

Reported-by: DNNYF (decolua/9router#1318)
Co-authored-by: DNNYF <74033321+DNNYF@users.noreply.github.com>

* fix(dashboard): type OAuth modal response body as Record<string, unknown> (t11 any-budget)

Switch the parseResponseBody casts from Record<string, any> to
Record<string, unknown> so OAuthModal.tsx stays within its t11 explicit-any
budget. getErrorMessage already takes unknown; the success paths typecheck
clean under strict:false. No runtime change.

---------

Co-authored-by: DNNYF <74033321+DNNYF@users.noreply.github.com>
… content parts (diegosouzapw#4345)

* fix(translator): accept AI SDK-style { type: image, image: "data:..." } parts (port from 9router#1330)

Several OpenAI-input translators only recognized images shaped as
`image_url.url` (or an object with `.source`/`.url`), so an AI SDK-style
content part where `image` is a bare data-URL STRING was silently dropped
before reaching a vision provider (OpenCode is one affected client; the gap
is generic). The OpenAI->Claude, OpenAI->Kiro and OpenAI->Gemini/Antigravity
translators now parse a string `image` data URL into each provider's native
image shape (Claude base64 source, Kiro images[].source.bytes, Gemini
inlineData).

Reported-by: mugnimaestra (decolua/9router#1330)
Co-authored-by: mugnimaestra <13349159+mugnimaestra@users.noreply.github.com>

* chore(quality): freeze openai-to-kiro.ts file-size at 807 (diegosouzapw#1330 +9, over 800 cap)

---------

Co-authored-by: mugnimaestra <13349159+mugnimaestra@users.noreply.github.com>
…iegosouzapw#4352)

* fix(dashboard): show a disabled connection's last error in the row (port from 9router#1447)

The provider card's error badge counts a disabled connection (isActive ===
false) that has an error — its effective status is still
error/expired/unavailable — but the connection row hid the lastError text for
disabled rows, so the operator saw the count without the cause. The row's
error-visibility decision is extracted into shouldShowConnectionLastError()
and now shows the error whenever there is one, regardless of the active
toggle.

Reported-by: ntdung6868 (decolua/9router#1447)
Co-authored-by: ntdung6868 <103993527+ntdung6868@users.noreply.github.com>

* chore(quality): rebaseline ConnectionRow.tsx file-size to 942 (diegosouzapw#1447 +1 import)

---------

Co-authored-by: ntdung6868 <103993527+ntdung6868@users.noreply.github.com>
…iegosouzapw#4347)

* fix(providers): bound OAuth connection-test probe with a timeout (port from 9router#1449)

The OAuth path of "Test Connection One-by-One" called bare fetch() with no
AbortController/signal, so a provider probe that accepted the socket but never
responded wedged the test queue forever. Both the initial probe and the
post-refresh retry are now bounded with AbortSignal.timeout(30s) — matching the
API-key path's existing budget — and a timed-out probe resolves as a failure
with a clear "Test timed out after 30s" message in the route's normal error shape.

Reported-by: ntdung6868 (decolua/9router#1449)
Co-authored-by: ntdung6868 <103993527+ntdung6868@users.noreply.github.com>

* chore(quality): rebaseline providers test route file-size to 887 (diegosouzapw#1449 + sibling diegosouzapw#1444 growth)

---------

Co-authored-by: ntdung6868 <103993527+ntdung6868@users.noreply.github.com>
… token (diegosouzapw#4353)

A Codex connection whose OAuth refresh is fully healthy but whose ChatGPT
account has been deactivated by the provider gets a 401 from the upstream
API. The connection test labeled that the same as a bad credential
("Token invalid or revoked" -> upstream_auth_error), so an operator could not
tell a deactivated account from a revoked token. The test now reads the
401/403 body and, when it indicates account deactivation, classifies it as
account_deactivated (which the dashboard already renders as "Account
Deactivated"); a plain auth 401 is unchanged.

Reported-by: ntdung6868 (decolua/9router#1444)

Co-authored-by: ntdung6868 <103993527+ntdung6868@users.noreply.github.com>
…oved (diegosouzapw#4348)

* fix(db): cascade-delete orphaned model aliases when a provider is removed (port from 9router#1409)

Deleting a custom provider removed its connections and node but left the
imported model-alias rows (key=<alias>, value="<providerId>/<model>") behind,
so re-importing the same provider was blocked by stale "already exists" aliases.
Add a deleteModelAliasesForProvider(providerId) DB helper that drops every alias
whose stored value begins with "<providerId>/", and call it from the provider-node
DELETE handler so a fresh import is unblocked.

Reported-by: nguyenvanhuy0612 (decolua/9router#1409)
Co-authored-by: nguyenvanhuy0612 <57367674+nguyenvanhuy0612@users.noreply.github.com>

* chore(quality): rebaseline models.ts file-size to 1221 (diegosouzapw#1409 + sibling diegosouzapw#1294 growth)

---------

Co-authored-by: nguyenvanhuy0612 <57367674+nguyenvanhuy0612@users.noreply.github.com>
…stom model (diegosouzapw#4349)

The POST /api/provider-models handler read the rest of the body but never
the two token-limit fields, and addCustomModel() had no parameter for them,
so the form values were dropped on write while the DB layer and /v1/models
catalog already round-trip inputTokenLimit/outputTokenLimit. Accept the two
optional limits in the schema, forward them through the handler, and persist
them in addCustomModel(). TDD: failing-then-passing unit test.

Reported-by: codename-zen (decolua/9router#1294)

Co-authored-by: codename-zen <263238141+codename-zen@users.noreply.github.com>
…w#4391)

One-time reconciliation of the docs with every user-facing feature shipped since
v3.8.20 (we had never done a dedicated pass, so debt had accumulated):

- README: new '✨ What's New' section (curated v3.8.20→v3.8.30 highlights).
- New guides: CLI-INTEGRATIONS (all setup-*/launch commands), MITM-TPROXY-DECRYPT
  (transparent-decrypt epic), CONTEXT_EDITING (delegated Anthropic clear_tool_uses).
- Refreshed: AUTO-COMBO (auto/<category>:<tier> + Arena-ELO), API_REFERENCE
  (x-omniroute-no-memory), MEMORY (int8 quantization + off-by-default), RESILIENCE
  (model-lockout success-decay), RTK, AGENTBRIDGE, TRAFFIC_INSPECTOR, GUARDRAILS,
  CLOUD_AGENT, ENVIRONMENT, SETUP_GUIDE, CLI-TOOLS, MCP-SERVER.
- Regenerated PROVIDER_REFERENCE (231 providers); synced the count in README/CLAUDE/AGENTS.
- Allowlisted external-tool env vars (OPENAI_API_BASE, PROMPTFOO_PROVIDER_KEY) and the
  STREAM_RECOVERY config-object name in the docs-accuracy gates.

All claims source-verified; check:docs-all (sync/counts/env/links/fabricated) passes.
Going forward this runs every release via generate-release step 6b.
…(native Claude) (diegosouzapw#4389)

Forced tool_choice now strips the adaptive thinking injection to avoid Anthropic 400. Thanks @NomenAK.
…ter#344) (diegosouzapw#4373)

OpenAI-style `image_url` parts with an `http://` or `https://` URL reached
`convertOpenAIContentToParts` and were dropped with only a `console.warn`,
because Gemini's `inlineData` requires base64 (the helper is synchronous and
cannot fetch+encode upstream assets). Gemini's `Part` schema, however, natively
accepts `fileData: { fileUri }` for remote URIs — the model fetches the asset
itself.

The helper now emits a `fileData` part (`mimeType: "image/*"`, inferred upstream
on fetch) for HTTP/HTTPS URLs instead of silently dropping them. Vision requests
that pass a URL — not a data: URI — now reach Gemini intact.

No behavioral change for:
- `data:` URIs → still emitted as `inlineData` with the parsed media type.
- Unsupported schemes (e.g. `ftp:`) → still skipped (Gemini would reject them).

The openai-to-claude side already passed HTTP/HTTPS URLs through as
`source: { type: "url", url }` (lines 573–578) — the upstream PR's Claude-side
change was already covered.

Regression test: tests/unit/gemini-helper-http-image-url-port344.test.ts
(4 cases: https URL, http URL, data: URI no-regression, unsupported-scheme guard).


Inspired-by: decolua/9router#344

Co-authored-by: Ibrahim Ryan <ryan@nuevanext.com>
…g Claude Code requests (port from 9router#663) (diegosouzapw#4374)

Claude-Code-compatible providers force the executor-level `stream` flag on
via `upstreamStream = stream || isClaudeCodeCompatible`
(open-sse/handlers/chatCore.ts), but the outgoing body keeps the caller's
original `stream: false`. The shared `stream && targetFormat === "openai"`
branch in DefaultExecutor.transformRequest then injected
`stream_options: { include_usage: true }` onto a body that still said
`stream: false`, and qwen upstream rejected the request with
`400 "'stream_options' only set this when you set stream: true"`. The same
rejection surfaced when the body carried `thinking` / `enable_thinking`.

The qwen branch now skips the injection (and strips any client-sent
`stream_options`) when the body explicitly says `stream: false` or
requests thinking, leaving regular qwen streaming requests with the
include_usage injection intact. Other providers are unaffected.

Adds a TDD regression with 4 cases covering both opt-out paths and the
normal-streaming positive control.


Inspired-by: decolua/9router#663

Co-authored-by: anuragg-saxenaa <anuragg.saxenaa@gmail.com>
…llowlist (port from 9router#998) (diegosouzapw#4372)

The OAuth callback at `/callback` previously fell back to
`window.opener.postMessage({ code, state, ... }, "*")` whenever the opener
was cross-origin. The fallback was intended to support remote-OmniRoute +
local-loopback callbacks (where opener and callback live on different
origins), but the same code path also delivers the OAuth code/state to any
hostile opener that pops the well-known callback URL — letting that
attacker complete the OAuth flow as the user.

Replace the wildcard fallback with iteration over a fixed allowlist:
`window.location.origin` (same-origin parent — the popup-mode dashboard)
plus Codex's fixed loopback helper (`http://localhost:1455` and the IPv4
literal `http://127.0.0.1:1455`). The browser drops `postMessage` to any
opener whose actual origin is not in `targetOrigin`, so the message reaches
only known parents and is silently dropped for any other. The same-origin
fallback path is unchanged — methods 2 (`BroadcastChannel`) and 3
(`localStorage` storage event) still cover same-origin openers that COOP
severed.

The `openerSameOrigin` probe stays in place to drive the auto-close vs
manual-copy UI decision (no behavior change for the success path).

Adds a regression test (`tests/unit/ui/oauth-callback-postmessage-scope.test.tsx`)
that mounts the page with a stubbed cross-origin opener and asserts no
`postMessage` call ever uses `"*"` and every call lands on an allowlisted
origin. The test failed against the pre-fix code (red), passes after the
fix (green) — TDD per CLAUDE.md hard rule diegosouzapw#18.

Partial port of upstream decolua/9router#998: the upstream PR also
re-enabled TLS verification on a DNS-bypass fetch in `open-sse/utils/proxyFetch.js`;
that part is N/A here because OmniRoute's `proxyFetch.ts` never disabled
TLS verification (no `rejectUnauthorized: false` anywhere in the file).


Inspired-by: decolua/9router#998

Co-authored-by: aeonframework <aeon@aeonframework.dev>
…iegosouzapw#4365)

Combo per-target timeout inherited the full FETCH_TIMEOUT_MS (600s) when a
combo did not set its own targetTimeoutMs, so a single hung/slow target stalled
the whole combo for up to 10 minutes before falling through to the next model.

Introduce DEFAULT_COMBO_TARGET_TIMEOUT_MS (120s) as the unset-default in
resolveComboTargetTimeoutMs (new 3rd arg) and wire it in phaseComboSetup. The
upstream ceiling (600s) and per-combo opt-out (targetTimeoutMs, up to the
ceiling) are preserved; single non-combo requests are unchanged. For streaming
requests this only bounds time-to-first-headers, so token generation is not cut
short.

TDD: failing-then-passing unit test in tests/unit/combo-config.test.ts.
…ispatchers (diegosouzapw#4362)

Primeiro incremento da de-dup dos 2 dispatchers de combo (handleComboChat +
handleRoundRobinCombo). O bloco de pre-check diegosouzapw#1731/#1731v2 (skip de target já
exhausted no provider/connection) era BYTE-IDÊNTICO nos dois (mesmas condições,
mesmas mensagens), diferindo só na tag de log e no control-flow.

- comboPredicates.ts: getExhaustedTargetSkipReason(target, exhaustedProviders,
  exhaustedConnections) — predicate PURO que retorna a mensagem de skip (ou null);
  cada dispatcher mantém seu próprio log-tag + control-flow (return null / continue)
  + fallbackCount. No mutate do stryker (cobertura de mutação).
- combo.ts: −20 linhas (os 2 blocos viram 1 chamada cada).
- 7 testes de caracterização travam condições + strings exatas.

Comportamento preservado: 376/376 testes combo (357 caracterização + 7 novos),
integração sse-correctness 5/5, typecheck 0, complexity neutro (1895), file-size
encolhe. Próximo incremento: de-dup do error-handling/exhausted-tracking (handleTargetError).
…ss both dispatchers (diegosouzapw#4366)

Segundo incremento da de-dup dos 2 dispatchers (handleTargetError). Após cada erro
de target, ambos rodavam um bloco quase-idêntico que marca o provider exhausted
(diegosouzapw#1731), a conexão connection-errored (#1731v2) ou o provider transiently rate-limited.

- combo/targetExhaustion.ts: applyComboTargetExhaustion(target, opts) — atualiza os
  3 Sets de exhaustion e retorna providerExhausted. As MUTAÇÕES de Set (que dirigem o
  skip de targets, lidas por getExhaustedTargetSkipReason) são BYTE-IDÊNTICAS nos dois;
  as diferenças reais viram parâmetros: tag, allAccountsRateLimited (termo extra do RR,
  false no handleComboChat), exhaustedLogLevel (info no handleComboChat, debug no RR).
  Connection-level extraído p/ markConnectionLevelExhaustion (privado, <15 complexity).
- combo.ts: −73 linhas; 4 imports órfãos removidos.
- 7 testes de caracterização travam as mutações + o return.

ÚNICA mudança de comportamento: o WORDING das mensagens de log do RR ganha o sufixo
'on remaining targets' (cosmético; mesmo #code, mesmas mutações, mesmos níveis de log).
376/376 combo (caracterização preservada), integração sse 5/5, typecheck 0, complexity
neutro (1895), file-size encolhe.
…omposition start) (diegosouzapw#4371)

Primeiro incremento da decomposição do chatCore.ts (5127 LOC, hot-path mais quente).
O guard de memória do topo do handleChatCore (rejeita 503 quando o heap V8 passa o
threshold de shed) vira um leaf testável, co-locado com o threshold em heapPressure.ts.

- heapPressure.ts: checkHeapPressureGuard(heapUsedMb, thresholdMb) — retorna o result
  503 pronto ou null. Byte-idêntico ao guard inline (mesmo check, mesma 503, mesmo warn).
  A figura de heap fica em telemetria INTERNA, nunca no response do cliente (Hard Rule diegosouzapw#12).
- chatCore.ts: o bloco inline (~22 ln) vira 3 linhas; import órfão de HEAP_PRESSURE_THRESHOLD_MB
  trocado por checkHeapPressureGuard.
- 3 testes novos (incl. assert Rule diegosouzapw#12: o MB medido não vaza no payload).

complexity-baseline 1895->1896: drift de base pós-diegosouzapw#4338 (medido com minhas mudanças
stashed = 1896); esta mudança é complexity-NEUTRA (helper complexity 2, handleChatCore só
perde código). 190/190 chatcore tests, typecheck 0, file-size encolhe.
…osouzapw#4383)

en-only i18n, fetch-start-timeout hardening, EngineConfigPage icon fix, CI build-artifact-reuse overhaul. Memory production hunk dropped as a no-op (tests kept). Thanks @JxnLexn.
…estore green) (diegosouzapw#4396)

The combo-dispatch cases in combo-stream-readiness-fallback.test.ts deliberately
fail `glm` (zombie streams / repeated 504s), which legitimately trips the
per-provider circuit breaker. That OPEN state is a module-level singleton, so it
leaked into the next test and combo.ts then SKIPPED `glm/*` targets entirely
("Skipping … circuit breaker OPEN"). That made "combo does not retry stream
readiness timeouts on the same model" never attempt glm/zombie — expected
['glm/zombie','openai/gpt-5.4-mini'] but got ['openai/gpt-5.4-mini'].

This was a pre-existing red on release/v3.8.31 (present at the cycle-open tip),
order-dependent: the test passes in isolation, fails after the preceding cases.
Add a test.beforeEach(resetAllCircuitBreakers) so each scenario starts from a
clean breaker slate. Test-isolation only — the breaker behavior is correct and
no production code or assertion changes. Full combo suite: 390/390 green.
…diegosouzapw#3932) (diegosouzapw#4381)

Combos UI split + next.config perf + 1-click local Redis launcher + bifrost relay. Review fixes (co-author): --rm/--restart conflict, error sanitization, UI/route names, dead-guard re-doc, bifrost Zod, IPv6 + CLI test fixes. Thanks @KooshaPari.
…iegosouzapw#4384)

OC parses model ids on '/'; combo keys now carry 'omniroute/' (was 'combo/'). Live-validated against the VPS via OpenCode. Thanks @herjarsa.
Finalize the v3.8.31 release: reconcile the CHANGELOG (full commit-to-bullet
coverage, 26 bullets across Features/Fixed/Security/Maintenance), refresh the
README What's New section, back-fill the .30/.31 sections into the 41 i18n
CHANGELOG mirrors, and add TAILSCALE_AUTHKEY to the env contract
(.env.example + ENVIRONMENT.md).
…e conflict

The same CodeQL false-positive fix landed twice — diegosouzapw#4386 on release/v3.8.31 and
diegosouzapw#4387 directly on main — with only the explanatory comment differing (the
assertion is byte-identical). Adopt main's comment wording on the release branch
so the release PR merges without a comment-only conflict.
diegosouzapw and others added 4 commits June 20, 2026 14:01
…souzapw#4373, any-budget diegosouzapw#4389, masking allowlist diegosouzapw#4384)

The release PR's full CI surfaced cumulative cycle drift the per-PR fast gates
skip:
- tests/unit/translator-openai-to-gemini.test.ts: realign 3 cases to diegosouzapw#4373's
  HTTP/HTTPS-URL fileData pass-through (they asserted the old warn-and-drop).
- scripts/check/check-t11-any-budget.mjs: base.ts budget 0→2 — diegosouzapw#4389 compares
  tool_choice against the string literal "any" (not a TS any type).
- config/quality/test-masking-allowlist.json: allowlist diegosouzapw#4384's opencode combos
  net-assert reduction (obsolete combo/ namespace removed).
No production behavior change.
Force a fresh pull_request CI run on the head carrying the cycle-drift fixes
(gemini diegosouzapw#4373 tests, any-budget diegosouzapw#4389, masking allowlist diegosouzapw#4384) — the prior
synchronize event did not spawn a ci.yml run.
…lose drift)

The Quality Ratchet failed on the release PR: eslintWarnings 3839 > baseline
3836. The +3 is end-of-cycle drift from legitimately-merged feature PRs
(diegosouzapw#4381/diegosouzapw#4383/diegosouzapw#4373/diegosouzapw#4389/diegosouzapw#4384/diegosouzapw#4410) — `any` is allowed (warn) in open-sse/
and tests/. Verified the release reconciliation files add 0 warnings (gemini
test delta 79<->79, mitm test 0). Same precedent as prior cycle-close
rebaselines. Authorized as part of the end-to-end release.
@Poid-ZA
Poid-ZA requested a review from diegosouzapw as a code owner August 4, 2026 17:53
@diegosouzapw
diegosouzapw changed the base branch from release/v3.8.31 to release/v3.8.50 August 5, 2026 05:01
The route read cacheLayer.getPromptCache(), a singleton with zero
callers anywhere in the request pipeline, so it always reported
0 hits/misses regardless of real cache activity. The dashboards
that consume this endpoint directly (health, usage budget cards)
were always showing a dead cache instance.

The actually-wired-in cache is semanticCache.ts, called from
chatCore.ts on every request. Added getMemoryLayerStats() there,
matching the same field shape the two dashboard consumers already
depend on (size/maxSize/bytes/maxBytes/hits/misses/evictions/
hitRate as a number), and repointed the route + its DELETE handler
at it. Removed the now-unused getPromptCache() singleton from
cacheLayer.ts; the LRUCache class it wrapped is still used by
semanticCache.ts directly.

Signed-off-by: Thor Holdings <wehnke@thorholdings.com>
@Poid-ZA
Poid-ZA force-pushed the fix/dashboard-cache-stats-wrong-source branch from 45f18da to 4871337 Compare August 5, 2026 18:35
@diegosouzapw

Copy link
Copy Markdown
Owner

Thanks for this contribution — the change itself is wanted! I'm closing this PR for a mechanical reason only (nothing wrong with your fix): the branch was cut from a very old point of the repo, so its tree now conflicts with release/v3.8.50 on dozens of unrelated files (docs, CI workflows, baselines, lockfiles). Merging it as-is could silently roll back months of unrelated work, and resolving 70+ conflicts by hand is riskier than recreating a branch that only contains your change.

How to recreate it cleanly (5 minutes):

# 1. Sync your fork's view of the upstream release branch
git fetch upstream release/v3.8.50        # add the remote first if needed:
                                          # git remote add upstream https://github.com/diegosouzapw/OmniRoute.git

# 2. Cut a FRESH branch from the current release tip (never from main or an old branch)
git switch -c <your-branch-name-v2> upstream/release/v3.8.50

# 3. Bring over ONLY your change. Two easy options:
#    a) cherry-pick your original commit(s):
git cherry-pick <sha-of-your-commit>      # repeat per commit; resolve only YOUR files
#    b) or re-apply the diff of just your files:
git checkout <old-branch> -- <path/to/your/file1> <path/to/your/file2>

# 4. Sanity: the diff must contain ONLY your files
git diff upstream/release/v3.8.50 --stat

# 5. Run the focused checks, push, and open the PR against release/v3.8.50
npm run typecheck:core && node --import tsx/esm --test tests/unit/<your-test>.test.ts
git push -u origin <your-branch-name-v2>

Two extra notes that will save you time:

  • Several base-side CI problems were fixed today (leaked failing tests, a wrong ESLint baseline, a missing MDX frontmatter). Any red you saw on this PR before was inherited from the base, not caused by your change — a fresh branch from today's tip starts green.
  • Please tick "Allow edits by maintainers" on the new PR: it lets us refresh your branch when the base moves, instead of asking you every time.

Reopen at will — or open the recreated PR and mention this one so the history links up. Happy to help if any step misbehaves.

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.

7 participants