Commit Graph

888 Commits

Author SHA1 Message Date
Ravi Tharuma
5a44c46b1d feat(resilience): scope auto-disable banned accounts to subscriptions (#10617)
* feat(resilience): scope auto-disable banned accounts to subscriptions

Prepaid API keys should stay in the routing pool after a permanent-ban
signal; subscription/OAuth accounts can still be deactivated. Default
scope remains all so existing installs do not change.

* docs(security): document auto-disable scope and log skipped prepaid keys

Keep the operator ban-detection page aligned with the new setting and
reuse the shared scope enum in the settings schema and dashboard radios.

* chore(changelog): name the auto-disable scope fragment for #10617

* docs(settings): treat free login seats as auto-disable targets

The first-cut scope is still all vs login-style auth. Copy now states
that paid subscriptions and free accounts both disable, while prepaid
API keys stay in the pool until per-account overrides exist.

* i18n: backfill autoDisableBannedScope keys across all locales

npm run i18n:sync-ui — the 6 new autoDisableBannedScope* keys landed
in en.json and vi.json but not the other 40 locales (including
pt-BR), tripping the pt-BR no-drift regression test (#6695).

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-18 10:53:24 -03:00
Ravi Tharuma
6003612000 fix(audio): fall back nested STT models when the prefix provider has no credentials (#10584)
* fix(audio): fall back nested STT models when the prefix provider has no credentials

Bare ids such as deepgram/nova-3 prefix-match the native provider and 400
when that key is missing, even if OpenRouter lists the same model. Retry
the gateway and mention qualified catalog ids in the error.

Closes #10583

* test(audio): scope whisper-1 fallback test to a 2-provider registry

nanogpt was added to AUDIO_TRANSCRIPTION_PROVIDERS (already merged,
unrelated to this fix) with a bare "whisper-1" model id, which now
intercepts findAlternateAudioProvider's first candidate before the
qualified-alias branch this test exists to cover. Scope the test to a
local {openai, openrouter} registry subset so it deterministically
exercises the qualified `${provider}/${model}` fallback regardless of
future providers that also list a bare "whisper-1" id.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-18 10:52:48 -03:00
Ravi Tharuma
3d0ffb49a4 feat(providers): complete Jina + Gemini Embedding 2 multimodal via OmniRoute (#10581)
* feat(providers): complete Jina AI via OmniRoute including Omni multimodal

Dashboard and env keys share one Jina credential pool, native v5 Omni
{text}/{image}/{content} docs pass through /v1/embeddings intact, and
classify/segment/search are proxied without a third unused Jina card.

* chore(changelog): name Jina complete-provider fragment for #10581

* feat(providers): make Gemini Embedding 2 multimodal work via OmniRoute

Route gemini-embedding-2 through embedContent/batchEmbedContents so N
OpenAI input items become N vectors, pass through native multimodal
parts, and use dashboard Gemini keys (GEMINI_API_KEY only as fallback).

* fix(providers): resolve rebase fallout for Jina/Gemini embeddings

- narrow the two new no-explicit-any violations introduced by this PR
  (validateJinaFoundationProvider's params + catch, search.ts's
  normalizeJinaSearchResponse data param)
- cast credentials to Record<string, unknown> at the two quota-preflight
  call sites in src/sse/services/auth.ts so the new JinaEnvCredentials /
  GeminiEnvCredentials union members type-check without loosening the
  allRateLimited narrowing used elsewhere in the same function

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-18 10:52:43 -03:00
CyrixJD115
9222528bdd fix(opencode): session stability, free-tier routing, and CLI defaults (#10571)
* fix(opencode): session stability, free-tier routing, and CLI defaults

- Wire generateSessionId() into opencodeHeaders so x-opencode-session
  is a deterministic fingerprint instead of randomUUID() per request,
  enabling upstream prompt caching across a conversation
- Thread request body through buildHeaders() so session fingerprint
  has access to model, system, messages, and tools
- Default CLI header synthesis to ON (opt-out via false), align
  values with 9router proven defaults (opencode/desktop/global)
- Auto-echo listing-valid model names for noAuth providers so
  response.model matches /v1/models listing
- Short-circuit free-tier model resolution to opencode provider first
  to prevent prefix inference misrouting when catalog is unreachable

* fix(opencode): make free-tier default flip self-consistent + add coverage

PR #10571 flipped OPENCODE_SYNTHESIZE_CLI_HEADERS to on-by-default and
changed the synthesized UA/client/project default values, but shipped
with 2 broken assertions in the existing #5997 regression test and no
coverage for the new session-fingerprinting, free-tier routing, or
noAuth echoModel logic (Hard Rule #18).

- Update tests/unit/opencode-cli-headers-synthesis-5997.test.ts to match
  the new on-by-default behavior and new default values; add an explicit
  opt-out coverage test so the forward-only path is still guarded.
- Fix 20 further test failures in tests/unit/opencode-executor.test.ts
  and tests/unit/refactor-buildHeaders-opencode.test.ts caused by the
  same default flip (pin OPENCODE_SYNTHESIZE_CLI_HEADERS=false for the
  characterization suites that predate #10571; use a genuinely
  CLI-looking UA where the preserved-UA test requires one).
- Fix a real bug found via TDD while adding the mandated free-tier
  routing regression test: the big-pickle/*-free short-circuit in
  open-sse/services/model.ts checked activeProviders?.has("opencode")
  literally, but getActiveProviderSet() canonicalizes every connection's
  provider id through resolveProviderAlias(), which rewrites "opencode"
  to "opencode-zen" via a manual override — so an active no-auth
  opencode connection could never satisfy the check. Now checks both
  opencode-family candidate ids. Proven with a test that fails on the
  original code and passes with the fix (both connections active with a
  stale synced catalog omitting big-pickle).
- Extract the noAuth-provider echoModel aliasing in chatCore.ts into a
  pure, directly-testable helper (open-sse/handlers/chatCore/noAuthEchoModel.ts),
  matching the existing chatCore god-file decomposition pattern.
- Add regression tests for generateSessionId()-based x-opencode-session
  fingerprinting (stable within a conversation, changes on model/message
  changes), the free-tier routing short-circuit, and the noAuth echoModel
  aliasing.
- Add the changelog.d/ fragment and sync docs/reference/ENVIRONMENT.md's
  OPENCODE_SYNTHESIZE_CLI_HEADERS/OPENCODE_USER_AGENT/OPENCODE_CLIENT/
  OPENCODE_PROJECT rows to the new defaults.

Does NOT resolve whether flipping OPENCODE_SYNTHESIZE_CLI_HEADERS's
default was the right call, and does NOT touch the separate open PR
#10357 which flips the same flag with a different literal default value
- that decision is left to the maintainer at merge time.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-18 10:52:33 -03:00
Ravi Tharuma
c2dbe2f1fb docs: add embeddings client runbook for Gemini 2 and Jina omni (#10569)
Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-18 10:52:29 -03:00
Ravi Tharuma
c767494ae4 feat(api): alias /v1/multimodal-embeddings to /v1/embeddings (#10568)
Jina-compatible clients POST /v1/multimodal-embeddings and currently get
HTTP 404 unknown_route from the catch-all.

Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-18 10:52:24 -03:00
rinseaid
458ab1aac0 fix(vision): preserve high detail for inline images (#10554)
* fix(vision): preserve high detail for inline images

* fix(vision): scope high-detail image default to OpenCode clients

defaultImageDetail() was applied at prepareUpstreamBody, the shared
upstream-body prep path for every provider and format, not just the
OpenCode path the fix targets. Gate it on isOpencodeClient (the
existing User-Agent/x-opencode-* header signal already used for
bypassDefaultToolLimit at this call site) so non-OpenCode callers keep
the provider's own image detail default. Adds a regression test
covering a non-OpenCode caller against the same opencode-zen provider.

* fix(vision): document and test the global vs OpenCode-only detail scope

The OpenCode-only high-detail default in chatCore/upstreamBody.ts
(defaultImageDetail, gated on isOpencodeClient) forwards the caller's
own image_url.detail and was already correctly scoped in a prior
commit on this branch.

The internal vision-bridge describe self-loop (visionBridgeHelpers.ts)
is architecturally global: VisionBridgeGuardrail runs for every
caller/provider whenever the target model lacks vision support, and
there is no client-identity signal at that layer to gate on. Its
describe prompt explicitly asks the vision model to transcribe visible
text, so requesting "high" detail unconditionally is justified on its
own merits (OCR accuracy), independent of the OpenCode motivation.

Adds a compatibility assertion proving the Anthropic wire-format
branch of the same describe self-loop carries no `detail` field (it
has no such concept) and is therefore unaffected by this default, and
documents the split (OpenCode-only forwarding vs. global describe
default) in docs/security/GUARDRAILS.md.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: rinseaid <rinseaid@rinseaid.net>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-18 10:52:11 -03:00
abhiisalright
15386495c2 fix(compression): add i18n support for less-code and terse-prose (#10498)
* fix(compression): add i18n support for less-code and terse-prose

Translates less-code output style to pt-BR, vi, ja, and id. Adds missing vi translation to terse-prose caveman mode. Removes less-code from English-only allowlist and updates matrix tests.

Fixes #10426

* docs(compression): add output styles coverage table

Adds the requested Output Styles matrix to the compression guide covering styles, supported languages, and intensity levels.

Fixes #10426
2026-08-18 10:51:08 -03:00
realize000
4c5535be4e Update SETUP_GUIDE.md (#10490)
* Update SETUP_GUIDE.md

* docs: correct Windows data directory note

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-18 10:50:58 -03:00
Rouzbeh†
497dd6f357 fix(memory): auto-check Qdrant health on mount and stop false-red badge (#10489)
* fix(memory): auto-check Qdrant health on mount and stop false-red badge

The Qdrant engine card on /dashboard/memory?tab=engine showed a red
"Error" badge after every page refresh even when Qdrant was healthy:
the badge derives its state from a health check, but the mount effect
only fetched settings + embedding models — health started as null and
the render treated `health?.ok` (undefined) as a failure. Clicking
"Test connection" (which runs the same server-side /readyz check)
immediately turned it green, proving the connection was fine.

Two changes:
- Auto-run the health check on mount once settings load and Qdrant is
  enabled, so a refreshed page reflects the real state (verified live:
  /api/settings/qdrant/health returns ok:true in ~2ms on a healthy
  compose deployment).
- While health has not been checked yet (null), render a neutral gray
  "Testing..." state instead of red — red is now reserved for an
  actual failed health check.

Regression test added (fails on the old code): with enabled settings
and a healthy mock, the card must hit /api/settings/qdrant/health on
mount and show statusActive, never statusError.

* chore(changelog): fragment for #10489

* Merge branch 'release/v3.8.50' into fix/qdrant-health-badge

* test(fix): refresh expired alibaba quota sample validity and onnxruntime pin for v3.8.50 base

- alibaba-free-tier-quota-fetcher.test.ts: sample quotaValidityPeriod
  (2026-08-16 16:00 UTC) is in the past, making every quota entry classify
  as expired/not_capable; bump to 2028-01-01 UTC so the text/merge
  classification tests exercise the intended path again.
- optional-transformers-dependency.test.ts: onnxruntime-node pin assertion
  updated from ~1.24.3 to ~1.27.0 to match package.json (bumped by #10403);
  the regular-not-optional intent is unchanged.

* test(fix): align optional-transformers-dependency with onnxruntime ~1.24.3 pin (base #10543)

* docs(fix): sync 150-migration count and document PROXY_LOG_INCLUDE_IPS (base drift #10348/#10507)

* fix(memory): re-check Qdrant health after saving settings

save() optimistically flipped enabled and started the PUT while the mount
effect could immediately GET /api/settings/qdrant/health against the OLD
persisted settings. If that GET won, it returned not_configured/failed and -
because health was non-null - the effect never retried after the PUT
succeeded, leaving a healthy Qdrant red until a manual Test connection.

Invalidate health (generation counter + setHealth(null)) at save start and
after a successful PUT, then explicitly schedule a fresh check: setting
health to null alone is not enough, React bails on the no-op when health is
already null (the exact GET-wins ordering). Stale responses are dropped via
the sequence guard so an in-flight pre-save check can never overwrite the
post-save result. Adds a regression test covering enable ordering.

Addresses PR #10489 review finding (issuecomment-5312271806).

* fix: narrow omniglyph transform result union (merge base aa912c42a typecheck gate)

* test(compression): align contract tests with base aa912c42a merge (providerTransport shape, engine metadata)

* fix(memory): silence set-state-in-effect on Qdrant auto health-check

The health-check re-check fix (3469234) introduced an effect that calls
checkHealth() (an async fetch that eventually calls setState) directly
from a useEffect gated on loading/enabled/health. The
react-hooks/set-state-in-effect rule flags this as a potential cascading
render, matching the same pattern already accepted elsewhere in the
dashboard (FreePoolTab.tsx, ConnectionsTable.tsx) for gated async
data-fetch effects. Suppress with the established inline convention;
no behavior change.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* fix(memory): drop unused set-state-in-effect disable (rule inert on pinned react-hooks 7.0.1)

The eslint-disable-next-line for react-hooks/set-state-in-effect is unused:
eslint-plugin-react-hooks@7.0.1 (lockfile-pinned) does not report this rule,
so the directive itself was flagged as a warning and the 'No new ESLint
warnings' CI gate failed with --max-warnings 0. The effect body only calls
checkHealth() (async fetch) with no raw setState, so no disable is needed.

* ci(quality): sync ratchet configs to release/v3.8.50 (0a74bfbde) merge

- re-freeze open-sse typecheck baseline at merged-tree live counts
  (64 stale entries dropped, 11 frozen; base video/usage drift covered)
- register tests/unit/video-bridge-drilldown-route.test.ts in stryker tap.testFiles
- regenerate skills/cli-contexts/SKILL.md (contexts migrate docs from CLI closure)

---------

Co-authored-by: Rouzbeh <rqzbeh@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-18 10:50:54 -03:00
Diego Rodrigues de Sa e Souza
d49ccdaaf1 fix(sse): gate structural chat admission shedding on real heap pressure (#10437)
* fix(sse): gate structural chat admission shedding on real heap pressure

Closes #10183, Closes #10268

3.8.49 (#9654/#9940) replaced the 3.8.48 heap-ratio shed
(heapUsed/heapLimit >= 0.75) in chatBodyAdmission.ts with an
unconditional CHAT_MAX_HEAVY_IN_FLIGHT=1 structural lease. A second
concurrent "structurally heavy" chat request (>=200 messages, >=64
tools, or >=32k estimated tokens — routine for coding-agent fan-out
like Hermes/Cursor/Claude Code) was hard-rejected with a retryable
HTTP 503 chat_admission_busy/structure_limit regardless of actual
heap pressure, even on a host with ample free RAM.

Restore the heap-conditional gate as an ADDITIONAL check layered on
top of (not a replacement for) the #9654 bounded-concurrency /
per-connection-lane protection: when heavyweight capacity is busy,
only enter the bounded-wait/shed path when a live heap-pressure probe
(heapUsed / v8 heap_size_limit >= OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO,
default 0.75) confirms real pressure. A healthy heap now admits the
second heavy request immediately via a no-op lease instead of parking
or shedding it. The probe is injectable via
admitChatStructure({ heapPressureCheck }) for deterministic tests.

Regression tests:
- tests/unit/bug-10183-admission-heavy-healthy-heap.test.ts (new,
  permanent): healthy-heap 2nd heavy request now admitted (was RED);
  genuinely pressured heap still sheds it.
- tests/unit/probe-10268-structural-503.test.ts (promoted to
  permanent): the exact reported 503 chat_admission_busy shape is
  still produced under real heap pressure, and the same fan-out is
  admitted on a healthy heap.
- tests/unit/chat-body-admission.test.ts,
  tests/unit/chat-body-admission-queue.test.ts,
  tests/unit/per-connection-admission-9654.test.ts updated to inject
  heapPressureCheck: () => true where they exercise the busy/shed
  path, preserving #9654/#4380 coverage.

Gates run: npm run typecheck:core (clean), eslint --suppressions-location
config/quality/eslint-suppressions.json on changed files (clean),
scripts/check/check-file-size.mjs (OK), scripts/check/check-test-discovery.mjs
(OK), focused admission suite (68/68 passing) and npm run test:unit
(in progress at commit time under heavy shared-devbox contention from
a 13-way parallel session fan-out; no admission-related failures
observed through 1873 lines of output, the sole failure seen was a
pre-existing unrelated proxy/search timeout consistent with known
load-induced flakiness, not a regression from this change).

⚠️ base-red inherited: #9985 — ESLint errors (2) from #10250

* docs(env): document OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO (#10183, #10268)

* fix(sse): bound the healthy-heap admission fast path (#10437)

The #10183/#10268 fix admitted a busy heavyweight request immediately
whenever the heap was healthy, via an unconditional no-op lease with no
bound of its own -- an unlimited number of "healthy heap" requests could
pile in ahead of the heap-pressure shed path, defeating the point of
admission control.

Adds an independent, bounded healthy-heap headroom budget
(CHAT_ADMISSION_HEALTHY_HEADROOM, tryAcquireHealthyHeadroom()) that the
healthy-heap fast path draws from; once exhausted, requests fall through
to the same bounded-wait/shed path used under real heap pressure, which
is otherwise unchanged. Also fixes a pre-existing gap in
per-connection-admission-9654.test.ts's shared-budget test, which needed
an explicit heapPressureCheck override to keep exercising the #10110
invariant now that a healthy heap gets bounded headroom instead of an
outright reject.

* docs(env): document OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM in .env.example

Documented in docs/reference/ENVIRONMENT.md but missing from .env.example,
caught by the env-doc-sync gate when combined with other PRs in the
release merge-train.

---------

Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
2026-08-18 10:50:09 -03:00
Diego Rodrigues de Sa e Souza
514573b1f6 fix(proxy-subscriptions): allow local/loopback proxy-subscription fetch URLs (#10416)
* fix(proxy-subscriptions): allow local/loopback proxy-subscription fetch URLs

The subscription fetch guard (fetchGuard.ts) unconditionally blocked all
loopback/private IP ranges as SSRF protection, but the same feature already
permits loopback for the routing half (coreEndpoint.ts's
ALLOWED_LOCAL_CORE_HOSTS) — so an operator could route traffic through a
loopback core but could not fetch a proxy list from a loopback HTTP server.

Make the fetch guard local-first by reusing the existing
areLocalProviderUrlsAllowed() policy (default ON) from
outboundUrlGuardPolicy.ts: loopback/private hosts are now allowed as fetch
targets by default, while cloud-metadata/link-local (169.254.0.0/16, incl.
169.254.169.254 IMDS) and the unspecified address stay blocked
unconditionally, mirroring the provider-validation guard's "block-metadata"
mode. Callers that want the old strict behavior can pass
{ allowLocal: false }.

Closes #10158.

* fix(proxy-subscriptions): unwrap IPv4-mapped IPv6 + full fe80::/10 range (#10416)

The #10158 SSRF guard left two gaps on the IPv6 side: an IPv4-mapped IPv6
literal (::ffff:a.b.c.d) skipped IPv4 range checking entirely, and the
link-local check only matched strings literally prefixed with "fe80"
instead of the full fe80::/10 range (fe80:: - febf:ffff::), so fe90::,
febf:ffff::, etc. were wrongly allowed through.

isIpv6Blocked() now unwraps mapped IPv4 addresses (both the dotted-quad
and WHATWG-normalized hex-group forms) and re-checks them against the
IPv4 rules, and link-local detection parses the first hex group's numeric
value against the 0xfe80-0xfebf range instead of a string prefix.

---------

Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
2026-08-18 10:49:52 -03:00
Tushar Agarwal
1089c24bc8 Remove/mimocode sunset provider (#10186)
* remove: drop sunset MiMoCode provider from model catalog

* remove: drop sunset MiMoCode provider from model catalog (shared.ts)

Remove unused imports, types, and comments from shared.ts.

* remove: MiMoCode provider (Xiaomi sunset) — executor, registry, no-auth config, icon, tests

* refactor(providers): finish MiMoCode removal — sweep remaining no-auth references

Drop the leftover mimocode entries from the no-auth provider controls, the
translate-path snapshot, the eslint suppressions, and the #3061 auth-loop
test. Re-point the fingerprint-pin (#6696) and proxy-noauth (#6272) tests at
opencode, which exercises the same fingerprint path, so the removal does not
break runtime behavior.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* docs(providers): reconcile provider/executor counts after MiMoCode sunset

The base's parallel doc-count sync (#10433) pinned 340 providers / 101
executors. With mimocode removed, live code has 339 providers and 100
executors; refresh the user-facing counts (package.json description,
llm.txt, README/AGENTS, i18n llm.txt, provider reference, diagrams) so the
check-docs-counts STRICT gate stays green.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* test(providers): fix orphaned mimocode references after MiMoCode sunset

The sunset removed mimocode/mcode from the free-onboarding candidates and
from FINGERPRINT_PROVIDERS, but two tests still referenced them:

- free-provider-onboarding-setup: the mimocode->theoldllm substitution
  introduced duplicate 'opencode' rows (impossible given the request-set
  dedupe) and the wrong display name; align expectations with the actual
  {opencode, theoldllm} dedupe behavior and 'The Old LLM (Free)' name.
- combo-system-prompt-templates-5501: resolveTargetFingerprint tested with
  provider 'mcode', which is no longer a fingerprint provider; point it at
  the remaining fingerprint provider 'opencode'.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: Tushar49 <Tushar49@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
2026-08-18 10:49:24 -03:00
Benson K B
2d50ec0789 feat(routing): add quota-aware provider scheduling — Phase 2 (#10126)
* feat(quota): Phase 2 adapters, reset timers, analytics, and dashboard API

* feat(routing): add quota-aware provider scheduling (opt-in)

* fix(db): rename migration to 148_provider_quota_state.sql

* fix(quota): harden quota state route, isolate phase2 tests, slim env diff

- route: requireManagementAuth + Zod body validation + buildErrorBody
  sanitization (Hard Rule #12); fix clearProviderQuotaState -> clearProviderQuota
- .env.example/ENVIRONMENT.md: drop ~20 foreign vars, keep only
  OMNIROUTE_QUOTA_AWARE_ROUTING (migration 148)
- tests/unit/quota-phase2.test.ts: DATA_DIR mkdtemp + resetDbInstance teardown

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* chore(ci): fix docs-sync + eslint-suppression drift for quota branch

CI gates flagged on PR #10126 head 43335f07:
- migration counts in README/AGENTS/llm.txt were stale (145 -> 146)
- regenerate docs/reference/PROVIDER_REFERENCE.md (gen-provider-reference)
- sync root llm.txt body into all 42 i18n mirrors (headers preserved)
- prune eslint suppressions that no longer occur

--no-verify: pre-commit docs-sync was failing on a pre-existing
release-base artifact (changelog 3.8.49 vs package 3.8.50) — fixed by
the changelog entry in the prior commit; re-verify in CI.

* chore(skills): regenerate agent skills (add omni-settings)

Merge-integrity CI gate flagged a missing generated skill. Regenerated
with check:agent-skills-sync --apply: +omni-settings, 45 unchanged.

* fix(ci): resolve Fast Quality Gates regressions on quota branch

- check-migration-numbering: migration 148 (provider_quota_state) landed
  on this branch, so the KNOWN_GAPS allowlist entry is stale — remove it
  (stale-enforcement 6A.3: 'REMOVA a entrada')
- open-sse/utils/stream.ts: duplicate sseCommentsEnabled import from a
  bad merge (lines 31 + 77) — TS2300 duplicate identifier; drop the
  duplicate so the open-sse typecheck gate is back within baseline

* docs: sync migration count to 149 after release merge

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* test(migrations): align 148 gap assertion after 148_provider_quota_state.sql landed

The phase-2 branch added 148_provider_quota_state.sql, and 148 was already
removed from KNOWN_GAPS in scripts/check/check-migration-numbering.mjs. The
frozen-allowlists assertion still expected 148 to be a gap, so it failed.
Flip the assertion to match the allowlist (same pattern as 143/147).

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

---------

Co-authored-by: benzntech <benzntech@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
2026-08-18 10:49:19 -03:00
Xiangzhe
0a74bfbdea feat(cli): relay-like CLI closure — target manifest, Codex TOML, Gemini launcher, guards
- canonical executable manifest (bin/cli/cli-manifest.mjs): run/configure/completion
  derive targets, aliases and --model wiring from one table; drift test cross-checks
  manifest x cliRuntime x UI catalog (tests/unit/cli/cli-manifest-drift.test.ts)
- dashboard Codex generator converged to ~/.codex/config.toml (modern Codex v0.137+,
  verified against codex-cli 0.147.0): conservative merge, env_key auth (key never
  written), refuses invalid TOML, reports legacy config.yaml as migration note
- omniroute run gemini: launcher over OmniRoute's /v1beta surface via
  GOOGLE_GEMINI_BASE_URL + isolated GEMINI_CLI_HOME forcing gemini-api-key auth
  (contract proven against @google/gemini-cli 0.50.0); ACP registration kept distinct
- opt-in real smoke harness for upstream CLIs (RUN_CLI_SMOKE=1, credential by env
  NAME, redacted output): tests/integration/upstream-cli-smoke.int.test.ts
- container-guard homologation for POST /api/cli-tools/apply (422 in container,
  dry-run preview allowed, host write passes) + docs; guard untouched
- typecheck: omniglyphAdapter union narrowing, usageTracking typed signatures
  (UsageLike, no any), models.ts isValidModel params — typecheck:core and
  typecheck:noimplicit:core now clean
- relay core (prior session of this effort): omniroute run for 6 CLIs, configure
  picker with per-context favorites/recents, contexts with optional keychain +
  0600 fallback, provider CRUD with recursive redaction, completion updates, docs
2026-08-18 08:25:16 -03:00
Xiangzhe
34bb018d21 docs(video): document fusion telemetry, drill-down byte budget, cache key dimensions and fixed dedup threshold 2026-08-18 08:25:16 -03:00
Xiangzhe
ebf3312fe7 docs(video): document timestamped contact sheets 2026-08-18 08:25:14 -03:00
Xiangzhe
bb22eeba8d feat(video): add isolated drill-down cache 2026-08-18 08:25:13 -03:00
Xiangzhe
edb3abf323 feat(video): add segment-aware sampling 2026-08-18 08:25:13 -03:00
Xiangzhe
bcd58975c5 docs(video): describe optional audio fusion 2026-08-18 08:25:12 -03:00
Xiangzhe
ad9384c3ea feat(video): preserve transcript provenance 2026-08-18 08:25:11 -03:00
Xiangzhe
ffa6849cc8 feat(video): add validated focus windows 2026-08-18 08:25:10 -03:00
Xiangzhe
596a1035c3 feat(video): add conservative frame deduplication 2026-08-18 08:25:09 -03:00
Xiangzhe
2c33638643 feat(video): add scene-aware sampling fallback 2026-08-18 08:25:07 -03:00
Diego Rodrigues de Sa e Souza
ea0cdc559c docs(compression): document the output-style catalog and its extension point (#10649)
The five output styles (terse-prose, less-code, ponytail, i-have-adhd,
terse-cjk) shipped in Phase 4 but COMPRESSION_GUIDE.md had zero mention of
them. Add the catalog table with per-style language coverage, the injection
contract (catalog order, single marker, shared boundaries once), the config
shape and back-compat note, plus an 'Adding an Output Style' recipe in
EXTENDING_COMPRESSION.md covering the matrix guard and translation floor.

Refs #10426

Co-authored-by: Xiangzhe <bakryun0718@proton.me>
2026-08-18 06:14:20 -03:00
Diego Rodrigues de Sa e Souza
8ba25e9318 docs: add the VS Code Copilot Chat guide and document the /v1/models prefix modes (#10648)
Adds docs/guides/VSCODE-COPILOT.md covering the OmniCopilot extension: install
from either store, connection setup, what the picker actually shows and why,
the dashboard-in-a-tab mode, and a troubleshooting table.

Documents two contracts that existed in code but nowhere in the docs:

- The ?prefix= query parameter on GET /v1/models, with the warning that
  "canonical" omits providers whose alias already is the canonical id — so
  "alias" is the safe direction for a de-duplicated list.
- MODELS_CATALOG_PREFIX_MODE in .env.example and ENVIRONMENT.md, matching how
  ARENA_ELO_SYNC_ENABLED and PII_REDACTION_ENABLED are already documented.

The fabricated-docs gate cannot see this flag being read, because
resolveFeatureFlag() indexes process.env by key rather than naming it; added
an allowlist entry explaining that, in the style of the existing entries.

Co-authored-by: Xiangzhe <bakryun0718@proton.me>
2026-08-18 05:51:58 -03:00
Diego Rodrigues de Sa e Souza
c164ed962b fix(providers): validate bailian-coding-plan against the Token Plan host (#10634)
* fix(providers): validate bailian-coding-plan against the Token Plan host

The catalog entry is the personal Alibaba Token Plan, but the region map still
resolved the retired Coding Plan hosts. #10290 moved only the open-sse registry
(inference) to token-plan.ap-southeast-1.maas.aliyuncs.com, leaving the dashboard's
key validation pointed at coding-intl.dashscope.aliyuncs.com.

That host rejects Token Plan keys with 401, and validateBailianCodingPlanProvider
maps 401/403 to "Invalid API key" — so adding a working key failed at the modal
while the same key served inference fine. Verified live 2026-08-18 with a valid
key: legacy host 401 invalid_api_key, Token Plan host 429 quota (auth OK).

- point both regions of ALIBABA_PROVIDER_ENDPOINTS at the Token Plan hosts,
  matching what docs/providers/ALIBABA-QWEN-PROVIDER-FAMILIES.md already stated
- keep the retired hosts recognized as presets, so connections saved with the old
  URL still follow the region selector instead of being pinned to a dead host
- keep image/video generation on the DashScope AIGC hosts, which the Token Plan
  host does not serve
- probe with a model this plan actually serves (qwen3-coder-plus was Coding Plan)

* test(providers): compare parsed hostnames in the legacy-host guard

CodeQL flags URL .includes() checks as js/incomplete-url-substring-sanitization.
The guard is an assertion, not a sanitizer, but comparing new URL().hostname is
strictly more precise anyway — same coverage, no substring pattern.

---------

Co-authored-by: Xiangzhe <bakryun0718@proton.me>
2026-08-18 05:51:34 -03:00
Xiangzhe
aa912c42a7 docs: update omni route video guides ranking layout 2026-08-17 16:16:58 -03:00
adevwithpurpose
63a6618d34 chore(release): synchronize localized llm mirrors 2026-08-17 12:01:43 -03:00
adevwithpurpose
fb2585530d chore(release): sync v3.8.50 base quality docs 2026-08-17 11:52:26 -03:00
Rouzbeh†
e3bca29bbc fix(docker): real image tags (bifrost/cliproxyapi) + complete OMNIROUTE_BASE_PATH runtime patcher (#10482)
* fix(docker): real image tags + complete OMNIROUTE_BASE_PATH runtime patcher

Three docker issues fixed:

1. Images that do not exist:
   - bifrost: ghcr.io/maximhq/bifrost:1.5.21 never existed (1.5.x tops at
     v1.5.16, all tags carry the v prefix) -> ghcr.io/maximhq/bifrost:v1.6.11
   - cliproxyapi: ghcr.io/router-for-me/* is not publicly pullable (403);
     the official prebuilt image is docker.io/eceasy/cli-proxy-api, where
     the pinned v6.9.7 exists -> docker.io/eceasy/cli-proxy-api:v6.9.7
   - Verified still-current: redis:8.6.5-alpine (already on Redis 8 since
     #9065; ioredis 5.10 is RESP2/3-compatible, no modules used) and
     qdrant:v1.12.4 -- both exist, unchanged.

2. OMNIROUTE_BASE_PATH ignored on prebuilt images (root cause):
   Next 16 (webpack and Turbopack) app-router renders SSR asset URLs from
   assetPrefix ALONE; basePath only affects routing. The runtime patcher
   (ensure-docker-base-path) rewrote basePath literals only, so a prebuilt
   root-path image patched to /omniroute served the page but every
   /_next/static shell reference stayed unprefixed (404 behind a subpath
   proxy), the RSC flight-payload chunk refs came from client-reference
   manifests baked with unprefixed paths, and the Turbopack client process
   shim ships an empty env object so the client never learns the subpath.
   Extended patch-standalone-base-path.mjs to also rewrite:
   - assetPrefix literals (mirrors the subpath for SSR asset URLs)
   - the NEXT_PUBLIC_OMNIROUTE_BASE_PATH env mirror in the inline config
   - the client process.env shim (.env={}) with the two basePath keys
   - every baked "/_next/static URL (manifests, media imports, .html pages)
   next.config.mjs now mirrors basePath into assetPrefix so REBUILT images
   bake prefixed assets too. E2E-verified on the published main-web image:
   HTML under /omniroute now has 16/16 prefixed JS srcs and 82/82 prefixed
   flight refs (was 13/9 + ~150 unprefixed), prefixed assets return 200.

* chore(changelog): fragment for #10482 (docker images + basepath patcher)

* chore(changelog): bullet-form fragment for #10482

* Merge branch 'release/v3.8.50' into fix/docker-compose-images-and-basepath

* test(fix): refresh expired alibaba quota sample validity and onnxruntime pin for v3.8.50 base

- alibaba-free-tier-quota-fetcher.test.ts: sample quotaValidityPeriod
  (2026-08-16 16:00 UTC) is in the past, making every quota entry classify
  as expired/not_capable; bump to 2028-01-01 UTC so the text/merge
  classification tests exercise the intended path again.
- optional-transformers-dependency.test.ts: onnxruntime-node pin assertion
  updated from ~1.24.3 to ~1.27.0 to match package.json (bumped by #10403);
  the regular-not-optional intent is unchanged.

---------

Co-authored-by: Rouzbeh <rqzbeh@users.noreply.github.com>
2026-08-17 08:24:27 -03:00
blarovse
24ef1dc3d4 Sanitize test fixtures, add developer .env guidance, and add gitleaks… (#10411)
* Sanitize test fixtures, add developer .env guidance, and add gitleaks workflow

- Replace realistic-looking AWS keys and PEM fixtures in unit tests with synthetic placeholders to avoid false positives from secret scanners.
- Add docs/DEVELOPER-ENVIRONMENT.md describing postinstall .env behavior and remediation guidance.
- Add .github/workflows/gitleaks.yml to run gitleaks on pull requests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add gitleaks baseline and CI baseline support; update ignore and PR body\n\n- Copy gitleaks-local.json -> gitleaks-baseline.json\n- Add --baseline-path to workflow\n- Allowlist baseline in .gitleaks.toml\n- Ignore gitleaks-local.json\n- Add PR_BODY.md with scan summary\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(security): fix gitleaks config, drop redundant baseline/CI, clean doc artifacts

- Fix the malformed .gitleaks.toml [[rules]] block: an inline [rules.allowlist]
  with only paths (no regex/path at rule level) made gitleaks refuse to load the
  config (`FTL Failed to load config ... both |regex| and |path| are empty`),
  turning the project's blocking check-secrets ratchet into a hard failure.
  Verified: check-secrets config now loads and exits 0.
- Reconcile with the existing gitleaks gate: remove the redundant
  .github/workflows/gitleaks.yml and root gitleaks-baseline.json (a second,
  differently-scoped scanning mechanism + an unreviewed 430-finding blanket
  baseline) — the project already runs scripts/check/check-secrets.mjs as a
  blocking ratchet in ci.yml/quality.yml and its .gitleaks.toml policy is to fix
  real findings, not blanket-allowlist them.
- Remove the stray PR_BODY.md automation artifact from the repo root.
- Fix the duplicated <div align="center"> tag in README.md.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: OmniRoute Bot <noreply@omniroute.local>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: blarovse <312250233+blarovse@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-17 08:23:10 -03:00
Ravi Tharuma
fbc67f1338 fix(models): honor MODELS_DEV_SYNC_ENABLED=0 over dashboard settings (#10299)
* fix(deps): bump nanoid, dompurify for 2 new Dependabot alerts (#189, #190)

Bumps: nanoid ^3.3.17 (was transitive, now overridden), dompurify ^3.4.13
(with monaco-editor scoped override). Closes Dependabot #189, #190.

Remaining #182-#188 (js-yaml + mermaid) already closed by #9651 merge —
awaiting Dependabot re-scan.

npm audit → 0 vulnerabilities.

* fix(repo): harden .gitignore to also ignore a _tasks symlink (/_tasks)

_tasks is a SEPARATE nested git repo (gitignored). The pattern _tasks/ (trailing
slash) ignores only a directory, not a SYMLINK named _tasks. A self-referential
_tasks symlink can slip in via git add -A and, once pulled, checkout materializes
it over the real _tasks repo (destroying plans/specs/hands-off). Anchored /_tasks
ignores the symlink too, preventing re-capture.

* Hide health-check excluded models from /v1/models catalog (#10026)

Mirror the request-time exclusion rule (provider_specific_data.excludedModels)
in the unified catalog builder: a model is hidden when its provider has
connections but none of them is eligible for it. Applied across the
PROVIDER_MODELS, synced, custom, alias-backed, and managed-fallback loops
so ghost models no longer appear as available.

Co-authored-by: ritheshcn25 <ritheshcn25@users.noreply.github.com>

* fix(models): memoize getModelsDevPricing (event loop / healthz) (#10055)

* fix(models): memoize getModelsDevPricing for /v1/models catalog

resolveCatalogPricing called getModelsDevPricing once per model while
building GET /v1/models. Each call re-scanned models_dev_pricing and
JSON.parsed every row (~10k SQL scans + multi-GB parse work), pegging
the event loop so even /healthz timed out (#9685, #10052).

Memoize the parsed map until saveModelsDevPricing / clearModelsDevPricing
and add a unit test for invalidation.

Signed-off-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com>

* fix(db): invalidate modelsDevPricing cache on DB reset (#10055)

Copilot review fixes:
1. Register invalidateModelsDevPricingCache() with DB state reset system
   so resetDbInstance() clears the process-local memo, preventing stale
   pricing data from surviving across DB reset/restore operations.
2. Add test assertion verifying DB reset bypasses the memo (Copilot #10055).

The process-local memo at modelsDevSync.ts:204 caches getModelsDevPricing()
results until saveModelsDevPricing()/clearModelsDevPricing() to avoid
re-scanning all pricing rows on every /v1/models request. Without this hook,
backup restore and test DB resets would serve stale cached data from the
previous connection.

Tests: npm run test:unit:serial -- tests/unit/modelsDevSync-extended.test.ts

---------

Signed-off-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com>
Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>

* fix(models): honor MODELS_DEV_SYNC_ENABLED=0 over dashboard settings

The file header already advertised this env var but nothing read it.
When catalog/compression pin the event loop, the dashboard (same process)
cannot turn models.dev sync off. Let 0/false/off win over sqlite so an
operator can recover with env + restart. Skip getModelsDevPricing SQL
scans while the kill switch is set.

* fix(models): restore prettier formatting after base merge

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* test(models): cover env kill switch during live settings updates

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Signed-off-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@outlook.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
Co-authored-by: ritheshcn25 <rithesh.chandran@snb.ca>
Co-authored-by: ritheshcn25 <ritheshcn25@users.noreply.github.com>
Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-17 08:02:47 -03:00
Gi99lin
b1a2ff6887 feat(proxy): non-destructive auto-disable mode for the proxy health scheduler (#10342)
* feat(proxy): add non-destructive auto-disable mode for the proxy health scheduler

PROXY_AUTO_REMOVE was the only opt-in action the background proxy health
scheduler could take on a consistently failing proxy, and it deletes the row.
For a manually-maintained proxy chain (multi-proxy pool/rotation, #6365) that
is too destructive just to exclude a temporarily-dead member.

Add PROXY_AUTO_DISABLE as a sibling flag: at the same consecutive-failure
threshold it soft-disables the proxy (status "dead") instead of removing it.
"dead" is already one of the statuses the pool/rotation alive-filter excludes,
so a disabled proxy drops out of the active chain immediately with no other
code changes. The scheduler keeps probing dead proxies on its normal interval,
and the existing recovery branch (previously autoRemove-only) re-activates it
automatically once it starts answering again.

decision.ts's decideProxyHealthAction() gets an optional `autoDisable` input
(defaults to false, so existing callers are unaffected) and a "dead" status
value; scheduler.ts wires the new PROXY_AUTO_DISABLE env flag through. If both
flags are set, auto-remove wins. getProxyHealthStats() now also surfaces the
registry `status` so operators can see when a proxy was auto-disabled, and
ProxyStatusBadge now treats the full "not alive" status set (not just the
literal string "inactive") as inactive in the dashboard.

* test(proxy): assert registry status in getProxyHealthStats output

The non-destructive auto-disable change added the live registry status to the
stats object returned by getProxyHealthStats. Align the pre-existing
db-proxies-crud assertion with the intended output shape.

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>

* fix(proxy): preserve auto-disabled status in dashboard edits

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
Co-authored-by: Gi99lin <Gi99lin@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-17 08:02:16 -03:00
Xiangzhe
b082d0735b fix(api-manager): allow empty combo restrictions (#10066)
* fix(api-manager): allow empty combo restrictions

Represent unrestricted Combo access explicitly as combo/* so an empty Allowed Combos list can deny every Combo without affecting direct model routes. Preserve existing keys through migration 149 and cover Dashboard, policy, routing-target, and migration behavior.

* docs: sync migration count to 149 after api-key combo-access migration

Merging release/v3.8.50 forward landed 149_api_key_combo_access.sql,
bumping the real migration count from 148 to 149. Updates README.md,
AGENTS.md, llm.txt (root + all 42 i18n mirrors, exact-copy requirement)
so the strict docs-counts-sync gate matches the live count again.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

---------

Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
Co-authored-by: xz-dev <xz-dev@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
2026-08-17 07:00:05 -03:00
Diego Rodrigues de Sa e Souza
5ca747f6a5 fix(sse): exclude search providers from credential-health scheduler sweep (#10435)
* fix(sse): exclude search providers from credential-health scheduler sweep

The credential-health scheduler's sweep() tested every active connection
every 5 minutes with no exclusion for search providers. For providers in
SEARCH_VALIDATOR_CONFIGS (tavily-search, exa-search, serper-search,
brave-search, google-pse-search, linkup-search, searchapi-search,
youcom-search), "validation" fires a real billed upstream query
(e.g. POST api.tavily.com/search), so the periodic sweep silently burned
quota with no user-initiated search.

Exclude connections whose provider id is registered in
SEARCH_VALIDATOR_CONFIGS from the sweep's connection-selection filter.
Non-search API-key/OAuth connections remain monitored (#9180, #9289
regressions verified green).

Closes #9970

* fix(docs): drop backticks around SEARCH_VALIDATOR_CONFIGS in ENVIRONMENT.md

The env/docs sync gate (check-env-doc-sync.mjs) treats any backtick-wrapped
SHOUTY_NAME as an env var reference. SEARCH_VALIDATOR_CONFIGS is a code
export, not an env var, so wrapping it in backticks made the #9970 doc note
trip the env/docs contract check (docMissingEnv). Drop the backticks so the
gate stops classifying it as an undocumented env var.

---------

Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
2026-08-17 05:48:48 -03:00
adevwithpurpose
810c6b9843 fix(release): clear v3.8.50 base quality reds 2026-08-17 04:58:39 -03:00
backryun
c6c134300b perf(electron): ship optional ML/browser deps as installable packs (#10382)
Stage 7 of issue #10321 moves the optional ML and browser automation dependency closures out of the desktop bundle into checksummed, versioned packs installed on demand through the omniroute packs command.

- scripts/build/optionalPackStaging.mjs stages pack members under .build/optional-packs, creates release tarballs, and emits optional-packs.index.json with per-member SHA-256 checksums.
- scripts/packs provides manifest, install, remove, and verification helpers plus the packs CLI commands.
- Runtime lookup includes installed pack node_modules directories, while LLMLingua and browser executors continue to degrade gracefully when packs are absent.

The measured darwin-arm64 staging closure was about 534 MB of the 929 MB standalone node_modules tree (57%).
2026-08-16 02:20:59 -03:00
backryun
2162289f0a perf(electron): verify better-sqlite3 v13 Node-API prebuilds instead of source rebuild (#10367)
better-sqlite3 v13 ships Node-API prebuilds for every packaged platform
(darwin/linux/linuxmusl/win32 x x64/arm64) inside the npm tarball, so the
Electron-ABI node-gyp source rebuild in prepare-electron-standalone.mjs is
obsolete. Replace it with a fail-fast prebuild verification that mirrors
better-sqlite3 lib/binding.js selection, and strip build/deps/src so the
packaged loader can only resolve the prebuild.

Verified locally on darwin-arm64: the same darwin-arm64.node prebuild loads
under both Node 24 (NODE_MODULE_VERSION 137) and Electron 43.3.0 under
ELECTRON_RUN_AS_NODE (148); DB create/migrate/read/write/close/reopen pass
in both runtimes and cross-runtime on each other's database files.

Issue #10321 Stage 6.
2026-08-16 02:20:53 -03:00
Brandon Bennett
6d9336088c fix(chat-body-admission): process-wide budget (#10110) (#10322)
* fix(chat-body-admission): process-wide budget (#10110)

Remove per-session admission lanes that multiplied the documented
"in one process" heavy/bytes bound by up to 64. All requests now admit
against ONE process-global ChatAdmissionController so the bound holds
against fake-credential sharding.

Per-request session identity survives only as a fairness scheduling key:
waiters are grouped per key and served round-robin (#9654) against the
shared budget — one connection's burst cannot starve others.

- src/shared/middleware/chatBodyAdmission.ts: delete lane map + LRU/TTL
  eviction; ChatAdmissionController is now the global budget with per-key
  FIFO queues + round-robin dispatchFair(). PerConnectionAdmissionController
  returns the same shared controller for every session. resolveSessionId
  stays as a scheduling key with honest re-scoping docs. snapshot() emits
  process-wide aggregates.
- tests/unit/chat-body-admission-aggregate-10110.test.ts: new U6 suite — 6
  deterministic tests (LRU-no-mint, TTL-no-mint, shared byte budget,
  16 MiB config, same-session recreation, round-robin fairness). RED on
  release/v3.8.50, GREEN post-fix.
- tests/unit/per-connection-admission-9654.test.ts: rewrite the tests that
  encoded the defect (per-session isolation) to assert the global-budget
  contract.
- docs/reference/ENVIRONMENT.md: OMNIROUTE_CHAT_ADMISSION_MAX_QUEUED_BYTES
  documented as process-wide; VIRTUAL_TTL_MS/VIRTUAL_MAX_SESSIONS deprecated.

* docs(changelog): add #10322 fragment for process-wide admission budget

* ci: retrigger checks after transient npm ci network failure in shard 3/4 (ETIMEDOUT)

---------

Co-authored-by: Brandon Bennett <brandonbennett@macbookair.myfiosgateway.com>
2026-08-16 00:46:13 -03:00
Ravi Tharuma
326d0e81cb docs(ops): k8s probe recommendations (TCP liveness, HTTP /healthz readiness) (#10297)
* docs(ops): recommend TCP liveness and HTTP /healthz readiness for k8s

Stock Docker HEALTHCHECK hits /api/monitoring/health (deep). Orchestrators
should not use that path for kubelet liveness. Document /healthz vs deep
health, note same-process event-loop limits, and link related issues.

* docs: add changelog fragment for #10297

---------

Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
2026-08-16 00:42:31 -03:00
SB Yoon
d46e8d72c9 feat(cli): refuse ephemeral container auto-config writes (#10057)
* feat(cli): refuse ephemeral container auto-config writes

Detect containerized OmniRoute and block CLI/API config writes into
throwaway homes unless a bind mount or explicit opt-in is present, and
honor compose host-profile CLI_CONFIG_HOME mounts outside the container home.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(changelog): name fragment for #10057

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: yansigit <yansigit@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
2026-08-16 00:42:14 -03:00
Paco Cartones
dd4a33d1d8 fix(providers): make the monsterapi deprecation from #8676 actually apply (#10234)
* fix(providers): make the monsterapi deprecation from #8676 actually apply

#8676 marked MonsterAPI deprecated after its domain stopped resolving, but
wrote the flag as `isDeprecated`. Nothing reads that key. The field the
codebase consumes is `deprecated`:

  src/shared/validation/providerSchema.ts   declares `deprecated`
  ProviderCard.tsx                          strikethrough + block icon + reason
  ProviderTestSlideOver.tsx                 warning
  providerOnboardingCatalog.ts              Boolean(provider.deprecated), sorts last
  ProviderOnboardingWizard.tsx              deprecated badge
  scripts/docs/gen-provider-reference.ts    gates the DEPRECATED note

Zod object schemas ignore undeclared keys, so `isDeprecated` never failed
validation - it was dropped silently. The deprecation therefore had no effect
anywhere, and tests/unit/8676-monsterapi-deprecation.test.ts asserted the same
unread key, so it stayed green while guarding nothing.

The committed docs/reference/PROVIDER_REFERENCE.md is the visible proof: the
generator renders predibase (which uses `deprecated`) with a DEPRECATED note,
while monsterapi still advertised "Get API key at monsterapi.ai" - a domain
that does not resolve (probed 2026-08-13: api.monsterapi.ai and monsterapi.ai
both 000, against api.openai.com 401 as a reachability control).

Rename the key, repair the regression test to assert the consumed field and to
reject the undeclared one, and refresh the generated reference row.

* fix(providers): name the changelog fragment for PR #10234

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: pacocartones <pacocartones@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
2026-08-16 00:14:20 -03:00
backryun
5239728d6f feat(providers): add Grok 4.6 and refresh DeepSeek V4 (#10195) 2026-08-16 00:13:41 -03:00
Bezrabotnyi
595d04dad9 feat(providers): add local ZCode ACP backend (#10184)
* feat(providers): add local ZCode ACP backend

* test(snapshots): regenerate translate-path golden for zcode provider

The new local ZCode ACP backend (zcode://app-server/stdio) was added to the
provider catalog but the translate-path golden snapshot was not regenerated,
so the combined suite (provider-translate-path-golden.test.ts) failed on the
merged tip. Regenerate the snapshot to include the zcode translate-path entry.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* docs(env): document ZCODE_* vars for the local zcode provider

Registers the 11 ZCODE_* env vars read by the zcode executor (.env.example
+ docs/reference/ENVIRONMENT.md) so the env-doc-sync gate stays green.

Co-authored-by: Diego Souza <8016841+diegosouzapw@users.noreply.github.com>

* test(autoCombo): include zcode in the glm-family provider set

#10184's local zcode backend advertises the full GLM_SHARED_MODELS
line-up (registry/zcode, authType none) — same documented case as auggie
and devin-cli-agentic. Update auto/glm provider-set assertion to include
it.

Co-authored-by: Diego Souza <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: roomhacker <roomhacker@bezrabotnyi.com>
Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-16 00:13:36 -03:00
Diego Rodrigues de Sa e Souza
5379493bed feat: add Video Bridge frame sampling (#10483)
Implements the secure, opt-in Video Bridge for issue #9760, including bounded FFmpeg frame extraction, capability-aware routing, telemetry, settings UI, localization, documentation, and regression coverage.
2026-08-15 14:23:29 -03:00
Reza Rezaei
774127be3f feat(providers): add tencent-aistudio-web cookie provider (tasw) (#10174)
* feat(providers): add tencent-aistudio-web cookie provider (tasw)

* fix(sse): remove orphaned DevinDesktopExecutor import from executor index

The "devin-desktop" executor key is unused (devin-desktop provider config
resolves to executor "devin-cli"); the imported ./devin-desktop.ts file
was never present, so executors/index.ts failed to load (ERR_MODULE_NOT_FOUND)
and broke every unit test that imports the executor registry (e.g.
tests/unit/deepseek-web.test.ts). Stale base sync carried this into the branch.
Remove the dead import/registration/export.

* fix(providers): restore DevinDesktopExecutor registration in executor index

The previous commit removed the devin-desktop executor import/registration/
export from open-sse/executors/index.ts, but the devin-desktop provider
registry still resolves executor "devin-desktop" and
tests/unit/devin-providers.test.ts asserts hasSpecializedExecutor("devin-desktop")
is true. The removal broke 6 tests in that file. Restore the three lines so
the live Devin Desktop executor keeps serving the provider.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* fix(providers): correct tencent-aistudio-web wrapper shape + provider count sync

Return {response,url,headers,transformedBody} instead of a raw fetch Response
(the executor contract every other executor in this file follows) and
re-wrap the upstream body so it uses the local Response constructor, not the
undici-patched one from globalThis.fetch.

Regenerate docs/reference/PROVIDER_REFERENCE.md and sync the 339->340
provider-count claims (README, AGENTS.md, llm.txt + 42 i18n mirrors,
package.json, promise-pillars/comparison-table/cli-terminal SVGs) that this
PR's new provider invalidated.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* docs(providers): sync readme-hero.svg provider count claim (339->340)

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* fix(providers): register tencent-aistudio-web web-session credential metadata + golden

Add the WEB_SESSION_CREDENTIAL_REQUIREMENTS entry for tencent-aistudio-web
(cookie-based, matching the executor's raw Cookie-header credential) and
regenerate the translate-path golden snapshot to include the new provider —
both were failing CI unit tests that enumerate every registered provider.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* fix(providers): align tencent-aistudio-web test with the wrapper-shape contract

The test asserted res.status/res.json() directly against executor.execute()'s
return value, matching the pre-fix (broken) raw-Response shape. Update it to
read res.response.status/res.response.json() — the {response,url,headers,
transformedBody} contract every executor in this codebase follows.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: MeRezaRezaei <MeRezaRezaei@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
2026-08-15 05:15:17 -03:00
Diego Rodrigues de Sa e Souza
1287b6a75d feat(ops): canary deploy with provenance gate, real smoke and rollback anchor (#10446)
Deploying the internal gateway was a manual build/pack/scp/npm-i/pm2-restart sequence with no record of what landed and no proof it served traffic. On 2026-08-14 that shipped a package built from a branch predating #10373: the process came up, health said 'healthy', and every request returned 502 until a human hit it.

scripts/ops/deployCanary.ts holds the policy as pure functions — refuse an artifact that is not traceable to the release line (reusing #10427), and grade the deploy on health PLUS at least one real completion. Zero probes fails: 'no probe ran' must never read as 'everything is fine', which is exactly how a broken egress path hides behind a green health check. Remote steps are argv arrays, never shell strings (Hard Rule #13), ordered so the rollback anchor is captured before the install overwrites it.

scripts/ops/deploy-canary.mjs performs the side effects, supports --dry-run, and prints the rollback command when the smoke fails.

Closes #10429
2026-08-15 03:22:09 -03:00
Diego Rodrigues de Sa e Souza
a36fbdcc8d fix(build): verify artifact provenance and expose buildSha on health (#10444)
The packaged artifact stamped dist/BUILD_SHA but nothing verified the SHA belonged to the release line, so a tarball built from a feature branch installed and served traffic indistinguishably from a release build. That is how the internal gateway ended up running a build that predated #10373 and answered every request with 502 'Executor result must contain a Response' — identifying it required SSH plus grepping the compiled chunks.

scripts/build/buildProvenance.ts classifies a build SHA against the release ref (pure functions, injected git probe). A missing SHA fails even with the canary override: an unidentifiable artifact cannot be vouched for. validate-pack-artifact enforces it on real packs (skipped under --policy-only, which runs without a build); OMNIROUTE_ALLOW_CANARY_BUILD=1 records a deliberate off-release-line build instead of failing it. /api/monitoring/health now exposes system.buildSha — absent when unknown, never fabricated.

Closes #10427
2026-08-15 02:44:52 -03:00
Diego Rodrigues de Sa e Souza
2a04b2415a fix(db): keep test runs off the operator's real DATA_DIR (#10432)
Any process that opened the DB without setting DATA_DIR resolved to ~/.omniroute/storage.sqlite — the operator's live database, provider credentials included. tests/_setup/isolateDataDir.ts only covers the npm scripts; the documented single-file test command and ad-hoc probes bypassed it (one did exactly that during #10334).

resolveWritableDataDir now redirects a test-context process with no DATA_DIR to a throwaway temp dir, stable per process. Redirect rather than throw, so the documented single-file command keeps working; OMNIROUTE_ALLOW_DEFAULT_DATA_DIR=1 opts back in and records the intent.

Closes #10428
2026-08-15 01:51:01 -03:00