* fix(dashboard): improve api try it functionality and allow manual key entry
* test(api): cover generateExampleFromSchema for the Try It panel (#4296)
Export generateExampleFromSchema from the /api/openapi/spec route and add a
unit test covering type handling, property-name heuristics, $ref/oneOf/anyOf/
allOf resolution, the 'required + first 3 optional' object policy, and the
depth-3 recursion guard — the example bodies the dashboard Try It panel
pre-fills. Rule #18 regression guard for the new helper.
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
---------
Co-authored-by: ci <ci@local>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
* fix: polyfill crypto.randomUUID for non-secure contexts
crypto.randomUUID() requires a secure context (HTTPS or localhost).
When accessing the dashboard over HTTP on a LAN IP, the function is
undefined, causing 'Failed to add account' errors on providers that
generate account IDs client-side (e.g. mimocode).
Adds a lightweight polyfill that falls back to a Math.random()-based
UUID v4 generator when the native API is unavailable.
* fix: address review comments on crypto.randomUUID polyfill
- Use crypto.getRandomValues() for cryptographic security instead of Math.random()
- Add typeof window !== 'undefined' guard to avoid ReferenceError in non-browser envs
- Use window.crypto for safe access instead of bare crypto reference
- Replace var with const and == with === for modern JS syntax
- Add fallback to Math.random() when getRandomValues is unavailable
- Add unit tests verifying valid UUID v4 format, version/variant nibbles,
uniqueness, and preference for getRandomValues over Math.random
* test(dashboard): regression guard for crypto.randomUUID polyfill (#4287)
Reads src/app/layout.tsx and asserts the blocking inline script installs a
guarded window.crypto.randomUUID polyfill (RFC4122 v4 shape, getRandomValues
preferred with a Math.random fallback). Fails on the pre-fix tree (no polyfill),
passes with the fix — Rule #18 regression guard for the non-secure-context
(HTTP/LAN-IP) dashboard breakage.
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
---------
Co-authored-by: pizzav-xyz <pizzav-xyz@users.noreply.github.com>
Co-authored-by: ci <ci@local>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
The compression "ultra" SLM tier (#4257) runs @atjsh/llmlingua-2 + transformers + tfjs
+ js-tiktoken in a worker thread shipped under dist/. These are optionalDependencies
installed into the ROOT node_modules on --include=optional, but the Next.js standalone
trace bundles ONLY @huggingface/transformers (3.5.2, pinned) into dist/node_modules —
not the dynamically-imported optionals.
Result: the worker resolves transformers from dist/node_modules (3.5.2) for its env
config but resolves @atjsh/llmlingua-2 from the ROOT, whose own transformers import
hits a DIFFERENT instance. The cacheDir config never reaches the instance llmlingua-2
uses, so the local model never loads and the SLM tier silently fails-open (and on a
root transformers 4.x, llmlingua-2 throws on the tokenizer API change).
Fix: postinstall co-locates the SLM optional closure from the root node_modules into
dist/node_modules (no-clobber, so the pinned dist transformers/onnxruntime stay), so
the worker resolves a single 3.5.2 instance and the local model loads.
VPS-validated (Rule #18): the co-located layout produced real 54.8% compression
(11520->5203 chars) via real ONNX inference on the production host, both the default
and the #4257 modelPath code paths.
- scripts/build/colocateOptionals.mjs: closure walk (deps+optionalDeps, skips the
transformers peer) + no-clobber co-location; idempotent + fail-soft
- wired into scripts/build/postinstall.mjs next to ensureSwcHelpers
- registered in package.json files + pack-artifact allow/required lists
- tests/unit/colocate-optionals.test.ts: closure, no-clobber, idempotence, gates
- docs/ops/RELEASE_CHECKLIST.md: note the auto co-location
The standalone server.cjs proxy intercepts AgentBridge requests inline (no
MitmHandlerBase / agentBridgeHook), so intercepted traffic never reached the
TS hook that pushes into globalTrafficBuffer — the Traffic Inspector stayed
empty for AgentBridge even on successful intercepts. Three gaps closed:
- _internal/ingest.cjs (new): pure payload builder + fire-and-forget poster
(never throws — capture must not break proxy traffic).
- server.cjs: intercept() accumulates response (bounded) + status/headers and
posts the captured entry to the local-only /internal/ingest endpoint in a
finally block; also captures error/4xx intercepts.
- manager.ts: resolves the ingest token via getIngestTokenForBootstrap() and
passes it to the spawned proxy so the endpoint accepts the post.
- authz management policy: exempt the loopback /internal/ingest endpoint from
management auth — it has its own shared-secret token gate, and server.cjs
has no dashboard cookie. Stays strictly loopback (LOCAL_ONLY gate unchanged).
- ingest route: masks secrets / strips hop-by-hop headers before buffering
(server.cjs sends raw over the token-gated loopback) — Hard Rule #12.
Tests: ingest shim (build/post/no-token/error) + route sanitization + 403 +
management-policy carve-out (loopback allow / remote LOCAL_ONLY).
CLI #10 of the series. `omniroute setup-goose` writes Goose's file-based
~/.config/goose/config.yaml (GOOSE_PROVIDER=openai, GOOSE_MODEL=<model>,
OPENAI_HOST=<root, NO /v1 — Goose appends the path itself>), merges to preserve
existing keys, and prints the guaranteed env-var recipe (the key lives in the env
/ OS keyring, never the config). Remote-aware (--remote/--api-key → context →
localhost); model via --model or interactive pick.
Researched against block/goose: provider openai + OPENAI_HOST base (no /v1).
Goose's wire (/v1/chat/completions) already validated → "OK".
Tests: resolveGooseTarget (/v1 strip, key), buildGooseConfig (provider/model/host
+ preserve), buildGooseEnvRecipe (env-ref key). 4 unit tests; cli-i18n green.
* feat(memory): x-omniroute-no-memory opt-out + memory off-by-default + token-cost UI alert
PRD-2026-06-19-no-memory-header. The gateway injects up to memorySettings.maxTokens
(~2k) of memory (and skills) context into every chat call for memory-enabled keys,
inflating tokens+cost ~137x for clients that manage their own context (e.g. Omniflow).
Three changes:
- A) x-omniroute-no-memory request header (mirrors x-omniroute-no-cache): when truthy
(true/1/yes), skip memory+skills injection for that request. New pure helper
isNoMemoryRequested() in chatCore/headers.ts; chatCore passes memoryOwnerId=null on
opt-out (a null owner disables both injection branches).
- B) Memory OFF by default: DEFAULT_MEMORY_SETTINGS.enabled true->false. Enabling injects
billed context per request, so it's now an explicit opt-in. Installs that already
enabled it keep it; unset installs default off (no migration seeds memoryEnabled).
- C) Settings -> Memory shows a token-cost warning callout when memory is enabled
(new settings.memoryTokenCostWarning i18n key, interpolating the configured maxTokens).
Tests: no-memory-header.test.ts (5, helper truthiness/case/Headers); memory-settings-default
and chatcore-memory-skills-injection aligned to the new off-by-default. 65/65 memory+chatcore
tests green; typecheck/lint/file-size/i18n(@65) clean.
* test(memory): enable memory in memory-tools test (memory now off by default)
The full CI unit suite flagged memory-tools.test.ts 'memory search ...' failing
after DEFAULT_MEMORY_SETTINGS.enabled flipped to false: omniroute_memory_search
routes through retrieveMemories, which returns [] while memory is disabled
(enabled:false → maxTokens 0). The memory MCP tools operate within the memory
subsystem, so the test now enables memory explicitly (updateSettings + cache
invalidation) — the realistic precondition for a client using the tools.
Aligns the test to the intentional off-by-default change; assertions unchanged.
CLI #8 of the series. Roo Code (RooVeterinaryInc.roo-cline, a Cline fork) keeps
live settings in opaque VS Code globalStorage, but supports Settings Import and
an `roo-cline.autoImportSettingsPath` (VS Code settings.json) that loads a JSON
at startup.
`omniroute setup-roo`:
- writes ~/.omniroute/roo-settings.json — a Roo provider profile
(providerProfiles.apiConfigs.OmniRoute: apiProvider=openai, openAiBaseUrl WITH
/v1 — Roo appends /chat/completions — openAiApiKey, openAiModelId).
- sets roo-cline.autoImportSettingsPath in VS Code settings.json when present
(preserves other settings).
- prints the guaranteed UI path (Settings → Providers → OpenAI Compatible) +
the "Import Settings" fallback.
- remote-aware; model via --model or interactive pick.
Researched against current Roo docs: OpenAI-compatible needs baseUrl WITH /v1 and
native tool-calling (OmniRoute supports it). Roo's wire (/v1/chat/completions)
already validated → "OK".
Tests: resolveRooTarget (/v1, key), buildRooImport (provider profile + /v1 + key
fallback), buildRooVscodeAutoImport (pointer + preserve). 5 unit tests; cli-i18n green.
CLI #7 of the series. Cursor stores its OpenAI key + "Override OpenAI Base URL"
in an opaque SQLite DB (state.vscdb) with no stable schema — not safe to
file-write. So `omniroute setup-cursor` prints the exact in-app steps and lists
real model names from /v1/models.
- Resolves apiBase WITH /v1 (Cursor appends /chat/completions) + key from
--remote/--api-key → active context → localhost.
- Prints Settings → Models → Override OpenAI Base URL + key + model-name steps,
with a clear caveat that the custom base URL powers Cursor's CHAT panel only
(Composer / inline-edit / autocomplete stay on Cursor's backend).
Researched against current Cursor behavior. Tests: resolveCursorTarget (/v1, key),
buildCursorInstructions (base URL + /v1 note + model samples + caveat). 4 unit
tests; check:cli-i18n green.
CLI #6 of the series. `omniroute setup-continue` writes Continue's file-based,
mergeable ~/.continue/config.yaml (shared by the VS Code/JetBrains extensions AND
the `cn` CLI) from the live model catalog.
- Each curated model → a Continue model entry: provider: openai, model: <id>,
apiBase WITH /v1 (Continue appends /chat/completions), apiKey:
${{ secrets.OMNIROUTE_API_KEY }} (secret referenced, never written), roles
[chat, edit, apply] (+ autocomplete for the fast tier).
- Merges into existing config.yaml (js-yaml load/dump): drops prior models on the
same apiBase, preserves the user's other models + top-level keys.
- Remote-aware (--remote/--api-key → active context → localhost); --only filter.
- Prints how to provide the key (shell env for cn; ~/.continue/.env for IDE).
Researched against current Continue docs: provider: openai + custom apiBase (with
/v1), the ${{ secrets.X }} syntax, roles, and that the `cn` CLI shares the same
config. Continue's wire (/v1/chat/completions) already validated → "OK".
Tests: buildContinueModels (provider/apiBase/secret/roles, fast→autocomplete,
skip uncategorised), mergeContinueConfig (replace-ours/keep-others/defaults),
resolveContinueTarget (/v1). 6 unit tests; check:cli-i18n green.
CLI #5 of the series. `omniroute setup-kilo` configures Kilo Code
(kilocode.kilo-code, a Cline/Roo descendant) to use OmniRoute.
Two surfaces (both written, matching the dashboard cli-tools/kilo-settings):
- ~/.local/share/kilo/auth.json — CLI mode: auth["openai-compatible"] =
{ apiKey, baseUrl (WITH /v1 — Kilo appends /chat/completions), model }.
- VS Code settings.json — extension: kilocode.customProvider (name/baseURL/apiKey)
+ kilocode.defaultModel. Only touched when the file already exists.
Remote-aware (--remote/--api-key → active context → localhost). Model via --model
or an interactive pick from /v1/models (Kilo's extension has no auto-discovery).
Prints the exact UI settings to paste. Merges both files (preserves existing).
Researched against current Kilo docs: confirmed openAiBaseUrl needs /v1 (unlike
Cline's root url), the openai-compatible keys, and the export/import + CLI surfaces.
Kilo's wire (/v1/chat/completions) already validated → "OK".
Tests: buildKiloAuth (provider + /v1 + merge + key fallback), buildKiloVscodeSettings
(kilocode.* keys + preserve), resolveKiloTarget (/v1 ensure, key win). 6 unit tests;
check:cli-i18n green.
After importing a provider key, vision-capable models (OpenRouter models whose
architecture declares image input, and other synced providers) were shown as
text-only in /v1/models and the dashboard, even though image requests worked.
Root cause: SyncedAvailableModel never captured a vision flag, and the catalog's
OpenRouter live-enrichment block (which derives vision from architecture.input_modalities)
is skipped once a provider has synced models. So the synced path emitted no vision.
Fix (mirrors the existing supportsThinking capture):
- modelDiscovery.normalizeDiscoveredModels derives supportsVision via the new
detectVisionInput() from architecture.input_modalities, the string
architecture.modality ("text+image->text"), or a top-level input_modalities.
- SyncedAvailableModel gains supportsVision; the read-normalize path preserves it.
- catalog.ts emits capabilities.vision for synced models and merges (not clobbers)
capabilities when the model already exists.
TDD: tests/unit/openrouter-vision-sync-4264.test.ts — capture unit test + an
end-to-end /v1/models assertion (RED before, GREEN after).
Closes#4264
CLI #4 of the series. Cline's VS Code extension keeps config in opaque VS Code
globalStorage (not file-writable); its CLI/standalone mode reads ~/.cline/data/.
`omniroute setup-cline`:
- writes ~/.cline/data/globalState.json (act/planModeApiProvider=openai,
openAiBaseUrl = ROOT url WITHOUT /v1 — Cline appends /v1/chat/completions —
openAiModelId + planModeOpenAiModelId) and ~/.cline/data/secrets.json
(openAiApiKey), both merged to preserve existing state. Matches the dashboard
cli-tools/cline-settings schema.
- remote-aware (--remote/--api-key → active context → localhost).
- model resolved via --model or an interactive pick from /v1/models (Cline has
no model auto-discovery).
- prints the exact VS Code extension settings (Base URL/key/model) to paste,
since the extension's storage can't be written directly.
Researched against the current Cline docs (saoudrizwan.claude-dev): confirmed
the openai-compatible keys, the Plan/Act split, and that openAiBaseUrl must be
the ROOT (no /v1). Cline's wire (/v1/chat/completions) already validated → "OK".
Tests: buildClineGlobalState (provider+root+model, merge-preserve),
buildClineSecrets (key + placeholder), resolveClineTarget (/v1 strip, key win).
6 unit tests; check:cli-i18n green.
Cloudflare's /ai/models/search returns { id: "<uuid>", name: "@cf/..." } where
name is the callable slug and id is an internal UUID. The cloudflare-ai discovery
config passed the raw objects through (parseResponse: data.result), so buildResponse
used id (the UUID) as the model id — the dashboard/import listed UUIDs instead of
@cf/... model names. Map each result's name -> id (mirrors the gemini/huggingface/
clarifai parseResponse normalizers in the same map); falls through to the local
catalog on error so import never breaks.
TDD: tests/unit/cloudflare-models-uuid-4259.test.ts (RED on UUID ids -> GREEN on slugs).
Closes#4259
Applies proven patterns from the free-claude-code reference adapters:
launch (Claude Code):
- always set ANTHROPIC_AUTH_TOKEN — a no-auth sentinel when none is resolved —
so newer Claude Code doesn't stop at its local login gate before contacting
OmniRoute (an open backend ignores the value; ANTHROPIC_API_KEY stays stripped).
launch-codex:
- remote-aware: resolves the root base URL + auth from --remote/--api-key, the
active context, then localhost (was localhost/--remote only).
- inject the `omniroute` provider via `-c` flags (model_provider + base_url +
env_key + wire_api=responses + requires_openai_auth=false) so it works WITHOUT
a pre-existing ~/.codex/config.toml.
- strip OPENAI_*/CODEX_* from the child env (defense-in-depth) and set
OMNIROUTE_API_KEY to the token or a sentinel. (Honest note: this does NOT
silence codex's refresh_token log noise — that comes from ~/.codex/auth.json,
is cosmetic, and does not block requests.)
- replace a hard-coded Tailscale IP in --remote help with a placeholder.
Tests: buildCodexEnv (strip + sentinel + no-mutate), buildCodexProviderArgs
(inline provider def), resolveCodexTarget; updated buildClaudeEnv sentinel test.
Validated remotely vs VPS v3.8.30: `launch-codex --remote ... exec` → "OK".
19 unit tests pass; check:cli-i18n green.
Reconcile baseline + test drift on release/v3.8.30 (complexity, opaque surface, search count, tproxy addon). Round-robin left as a canary for the undici-dispatcher issue.
Brings Claude Code to parity with the Codex CLI integration.
- `omniroute launch` is now remote-aware: --remote <url>, --profile <name>,
--api-key. Resolves base URL + auth from the active context (so
`omniroute connect <vps>` then `omniroute launch` just works), accepts a bare
port OR a full base URL, health-checks the (possibly remote) server, and sets
CLAUDE_CONFIG_DIR for the chosen profile.
- `omniroute setup-claude` (new): fetches the live /v1/models catalog and writes
~/.claude/profiles/<name>/settings.json per model. Claude Code has no native
profile files, so CLAUDE_CONFIG_DIR is the idiomatic mechanism. Reuses the SAME
profile names as setup-codex (glm52, kimi-k27, …) via the shared categoriseModel
(now exported). The auth token is NEVER written to disk — launch injects it.
- Docs: docs/guides/CLAUDE-CODE-CONFIGURATION.md + README index. i18n (en + pt-BR).
- Tests: setup-claude profile generation (incl. "no token on disk"), buildClaudeEnv
(port + URL + CLAUDE_CONFIG_DIR), resolveLaunchTarget.
check:cli-i18n + check-docs-sync green; 13 unit tests pass.
Introduce a design system and visual identity plan to unify the OmniRoute
dashboard and marketing site. The document covers color alignment,
missing design tokens, and the implementation strategy for a consistent
brand experience.
* feat(cli): Codex CLI launcher + setup commands
Two new CLI subcommands mirroring the `launch`/`configure` pattern, for driving the
OpenAI Codex CLI against OmniRoute:
- `omniroute launch-codex` — boots OmniRoute (if needed) and launches Codex CLI
pointed at it (local or remote VPS), with no manual env/config editing.
- `omniroute setup-codex` — generates ~/.codex profile files from OmniRoute's live
model catalog.
Registered in the command registry; en.json + pt-BR.json locale sections added
(keeps the cli-i18n-catalog top-level parity gate green). CODEX-CLI-CONFIGURATION.md
refreshed. Consolidated from work-in-progress that was uncommitted on the shared
checkout; reconstructed on release/v3.8.30.
* chore(vscode): reduce git repo-detection overhead for nested worktrees
Disable VS Code's git auto-repository-detection / submodule scan / autofetch so the
Source Control view stops indexing the ~44 nested repos (worktrees + _references/*
+ _mono_repo/*), which caused constant "validating" churn. Only the root repo is
tracked. Editor-only settings; no runtime impact.
OmniRoute v3.8.29 — 115 commits since v3.8.28. Full CHANGELOG + 41 i18n mirrors. All content quality gates green (build, unit 8/8, vitest 188/188, PR test policy, quality gates extended, docs sync, quality ratchet). Remaining red CI checks are pre-existing release flakes (coverage-shard/integration/node-compat teardown), a new transitive undici advisory in electron devDeps, and a workflow-level CodeQL fail (0 open alerts). VPS-validated by the operator.
A cycle-internal docs housekeeping commit (635350ebb) relocated this internal
planning doc from docs/ root into docs/guides/, which `source.config.ts` globs
into the published fumadocs `docs` collection. The file has no frontmatter, so
`next build` (build:cli / Docker / Electron) failed to compile it:
`[MDX] invalid frontmatter … title: expected string, received undefined`,
breaking ALL three release-build fragments (npm/Docker/Electron) at release time.
Moving it back to docs/ root (which is NOT globbed by the collection) restores
the pre-cycle state and matches the documented intent — DOCUMENTATION_AUDIT_REPORT
lists it among docs/-root files that should never be published to the site.
Verified locally: `npm run build:cli` now completes green. No inbound links.
* chore(release): open v3.8.28 development cycle
* fix(ws): warm SSE auth import on LiveWS startup; relocate boot test to integration (#4063)
The live dashboard WebSocket sidecar lazily import()-ed the SSE auth module
inside the connection handler, only on the API-key path. That cold import pulls
in hundreds of transitive modules and takes ~7s under tsx, blocking the
single-threaded event loop. The first API-key WebSocket connection therefore
stalled the loop long enough that any connection arriving in that window — e.g.
a same-origin cookie client — could not complete its handshake and timed out.
This was deterministic, not an "env flake": the boot test fires an API-key
connection immediately followed by a cookie connection, so the cookie connection
always raced the cold import and timed out (reproduced 3/3 locally and red on
every CI run; proven via instrumented probes — reversing the order or warming
the module first makes both connections open in ~20ms).
Fix:
- Memoize the auth-module import and warm it once at startup (before listen), so
connection handling never pays the cold-import cost. Real improvement: the
first API-key client no longer stalls the event loop for concurrent clients.
- Relocate the boot test from tests/unit/cli to tests/integration. It spawns a
real subprocess + WS server + SQLite (~9-11s); under the unit suite's
--test-concurrency=20 it contended for CPU and destabilized the shard. The
serial integration runner is its correct home; it still guards #4004's
cookie-parse fix on every PR via the integration CI job.
- Bump the test's startup/overall timeouts to absorb the eager auth warm.
Makes `npm run test:unit` deterministically green (the only remaining unit red).
Validated: relocated test 3/3 green via the integration runner (was 3/3 red);
typecheck:core + eslint clean; confirmed it no longer matches the test:unit glob
and does match tests/integration/*.test.ts.
* fix(ws): start LiveWS sidecar with cwd at package root (#4055) (#4064)
* chore(deps): bump ossf/scorecard-action from 2.4.0 to 2.4.3 (#4045)
Integrado em release/v3.8.28. Patch de SHA do ossf/scorecard-action (2.4.0→2.4.3), mantém SHA-pin. Reds de CI são exclusivamente os shards flaky pré-existentes branch-wide (Unit 7/8, Integration, Coverage 7/8, Node 1/2) — não relacionados ao bump (PR deps-only).
* deps: bump electron from 42.4.0 to 42.4.1 in /electron (#4049)
Integrado em release/v3.8.28. Patch do electron (42.4.0→42.4.1). Reds de CI: shards flaky pré-existentes + PR Test Policy = falso-positivo (mudança deps-only sob electron/ não comporta teste de código) + Node 26(2/2) sem step (flake/infra). Precedente #3913/#3914 (electron dependabot mergeado nessas condições).
* fix(auto): resolve built-in auto catalog combos (#4058)
Integrado em release/v3.8.28. Resolve os IDs de catálogo `auto/*` built-in (combos virtuais) — corrige o 400 "No auto combos configured" em auto/best-coding etc. Ajuste de review: os mapas AUTO_TEMPLATE_VARIANTS/VALID_AUTO_VARIANTS duplicados em chat.ts e chatHelpers.ts foram extraídos para open-sse/services/autoCombo/builtinCatalog.ts (DRY), devolvendo chatHelpers.ts <800 LOC; baseline de chat.ts rebaselinado 1432→1458 (lógica nova). Fast QG + semgrep + dast verdes; 22/22 testes.
* chore(docs): update Discord invite link to a non-expiring one (#4067)
* chore(deps): freeze @huggingface/transformers in dependabot (hard-pin) (#4066)
Integrado em release/v3.8.28. Congela @huggingface/transformers no dependabot (pin exato 3.5.2, load-bearing p/ LLMLingua + memory embeddings, VPS-validado #4014). Fast QG + semgrep + dast verdes.
* ci(quality): flip TIA impacted-unit-tests gate from advisory to blocking (#4069)
The pre-existing release unit test-debt that kept the TIA "Impacted unit tests"
step advisory has been cleared:
- #4030 restored 16 lossless Zod/registry reds (from the oyi77 modularize refactors).
- #4063 fixed the last red — the LiveWS boot test — which was a real deterministic
event-loop stall in the WS sidecar (cold ~7s lazy auth import racing a second
connection), not an env flake; fixed (warm the import at startup) and relocated to
the integration suite.
A full workflow_dispatch ci.yml run on release/v3.8.28 then showed all 8 Unit Tests
shards green. The remaining Integration Tests / Quality Ratchet reds are pre-existing
and unrelated (combo/resilience env-flakes; eslint/i18n baseline drift).
Removing continue-on-error makes PR->release block on unit-test regressions in the
TIA-selected impacted set (fail-safe still runs the full unit suite on hub/unmapped
changes). typecheck:core was already blocking. Closes the fast-gates "no tests on
PR->release" hole (Quality Gate v2 / Fase 9, P2).
* docs(compression): document LLMLingua optional deps + on-demand install (#4061)
Integrado em release/v3.8.28. Docs LLMLingua optional deps + on-demand install (F3.1).
* feat(dashboard): Combo Studio connection-cooldown badge (U1b Slice 2) (#4068)
Integrado em release/v3.8.28. Combo Studio connection-cooldown badge (U1b Slice 2 / F5.1).
* feat(compression): record Context Editing telemetry (engine: context-editing) (#4062)
Integrado em release/v3.8.28. Context Editing telemetry (F4.1).
* feat(sse): Context Editing relay coverage + 400-fallback (#4065)
Integrado em release/v3.8.28. Context Editing relay coverage (cc-*) + 400-fallback (F4.2/F4.3). Conflito de file-size-baseline.json (vs #4062) resolvido por união (ambas justificativas + base.ts 1292 + chatCore.ts 5898). Validado local no tree mergeado: typecheck:core ✓, eslint ✓, check:file-size ✓, 4/4 testes ✓; semgrep + semgrep-cloud verdes. Fast QG enfileirado (saturação de runner) — mergeado nos gates de política verificados (precedente #4034/#4020).
* feat(providers): add OrcaRouter (OpenAI-compatible routing gateway) (#4070)
Integrado em release/v3.8.28. Adiciona o provider OrcaRouter (OpenAI-compatible, API-key, DefaultExecutor). Ajuste de review: rebaseline de file-size de providers.ts 3147→3159 (+12 da entrada OrcaRouter). Validado local no tree sincronizado: provider-consistency ✓, docs-counts STRICT 227 ✓, typecheck:core ✓, teste 3/3 ✓, eslint ✓; semgrep + semgrep-cloud verdes. Fast QG/dast enfileirados (saturação de runner) — merge nos gates de política verificados (precedente #4034/#4065).
* test(infra): isolate DATA_DIR per test process; raise Stryker concurrency 1→4 (#4078)
* test(infra): isolate DATA_DIR per test process; raise Stryker concurrency 1→4
Every test process resolved DATA_DIR to the same default (~/.omniroute) when the env
var was unset (src/lib/dataPaths.ts::resolveDataDir), so concurrent test files opened
the SAME on-disk storage.sqlite. node:test spawns a process per file and Stryker spawns
one per sandbox, so this shared file caused cross-file state races:
- SQLite lock contention that hung `npm run test:unit` under high --test-concurrency
(the ~95-min local hang), and
- the non-deterministic baseline that forced stryker.conf.json to concurrency: 1, which
in turn could not finish the ~15k-mutant run inside the nightly timeout (the cancelled
2026-06-16/17 nightly-mutation runs) — blocking Quality Gate v2 / Fase 9 Onda 2.
open-sse/utils/setupPolyfill.ts could NOT host the fix: it is imported by production
(bin/omniroute.mjs, proxyFetch.ts, proxyDispatcher.ts), where redirecting DATA_DIR would
point the live SQLite DB at a throwaway temp dir. So this adds a TEST-ONLY
tests/_setup/isolateDataDir.ts that gives each process its own temp DATA_DIR when none is
set (tests that set DATA_DIR explicitly still win), wired via --import into the test,
mutation and CI invocations.
Verified:
- Stryker dry-run A/B at concurrency=4: FAILS without the isolation import
(account-fallback-service tap exit 9, a cross-file race) and PASSES with it.
- Full `npm run test:unit` green with isolation (0 fail; a one-off
chatcore-translation-paths timeout flake did not reproduce and passes 3/3 isolated)
and noticeably faster — the DB lock contention is gone.
- New tests/unit/isolate-datadir.test.ts guards the contract (unique temp DATA_DIR when
unset; explicit DATA_DIR respected).
Wired the --import into: package.json (13 test scripts), stryker.conf.json (tap.nodeArgs
+ concurrency 1→4), .github/workflows/quality.yml (TIA step), ci.yml (the 5
unit/coverage/integration commands), and bumped nightly-mutation.yml timeout 120→180 for
the first cold run before the incremental cache is seeded.
* ci(quality): run the TIA gate at CI concurrency (4) to stop oversubscription flakes
The TIA "Impacted unit tests" step (made blocking in #4069) ran its fail-safe via
`npm run test:unit` — concurrency=20, tuned for multi-core dev machines. On a 4-vCPU CI
runner that is 5x oversubscribed, so timing-sensitive tests flake under the load (e.g.
`db-backup-extended` "The database connection is not open", `chatcore-translation-paths`
upstream-timeout). That intermittently fails a blocking gate on legitimate PRs — exactly
what surfaced on the DATA_DIR-isolation PR, whose package.json/workflow changes trip the
__RUN_ALL__ fail-safe.
Run both the impacted set and the fail-safe at --test-concurrency=4, matching the stable
ci.yml unit job. Adds a `test:unit:ci` script (test:unit at concurrency=4). The DATA_DIR
isolation in this PR keeps the parallel run race-free, so the only change here is matching
the runner's core count. Verified locally: db-backup-extended passes 8/8 in isolation
(5 with isolation, 3 without).
* docs(quality-gates): reconcile gate inventory with ci.yml + add ROI rationalization backlog (#4095)
The "authoritative" gate inventory in QUALITY_GATES.md had drifted from ci.yml: it omitted
9 wired gates — `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`
(lint job), `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`,
`check:codeql-ratchet` (quality-gate job), and `check:pr-evidence` (pr-test-policy job).
You can't rationalize an inventory you can't trust, so this reconciles it first.
Adds those 9 rows to their job tables and a "Rationalization Backlog (ROI review)" section
capturing the Fase 9 Onda 3 findings: mechanical merge/dedup candidates (CVE scanners
audit:deps↔osv, the two complexity ESLint passes, cycles↔circular-deps, the two /api
anti-hallucination gates, the doubly-run check:docs-sync, check:node-runtime ×11) and the
operator-only flip/drop decisions (typecheck:noimplicit vs the type-coverage ratchet,
test:vitest:ui parked fails, check:secrets frozen FPs, openapi-security-tiers, pr-evidence,
the orphaned semgrep baseline). Also flags the undocumented advisory docs-lint job and the
standalone scanner workflows.
Docs-only — no gate behavior changes. The merges (CI changes) and flips (policy) are
deferred to operator-scoped follow-ups; this PR only makes the map accurate.
* test(dashboard): smoke e2e for the Combo Live Studio page (#4075)
Integrated into release/v3.8.28
* fix(sse): friendly 413 message for ChatGPT web payload-too-large (#4080)
Integrated into release/v3.8.28
* feat(sse): port Claude Code quota-probe bypass + command meta-request helpers (#4083)
Integrated into release/v3.8.28
* feat(api): exact offline token counting for count_tokens fallback via tiktoken (#4087)
Integrated into release/v3.8.28
* feat(compression): RTK learn/discover (sample source + API + UI) (#4088)
Integrated into release/v3.8.28
* feat(dashboard): 2026-06-17 free-tier refresh — honest catalog, uncapped + boost tiers, Layout A budget table (#4089)
Integrated into release/v3.8.28
* feat(mitm): capture-pipeline self-test route (Gap 12) (#4093)
Integrated into release/v3.8.28
* fix(mitm): crash-safe system-state teardown + socket timeouts (ProxyBridge-inspired hardening) (#4084)
Integrated into release/v3.8.28 (Fast QG TIA red = 3 pre-existing timing flakes verified passing locally 82/82; PR own tests green)
* feat(mitm): attribute intercepted requests to originating process (Gap 1) (#4085)
Integrated into release/v3.8.28 (Fast QG TIA red = 3 pre-existing timing flakes verified passing locally 82/82; PR own tests green)
* fix(sse): route image requests only to confirmed-vision combo targets (#4071)
Integrated into release/v3.8.28
* fix(security): injection guard respects INJECTION_GUARD_MODE DB feature flag (#4077)
Integrated into release/v3.8.28
* fix(ws): proxy LAN /live-ws upgrades and add unset JWT_SECRET warning (#4079)
Integrated into release/v3.8.28
* fix(dev): force webpack in custom dev server (Turbopack 16.2.x panics) (#4092)
Integrated into release/v3.8.28
* ci(quality): dedup the doubly-run check:docs-sync + record validated ROI backlog (#4099)
Onda 3 (gate ROI-review) Phase 2. Two parts, both low-risk:
1. Remove the standalone `check:docs-sync` from the `lint` job — it already runs in the
`docs-sync-strict` job (via `check:docs-all`) and the husky pre-commit hook, so the
`lint`-job copy was a pure duplicate. No coverage lost.
2. Update the Rationalization Backlog in QUALITY_GATES.md with trust-but-verify findings:
several "obvious" merges/flips from the ROI review turned out to hide debt and are NOT
clean drop-ins —
- CVE merge (audit:deps→osv): different semantics (hard high/critical vs regression-ratchet) — keep both.
- cycles→circular-deps: dpdm reports 91 cycles (can't promote to blocking) and is broader-scope than the green curated check:cycles — keep both.
- openapi-security-tiers flip: blocked by traffic-inspector routes missing the x-loopback-only annotation.
- complexity + /api merges: valid but real config/script surgery — deferred.
- node-runtime ×11: ~10s savings vs a cheap guard — low ROI, skip.
The remaining flips (typecheck:noimplicit, test:vitest:ui, check:secrets, pr-evidence,
semgrep) are operator policy decisions, left for the owner.
* chore(deps): bump actions/github-script from 7 to 9 (#4046)
Integrated into release/v3.8.28 (dependabot GH-Action bump; SHA-pin preserved)
* chore(deps): bump actions/setup-node from 4 to 6 (#4048)
Integrated into release/v3.8.28 (dependabot GH-Action bump; SHA-pin preserved)
* chore(deps): bump actions/upload-artifact from 4 to 7 (#4044)
Integrated into release/v3.8.28 (dependabot GH-Action bump; SHA-pin preserved)
* chore(deps): bump actions/cache from 4.3.0 to 5.0.5 (#4047)
Integrated into release/v3.8.28 (dependabot GH-Action bump; SHA-pin preserved)
* deps: bump the development group with 10 updates (#4051)
Integrated into release/v3.8.28 (dependabot dev group; cyclonedx 4->5 verified compatible with the SBOM invocation --ignore-npm-errors/--output-format JSON/--output-file)
* fix(dashboard): event-driven fail-open auto-refresh for embedded log views (#4054) (#4103)
The Request Logger gated each auto-refresh tick on a static
document.visibilityState === "visible" read. Hosts that report a permanent
non-"visible" state without ever firing a visibilitychange event (Docker
dashboard wrappers, embedded/proxied webviews) froze auto-refresh entirely —
only the manual Refresh button worked, a regression from 3.8.24's unconditional
polling.
The pause is now event-driven and fail-open: visibleRef starts true and is only
flipped to false on a real visibilitychange → hidden transition, so a host that
never signals a genuine background transition keeps polling, while normal
browser tabs still pause when actually backgrounded.
Regression test reproduces the misreporting-host case (RED) and the perf guard
is re-encoded under the event-driven semantics.
* fix(docker): raise build-stage Node heap to stop production-build OOM (#4076) (#4104)
The Docker builder stage ran `npm run build` with V8's default heap ceiling
(~2 GB). After #4052 forced the heavier webpack engine (Turbopack panics on this
Next.js version), the production optimization pass exceeded that ceiling and the
build died with "FATAL ERROR: ... JavaScript heap out of memory" at
[builder] npm run build.
The builder stage now sets NODE_OPTIONS=--max-old-space-size (default 4096 MB,
overridable via --build-arg OMNIROUTE_BUILD_MEMORY_MB) before the build; the
value propagates to the spawned next build (resolveNextBuildEnv spreads
process.env). Build-only — the runtime heap on the runner stage is unchanged,
and CI/local builds (which invoke npm run build directly) are unaffected.
Regression guard: tests/unit/dockerfile-build-heap-4076.test.ts asserts the
builder stage sets the heap ceiling, before npm run build, at >= 4096 MB.
* feat(agent-bridge): portable JSON import/export of config (Gap 4) (#4094)
Integrated into release/v3.8.28
* feat(cli): add 'omniroute launch' zero-config Claude Code launcher (#4097)
Integrated into release/v3.8.28 (Fast QG TIA red = pre-existing env-doc-contract drift [MITM_IDLE_TIMEOUT_MS/TURBOPACK from #4084/#4092] + opencode-plugin-dist env flake; #4097 own test 3/3 green)
* feat(mitm): loop-guard self-check + verbosity control in server.cjs (Gaps 14+15) (#4101)
Integrated into release/v3.8.28 (rebased onto release — dropped the already-squash-merged #4084 commits; only the Gaps 14+15 loop-guard/verbosity delta remains)
* feat(sse): generic 400 field-downgrade retry + Groq field stripping (#4096)
Integrated into release/v3.8.28
* feat(providers): add Wafer AI (Anthropic-compatible, Bearer auth) (#4098)
Integrated into release/v3.8.28
* chore(docs)
* fix(responses): clear /v1/responses keepalive timer on cancel/abort (timer + CPU leak) (#4105)
Integrated into release/v3.8.28 (r7).
* perf(gemini): cache reasoning close-tag regex instead of recompiling per token (#4106)
Integrated into release/v3.8.28 (r7).
* fix(usage): reap orphaned pending-request details (unbounded memory leak) (#4107)
Integrated into release/v3.8.28 (r7).
* perf(stream): use structuredClone instead of JSON round-trip for per-chunk reasoning split (#4108)
Integrated into release/v3.8.28 (r7).
* fix(dashboard): restore Update Available banner with npm-binary-free version fallback (#4100) (#4112)
getLatestNpmVersion() derived the latest version only from the npm CLI binary and returned null on any error, so Docker/desktop/locked-down installs without npm on PATH silently hid the home banner even when an update existed. Add resolveLatestVersion() (npm CLI -> registry HTTP fallback -> logged warning) and harden version parsing for v-prefix/pre-release strings. Extracted into testable src/lib/system/versionCheck.ts with TDD coverage.
* fix(auth): prune expired entries from login brute-force guard map (unbounded growth) (#4111)
Integrated into release/v3.8.28 (r8)
* fix(logger): hard-cap the error-dedup map to bound memory under unique-message bursts (#4113)
Integrated into release/v3.8.28 (r8)
* fix(circuit-breaker): enforce MAX_REGISTRY_SIZE (declared but never applied) (#4114)
Integrated into release/v3.8.28 (r8)
* perf(obfuscation): cache per-word regexes instead of recompiling every request (#4109)
Integrated into release/v3.8.28 (r8)
* perf(registry): precompute model->provider index in parseModelFromRegistry (#4110)
Integrated into release/v3.8.28 (r8)
* fix(timers): unref background interval timers so they don't block clean shutdown (#4117)
Integrated into release/v3.8.28 (r8)
* fix(webhook): clear abort timer in finally to avoid dangling timers on fetch error (#4115)
Integrated into release/v3.8.28 (r8)
* fix(combo): detach per-target listener from shared hedge abort signal (#4116)
Integrated into release/v3.8.28 (r8)
* chore(release): finalize v3.8.28 CHANGELOG + reconcile env-doc contract
- Build the complete [3.8.28] CHANGELOG section (55 bullets) covering every
commit since v3.8.27, grouped by type with PR back-references and human
contributor attribution (artickc's memory-leak/perf cluster, OrcaRouter,
Wafer AI, MITM gaps, etc.); move the OrcaRouter bullet out of [Unreleased].
- Inject the EN [3.8.28] section into all 41 i18n CHANGELOG mirrors (parity).
- Reconcile the env/docs contract: document MITM_IDLE_TIMEOUT_MS + MITM_VERBOSE
in .env.example and ENVIRONMENT.md; allowlist the framework-internal TURBOPACK
and the Claude Code ANTHROPIC_AUTH_TOKEN in check-env-doc-sync.
- Fix 3 broken relative links in docs/providers/AGENTROUTER.md (regressed when
the file was relocated this cycle) so docs-sync-strict passes.
* fix(quality): treat test→test renames as relocations, not deletions
The anti-test-masking gate's subcheck-1 collected deleted AND renamed test
files via `--diff-filter=DR --name-only` and flagged every one as "deleted —
human review required", contradicting its own documented contract ("DELETADOS
ou renomeados-e-NÃO-substituídos"): a rename test→test IS a substitution (the
test moved, coverage preserved). This false-positived on #4063's legitimate
relocation of live-ws-startup.test.ts (unit/cli → integration, asserts 2→2)
and would block every PR that relocates a test — surfacing only at release-day
because the Fast QG (PR→release) doesn't run test-masking.
The gate now parses `--name-status -M`: true deletions and test→non-test
renames still flag; a test→test rename is run through the assert-reduction
check across the move, so a clean relocation passes while gutting-via-rename
(dropped asserts / new tautologies / skips) still fires. Adds
partitionDeletedRenamed + 6 regression tests.
---------
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Demiurge The Single <megamen932@gmail.com>
Co-authored-by: jinhaosong-source <jinhao.song@myflashcloud.com>
Co-authored-by: diego-anselmo <contato@diegoanselmo.com.br>
Co-authored-by: Felipe Almeman <4226997+zhiru@users.noreply.github.com>
Co-authored-by: Rahul sharma <sharmaR0810@gmail.com>
Co-authored-by: Chirag Singhal <76880977+chirag127@users.noreply.github.com>
Co-authored-by: NOXX - Commiter <artur1992123@mail.ru>
The v3.8.27 release Docker build failed on BOTH linux/amd64 and linux/arm64 with a
non-recoverable Turbopack panic — `TurbopackInternalError: internal error: entered
unreachable code: there must be a path to a root` in `ImportTracer::get_traces` (during
issue reporting), at Dockerfile `RUN npm run build`. Deterministic (not transient), so a
re-run does not help. The webpack build is the proven engine — `build:release` (deployed
to the VPS), the CI `Build` job, and `npm run build:cli` all use it and are green. Switch
the Docker build to webpack (OMNIROUTE_USE_TURBOPACK=0); re-enable once the upstream
Turbopack tracer bug is fixed. Documented in QUALITY_GATE_PLAYBOOK Parte 6.
* chore(release): open v3.8.27 development cycle
* fix(security): polynomial ReDoS in comboAgentMiddleware regex (#3982)
* fix(security): eliminate polynomial ReDoS in comboAgentMiddleware <omniModel> regex (CodeQL js/polynomial-redos)
CACHE_TAG_PATTERN wrapped the tag in an unbounded `(?:\\n|\n|\r)*` prefix/suffix.
On an unanchored `.test()`/`.exec()` that is O(n²) on inputs with many newlines
(CodeQL js/polynomial-redos, alerts #612/#613). The surrounding runs are irrelevant
to detecting/capturing the tag, so the detection pattern now matches only the core
`<omniModel>([^<]+)</omniModel>`; the global strip pattern still consumes the
wrapping newlines (combo.ts streaming, #531) but BOUNDED ({0,16}) so it stays linear.
Behavior preserved: detection, model extraction, multi-tag stripping (#454) and
blank-line cleanup all unchanged (107 related tests green). Adds ReDoS-safety
regression tests (50k-newline inputs complete in <1ms).
* docs(changelog): add #3982 ReDoS fix to [3.8.27]
* ci(security): harden workflows — artipacked persist-credentials + cache-poisoning + SC2086 (#3965)
* Refine provider quota card display (#3969)
Integrated into release/v3.8.27
* feat: add sidebar group separator toggles (#3971)
Integrated into release/v3.8.27
* Gate control-plane proxy direct fallback (#3963)
Integrated into release/v3.8.27
* Capture actual upstream provider requests (#3941)
Integrated into release/v3.8.27
* ci(quality): flip require-tighten + osv + Trivy to blocking (v3.8.27 cycle-end) (#3984)
* fix(resilience): respect connection cooldown stored as numeric epoch (#3954) (#3995)
rate_limited_until is a TEXT column, but setConnectionRateLimitUntil (Antigravity full-quota path) persists a raw epoch number that SQLite coerces to a numeric string ("1781696905131.0"). The selection predicate isAccountUnavailable then did new Date("1781696905131.0") -> NaN, so the cooling connection was never skipped and the router kept dispatching to rate-limited accounts. Normalize numeric-epoch strings (and number/Date/ISO) via a shared cooldownUntilMs() helper in isAccountUnavailable / getEarliestRateLimitedUntil / filterAvailableAccounts / parseFutureDateMs. ISO behavior preserved.
* fix(providers): fetch live /models for LLM7 and BytePlus (#3976) (#3996)
llm7 and byteplus carry a real modelsUrl but were not classified by any live-fetch branch of the model-import route, so their hardcoded 4-entry registry catalog was served (source local_catalog) instead of the upstream catalog. Add both to NAMED_OPENAI_STYLE_PROVIDERS so the route probes <baseUrl>/models and serves the live list, falling back to the local catalog only on fetch failure.
* fix(dashboard): logs auto-refresh reads live visibility, not a stale mount ref (#3972) (#3997)
The auto-refresh interval gated each tick on visibleRef, seeded once at mount and updated only by a visibilitychange event. A tab mounted while document.visibilityState is 'hidden' (background load, bfcache, embedded/proxied webviews) with no later visibilitychange left the ref false forever, so the interval ticked but never fetched — only the manual button worked. Read the live document.visibilityState in the tick instead.
* feat(compression): add Indonesian caveman rules and language pack (#3975)
Integrated into release/v3.8.27
(cherry picked from commit c9b5b1a892)
* fix(combo): shuffle strict-random fallback remainder to spread load (#3959) (#3998)
strict-random shuffled only the deck-selected slot 0 and left the fallback remainder in fixed priority order, so after a failing deck pick the chain always fell through to the same top-priority model — a persistently-failing model was retried on essentially every request and fallback load never spread across peers. Shuffle the remainder too (like the random strategy).
* Add provider auth visibility controls (#3953)
Integrated into release/v3.8.27
* fix(claude): forward client tool-search-tool anthropic-beta on the Claude OAuth path (#3974) (#3999)
The client-negotiated anthropic-beta: tool-search-tool-2025-10-19 was dropped on both Claude code paths (default executor rebuilt from static ANTHROPIC_BETA_CLAUDE_OAUTH; selectBetaFlags only read the client beta to gate thinking/effort), so claude.ai rejected deferred-tool requests with 400 'Tool reference not found'. Add an allowlist-merge (mergeClientAnthropicBeta) that unions the client's allowlisted betas into the outbound set on both paths, preserving #3415 (no forced thinking/effort).
* feat(providers): add model search filter to provider dashboard (#3950)
Integrated into release/v3.8.27
* fix(vision-bridge): force bridge for tokenrouter deepseek models (#3946)
Integrated into release/v3.8.27
* fix(executor): strip stream_options on non-streaming requests (#3884) (#4000)
Clients that send stream_options:{include_usage:true} regardless of stream (e.g. the OpenAI Python SDK) had it passed through on non-streaming calls; NVIDIA NIM rejected it with 400 'Stream options can only be defined when stream=True'. DefaultExecutor.transformRequest only injected/cleared stream_options on the streaming branch and never stripped a client-sent value when stream=false. Add a !stream strip branch; the streaming injection path is unchanged. Global to openai-compat providers.
* fix(qwen-web): cookie validation false-positive - check response body for user object (#3958)
Integrated into release/v3.8.27
* fix(db): persist backup retention days (#3970)
Integrated into release/v3.8.27
* 大量UI显示和i18n优化 (#3973)
Integrated into release/v3.8.27
* deps: bump the npm_and_yarn group across 1 directory with 2 updates (#3943)
Integrated into release/v3.8.27
* deps: bump form-data from 4.0.5 to 4.0.6 (#3944)
Integrated into release/v3.8.27
* deps: bump vite from 8.0.5 to 8.0.16 (#3942)
Integrated into release/v3.8.27
* chore(quality): re-baseline validation.ts 4407->4428 (#3958 qwen body-check)
The qwen-web validation body-check merged in #3958 pushed validation.ts past its
frozen size on the integrated release tip. Bump the baseline with justification;
no logic is separately extractable from the existing qwen-web validation branch.
* deps: bump the production group with 13 updates (#3915)
Integrated into release/v3.8.27 — low-risk group (playwright 1.60→1.61 minor + transitive patches; fumadocs-core 16.9→16.10 minor).
* chore(deps): ignore jscpd major bumps (v5 Rust rewrite breaks the duplication gate)
Our duplication ratchet (scripts/check/check-duplication.mjs) is pinned to jscpd@4
and parses jscpd-report.json against a frozen baseline. jscpd v5 is a native Rust
binary with no Node.js API and a different report/bin, so a major bump would break
the gate. Migrate deliberately, not via dependabot. Closes the noise from #3916.
* fix(perplexity-web): parse schematized diff_block stream so answers aren't empty (#4001)
Integrated into release/v3.8.27 — schematized diff_block parsing follow-up to #3938.
* refactor: modularize providerRegistry.ts into 159 individual provider plugins (#3993)
Modularize provider registry (#3594). Integrated into release/v3.8.27 after rebase + behavior-preservation verification (provider-consistency gate 159/232/0, typecheck, registry tests, build 556/556).
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
* fix(registry): restore byteplus + mimocode dropped by #3993 modularization
The provider-registry modularization (#3993) was cut from a base predating the
byteplus (#3877) and mimocode (#3837) registry entries, so merging it silently
dropped both providers (getRegistryEntry returned undefined → validation reported
'not supported'). Re-add them as registry modules in the new structure; registered
count 159→161, provider-consistency 161/232/0.
Also align the pre-existing qwen-web validator test to #3958: since the validator
now requires a real `user` object in the 200 body, the mock must carry one.
* refactor: modularize schemas (non-stacked) (#3988)
Modularize validation schemas (#3594). Integrated into release/v3.8.27 after rebase (reconciled the merged hiddenSidebarGroupLabels #3971 + intelligenceSyncRequestSchema into the new modules) + behavior verification (typecheck, 195 schema/settings/validation tests, build 556/556).
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
* fix(default-executor): honor custom providerSpecificData.baseUrl for OpenAI-format providers (#4002)
Integrated into release/v3.8.27 — honor custom providerSpecificData.baseUrl in DefaultExecutor (openai-format), tested.
* feat(openai): honor custom base URL in model discovery + complete openai/codex pricing (#4005)
Integrated into release/v3.8.27 — openai model-discovery honors custom base URL (SSRF-guarded) + pricing rows for new openai/codex models. Tested + baselines bumped.
* fix(live-ws): bridge sidecar events to dashboard (#4004)
Integrated into release/v3.8.27 — repair LiveWS sidecar (startup, same-origin /live-ws, main→sidecar compression.completed bridge, early-msg queue). Fixed the cookie-parse regex (\s) + added a focused unit test; baseline bumped for the non-blocking chatCore bridge.
* docs(troubleshooting): note MITM proxy cannot intercept Windows-host apps under WSL (#4003)
Integrated into release/v3.8.27 — MITM/WSL troubleshooting note.
* fix(repo): untrack accidentally-committed root node_modules symlink + gitignore it
A worktree node_modules symlink (-> the main checkout's node_modules) was staged by a
`git add -A` during the #3988 merge and committed into 05213ac6a. The symlink points
at the repo's own node_modules path, so checking it out turns the main checkout's
node_modules into a self-referential symlink (breaking tsx/all node ops). Untrack it and
add a root-anchored /node_modules ignore so the symlink form can't be re-committed (the
existing 'node_modules/' only matches directories).
* fix(quality): allowlist socks dep (declared by #4004, never allowlisted)
socks@^2.8.7 was added to package.json in #4004 (LiveWS sidecar, 02302131f)
as a phantom-dep cleanup but never added to dependency-allowlist.json, so
check:deps has been red on the release tip ever since. socks is the standard
SOCKS proxy client (dep of fetch-socks), legitimate and years old.
* feat(sse): real LLMLingua-2 ONNX compression engine (stable) (#4014)
Integrated into release/v3.8.27.
Adjustments before merge:
- Synced with the current release tip (was 11 commits behind).
- Added the 3 LLMLingua-2 ONNX optional-runtime deps to dependency-allowlist.json
(@atjsh/llmlingua-2, @tensorflow/tfjs, js-tiktoken) — the only gate that was red.
- socks was allowlisted directly on release (separate fix d7db5c73d; it was declared
by #4004 but never allowlisted, leaving check:deps red release-wide).
Verified locally: check:deps OK, file-size OK, public-creds OK, provider-consistency
161/232/0, typecheck:core clean, 24/24 LLMLingua tests pass. The only remaining Fast-QG
red is the pre-existing #3972 orphan test (request-logger-autorefresh-visibility-3972.test.tsx),
which is release-wide and unrelated to this PR.
* test(dashboard): rehome #3972 logs auto-refresh test so a runner collects it
tests/unit/request-logger-autorefresh-visibility-3972.test.tsx (added by #3972
via #3997) sat at the top level of tests/unit/ as a .tsx vitest test, which NO
runner collects: the node runner only globs *.test.ts, and test:vitest:ui only
runs tests/unit/ui. So the #3972 regression guard never executed in CI and
check:test-discovery was red release-wide. Move it under tests/unit/ui/ (the
collected vitest:ui path) and fix the relative import depth. Verified: the test
now runs and passes (2/2), and check:test-discovery is green.
* feat(compression): capture per-engine analytics (#3960) + Lite schema fix (#3952) (#4018)
Captures the net-new value from #3960 (per-engine breakdown analytics) and #3952 (Lite engine schema fix) onto release/v3.8.27. Fast QG green; 622/622 compression+analytics tests pass.
* fix(sse): guard model-less registry entries in getUnsupportedParams (mimocode) (#4015)
Real bugfix: guard model-less registry entries (mimocode) in getUnsupportedParams so handleChatCore no longer throws 'entry.models is not iterable' / reports 'All models failed' for unrelated requests. Includes a regression test. Fast QG green.
* feat(ci): Quality Gate v2 — Onda 0 + Onda 1 (gate flips, TIA, SAST, DAST-smoke, mutation infra) (#4016)
* docs(ops): add quality-gate assessment + replication playbook (Fase 9 foundation)
* feat(ci): flip oasdiff breaking-change gate to blocking (ratchet)
* docs(ops): deliver main branch-protection ruleset for owner to apply
* fix(ci): run typecheck:core in PR->release fast-gates (close fast-gates hole, part 1)
* perf(mutation): enable Stryker incremental mode + cache (scales the 60/80 rollout)
* feat(ci): commit CodeQL advanced config (security-extended), replacing default-setup
* feat(ci): version semgrep SAST workflow (owasp/secrets), advisory
* feat(quality): TIA test-impact map builder (import-graph; map built at runtime, gitignored)
* feat(quality): TIA impacted-test selector with run-all fail-safe
* fix(ci): run TIA-impacted unit tests in PR->release fast-gates (build map at runtime, fail-safe full)
* feat(ci): DAST-smoke per-PR (schemathesis subset + promptfoo injection-guard, blocking)
* fix(ci): unbreak Fase 9 PR CI (MDX frontmatter, CodeQL conflict, dast-smoke advisory)
- Add MDX frontmatter to docs/ops/{BRANCH_PROTECTION_MAIN,QUALITY_GATE_PLAYBOOK}.md.
fumadocs rejects frontmatter-less docs -> 'npm run build' failed -> broke dast-smoke's
build step (the release fast-gates never runs build, so this only surfaced on the PR).
- codeql.yml: workflow_dispatch-only until the owner switches repo CodeQL Default->Advanced
(advanced configs cannot be processed while default setup is enabled; documented inline).
- dast-smoke.yml: job-level continue-on-error (advisory) so this brand-new gate matures
before it blocks (repo convention: advisory -> blocking).
* ci(quality): make TIA unit-test step advisory until release test-debt is cleared
release/v3.8.27 carries ~17 pre-existing failing unit tests (budget #3537, apiKey
#3552, several Zod schemas, Puter/Qwen executors, mimocode entry, etc.) unrelated to
this PR — the new 'run tests on PR->release' gate surfaced them. Per the repo's
advisory->blocking convention, this step enters advisory (it still runs + reports)
so pre-existing debt doesn't block the gate program. typecheck:core stays blocking.
Flip to blocking (remove continue-on-error) once the release suite is green.
* fix(sse): preserve Kiro streaming finish_reason tool_calls (#3980) (#4025)
* fix(guardrails): preserve original image when vision-bridge describe fails (#4012) (#4026)
* feat(api): advertise combo capabilities on import surfaces (#3979) (#4027)
* feat(sse): delegated Anthropic Context Editing for Claude (clear_tool_uses) (#4021)
Opt-in Claude-only delegated compression: injects context_management.clear_tool_uses_20250919 at the Claude pre-serialization chokepoint (composes with clear_thinking, thinking first), threaded via ExecuteInput from handleChatCore. Pure edit-builder + 11 tests (7 unit + 4 e2e fetch-capture). Beta context-management-2025-06-27 already advertised; allowlist done. Telemetry/400-fallback/claude-web coverage deferred.
* fix(opencode): map x-session-affinity to x-opencode-session for custom providers (#4022) (#4028)
* fix(dashboard): Playground Compare tab loading + HTTP method guard (#4024)
randomUUID non-HTTPS fallback + static CompareTab import; raw HTTP TRACE->405 method guard wired into dev + standalone servers. Integrated into release/v3.8.27.
* refactor(dashboard): settings UI layout + API Keys naming (#4020)
Presentation/relabel refactor of the Settings dashboard (API Manager -> API Keys), card relocations, Toggle adoption, present-but-disabled engine steps. Auth-file changes are string/comment-only (no behavior change). Integrated into release/v3.8.27.
* fix: restore unit regressions dropped by lossy schema/registry modularizations (#4030)
Restores schema fields (combo reasoningTokenBuffer, budget-0 #3537, openrouter preset, proxy family #3777, resilience degradation/providerCooldown), qwen-web v2 endpoint+catalog, mimocode models key — all dropped by #3988/#3993 — and aligns 3 tests to #3941/#3993. Verified: 8 failing regression tests on release tip -> 131/131 green on this branch. Integrated into release/v3.8.27.
* fix(api): return 400 (not 500) for malformed JSON on /api/auth/login (#4031)
Wrap request.json() so a malformed/non-JSON login body returns a structured 400 instead of falling through to the 500 catch. Fixes the schemathesis high-risk-endpoint DAST finding (verified: schemathesis step now passes). +TDD test. Integrated into release/v3.8.27.
* feat(dashboard): real circuit-breaker state in the Combo Live cascade (U1b) (#4029)
Overlays real provider circuit-breaker state (GET /api/monitoring/health) onto the Combo Live cascade as a 'CB: OPEN · 41s' badge. Pure enrichRunWithBreakers + fail-soft useProviderBreakerHealth poll; graceful when health is absent. +13 tests. Integrated into release/v3.8.27.
* Fix promptfoo security assertion parsing (#4032)
* chore(deps): dependabot security bumps + drop unused gray-matter (#4036)
Integrated into release/v3.8.27 — dependabot security bumps (form-data/js-yaml/protobufjs/dompurify/hono) + drop unused gray-matter. Unblocks the npm audit:deps gate (Lint) branch-wide.
* fix(ci): scope TIA to node:test unit files only (mirror test:unit glob) (#4035)
Integrated into release/v3.8.27 — scopes the advisory TIA step to the test:unit node:test glob, fixing the 99 false failures. +4 TDD.
* Refine compression settings, storage labels, and sidebar grouping (#4033)
Integrated into release/v3.8.27 — relocate Token Saver into Compression Settings (controlled component), reorder Security/Authz tabs, storage labels + i18n relabel. Thanks @rdself!
* [codex] add per-key local usage command (#4034)
Integrated into release/v3.8.27 — per-key local @@om-usage command (cached quota, no upstream routing). Rebased onto modularized schemas/keys.ts + file-size rebaseline. Thanks @Witroch4!
* chore(release): reconcile v3.8.27 CHANGELOG + i18n mirrors
* ci(quality): unblock v3.8.27 release gates (zizmor pin + test-masking allowlist)
- zizmor ratchet (151→139, no regression): SHA-pin every action ref ADDED this
cycle — codeql/dast-smoke/semgrep (3 new workflows) + trivy-action (docker-publish)
+ actions/cache (nightly-mutation). Pre-existing tag refs keep the repo convention.
- test-masking: add config/quality/test-masking-allowlist.json + allowlist support in
check-test-masking.mjs (exempts ONLY the net-assert-reduction signal; tautology/skip/
deletion still fire). Allowlists 2 verified-legitimate reductions:
appearance-widget-settings-schema (#4033 removed showTokenSaverOnEndpoint field) and
dashboard-shell-tabs (#3973 tabs→redirect refactor, asserts replaced). +4 gate tests.
* test(quality): reword test-masking self-test comments to avoid literal masking patterns
The added allowlist-test comments contained the literal strings 'assert.ok(true)' and
'.skip' which the masking detector's own regexes match as text — making the gate flag
its own test file (net +1 tautology/skip/extended-tautology vs main). Reworded to plain
prose ('a new tautology', 'a new skip marker'); test logic unchanged (24/24 pass).
* fix(quality): unblock v3.8.27 release — align 3 stale tests + restore modularized settings-schema parity
Release-PR full CI surfaced 3 deterministic test failures (no live product regression),
all stale vs legitimate cycle changes:
- settings-schema parity (#3988): the modularized updateSettingsSchema barrel
(schemas/settings.ts) had diverged from the canonical settingsSchemas.ts (45 vs 85
fields — 40 dropped + 6 extra), a lossy-modularization dead-code copy. Re-export from
the canonical source so the barrel can never diverge again (runtime already uses
canonical). Parity test now passes.
- api-manager permissions modal: #4034 added a 4th self-service switch (per-key usage
allowance); a11y invariant (every switch type="button") still holds. Updated the
static count 3 -> 4.
- pack-artifact policy: dist/http-method-guard.cjs became a required runtime path;
added it to the test's expected missing-paths list.
Also documents the gate gap for Fase 9 (QUALITY_GATE_PLAYBOOK Parte 6): G1 run the
deterministic unit layer + test-masking on PR->release (not just PR->main), G2 a
modularization-parity gate (would have caught the #3988 drop at its PR), G3 flake
quarantine. Env flakes (LiveWS startup timeout, integration server-startup cascade)
are pre-existing/CI-env, triaged separately.
---------
Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
Co-authored-by: Veier04 <118300867+Veier04@users.noreply.github.com>
Co-authored-by: Felipe Sartori <felipesartori.ti@gmail.com>
Co-authored-by: WormAlien <164898390+WormAlien@users.noreply.github.com>
Co-authored-by: thezukiru <121331256+thezukiru@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: NOXX - Commiter <artur1992123@mail.ru>
Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com>
Co-authored-by: Demiurge The Single <megamen932@gmail.com>
Co-authored-by: Witroch4 <witalo_rocha@hotmail.com>
Brings the release/v3.8.27 fix (#3982) to main so CodeQL alerts #612/#613 close
on the next scan. Code + regression test only; the [3.8.27] CHANGELOG bullet lives
on release/v3.8.27 and reaches main when v3.8.27 ships (identical file → no merge
conflict). Detection pattern drops the unbounded surrounding newline run; global
strip pattern bounds it ({0,16}). Behavior unchanged (107 related tests green).
The plugin became ESM-only when the CJS bundle was dropped to fix the OpenCode loader
(#3883), so tests/scaffold.test.ts's 'CJS default export resolves via require()' test
fails at publish time with 'Cannot find module ../dist/index.cjs' (it only runs in the
npm-publish opencode-plugin job, so the cycle never caught it). Replaced with an ESM
import of the built dist/index.js asserting the same v1 { id, server } shape; dropped the
now-unused createRequire import. omniroute@3.8.26 itself already published fine.
The v3.8.25→v3.8.26 #3874 fix bumped npm-publish.yml's publish job to contents:write
(gh release upload for the SBOM). electron-release.yml calls that workflow as a reusable
job (publish-npm) but only granted contents:read — a reusable job cannot request more
than the caller grants, so GitHub rejected the v3.8.26 electron run at startup
(startup_failure). Aligns the caller permission to contents:write.
* chore(release): continue v3.8.25 development cycle after main code-sync (r5)
main fast-forwarded to release/v3.8.25 (#3863): unblocked Build+Docker via
#3864, plus #3837 (mimocode proxy) and #3862 (trivy bump). This marker
re-opens the umbrella PR for further v3.8.25 work. No version bump.
* fix(db): persist the Keep-latest-backups retention setting (#3834) (#3867)
* fix(oauth): clear GitLab Duo setup message instead of 500 (#3861) (#3868)
* test(oauth): prove refresh_token preserved on real gemini-cli/antigravity dispatch (#3850) (#3869)
* feat(compression-ui): unified compression config UI — per-engine pages + combos editor + menu + WS default-on (#3860)
Integrated into release/v3.8.25 — feat(compression-ui): unified compression configuration UI (Compression Hub + per-engine Lite/Aggressive/Ultra pages + combos editor + sidebar entry + live-WS default-on). File-size re-baselined for sidebarVisibility.ts/chatCore.ts growth; orphan ws test relocated to a collected path.
* docs(changelog): complete the v3.8.25 release notes + credit all contributors
Audited every commit since v3.8.24 and filled the gaps the [3.8.25] section
was missing: a New Features section (compression engines + Compression Studios
#3848, compression UI #3860, injection-guard #3857, kiro discovery #3836, Veo
#3839, mimocode proxy #3837, Arena ELO flag #3821), 9 more Fixed entries
(#3811/#3807/#3759/#3849/#3838/#3835/#3814/#3820/#3819), a Security section
(CCR IDOR #3859, supply-chain #3824), and an Internal/Quality section. Every
contributor and issue reporter is now credited.
* docs(changelog): restore + complete the v3.8.25 release notes
Re-adds CHANGELOG.md (a prior server-side commit accidentally dropped it) with
the complete, audited [3.8.25] section: New Features, the full Fixed list,
Security & Hardening, and Internal/Quality — every contributor and issue
reporter credited.
* chore(release): finalize v3.8.25 — reconcile CHANGELOG + i18n mirrors, document OMNIROUTE_MAX_PENDING_MIGRATIONS, green the unit suite
Release-gate reconciliation for v3.8.25:
- CHANGELOG: dated 2026-06-14, linked #3826, rolled up file-size re-baselines (#3823/#3833),
recorded the test-greening; re-synced all 41 i18n CHANGELOG mirrors.
- Documented OMNIROUTE_MAX_PENDING_MIGRATIONS (#3416) in .env.example + ENVIRONMENT.md.
- Greened the unit suite (was merged red on 4 CI shards): aligned 10 stale tests to this
cycle's intended behavior (#3838/#3822/#3501/SOCKS5/Vertex-Express/Antigravity) and the
same-provider 503 fall-through test; de-flaked the compression benchmark reproducibility
and ServiceSupervisor crash tests. No production code changed.
* ci(security): clear OpenSSF Scorecard code-scanning noise + harden workflow token permissions
The Security tab held 155 open alerts, ALL from the advisory OpenSSF Scorecard tool
(#3824) — supply-chain/posture scores, not code vulnerabilities — which drowned out
real CodeQL findings.
- scorecard.yml: stop uploading SARIF to the code-scanning tab (drop the upload-sarif
step + the now-unused security-events: write). The run still produces the OpenSSF
badge (publish_results) and a downloadable SARIF artifact.
- TokenPermissions hardening (the high-severity, genuinely-valuable subset): set each
workflow's top-level token to read-only and grant the exact writes at the job level
that needs them — npm-publish (id-token/packages on publish jobs), docker-publish
(packages on build), electron-release (contents on build/release, id-token/packages
on publish-npm), build-fork (packages on build), claude (empty top-level; job grants
its own). The 155 existing alerts were dismissed.
Not adopting repo-wide SHA-pinning (143 PinnedDependencies advisories) — declined.
* test(integration): align stale wiring/socks5 integration tests to this cycle's behavior
These were red on the CI Integration job (pre-existing). No production code changed:
- integration-wiring: the combos page no longer renders a per-page EmailPrivacyToggle
(#3822 consolidated it into Settings → Appearance); the provider-detail test-result
masking and upstream-proxy copy moved to decomposed components (#3501
BatchTestResultsModal / UpstreamProxyCard) — assertions now read the owning files.
- api-routes-critical: SOCKS5 is now enabled by default (opt-out), so the disabled-
rejection test must set ENABLE_SOCKS5_PROXY=false explicitly (an unset env now means
enabled).
(The ~32 live-Gemini integration tests are gated on OMNIROUTE_API_KEY and skip in CI;
they only 'fail' locally when that key is present without a running server.)
Integrated into release/v3.8.25 — feat(mimocode): per-account proxy for multi-account round-robin (runWithProxyContext per account, keyed by fingerprint). Orphan test relocated to a collected vitest path (14/14 green).
main was fast-forwarded to release/v3.8.25 (#3805); this marker re-opens the
umbrella PR so further v3.8.25 work keeps flowing to main. No version bump —
development continues on the current v3.8.25 line.