mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-03 13:52:09 +03:00
2c0fd047049751e73acdbc97e6982e6c19b8e0c7
1547 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2c0fd04704 |
feat: implement 5 harvested feature requests (#4239, #4155, #3841, #3266, #4240) (#4313)
* feat(providers): add OpenAdapter, dit.ai and TokenRouter OpenAI-compatible providers (#4239, #4155, #3841) Three community-requested OpenAI-compatible aggregators register as standard named OpenAI-style providers (the zenmux pattern): live /v1/models discovery via NAMED_OPENAI_STYLE_PROVIDERS, falling back to a seeded catalog on upstream error. No custom executor/translator — default OpenAI passthrough. - OpenAdapter https://api.openadapter.in/v1 (free tier) #4239 - dit.ai https://api.dit.ai/v1 (dynamic-pricing) #4155 - TokenRouter https://api.tokenrouter.com/v1 (free MiniMax model) #3841 Base paths confirmed live (each returns a 401 OpenAI-style error body). Seed catalogs are intentionally minimal (author/doc-cited ids only; TokenRouter deepseek ids come from production via #3946); full upstream model lists arrive through live discovery once a key is configured. * feat(combo): per-step account allowlist for round-robin over a connection subset (#3266) A combo model step can now carry a first-class `allowedConnectionIds` so a round-robin / weighted strategy is scoped to a subset of a provider's connections (e.g. {foo1, foo2}) without hand-pinning one step per account. - steps.ts: parse `allowedConnectionIds` on the model step (trim + drop empty) - comboStructure.ts: second writer — propagate the step allowlist onto the resolved target (tag routing is the first writer) - autoStrategy.ts: when a step allowlist AND tag routing both apply, intersect them (most-restrictive wins); empty intersection drops the target - builderDraft.ts + combos UI: optional 'Restrict to accounts' picker in the Precision step editor (a pinned single account still takes precedence) The downstream credential-selection filter (auth.ts) already honours allowedConnectionIds, so a round-robin scoped to {foo1, foo2} provably never selects foo3/foo4 (regression test included). Ships the enhancement only; the #2829 bug-triage half stays open pending the reporter. * feat(dashboard): category (media serviceKind) filter on the providers page (#4240) Add a media-category filter row (Image / Video / Music / Text→Speech / Speech→Text / Embedding) to /dashboard/providers that composes with the existing search, free-only and 'show configured only' filters. - serviceKindIndex.ts: client-side resolver unioning a provider's declared serviceKinds with the registry-derived media kinds (memoised) - providerPageUtils: filterConfiguredProviderEntries gains a serviceKindFilter argument; threaded through every provider section on the page - ProviderSummaryCard: a second chip row drives the serviceKind filter Membership is derived from the backend media registries, so a provider that serves a kind is surfaced even when it never declared serviceKinds — keeping the UI in lockstep with the backend (mirrors the media-providers pages). * chore(quality): rebaseline file-size for the v3.8.30 harvested features Four frozen files grew from their own additive feature wiring (#4239/#4155/#3841 providers, #3266 combo allowlist UI, #4240 serviceKind filter): - src/shared/constants/providers.ts 3169->3213 (3 provider entries) - src/app/api/providers/[id]/models/route.ts 2554->2560 (3 NAMED set entries) - src/app/(dashboard)/dashboard/combos/page.tsx 4350->4385 (allowlist picker) - src/app/(dashboard)/dashboard/providers/page.tsx 1925->1927 (serviceKind state) All cohesive additive wiring at existing chokepoints; rationale recorded in the _rebaseline_2026_06_19_v3830_harvest_features key. |
||
|
|
7ce875f404 |
feat(providers): provider model sweep — live discovery, refreshed catalogs, dead-provider cleanup (#4324)
* feat(providers): refresh core official model catalogs (sweep lote 1) Adiciona modelos GA atuais (verificados online) aos provedores oficiais core: - openai: gpt-5.5-pro, gpt-5.4-pro - anthropic: claude-opus-4.8 + claude-fable-5 (sampling fixo 4.7+, espelha 4.7), claude-opus-4.5 - groq: qwen/qwen3.6-27b, openai/gpt-oss-safeguard-20b - xai: grok-build-0.1 Fase 4 do provider-model-sweep. provider-consistency/file-size/typecheck:core verdes. * feat(providers): wire live /models discovery for 7 openai-style providers (sweep lote 2) venice, deepinfra, wandb, pollinations, nscale, inference-net and moonshot each expose a real live `<baseUrl>/models` catalog (the sweep probed each upstream), but were classified fixed-official, so import served their small hardcoded seed and re-staled the catalog. Add them to NAMED_OPENAI_STYLE_PROVIDERS so import does a live `<baseUrl>/models` fetch, keeping the registry seed only as the offline fallback — same fix shape as #4249 (vercel-ai-gateway) / #4202 (zenmux) / #3976 (llm7/byteplus). siliconflow was already classified. TDD regression in tests/unit/provider-sweep-live-discovery.test.ts pins each derived /models URL + the local-seed fallback path. file-size baseline bumped 2538->2548 (+10 = 7 Set entries + 3-line comment; not extractable). * feat(providers): wire live /models discovery for 12 aggregator marketplaces (sweep lote 3) crof, featherless-ai, ovhcloud, sambanova, orcarouter, uncloseai, opencode-go, baseten, hyperbolic, nebius, scaleway and together are GPU-cloud / aggregator marketplaces hosting large, volatile OSS catalogs. The sweep probed each and confirmed a live `<baseUrl>/v1/models` endpoint (200 public or 401/403 = exists + keyed), yet they were classified fixed-official and served a small hardcoded seed. Add them to NAMED_OPENAI_STYLE_PROVIDERS so import does a live `<baseUrl>/models` fetch (graceful fallback to the registry seed on any upstream error), keeping the catalog fresh instead of re-staling a hardcoded list. Extends tests/unit/provider-sweep-live-discovery.test.ts to 20 cases pinning each derived /models URL. file-size baseline bumped 2548->2564 (+16; not extractable). * feat(providers): add verified new models to nvidia, meta-llama, morph (sweep lote 4) Curated first-party / specialist menus (kept hardcoded — their per-model flags like toolCalling/supportsReasoning can't be inferred from a live catalog): - nvidia: + stepfun-ai/step-3.7-flash, deepseek-ai/deepseek-v4-flash (supportsReasoning), moonshotai/kimi-k2.6 — all confirmed present in the live NIM /v1/models catalog. minimaxai/minimax-m3 deliberately left out per #3329 (now listed, but its inference still needs confirmation before re-adding). - meta-llama: + Llama-3.3-8B-Instruct. - morph: + morph-qwen35-397b, morph-minimax27-230b, morph-qwen36-27b, morph-dsv4flash (Morph-hosted fast models, with context lengths). Skipped this batch after review: upstage solar-pro2 (older than the solar-pro3 already in the registry); longcat LongCat-2.0-Preview (deliberately commented out). * feat(providers): refresh Chinese first-party model catalogs, online-verified (sweep lote 5) Each registry held a single stale id; refreshed against official docs after per-id online verification (subagent research, cross-checked against first-party sources). Rejected/omitted entries are documented inline. - baidu: + 15 ERNIE ids (5.0/5.1 are the current flagships, confirmed live on Qianfan). - doubao: + 8 Seed-2.0/1.x dated Ark ids (Seed 2.0 GA 2026-02-14, confirmed real). - sensenova: + 8 SenseChat/SenseNova ids (V6.5-Pro flagship; 6.7-flash-lite lowercase). - tencent: + hunyuan-turbos-latest/t1-latest/vision/functioncall/lite. Dropped legacy standard/-256K/code/role + pinned turbos-20250226. NOTE: legacy Hunyuan platform EOLs turbos/t1 on 2026-06-22 (migrating to TokenHub/hy3-preview) — revisit. - baichuan: + Baichuan4-Turbo/Air, Baichuan3-Turbo/-128k (official pricing page). - stepfun: + step-3.7-flash (flagship), step-3.5-flash(-2603), step-1o-turbo-vision. - iflytek: + 4.0Ultra, max-32k, generalv3, pro-128k, lite (exact HTTP domains). - sparkdesk: + 4.0Ultra, generalv3, pro-128k. Rejected spark-x (separate /v2|/x2 endpoint). - volcengine: + doubao-seed-2-0-pro-260215, kimi-k2-5-260127 (Ark-hosted). * feat(providers): add verified models to kie, nlpcloud, publicai (sweep lote 6) - kie: + claude-opus-4-8, gemini-3-5-flash (current flagships the proxy surfaces; gemini-3-pro skipped — registry already carries the newer gemini-3-1-pro). - nlpcloud: + chatdolphin, dolphin (branded models), finetuned-llama-3-70b, llama-3-1-405b. Host confirmed reachable. - publicai: + Apertus-8B, Gemma-SEA-LION-v4-27B, Olmo-3-7B, EuroLLM-22B (open models). Skipped after review: minimax M2/M2.1 (older than the M2.5 floor the registry curates); yi (api.lingyiwanwu.com degraded + 01.AI exited foundation models); llamagate (host llamagate.ai unreachable, code 000) — both flagged for Track C. * feat(providers): finish Track B tail — cloudflare-ai, bailian, suno, +5 (sweep lote 7) - cloudflare-ai: + 7 Workers AI catalog ids (llama-3.3-70b-fp8-fast, qwen2.5-coder-32b, qwq-32b, llama-3.2-3b, glm-4.7-flash, kimi-k2.6, gemma-4-26b). - bailian-coding-plan: + qwen3.7-plus, qwen3-coder-plus, qwen3-coder-next, glm-4.7. - suno: + chirp-fenix (V5.5), chirp-crow (V5). - monsterapi: + Meta-Llama-3.1-8B, Llama-3.3-70B. - huggingchat: + Qwen3-235B-A22B, Mistral-Small-3.1-24B. - vertex-partner: + claude-opus-4-8, claude-opus-4-6. - puter: + google/gemini-3.5-flash. - codestral: + codestral-2508. Skipped after verification: windsurf + devin-cli — docs.devin.ai exposes DASHED ids (claude-opus-4-8-low, MODEL_PRIVATE_4 for "Grok Code Fast 1", minimax-m2-5) while the registry uses DOTTED (claude-opus-4.7-max); id-form ambiguity needs owner confirmation before adding 13+ entries. leonardo/ideogram (image UUID-vs-friendly convention), glmt (shared GLM_SHARED_MODELS, redundant with the live `glm` provider). * fix(providers): drop retired models, add codestral-2405 forward (sweep lote 8, Track C C1) Confirmed removals that interacted with the sweep's adds: - codestral: drop codestral-2405 (retired 2025-06-16, Mistral official docs) from the menu + add a codestral-2405 -> codestral-2508 deprecation alias so old configs forward. - monsterapi: drop llama-3-8b-fuse (no longer evidenced in the catalog). - volcengine: drop kimi-k2-thinking-251104 (retired on Ark; superseded by kimi-k2-5-260127). * chore(providers): mark 6 dead providers deprecated (sweep lote 9, Track C C2) The sweep verified these providers are no longer reachable/operational, so flag them with the existing deprecation mechanism (deprecated:true + a deprecation risk notice) instead of silently offering non-working options. Conservative — plumbing (executors/icons/free-catalogs) is left intact; only the UI-facing metadata changes. - kluster, glhf, predibase, inclusionai, galadriel: api host DNS no longer resolves. - phind: API shut down 2026-01 (www.phind.com/api/chat no longer serves). Not touched: gemini-cli (Google OAuth infra still live), qwen (already deprecated), chipotle (easter-egg, out of scope). file-size baseline bumped 3169->3198. * fix(providers): replace retired LongCat-Flash line with LongCat-2.0-Preview (sweep lote 10) The LongCat-Flash-* models (Lite/Chat/Thinking/Omni-2603) were officially retired 2026-05-29; the current longcat.chat/platform docs expose only LongCat-2.0-Preview (confirmed via WebFetch of the live API docs). Swap the stale 4-model seed for the single current model so the provider stops offering dead ids. * chore(quality): reconcile antigravity.ts file-size baseline 1664->1680 #4309 (Undici socket-leak fix) grew antigravity.ts by +26 lines but its file-size baseline was not bumped at merge time; reconcile it here on the combined tree so the release file-size gate stays green (Rule #9, release-volatile reconciliation). |
||
|
|
831bd0a7b3 |
feat(quality): make the a11y gate real (@axe-core/playwright in nightly) (#4321)
* feat(quality): instala @axe-core/playwright + allowlist (T13)
Pré-requisito do gate de a11y real. O spec tests/e2e/a11y.spec.ts já tem o
mecanismo REQUIRE_AXE=1 que falha se o pacote estiver ausente quando exigido —
faltava só o pacote + o job nightly + o baseline real.
- @axe-core/playwright@^4.11.3 em devDependencies (via --package-lock-only,
não toca o node_modules compartilhado das worktrees)
- adicionado à dependency-allowlist (check:deps OK, 127 deps)
- vuln-ratchet OK (1 moderate pré-existente, baseline 10 — axe não regride)
* feat(quality): job a11y nightly (REQUIRE_AXE=1) + gate per-PR (T13)
- novo job 'a11y' em nightly-resilience.yml: o webServer do Playwright builda o
Next (build-next-isolated.mjs) e sobe o standalone sozinho (sem artefato pré-
buildado como o test-e2e do ci.yml), REQUIRE_AXE=1 roda a análise axe real.
- gate de nightly no spec: a11y.spec.ts é casado pelo glob 'tests/e2e/*.spec.ts'
do job per-PR (9 shards); sem gate, instalar @axe-core/playwright ligaria axe
em TODO PR (e falharia em baseline 0). Os 4 testes de página agora skipam a
menos que REQUIRE_AXE=1 — per-PR fica rápido/verde, nightly roda real.
- zizmor 139->145: +3 drift pré-existente da base
|
||
|
|
facbb1964f |
feat(quality): unblock R1 — test-redundancy measurement via disableBail (#4322)
* feat(quality): config stryker disableBail para medir redundância (R1) Estende stryker.conf.json com disableBail:true (killedBy lista TODOS os killers, não só o primeiro) + incremental:false (medição limpa). One-off, não toca o nightly — só roda via mutation-redundancy.yml. * feat(quality): workflow on-demand de mutação disableBail (R1) mutation-redundancy.yml (workflow_dispatch): roda os 6 leaf-batches combo+chatCore do nightly com o override disableBail e sobe os reports. Batches granulares (d/e/f/ g/h/i) em vez de 2 mega-batches: disableBail é mais caro e Stryker só grava o report se COMPLETAR, então cada batch cabe nos 300min. zizmor 139->145: +3 drift de base + 3 do workflow (@vN, convenção do repo). Ver nota. * feat(quality): mutation-radiography --candidates (lista de prune R1) Reusa aggregateRadiography (DRY) em vez de um script novo: redundancyCandidates() retorna 🔴 empty ∪ 🟠 redundant (zero kills únicos) = candidatos a prune. Sob disableBail o killedBy é completo, então 🟠 redundant fica ACURADO. CLI --candidates emite a lista com o aviso do gate humano (excluir segurança/contrato/repro). |
||
|
|
0d6c5686d2 |
feat(dashboard): list MITM hosts-file entries in the tool card (#4325)
The CLI-tools MITM card's "How it works" section showed a single hardcoded example domain (only antigravity/kiro). It now lists every 127.0.0.1 <host> entry for the selected tool, so users on locked-down machines — where the automatic, sudo-gated hosts-file edit isn't available — can add them manually. Hosts come from a client-safe projection of the canonical src/mitm/targets registry (MITM_TOOL_HOSTS), kept in lock-step with the registry by a sync test (no duplicated source of truth). Co-authored-by: mrcyclo <13806369+mrcyclo@users.noreply.github.com> |
||
|
|
5c68048650 |
fix(sse): release reader and cancel stream on abort/error to prevent Undici pool socket leak (#4309)
* fix(sse): release reader and cancel stream on abort/error to prevent Undici pool socket leak * test(sse): regression guard for Undici socket release on abort (#4309) Proves collectStreamToResponse releases the reader and cancels the response body on the abort/error path (fails without the fix, passes with it) — Rule #18. Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> --------- Co-authored-by: Dmitry Kuznetsov <dmitry@kuznetsov.me> Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> |
||
|
|
3b21a952fc |
fix(kiro): emit early role-only start chunk to release stream-readiness gate (#4311)
* fix(deps): bump undici to 7.28.0 and dompurify to 3.4.11 (security) (#4304) * fix(deps): bump undici to 7.28.0 and dompurify to 3.4.11 (security) Resolves Dependabot alerts on package-lock.json and electron/package-lock.json: - undici 7.x -> 7.28.0: TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent (GHSA-vmh5-mc38-953g, HIGH) + cross-user information disclosure via shared-cache whitespace bypass (GHSA-pr7r-676h-xcf6, MEDIUM). Fixed in the root (jsdom transitive) and electron lockfiles. - dompurify -> 3.4.11: permanent ALLOWED_ATTR pollution via setConfig() bypassing the hook clone-guard (GHSA-cmwh-pvxp-8882, MEDIUM). Bumped the overrides floor from ^3.4.9 to ^3.4.11. Also bumps node-gyp's transitive undici 6.26.0 -> 6.27.0, clearing the <6.27.0 advisories (WebSocket DoS, Set-Cookie handling) surfaced by npm audit. Lockfile/override-only change; no production source touched. * ci(quality): exclude dependency manifests/lockfiles from PR test-policy The PR test-policy gate classifies any changed file under src/, open-sse/, electron/, or bin/ as production code requiring tests. This false-flags lockfile/manifest-only changes (e.g. this Dependabot security bump touching electron/package-lock.json), since a lockfile cannot have a meaningful unit test. Adds package.json / package-lock.json to EXCLUDED_PATTERNS, consistent with the existing .md/.yaml/.yml exclusions. Real production-code changes remain flagged. * fix(kiro): emit early role-only start chunk to release stream-readiness gate CodeWhisperer sends framing/metadata frames before the first content token; on large/agentic contexts that gap can be many seconds. ensureStreamReadiness holds the whole response from the client until it sees a useful SSE frame, so without an early frame the client sees a frozen connection up to STREAM_READINESS_TIMEOUT_MS (180s in VibeProxy) then a burst. Emit a role-only chat.completion.chunk on the first parsed AWS EventStream frame (a non-ping structured payload that satisfies hasStreamReadinessSignal) to hand off immediately, mirroring Claude message_start / OpenAI response.created. --------- Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
ad4338449c |
fix(quality): complexity gate covers bin/+electron + tracked-artifacts in pre-commit (#4318)
* fix(quality): complexity gate varre bin/ + electron (6A.11, fake-green fix)
ESLINT_ARGS passava só 'src open-sse', mas o config eslint.complexity.config.mjs
e o complexity-baseline.json já documentavam o escopo src+open-sse+electron+bin.
A edição do scan nunca tinha sido aplicada: o gate alegava cobrir bin/electron e
nunca os varria (fake-green — uma god-function nova em bin/ passava verde).
- exporta ESLINT_ARGS + adiciona 'electron' e 'bin' (casa o config)
- teste de build trava o escopo do scan
- baseline 1887->1888: electron+bin medem 0 (widening 0-custo); o +1 é drift
pré-existente de src/open-sse da base
|
||
|
|
0abc00cf2a |
fix(translator): clamp Responses API call_id to 64 chars (#4317)
* fix(translator): clamp Responses API call_id to 64 chars (port from 9router#396) The OpenAI Responses API rejects call_id values longer than 64 characters with a 400. Long upstream tool-call ids (some clients emit ids well over the limit) were forwarded verbatim. Clamp the id deterministically on both the function_call item and its matching function_call_output, so the pair stays matched through the orphaned-output filter and the request is accepted. Reported-by: ngapngap (https://github.com/decolua/9router/issues/393) Co-authored-by: Anurag Saxena <17893081+anuragg-saxenaa@users.noreply.github.com> Co-authored-by: ngapngap <27039619+ngapngap@users.noreply.github.com> * chore(quality): bump translator-openai-responses-req file-size baseline 1011->1047 The clamp-call_id regression test (+36 lines) grew the test file past its frozen baseline; bump it in the same change (Rule #9). --------- Co-authored-by: Anurag Saxena <17893081+anuragg-saxenaa@users.noreply.github.com> Co-authored-by: ngapngap <27039619+ngapngap@users.noreply.github.com> |
||
|
|
032387401a |
fix(oauth): GitHub Copilot token refresh sends the public client_id (#4320)
GitHub Copilot is a public device-flow OAuth client (client_id, no client_secret), but the github provider config never populated clientId. The standalone refresh path omitted client_id (buildFormParams drops undefined) and the executor path sent the literal "client_id=undefined&client_secret=undefined" — both rejected by GitHub, so a Copilot connection got stuck once its short-lived token expired and the long-lived refresh path was needed. Populate the provider clientId from the embedded public cred (resolvePublicCred, never a literal) and only send client_secret when one exists. The prior github refresh test patched a fake clientId/clientSecret onto PROVIDERS.github, masking the broken real config — it now exercises the real config. Co-authored-by: Manuel B. <1494154+baslr@users.noreply.github.com> |
||
|
|
466d3cf6eb |
fix(open-sse): inner-ai stops silently rerouting unmatched models to models[0] (#4310)
Inner.ai's live model list is plan-gated, and findModel() fell back to models[0] (the first live model, typically gpt-4o) whenever the requested model did not match by exact/case-insensitive/substring. That silently rerouted every model not exposed by the plan to gpt-4o, so users reported that only gpt-4o ever responded. findModel() now returns null on no match; the single caller already builds a synthetic entry carrying the actually-requested model name, so the request is sent for the model the user asked for and Inner.ai can surface a clear error if the plan does not expose it. TDD: tests/unit/inner-ai-find-model.test.ts (no-match -> null red->green; exact / case-insensitive / substring / empty-list regression guards). |
||
|
|
6912664133 |
fix(sse): retry direct socket failures on a fresh no-keep-alive dispatcher (#4252) (#4319)
The default direct dispatcher pools keep-alive sockets for up to
fetchKeepAliveTimeoutMs (4s). Edges like nvidia / opencode-zen silently
close idle keep-alive sockets within that window, so the next request
reusing a pooled socket fails with UND_ERR_SOCKET ("other side closed") —
in bursts. proxyFetch retried once, but the retry reused the SAME pooled
dispatcher and could grab another stale socket, then fell through to native
fetch (which also pools) → the job sat in the rate-limit queue until the
30s timeout → 502 + circuit breaker open.
Add getRetryDispatcher() (no keep-alive, no pipelining, mirrors the proxy
dispatcher mitigation) and use it for the retry attempt so it opens a fresh
socket that can't be a dead pooled one. The first attempt still uses the
pooled dispatcher, preserving healthy keep-alive reuse.
Regression test (DI mock) asserts the retry uses getRetryDispatcher(), not
getDefaultDispatcher() (RED before, GREEN after).
Refs #4281 (describeFetchCause diagnostics). Closes #4252
|
||
|
|
9a678497ad |
fix(sse): stop combo at the first body-specific 400 (#4279) (#4316)
The #2101 guard that detects a body-specific 400 (context overflow / malformed / model-access-denied) logged "stopping combo" but executed a bare `break`, which only exits the inner retry loop. executeTarget then returns null, and the outer target loop treats null as "this target produced nothing" and advances to the next model — so the guard never actually stopped fallback, and a combo of N targets that all reject the same request body tried all N (the report shows a 143-model Codex combo marching through every target). Surface the 400 via the {ok,response} contract (mirrors the 499 client-disconnect path) so the outer loop resolves the combo and stops. Regression test: a 3-target priority combo whose targets all return a body-specific 400 must stop after target 1 (RED before, GREEN after). Closes #4279 |
||
|
|
db7c8c5edc |
fix(pollinations): handle auth-required premium models (#4266)
Pollinations now requires API keys for premium models (claude, gemini, midijourney). The executor surfaces an actionable 401 with the keyless-model list, chatCore preserves the upstream HTTP status (401 -> authentication_error instead of 502), and the free catalog marks the premium models as key-required. Rebased onto the release tip and reconciled the file-size baseline (chatCore 5128). Thanks @oyi77. |
||
|
|
84bf5dc7de |
fix(sse): recover reconstructed message when Responses terminal output is textless (#3948) (#4315)
A Responses-API target (codex/cx) streams from upstream even on stream:false. Its terminal `response.completed` snapshot can carry a non-empty `output` that lacks the assistant message item (e.g. only a reasoning item) even though the streamed output_text deltas reconstructed a full message. parseSSEToResponsesOutput preferred the terminal output wholesale, dropping the reconstructed text → empty content on stream:false (hit via n8n, which defaults to stream:false). Fall back to the reconstructed delta output when the terminal output has no message item but the reconstruction does; the terminal snapshot still wins when it already carries the message. Regression test feeds a synthetic codex SSE (reasoning-only terminal + message deltas) and asserts the assistant text survives, plus a control case where the terminal carries the message. Closes #3948 |
||
|
|
6103288c48 |
fix(executors): preserve tool-name casing on native Claude OAuth (#4307) (#4314)
The native-Claude OAuth anti-fingerprint cloak renames a tool named `read` to `Read` on the wire and records the reverse alias on a non-enumerable `_toolNameMap`, which the response side un-cloaks to restore the client's original casing. Since v3.8.27 (#3941/#3968) `execute()` returned a JSON-round-tripped `serializedBody` as `transformedBody`; the round-trip drops the non-enumerable map, so the restore saw an empty map and the cloaked `Read` streamed verbatim to the client. Re-attach the live `_toolNameMap` onto the serialized body before returning (non-enumerable, mirrors antigravity.ts::attachToolNameMap) so tool-name casing round-trips correctly. Regression test exercises base.ts execute() through the claude-OAuth cloak path and asserts the returned transformedBody carries the reverse map. Closes #4307 |
||
|
|
a23d0d678a |
fix(api): semantic-cache HIT bills incremental cost 0 + X-OmniRoute-Cost-Saved (PRD-2026-06-19) (#4308)
Cache HITs now report Response-Cost 0 (incremental) and surface the avoided cost in X-OmniRoute-Cost-Saved. MISS path unchanged. TDD + per-key isolation guard. |
||
|
|
23455fdb0a |
feat(cli): setup-gemini — point the Gemini CLI at OmniRoute's native /v1beta endpoint (#4303)
The Gemini CLI is not OpenAI-compatible — it speaks the native Gemini API. OmniRoute exposes a Gemini-native surface at /v1beta, so setup-gemini emits the @google/genai env recipe (GOOGLE_GEMINI_BASE_URL root + GEMINI_API_KEY) and optionally writes ~/.gemini/settings.json (model). Remote-aware (resolves baseUrl + key from the active context, --remote or --api-key). Documents the cached-Google-login caveat that can override the base URL. Completes the per-CLI setup series (Codex, Claude, OpenCode, Cline, Kilo, Continue, Cursor, Roo, Crush, Goose, Qwen, Aider, Gemini). |
||
|
|
0ab1876008 |
feat(mitm): translate Antigravity cloudcode end-to-end (Gap B) (#4299)
The Antigravity IDE speaks cloudcode (the Gemini payload wrapped under
`request`) and expects a cloudcode reply ({response:{candidates}}). The
AgentBridge proxy forwarded that envelope verbatim to /v1/chat/completions
(OpenAI), which 400s on the missing `messages` field — so the IDE could be
decrypted/intercepted but never actually routed to a provider.
Wire the inbound cloudcode path, reusing the already-registered bidirectional
translators (no new translators needed):
- provider.ts: detectFormatFromEndpoint classifies the /antigravity path as
sourceFormat "antigravity" (mirrors /messages -> claude), so the pipeline
translates request antigravity->openai and response openai->antigravity.
- /v1/antigravity route (new): cloudcode-compatible endpoint — just calls
handleChat (mirrors /v1/messages).
- server.cjs: routes cloudcode envelopes to /v1/antigravity (translates both
ways) and plain OpenAI bodies to /v1/chat/completions, via a testable shim.
Tests: forward-target shim (cloudcode vs openai routing) + endpoint format
detection. The antigravity<->openai translators are already covered by
translator-antigravity-to-openai / translator-resp-openai-to-antigravity.
Stacked on #4285 (Gap A). Full Antigravity-IDE e2e validates on the next
standalone deploy (provider.ts + the route compile into .next).
|
||
|
|
915991c762 |
fix(codex): isolate Spark quota scope (#4293)
* fix(codex): isolate Spark quota scope * fix(codex): address Spark quota review feedback * fix(ci): update Electron undici override * fix(ci): update root undici overrides * test(integration): sync stale expectations * test(tproxy): tolerate available native addon * test(tproxy): avoid environment-specific skips * test(tproxy): keep assertion count stable * fix(ci): stabilize quality and tproxy checks * chore(ci): rebaseline auth file size * fix(ci): extend node compatibility budget * chore(quality): reconcile complexity + file-size baselines after release/v3.8.30 merge (#4293) Measured on the actual merged tree (not the PR's main-based estimate): complexity 1885->1887 (+2); file-size auth.ts 2219->2279, chatCore.ts 5116->5125, accountFallback.ts 1727->1731, + the 4 Codex test files. Drift test-file conflicts (search-providers-catalog, tproxy-transparent-socket, integration-wiring) resolved to the already-merged release versions (#4276). Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> --------- Co-authored-by: ci <ci@local> Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> |
||
|
|
165d9cdae9 |
feat(cli): setup-aider — configure Aider for OmniRoute (.aider.conf.yml + env recipe) (#4302)
CLI #12 of the series. `omniroute setup-aider` writes Aider's ~/.aider.conf.yml (openai-api-base = ROOT url, NO /v1 — LiteLLM appends /v1/chat/completions — model: openai/<id>), merges to preserve existing config, and prints the env recipe (OPENAI_API_BASE + OPENAI_API_KEY in the env, never the file) plus the headless command (aider --message ... --yes). Remote-aware; model via --model or interactive pick. Researched against aider.chat: OpenAI-compatible via OPENAI_API_BASE (base, no /v1) + --model openai/<id>. Aider's wire (/v1/chat/completions) already validated → "OK". Tests: resolveAiderTarget (/v1 strip, key), buildAiderConfig (openai-api-base + openai/<model> + preserve), buildAiderRecipe (env-ref key + headless). 4 unit tests; cli-i18n green. |
||
|
|
b01b72052f |
fix(dashboard): improve API try it functionality (#4296)
* 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> |
||
|
|
b56b7b1914 |
fix: polyfill crypto.randomUUID for non-secure contexts (#4287)
* 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> |
||
|
|
133432b523 | fix(proxy): allow concurrent proxy dispatcher streams (#4288) | ||
|
|
0acb8d0aeb |
fix(build): co-locate llmlingua SLM optionals into dist/node_modules (postinstall) (#4286)
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 |
||
|
|
efbe0a6af1 |
fix(mitm): surface AgentBridge traffic in the Traffic Inspector (D4 ingest) (#4285)
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). |
||
|
|
0ec476b755 |
feat(cli): setup-qwen — configure Qwen Code for OmniRoute (settings.json modelProvider) (#4301)
CLI #11 of the series. `omniroute setup-qwen` writes Qwen Code's file-based ~/.qwen/settings.json: an openai `modelProvider` (id omniroute, authType openai, baseUrl WITH /v1, envKey OMNIROUTE_API_KEY — secret stays in the env), selects it, sets the model. Merges (de-dupes the omniroute provider, preserves the rest). Remote-aware; model via --model or interactive pick; headless test `qwen -p`. Researched against QwenLM/qwen-code: modelProviders authType openai, baseUrl /v1, envKey reference. Qwen's wire (/v1/chat/completions) already validated → "OK". Tests: resolveQwenTarget (/v1, key), buildQwenSettings (openai provider + /v1 + envKey + model, de-dupe + preserve). 4 unit tests; cli-i18n green. |
||
|
|
25f9dac9e9 |
feat(cli): setup-goose — configure Goose for OmniRoute (config.yaml + env recipe) (#4300)
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. |
||
|
|
26dd5d775c |
feat(cli): setup-crush — OmniRoute openai-compat provider in crush.json (#4298)
CLI #9 of the series. `omniroute setup-crush` writes Crush's file-based ~/.config/crush/crush.json with an `openai-compat` provider for OmniRoute: base_url WITH /v1, api_key referenced as $OMNIROUTE_API_KEY (secret off disk), curated catalog models with context_window. Merges (preserves existing config). Remote-aware (--remote/--api-key → active context → localhost); --only filter. Researched against charmbracelet/crush: openai-compat provider type, base_url needs /v1, $VAR api_key references. Crush's wire (/v1/chat/completions) already validated → "OK". Tests: resolveCrushTarget (/v1, key), buildCrushProvider (openai-compat + env-ref + curated models + skip-unknown), mergeCrushConfig (preserve). 4 unit tests; cli-i18n green. |
||
|
|
bf5b615969 |
feat(memory): x-omniroute-no-memory opt-out + memory off-by-default + token-cost alert (PRD-2026-06-19) (#4290)
* 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. |
||
|
|
5b40069b71 |
feat(cli): setup-roo — configure Roo Code for OmniRoute (import JSON + autoImport + UI) (#4292)
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. |
||
|
|
9616b65b53 |
feat(cli): setup-cursor — print Cursor setup steps for OmniRoute (#4291)
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. |
||
|
|
138ea6628d |
feat(cli): setup-continue — generate ~/.continue/config.yaml for OmniRoute (#4289)
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. |
||
|
|
70bd6fbcc9 |
feat(cli): setup-kilo — configure Kilo Code for OmniRoute (CLI auth + VS Code settings) (#4284)
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. |
||
|
|
6f16faa039 |
fix(models): keep vision capability for imported (synced) models (#4264) (#4283)
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
|
||
|
|
158a2246dd |
feat(cli): setup-cline — configure Cline for OmniRoute (CLI files + VS Code hints) (#4280)
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. |
||
|
|
550440f65f |
fix(providers): Cloudflare Workers AI discovery uses model names, not UUIDs (#4259) (#4282)
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
|
||
|
|
98b0d5e51e |
fix(sse): surface undici err.cause on dispatcher failure (#4281)
Surface err.cause + propagate diagnosable error fast on undici dispatcher failure. TDD. #4252. |
||
|
|
5c4b0e327d |
fix(cli): harden launch/launch-codex with free-claude-code patterns (#4278)
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. |
||
|
|
871d109066 |
feat(quality): cap test-file size (anti-reinflation Layer 1) — freeze god-tests, cap new at 800 (#4273)
Layer 1 anti-reinflation: cap test-file size (freeze god-tests, cap new at 800). Gate validated green against the full combined tree. |
||
|
|
1696944dce |
feat(cli): OpenCode setup commands (openai-compatible provider + remote-aware plugin) (#4277)
setup-opencode: remote-aware openai-compatible provider generator; API key referenced by env var (never on disk). 6 tests. |
||
|
|
b7414c21c5 |
ci(mutation): split over-budget batches by range/pair so every batch fits the job cap (#4272)
Split over-budget mutation batches by range/pair + union same-file mutants across sibling batches. CI-only + TDD. |
||
|
|
ec4d94f4c1 |
test(ci): reconcile release/v3.8.30 baseline + test drift (#4276)
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. |
||
|
|
3e6be47012 |
feat(cli): Claude Code launcher + setup commands (remote mode + profiles) (#4274)
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. |
||
|
|
3c9883bb73 |
Release v3.8.29 (#4126)
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. |
||
|
|
f165efcd0b |
Release v3.8.28 (#4053)
* 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> |
||
|
|
fa367dd99e |
Release v3.8.27 (#3968)
* 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 |
feat(compression): add Indonesian caveman rules and language pack (#3975)
Integrated into release/v3.8.27 |
||
|
|
7509a32e9d |
fix(security): polynomial ReDoS in comboAgentMiddleware regex → main (#3983)
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). |
||
|
|
4d21044ba5 |
fix(release): post-merge quality gates to main for v3.8.26 (#3964)
Cherry-picks #3961 + #3962 from release/v3.8.26 to main (parity before tagging). |