Skip to content

feat(dashboard): collapsible JSON tree viewer for request/response payloads - #11703

Merged
diegosouzapw merged 59 commits into
cheaper-inference:release/v3.8.51from
hartmark:feat/collapsible-json-tree-viewer
Aug 30, 2026
Merged

diegosouzapw merged 59 commits into
cheaper-inference:release/v3.8.51from
hartmark:feat/collapsible-json-tree-viewer

Conversation

@hartmark

@hartmark hartmark commented Aug 26, 2026 •

Copy link
Copy Markdown
Contributor

What Problem This Solves

RequestLoggerDetail's PayloadSection and StreamSection rendered every request/response body, pipeline stage, and event-stream JSON chunk as a flat <pre> text dump. Finding anything in a nested payload (tool call args, message arrays, deeply nested provider responses) meant scrolling through the whole raw JSON string.

Why This Change Was Made

Six commits, applied in this order, each building on the last:

  1. feat(dashboard): render log payloads as a collapsible JSON tree — PayloadSection renders parseable JSON as a collapsible tree (react-json-view-lite) instead of raw text, first two levels expanded by default. Falls back to the original <pre> dump for anything that isn't valid JSON.
  2. fix(dashboard): render event-stream JSON as trees, fix call-id/conversation-id display — extends the same tree rendering to StreamSection's SSE event chunks (splitting a raw stream capture into text/JSON segments), plus fixes call-id and conversation-id display.
  3. feat(dashboard): add collapse/expand-depth controls to JSON tree boxes — a shared control row (collapse all / one level / current level / expand one level / expand all) next to each box's copy button. One global level (zustand + persist, matching the existing themeStore pattern) keeps every JSON tree box on the page in sync and survives reloads/navigation.
  4. feat(dashboard): per-section expand levels + timestamp hover tooltips — expand level becomes per-section instead of one global setting (so expanding one box doesn't blow out every other box on the page), plus hover tooltips on raw epoch timestamp values showing a human-readable date.
  5. fix(dashboard): stop expand-state reset on individual node toggle, scroll overlay backdrop — react-json-view-lite was re-seeding every node's expand state on unrelated re-renders (memoized the callback), and the modal's backdrop margin didn't forward wheel-scroll to the panel.
  6. refactor(dashboard): replace react-json-view-lite with react18-json-view — react-json-view-lite renders its expand/collapse icon inline before the key label, so sibling leaf/branch labels at the same depth never align (the icon's width displaces one but not the other). react18-json-view renders the fold chevron after the value's opening brace, fixing the alignment class of bug structurally rather than through position/CSS tuning (an earlier CSS-only attempt worked for one nested tree but broke multi-chunk SSE stream views, and was reverted — see the git history on this branch).

This PR was originally built well before release/v3.8.51 existed and had drifted ~3 months behind it. Rebuilt cleanly on top of current .51 (six sequential cherry-picks, conflicts resolved against the intervening independent changes to these same files — nothing about the feature's own logic changed).

User Impact

  • Nested request/response bodies, tool call arguments, and SSE event chunks are readable as a collapsible tree instead of a raw text dump.
  • Per-section expand-depth controls, persisted across reloads and navigation.
  • Timestamp values show a human-readable date on hover.
  • Falls back to the original <pre> dump unchanged for non-JSON content — no behavior change there.

Evidence

  • Original commits were each verified live against the omniroute-dev dashboard with real production data (Playwright) at the time they were authored — see individual commit messages for specifics (all 7 boxes syncing on a control click, level persisting across reload/navigation, a real provider timestamp showing correctly on hover).
  • Rebuilt on current release/v3.8.51: npx tsc --noEmit -p tsconfig.typecheck-core.json clean, eslint clean on all touched files.
  • Tests: payload-section-collapsible-json.test.tsx + timestamp-titles.test.tsx (vitest, 8/8) and json-tree-expand-store.test.ts + short-call-id.test.ts (node:test, 11/11) all passing on the rebuilt branch.

Update: fixed failing CI (pt-BR i18n key parity)

Unit Tests fast-path was red: this PR's new requestLogger.detail.{collapseAllLevels,collapseOneLevel,currentExpandLevel,expandOneLevel,expandAllLevels} strings landed in en.json but were never mirrored into src/i18n/messages/pt-BR.json, which tests/unit/i18n-pt-br.test.ts hard-fails on for any key drift (the #6695 regression guard — pt-BR is the one locale with exact key-parity enforcement; the other 40 locales use a lenient coverage-percentage gate that a 5-key gap doesn't trip). Added the 5 missing pt-BR translations, following the existing Expandir/Recolher convention used elsewhere in the same file. Confirmed 0 missing keys post-fix (ran the test's own flatten/diff logic directly against both files).

The other fast-path unit-test failures visible on this PR's CI (combo-provider-cooldown, gamification aggregate-profile, hard-session-lease-bypass-inventory, provider-node-reserved-prefix registry scan, etc.) are unrelated to this PR's diff — they touch subsystems this PR never changes, and the identical failures also appear on #11686 (an unrelated dashboard fix targeting the same release/v3.8.51 base), so they look like pre-existing base-branch breakage rather than something introduced here. Leaving those alone rather than expanding this PR's scope to a release-branch-wide CI repair.

RequestLoggerDetail's PayloadSection rendered every request/response
body and pipeline stage as a flat <pre> text dump, forcing operators to
scroll through the entire raw JSON to find anything in a nested
request. Parseable JSON payloads now render as a collapsible tree via
react-json-view-lite, with the first two levels expanded by default
and deeper nesting collapsed. Falls back to the original <pre> dump
for anything that is not valid JSON (e.g. a captured error string),
since the json prop is display text that is not guaranteed parseable.

Reuses the existing useTheme hook for dark/light styling instead of
duplicating theme detection. react-json-view-lite was added to
config/quality/dependency-allowlist.json (MIT, zero runtime deps,
~116KB unpacked) with explicit approval.
…sation-id display

Three related log-detail-view fixes:

- Event stream (provider/client/openai raw SSE capture) rendered as one
  flat <pre> text dump. Now reconstructs each SSE event from the raw,
  timestamp-chunked capture and renders any data: payload that parses as
  JSON as a collapsible tree (same react-json-view-lite treatment as
  PayloadSection), falling back to plain text for keep-alives/[DONE]/non-
  JSON frames.

- ToolCallBlock/ToolResultBlock truncated call/tool-use ids with a bare
  id.slice(0, 8) on the whole id including its decorative prefix. Ids like
  call-<uuid> or the composite call_call-<uuid>_fc_cal_<hex> form left as
  little as 3 significant characters visible, and most calls in a
  conversation showed the identical "call_cal" — making concurrent tool
  calls visually indistinguishable. shortCallId() now finds the actual
  uuid/hex segment and truncates that (git-log-short-hash style), plus a
  title attribute exposes the full id on hover.

- The metadata grid's "—"/"…" placeholders were bare JSX text,
  not JS string literals, so JSX rendered the literal 6-character escape
  sequence instead of the em dash/ellipsis (e.g. conversation id showed
  "conv_bf015680-3191-4…"). Moved them into string expressions
  ({"—"}/{"…"}) so the escape is actually interpreted.

Verified live against the omniroute-dev dashboard with real production
log data (Playwright, logged in via the dev password): event stream
renders as bold-keyed trees, tool-call ids are now distinct
(c560237/1f0f901/5b891a3/bf7c895 instead of four identical call_cal),
conversation id shows a real "…", and tooltips expose full ids on hover.
PayloadSection and StreamSection each render one or more
react-json-view-lite trees at a fixed, hardcoded expand depth with no
way to see more or less without re-opening the browser dev tools. Adds
a shared control row (collapse all, collapse one level, current level
[0-indexed], expand one level, expand all) next to each box's existing
copy button.

The level is one global setting (jsonTreeExpandStore, zustand +
persist, matching the existing themeStore/emailPrivacyStore pattern)
rather than per-box state: every JSON tree box on the page stays in
sync live when any control is clicked, and the level survives page
reloads and navigating to a different request.

Verified live against the omniroute-dev dashboard with real production
data (Playwright): clicking a control updates all 7 boxes on the page
simultaneously, the level persists across a full reload, and it still
carries over after navigating to a completely different log entry.
Two follow-ups to the collapse/expand controls:

- The expand level was one setting shared by every JSON tree box on the
  page. Different payload/stream boxes carry "interesting" data at
  different nesting depths, so jsonTreeExpandStore now tracks a level
  per sectionId (a stable, locale-independent identifier -- e.g.
  "openaiRequest", "providerEventStream" -- passed alongside each
  section's translated title) instead of one global level. The same
  sectionId across different log entries still shares a level, so it
  still carries over between requests, just scoped per box.

- Number values that are actually Unix timestamps (e.g. `created:
  1787562749` in an event-stream payload) rendered as a bare number with
  no way to read the actual date without doing epoch math by hand.
  react-json-view-lite exposes no per-value render hook (checked its
  type defs), so useTimestampTitles marks number values with a stable
  className via a style override and walks the rendered tree (via
  MutationObserver, since expanding an individual node is internal
  library state that wouldn't otherwise trigger a re-check), adding a
  human-readable title/tooltip to values whose field name looks like a
  timestamp and whose value is a plausible Unix epoch (seconds or ms) --
  without changing the value actually displayed.

Verified live against the omniroute-dev dashboard with real production
data: clicking "expand all" on one section (OPENAI REQUEST -> level 64)
leaves a sibling section (PROVIDER REQUEST) at its own level 2, and a
real `created` timestamp value in a provider event-stream payload now
shows "24 Aug 2026, 10:02:01" on hover while still displaying the raw
epoch number.
…roll overlay backdrop

react-json-view-lite re-seeds every node's expand state whenever the
shouldExpandNode reference changes, not just on mount -- an inline arrow
function recreated it on every unrelated re-render, wiping out manually
expanded/collapsed nodes. Memoize it so it only changes when expandLevel
itself does.

Also forward wheel events from the modal's backdrop margin to the panel,
since only the content area under the cursor previously scrolled.
react-json-view-lite renders its expand/collapse icon inline immediately
before the key label, present only on expandable nodes -- sibling
leaf/branch labels at the same depth never align, since the icon's width
displaces one but not the other. A CSS-only fix attempted earlier this
session (position:absolute icon gutter) worked for a single nested tree but
broke a multi-chunk SSE stream view (multiple stacked top-level trees), and
was reverted.

react18-json-view renders the fold chevron *after* the value's opening
brace, not before the key -- NameValue always renders the key first,
unconditionally, so the key's horizontal position never depends on whether
its value is expandable. This fixes the alignment class of bug structurally
rather than through position tuning, which is more robust against layout
contexts a narrower fix wasn't tested against (verified against both a
single-tree payload section and a multi-chunk stream view; see PR).

Also:
- useTimestampTitles: customizeNode (key+value together) replaces the old
  DOM sibling-walk field-name lookup, but keeps the actual tooltip-setting
  DOM pass, so the marked value's displayed text is never touched.
- .jv-size-chevron gets padding + a matching negative margin to grow the
  mobile tap target without shifting sibling layout -- safe here because
  it's a trailing decoration after the brace, not a leading element
  siblings align against.
@hartmark
hartmark requested a review from diegosouzapw as a code owner August 26, 2026 20:49
hartmark and others added 23 commits August 28, 2026 14:32
…iewer keys

This PR's new requestLogger.detail.{collapseAllLevels,collapseOneLevel,
currentExpandLevel,expandOneLevel,expandAllLevels} keys were added to en.json
but never mirrored into pt-BR.json, which the cheaper-inference#6695 regression guard
(tests/unit/i18n-pt-br.test.ts) hard-fails on for any key drift.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vk2cBRQdErwRtPea8VuhG6
Bring inherited Fast Quality Gates / ESLint / unit-test / docs-sync fixes from cheaper-inference#11940/cheaper-inference#11955/cheaper-inference#11975.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
…koff (Gemini ban prevention) (cheaper-inference#11762)

Root-caused via a real Gemini-ban incident log: deprecated-model 404/410s (e.g. gemini-2.5-flash "no longer available to new users") fell through checkFallbackError's generic transient-cooldown branch, so combo/auto-routing kept re-selecting a permanently dead model every cooldown window forever — the hammering that got the account flagged as abusive.

Fix: `MODEL_PERMANENTLY_UNAVAILABLE_PATTERNS` + `isModelPermanentlyUnavailable()` classify these as a 24h lockout instead, surfaced via `quotaResetHintMs` so combo's per-request model-lockout honors it in full.

Validated: 6/6 new tests + 133/133 existing accountFallback/error-classification tests, no regressions. Thanks for tracing this end-to-end with real production logs!
…ng-suspended accounts (cheaper-inference#11774)

Follow-up to cheaper-inference#11762, same bug class hitting freeaiapikey (410 permanently-moved endpoint) and fireworks (412 billing-suspension) — both fell through checkFallbackError's generic transient-cooldown branch and got retried every ~1 minute for a full day.

Fix: `ENDPOINT_PERMANENTLY_MOVED_PATTERNS`/`isEndpointPermanentlyMoved()` → 24h lockout; `ACCOUNT_SUSPENDED_BILLING_PATTERNS`/`isAccountSuspendedForBilling()` → treated as credits-exhausted (1h cooldown), independent of status code so it also catches Fireworks' 412.

cheaper-inference#11762 landed first and touched the same file — rebased/re-merged onto the updated tip (additive, no logic changes) and re-validated: 13/13 tests pass. Thanks for tracing this with real production logs again!
… requests (cheaper-inference#11781)

Follow-up to cheaper-inference#11762/cheaper-inference#11774, same bug class in combo's own model-lockout wiring: GitHub rejects several models (gpt-5.4, gpt-5.3-codex, etc.) with a 400 that's permanently unavailable for this account's Copilot integration, but nothing recorded a cross-request lockout — combo's cheaper-inference#5249 in-request advance guard is correct but doesn't persist, so the same doomed model gets retried from scratch on every new request, indefinitely.

Fix: on a model-scoped 400 (`isModelScoped400`), call `lockModelIfPerModelQuota(provider, connectionId, rawModel, "model_capacity", 1h)`. GitHub already has per-model-quota enabled, so only the rejected model locks — siblings keep working. `isModelLocked()` is already checked pre-dispatch, so no other wiring needed.

Validated: 3/3 new tests + fixed a pre-existing test-isolation gap in combo-model-scoped-400-advance.test.ts (shared model name across sub-tests without clearing lockout state). Thanks!
…EADME (cheaper-inference#11772)

Finishes the Freepik → Magnific rebrand from cheaper-inference#10594 across 40 locale files and 3 README feature-list bullets (README.md, docs/i18n/it, docs/i18n/tr) — legacy `freepik` alias intentionally left in code/tests/redirects for backward compatibility, and historical CHANGELOG entries left untouched as documented history.

The README bullet had base-drifted since the PR branched (release tip's "What's New" changelog snippet had already dropped two providers mentioned nowhere else in the codebase, unrelated to this PR's scope) — resolved by keeping the tip's current bullet shape and applying only the Freepik→Magnific rename on top, in both the combined-worktree validation and the pushed branch.

Validated: all 40 edited locale JSON files parse; re-verified after resync onto the updated tip (post cheaper-inference#11762/cheaper-inference#11774/cheaper-inference#11781).
…t createTask flow (cheaper-inference#11296) (cheaper-inference#11985)

flux/kontext is catalogued with isMarket: true, so handleKieImageGeneration
routed it through KIE's unified Market createTask endpoint with
model: "flux/kontext". KIE does not expose Flux Kontext through the Market
catalog at all -- it lives under a dedicated API tree
(POST /api/v1/flux/kontext/generate, poll GET /api/v1/flux/kontext/record-info,
models flux-kontext-pro/flux-kontext-max) -- so the Market endpoint rejected it
with "model name not supported", matching the reporter's exact error text.

Special-case flux/kontext ahead of the isMarket branch so it hits the
dedicated endpoint/payload shape instead of being treated as a Market entry.
z-image/4.0-*/4.5-* remains intentionally untouched (still blocked on
reporter/live confirmation per the existing in-code comment).

Co-authored-by: Markus Hartung <mail@hartmark.se>
…outs (cheaper-inference#11500) (cheaper-inference#11989)

* fix(db): rate-limit Arena ELO fetch-failure warnings on repeated timeouts (cheaper-inference#11500)

* test(quality): split the cheaper-inference#11500 fetch-failure-dedup tests into their own file

tests/unit/arena-elo-sync.test.ts crossed the 1000-line new-test-file cap
(file-size gate, PR mode). The two new tests don't need the file's DB
fixture (fetchArenaLeaderboards() never touches the DB), so they move to a
self-contained sibling file instead of growing the frozen suite.

---------

Co-authored-by: Markus Hartung <mail@hartmark.se>
…te path (cheaper-inference#11707) (cheaper-inference#11984)

enforceCodexResponsesLiteParallelToolCalls() forces parallel_tool_calls:false
at the top of CodexExecutor.execute(), but transformRequest() early-returns
the body before its RESPONSES_API_ALLOWLIST field filter only when
_nativeCodexPassthrough is set. Any request that reaches the codex
executor via the translated (non-native-passthrough) path never gets that
flag, so the allowlist filter silently deleted parallel_tool_calls right
before the fetch body was sent, reproducing the reported upstream
rejection ('X-OpenAI-Internal-Codex-Responses-Lite requires
parallel_tool_calls to be false') for every model.

Add parallel_tool_calls to RESPONSES_API_ALLOWLIST so the value survives
the translated path too. Update the sibling cheaper-inference#2608 allowlist test that
previously asserted parallel_tool_calls gets stripped like other Chat
Completions-only fields -- it is a legitimate Responses API field that
must now survive.

Co-authored-by: Markus Hartung <mail@hartmark.se>
…publish's opencode-plugin skip check (cheaper-inference#11787) (cheaper-inference#11990)

* fix(cli): drop the never-produced dist/index.cjs requirement from prepublish's opencode-plugin skip check (cheaper-inference#11787)

* test(build): resolve tsup/npm portably in the cheaper-inference#11787 regression test instead of a hardcoded .bin path

The old test assumed @omniroute/opencode-plugin/node_modules/.bin/tsup
already existed. A fresh checkout (CI's npm ci never installs this
standalone package's own deps) has no such node_modules at all, so the
test failed with MODULE_NOT_FOUND in CI while passing locally on a devbox
that had installed it before. Mirror scripts/build/prepublish.ts's own
install-then-resolveLocalBinEntry approach.

---------

Co-authored-by: Markus Hartung <mail@hartmark.se>
…ked rows (cheaper-inference#9133) (cheaper-inference#11994)

* fix(sse): stop the auto-combo candidates inspector from dropping blocked rows (cheaper-inference#9133)

prepareVirtualAutoComboInputs applied filterResilienceBlockedCandidates
before the cheaper-inference#7819 read-only candidate inspector ever saw the pool, so a
model-locked or cooled-down candidate silently disappeared from
/auto-combo/*/candidates instead of showing up as reachable:false with a
reason (modelLocked/connectionCooldown/breakerState were dead fields by
construction). Add an opt-in `skip` parameter so the inspector builds its
own unfiltered pool; routing (createVirtualAutoCombo/createBuiltinAutoCombo
called without a prepared override) is unchanged. Also aligns
isModelLocked's model argument to the bare model id, matching every lock
writer and the routing-side filter, instead of the "provider/model" string.

Regression test: tests/unit/auto-combo-candidates-locked-model-visible.test.ts
(red before the fix — locked account's row silently missing; green after).

* chore(quality): register the cheaper-inference#9133 regression test in stryker tap.testFiles

tests/unit/auto-combo-candidates-locked-model-visible.test.ts covers
open-sse/services/accountFallback.ts (via isModelLocked) but wasn't listed,
so its mutant kills wouldn't count toward mutation coverage.

---------

Co-authored-by: Markus Hartung <mail@hartmark.se>
…SBOM on dispatch (twin of cheaper-inference#11982 + cheaper-inference#12020) (cheaper-inference#12022)

* fix(release): resync the electron lockfile, build a dispatch from a repaired ref, keep curated notes, attach the SBOM on dispatch (release/v3.8.51 twin of cheaper-inference#11982 + cheaper-inference#12020)

Same four changes as cheaper-inference#11982 and cheaper-inference#12020 on main, applied to this branch's own copies:

- electron/package-lock.json regenerated (271 -> 284 entries): the optional
  electron-builder-squirrel-windows subtree was missing and `npm ci` refused the lock
  (EUSAGE) on the Linux and macOS legs; a clean `npm ci --ignore-scripts` on the
  result exits 0.
- electron-release.yml: `build_ref` dispatch input (default: the version tag) and
  `generate_release_notes` only on the tag push (a re-attach dispatch appended
  GitHub's auto notes to the curated body on v3.8.50).
- npm-publish.yml: the SBOM attaches to the GitHub Release on workflow_dispatch
  publishes too, whenever a release for the tag exists.

actionlint and prettier clean; electron-release-desktop-channel-8949,
electron-release-efficiency, electron-release-latest-yml.repro, check-workflows
and npm-publish-artifact-provenance suites pass.

* fix(release): validate build_ref in the validate job before any checkout uses it

CodeQL (actions/cache-poisoning/poisonable-step, high) on release/v3.8.51 — the
default branch: a raw dispatch input checked out next to setup-node's npm cache is a
cache-poisoning vector. The input now goes through the validate job's regex
allowlist (main or release/vX.Y.Z, empty = the version tag) and every build job
checks out needs.validate.outputs.build_ref, never the input itself.

* fix(release): drop the build_ref input — a dispatch builds the ref it is dispatched on

CodeQL (actions/cache-poisoning/poisonable-step) tracks the input through the
validate job's output regardless of the regex allowlist: an input-controlled
checkout next to setup-node's npm cache on the default branch is a cache-poisoning
vector. The ref is not an input any more; the checkouts use github.ref, so
`gh workflow run electron-release.yml --ref v3.8.50 -f version=v3.8.50` rebuilds
the tag and `--ref main` builds the repaired line. The tag-push path is unchanged.
…heaper-inference#12021)

* fix(ci): stop hosted docker-publish OOM and unpaint Build (advisory)

docker-publish was firing 8 concurrent hosted builds on every merge
storm; each died ResourceExhausted in npm run build (cheaper-inference#11976). One
publish per ref, webpack instead of Turbopack so native RSS stays
inside the V8 heap we can cap. Build (advisory) is skipped: continue-on-error
still reports FAILURE and was painting every fork PR red.

Closes cheaper-inference#11976

* fix(ci): run docker-publish amd64 on omni-build and share the heavy lane

The .113 box is 31 GB / 32 cores — enough for one next-build. Hosted
ubuntu-24.04 is ~7 GB and ResourceExhausted every publish (cheaper-inference#11976).
amd64 now targets [self-hosted, omni-build] (Turbopack) when
USE_VPS_RUNNER is on, joins the existing heavy-build-main group so it
queues beside ci.yml Build instead of becoming a third heavy, and
falls back to hosted + webpack if the VPS is off. arm64 stays on
ubuntu-24.04-arm with webpack (no ARM box).

* test(ci): align the advisory-build contract with the hosted-OOM skip

if: ${{ false }} tripped zizmor obfuscation (194→195). Bare if: false
skips the job without a new finding. The cheaper-inference#7307 test now pins the skip
and keeps the job body as the restore recipe.
…t a driver line the primary path never prints (twin of cheaper-inference#12032) (cheaper-inference#12047)

* fix(release): the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (release/v3.8.51 twin of cheaper-inference#12032)

Same change as cheaper-inference#12032 on main: the packaged app opens SQLite during the smoke but
its primary open path prints no "[DB] Driver: …" line (only the recovery path and
the sql.js fallback do), so the cheaper-inference#7592 assertion failed every Linux release leg. The
guard rejects the sql.js fallback line, accepts a native driver line, and otherwise
accepts demonstrable database activity; after readiness the smoke requests
/api/monitoring/health and waits for that activity outside the readiness loop.
electron-smoke-script suite 10/10.

* fix(release): reapply the smoke rework on top of release/v3.8.51's own copy of the script

The previous commit copied main's file wholesale and dropped this branch's
ensureSmokeEnvDirs(currentPlatform) fix and its tests; this reapplies only the
DB-open evidence change as a patch. electron-smoke-script suite green.
…er-inference#12048)

* docs(ops): the .113 heavy-build ceiling is one runner, not two

Two concurrent next-builds (15.4 GB + 17.2 GB RSS) OOM-killed one on 2026-08-29 17:26 UTC;
systemd booked the kill on the other runner's unit and its job died with the same
"shutdown signal" text a hosted-runner OOM shows. omni-build now lives on
omniroute-113-5 only; 113-6 keeps omni-release. The janitor ceiling counts every
listener on the box (4 OmniRoute + OmniHeuris + OmniMind = 6). The second heavy slot
returns when the Proxmox VM gets more RAM; the exact command is in the doc.

* docs(ops): apply the single-heavy-slot text (previous commit only carried formatting)
…ence#12050)

Turbopack had 31 GB on omniroute-113-6 and still panicked
(TurbopackInternalError: there must be a path to a root, run
33253576569). The same tree's arm64 webpack build on hosted ARM
succeeded. Dockerfile already documents webpack as the Docker
escape hatch. Keep amd64 on the one omni-build slot (cheaper-inference#12048).
cheaper-inference#11600) (cheaper-inference#11996)

Fixes the blocking Lint job's own ci.yml cache: PR cheaper-inference#11963 removed the stale restore-keys fallback from quality.yml but left ci.yml's two "Restore ESLint file cache" steps carrying the same prefix-match fallback that lets a cache from a different lint config report stale per-file verdicts. Byte-level parity with cheaper-inference#11963's already-merged fix.

Deliberately half of cheaper-inference#11600 — the other half (run-eslint-json.mjs) is covered by PR cheaper-inference#11983 from a parallel session, so the two don't collide on the same file.
… list (cheaper-inference#11949) (cheaper-inference#11995)

Local no-API-key providers (ollama-local, lm-studio, vllm, etc.) were invisible in the Qdrant embedding-model dropdown because configuredProviders required a real apiKey or OAuth. Extended the filter to also include providerAllowsOptionalApiKey(connection.provider) — the same canonical helper already used for the identical check elsewhere. TDD: 21/21 integration tests pass (was 20/21 before the fix).
…aper-inference#11861) (cheaper-inference#11993)

Fixes a copy-paste label typo (Hermes-4-405B mislabeled "7B") in both the registry and the free-model catalog data, spotted in the cheaper-inference#11861 comment thread. TDD: 3/3 tests, generic parameter-size consistency check + exact regression guard.
…start (cheaper-inference#11885) (cheaper-inference#12005)

Fixes three defects in the "update doesn't restart the running process" bug class: CLI update guidance now detects a live server and tells the operator to restart instead of implying the update is already live; the dashboard's Update button tries OmniRoute's own PID-file supervisor before falling back to pm2 instead of hardcoding pm2 and silently skipping; getLatestVersionFromNpmCli now uses --prefer-online (same fix pattern as cheaper-inference#4376). TDD throughout, 63/63 targeted regression tests pass.
HouMinXi and others added 20 commits August 29, 2026 19:51
cheaper-inference#11999) (cheaper-inference#12033)

Commander's top-level global --base-url option was shadowing the flag on `omniroute nodes add/update/validate`, rejecting the command with "required option '--endpoint <url>' not specified" even when --base-url was correctly supplied. Now both flags are accepted on all three subcommands, falling back to whichever the user passes.
…re ratio (cheaper-inference#12017)

Real production incident (2026-08-29): the resource-pressure guard ratioed raw cgroup v2 memory.current (which counts reclaimable page cache) against memory.max, so a busy host with ~3GiB of page cache latched a global 503 across every model for 26 minutes even though PSI/OOM/memory.events all showed zero real pressure — the kernel would have reclaimed those pages instantly. Fix: ratio the working set (current - file) for the trip/recovery check, falling back to the raw ratio when memory.stat is missing/stale/zero (never clamping to a false zero-pressure reading).

12 new tests including direct incident reproduction (raw 95%/workingset 32% stays normal) + bug-injection round trips. Full resource-pressure + admission suites green (48/48, re-verified in this batch together with the other 3 PRs: 57/57).
…ip (cheaper-inference#11911) (cheaper-inference#12013)

When an auto/*/lkgp combo target failed into exhaustion (e.g. an unauthenticated free-tier 401) or was skipped pre-dispatch (cooldown, model lockout, unavailability), the Last Known Good Provider pin was never cleared — so subsequent requests kept re-selecting the same dead provider, causing repeated failures and mass-skipping instead of falling through to a healthy target. Centralizes invalidation into clearStaleLKGP(), invoked from both handleComboChat and handleRoundRobinCombo on exhaustion, pre-dispatch skip, and body-specific 400 termination.
…eaper-inference#11795 Phase 2) (cheaper-inference#12051)

Boarded together with Phases 3-5 (cheaper-inference#12052, cheaper-inference#12053, cheaper-inference#12055) and validated in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles, check-db-rules all green. Mechanical import-path migration only, no behavior change. Thanks for the phased, well-tested cleanup.
…orts (cheaper-inference#59) (cheaper-inference#12052)

Boarded together with Phases 2, 4, 5 (cheaper-inference#12051, cheaper-inference#12053, cheaper-inference#12055) and validated in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles, check-db-rules all green. Mechanical import-path migration only, no behavior change. Thanks for the phased, well-tested cleanup.
…(Windows/Node 24 build crash) v.50/.51 (cheaper-inference#11706)

Confirmed the bug is real and unfixed on the current tip before merging: `scripts/build/prepublish.ts` line 332 was still calling `execFileSync(NPX_BIN, ...)` directly (raw win32 npx.cmd spawn), the exact CVE-2024-27980 shim pattern the file's own header warns about. Root cause, fix, and evidence match — routing through the existing `runBuildTool()` helper. Thanks for catching the one call site the earlier refactor missed.
…omain off the @/lib/localDb barrel import (cheaper-inference#11795 Phase 4) (cheaper-inference#12053)

Resynced onto the release tip after cheaper-inference#12051/cheaper-inference#12052 landed. One real conflict in open-sse/services/combo.ts at both LKGP-clear call sites (handleComboChat + round-robin path): the release tip already has cheaper-inference#12013's clearStaleLKGP() helper, which this PR's branch predates — kept the current helper call at both sites, discarding the pre-refactor inline pattern. typecheck:core and the open-sse test suite (vitest, 9/9 on volumeDetector) both green after resync. Thanks for the well-scoped Phase 4 migration.
cheaper-inference#11795 Phase 5) (cheaper-inference#12055)

Resynced onto the release tip after cheaper-inference#12051/cheaper-inference#12052/cheaper-inference#12053 landed. Same LKGP-clear conflict as cheaper-inference#12053 (kept the current clearStaleLKGP() helper at both call sites). One additional issue this final phase's combined-worktree validation surfaced: clearStaleLKGP() itself (added by cheaper-inference#12013, which none of the 4 phase PRs could have seen since it landed after they were authored) still had a dynamic `await import("@/lib/localDb")` — a real break once this PR deletes the barrel. Fixed to `await import("@/lib/db/settings")`, matching the direct-import pattern used at every other call site. typecheck:core, check-db-rules, check:cycles, and the eslint-import-boundaries regression test (3/3, including "G14 rejects localDb barrel imports") all green after resync — zero barrel-importing production files remain. Nice clean 5-phase migration, and thanks for taking on the full cheaper-inference#11795 cleanup.
…heaper-inference#12003)

Boarded with cheaper-inference#11841/cheaper-inference#11840/cheaper-inference#11839 in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles all green; 24/24 focused tests pass. Both fixes are surgical and well-reasoned: explicit ensureDbInitialized() call for MCP stdio (verified the function exists at src/lib/db/core.ts:1496) avoids a startup race, and the reasoningContent fallback prevents empty message.content when only reasoning was returned. Thanks for tracking down both root causes.
… hot-reloading (cheaper-inference#11841)

Boarded with cheaper-inference#12003/cheaper-inference#11840/cheaper-inference#11839 in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles all green; 24/24 focused tests pass. Clean, well-contained addition mirroring the existing systemTransforms hot-reload pattern, tested for both set and cleared states. Thanks for the tidy runtime-config feature.
…routing (cheaper-inference#11840)

Boarded with cheaper-inference#12003/cheaper-inference#11841/cheaper-inference#11839 in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles all green; 24/24 focused tests pass. Contained fix — preserves the unstripped model string for passthrough providers (cline/kilocode) only when the combo actually redirected to a passthrough provider. Thanks for the regression coverage.
…per-inference#11839)

Boarded with cheaper-inference#12003/cheaper-inference#11841/cheaper-inference#11840 in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles all green; 24/24 focused tests pass. relayMode is opt-in and defaults to standard, so this is backward-compatible as claimed — verified the plumbing through resolveUniversalHandoffConfig/resolveContextRelayConfig/selectMessagesForSummary. Thanks for the clean, well-tested addition.
Boarded with cheaper-inference#12075 in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles all green; 77/77 focused tests pass. CI-contract-only reconciliation as described — no production behavior change, and the referenced files (lkgp-stale-pin-exhaustion-11911.test.ts, cli-nodes-commands.test.ts) confirmed already present and correctly aligned. Thanks for keeping this separate from the dev-bundler phase PRs.
Boarded with cheaper-inference#12082 in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles all green; 77/77 focused tests pass. Genuinely conservative as described — dev-only Tailwind/webpack scanning bounds, production chunking untouched. The later phases of cheaper-inference#12074 (2/3/4/4b) are being held for a dedicated review given their combined architectural weight (DB init graph, credential refresh, process lifecycle, network dispatch boundary) — flagged separately on those PRs. Thanks for the clean Phase 1 baseline.
…ns (cheaper-inference#11760)

Boarded with cheaper-inference#11741 (a duplicate fix for the same underlying issue cheaper-inference#11739). Compared both implementations directly: this one is technically superior — a dedicated resolveIncomingCorrelationId() helper that strips CRLF (header-injection prevention) and bounds length to 1-256 chars, with 4 unit tests covering those edge cases. cheaper-inference#11741's simpler `header || generateRequestId()` has no sanitization. Closing cheaper-inference#11741 with credit. Validated in a combined worktree: typecheck:core, check:dashboard-typecheck, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles, check-deps all green; 84/84 + 43/43 focused tests pass across this batch. Thanks for the careful sanitization work.
…heaper-inference#11758)

Boarded in a combined worktree with 6 other PRs: typecheck:core, check:dashboard-typecheck, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles, check-deps all green. Verified the root-cause diagnosis directly against the code: isConnectionUnavailableToAuxiliaryActivity() does return true for any connection reachable by an active exclusive lease regardless of whether the lease is actively serving a request, confirming the fix's scoping is correct. The change is surgically limited to providerLimits.ts's live-usage-fetch path — the shared isolation function and its other call sites (warmupScheduler, quotaAutoPing, modelTestRunner, etc.) are untouched. Well tested (214 lines across 3 test files). Thanks for tracking this down.
…ard (cheaper-inference#11755)

Boarded in a combined worktree: typecheck:core, check:dashboard-typecheck, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles, check-deps all green; 10/10 focused tests pass. Real SSRF gap confirmed — the default "block-metadata" guard mode fell through to the unchecked parseOutboundUrl() while 3 other call sites of the same guard mode already routed through parseAndValidateNonMetadataUrl(). Good catch that the existing test suite only ever exercised "public-only" explicitly. Retargeted from the stale release/v3.8.50 base to release/v3.8.51. Thanks for closing a real cloud-metadata SSRF exposure.
Boarded in a combined worktree: typecheck:core, check:dashboard-typecheck, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles, check-deps all green. Clean, self-contained addition (5 new files, 0 modifications to existing code) that mirrors the existing dashboard-typecheck baseline-ratchet pattern. Thanks for closing a real coverage gap — API routes had no dedicated typecheck gate.
@diegosouzapw
diegosouzapw merged commit 5684589 into cheaper-inference:release/v3.8.51 Aug 30, 2026
3 of 7 checks passed
diegosouzapw added a commit to b3nw/OmniRoute that referenced this pull request Aug 30, 2026
RequestLoggerV2.tsx transitively imports raw .css files (via
RequestLoggerDetail -> react18-json-view, merged in cheaper-inference#11703), which the
Node native test runner cannot load. Relocating the pure helper avoids
pulling in that import chain from a plain unit test.
diegosouzapw pushed a commit that referenced this pull request Aug 30, 2026
Mostra a % de cache nos logs de requisição, com teste próprio (`request-logger-cache-percentage.test.ts`). Validado no worktree combinado do lote.

Pequeno ajuste feito por cima: `formatCachePercentage` movida de `RequestLoggerV2.tsx` para `src/shared/utils/formatting.ts`. `RequestLoggerV2.tsx` importa `RequestLoggerDetail`, que desde o #11703 (mergeado nesta mesma sessão) importa CSS bruto de `react18-json-view` — algo que o Node native test runner não consegue carregar. O teste original importava a função direto do componente e quebrava por causa dessa cadeia de import, não por bug na PR. Movida a função (pura, sem dependências) para o utils compartilhado; ajustado o import do componente e do teste. Typecheck limpo, teste passando (6/6).
diegosouzapw added a commit that referenced this pull request Aug 30, 2026
…via globals.css + CJS/ESM interop (#11703 base-reds) (#12114)

* fix(dashboard): make RequestLoggerDetail loadable outside Next — CSS via globals.css, CJS/ESM interop for react18-json-view

Origin: #11703 (5684589) imported `react18-json-view/src/{style,dark}.css` at
module level in RequestLoggerDetail(.sections).tsx and relied on the bundler's
default-import interop. Next is fine with both, but every test that renders the
component died on the release tip:

- node:test / tsx: ERR_UNKNOWN_FILE_EXTENSION ".css" — request-log-detail-layout,
  request-log-detail-stream, request-logger-detail-copy-all,
  request-timeline-lane-allocation (4 unit shards red on every PR).
- esbuild bundle-safety check (media-page-client-browser-bundle): cannot resolve the
  .css specifiers.
- node ESM resolves the package's CJS `main` (no `exports` map), so the default
  import is the module namespace: "Element type is invalid … got: object".

Fix at the source: the two stylesheets are @imported from src/app/globals.css
(same as material-symbols / fumadocs), and the component unwraps `mod.default ??
mod` like redisQuotaStore/keytar already do. Also adds the 5 vi strings #11703
introduced (requestLogger.detail.{collapseAllLevels,collapseOneLevel,
currentExpandLevel,expandOneLevel,expandAllLevels}) — vi has strict parity.

Refs #11703

* refactor(dashboard): move the react18-json-view interop into shared/components/jsonView.ts

RequestLoggerDetail.tsx is frozen by check:file-size (1111 lines, cannot grow); the
inline interop pushed it to 1118. One tiny module serves both components and keeps
the CSS-import warning in a single place.
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…yloads (cheaper-inference#11703)

Resynced onto the release tip. Two fixes applied during boarding: (1) the branch forked before the recent optionalDependencies placement of @huggingface/transformers and onnxruntime-node — its own diff re-added both into "dependencies" as duplicates alongside the real new dependency (react18-json-view); removed the duplicates, ran npm install to sync the lockfile. (2) config/quality/dependency-allowlist.json referenced the wrong package name (react-json-view-lite, an earlier iteration per the PR body) — the code actually imports react18-json-view; fixed the allowlist entry to match. RequestLoggerDetail.tsx crossed its frozen file-size cap (1018->1111); rebaselined with a note — the PR does split out the new logic (RequestLoggerDetail.sections.tsx, JsonTreeExpandControls.tsx, useTimestampTitles.ts, jsonTreeExpandStore.ts, all well under cap), the growth here is irreducible wiring. typecheck:core, check:dashboard-typecheck, check:file-size, check-deps all green after resync; 8/8 vitest + 11/11 native tests pass. Nice, well-structured 6-commit feature with full i18n and good test coverage. Thanks!
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…ence#11970)

Mostra a % de cache nos logs de requisição, com teste próprio (`request-logger-cache-percentage.test.ts`). Validado no worktree combinado do lote.

Pequeno ajuste feito por cima: `formatCachePercentage` movida de `RequestLoggerV2.tsx` para `src/shared/utils/formatting.ts`. `RequestLoggerV2.tsx` importa `RequestLoggerDetail`, que desde o cheaper-inference#11703 (mergeado nesta mesma sessão) importa CSS bruto de `react18-json-view` — algo que o Node native test runner não consegue carregar. O teste original importava a função direto do componente e quebrava por causa dessa cadeia de import, não por bug na PR. Movida a função (pura, sem dependências) para o utils compartilhado; ajustado o import do componente e do teste. Typecheck limpo, teste passando (6/6).
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…via globals.css + CJS/ESM interop (cheaper-inference#11703 base-reds) (cheaper-inference#12114)

* fix(dashboard): make RequestLoggerDetail loadable outside Next — CSS via globals.css, CJS/ESM interop for react18-json-view

Origin: cheaper-inference#11703 (a4fd3d0) imported `react18-json-view/src/{style,dark}.css` at
module level in RequestLoggerDetail(.sections).tsx and relied on the bundler's
default-import interop. Next is fine with both, but every test that renders the
component died on the release tip:

- node:test / tsx: ERR_UNKNOWN_FILE_EXTENSION ".css" — request-log-detail-layout,
  request-log-detail-stream, request-logger-detail-copy-all,
  request-timeline-lane-allocation (4 unit shards red on every PR).
- esbuild bundle-safety check (media-page-client-browser-bundle): cannot resolve the
  .css specifiers.
- node ESM resolves the package's CJS `main` (no `exports` map), so the default
  import is the module namespace: "Element type is invalid … got: object".

Fix at the source: the two stylesheets are @imported from src/app/globals.css
(same as material-symbols / fumadocs), and the component unwraps `mod.default ??
mod` like redisQuotaStore/keytar already do. Also adds the 5 vi strings cheaper-inference#11703
introduced (requestLogger.detail.{collapseAllLevels,collapseOneLevel,
currentExpandLevel,expandOneLevel,expandAllLevels}) — vi has strict parity.

Refs cheaper-inference#11703

* refactor(dashboard): move the react18-json-view interop into shared/components/jsonView.ts

RequestLoggerDetail.tsx is frozen by check:file-size (1111 lines, cannot grow); the
inline interop pushed it to 1118. One tiny module serves both components and keeps
the CSS-import warning in a single place.
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.