diff --git a/.env.example b/.env.example
index cf147c7f22..ebb1f7073c 100644
--- a/.env.example
+++ b/.env.example
@@ -2232,3 +2232,30 @@ QUOTA_STORE_DRIVER=sqlite # sqlite | redis
# Used by: open-sse/services/usage/hyperagent.ts
# ─────────────────────────────────────────────────────────────────────────────
# HYPERAGENT_USAGE_URL=https://hyperagent.com/api/settings/billing/usage
+
+# ─────────────────────────────────────────────────────────────────────────────
+# Browser-login VNC sessions (optional — src/lib/vncSession/manifest.ts)
+# Containerized Chromium+VNC used for interactive browser-login credential
+# capture via /api/vnc-session. All optional — defaults target the bundled
+# `omniroute-vnc-chromium:local` image; override only for a custom image, ports,
+# or lifecycle tuning.
+# ─────────────────────────────────────────────────────────────────────────────
+# OMNIROUTE_VNC_IMAGE=omniroute-vnc-chromium:local
+# OMNIROUTE_DOCKER_BIN=docker
+# OMNIROUTE_VNC_CONTAINER_VNC_PORT=3000
+# OMNIROUTE_VNC_CONTAINER_CDP_PORT=9223
+# OMNIROUTE_VNC_CONTAINER_PROFILE_DIR=/config
+# OMNIROUTE_VNC_PROFILE_DIR=
+# OMNIROUTE_VNC_IDLE_MS=600000
+# OMNIROUTE_VNC_MAX_MS=1800000
+# OMNIROUTE_VNC_MAX_SESSIONS=4
+# OMNIROUTE_VNC_READY_MS=45000
+# OMNIROUTE_VNC_HARVEST_MS=20000
+# OMNIROUTE_VNC_CHROMIUM_ARGS=--remote-debugging-port=9222 --no-first-run --no-default-browser-check
+
+# ─────────────────────────────────────────────────────────────────────────────
+# Data-dir alias (optional — open-sse/services/notionThreadSessions.ts)
+# Legacy fallback for DATA_DIR, checked only after DATA_DIR and
+# OMNIROUTE_DATA_DIR are both unset. Locates the Notion web-thread session cache.
+# ─────────────────────────────────────────────────────────────────────────────
+# VIBEPROXY_DATA_DIR=
diff --git a/README.md b/README.md
index b0d070df0a..8715a6ca9a 100644
--- a/README.md
+++ b/README.md
@@ -122,6 +122,27 @@
+# 🆓 Works the second you install it — no keys, no config
+
+
+
+> **Install → point your tool at the endpoint → it already answers.** OmniRoute ships with keyless free providers (OpenCode Free, Felo) already wired into the `auto` combo, so a **fresh install responds out of the box** — no API key, no signup, no configuration.
+
+```bash
+# Fresh install, zero credentials — `auto` already works:
+curl http://localhost:20128/v1/chat/completions \
+ -H "Content-Type: application/json" \
+ -d '{"model":"auto","messages":[{"role":"user","content":"Hello!"}]}'
+```
+
+- ✅ **`auto` responds immediately** — OmniRoute builds a virtual combo from the built-in keyless free providers and routes to a healthy one, with no setup.
+- ➕ **Add more providers anytime** — drop in a Claude / GPT / Gemini key (or any of the 278 providers) from the dashboard and they join the `auto` pool automatically.
+- 🎛️ **Build your own free combos** — chain your free tiers with any of the 18 routing strategies so you never run out of quota.
+
+Prefer a specific free backend? Call it directly, e.g. `oc/…` (OpenCode Free) or `felo/…` (Felo). Then graduate to `auto` and let OmniRoute pick.
+
+
+
# 💥 The Promise
@@ -265,7 +286,7 @@ All **18** strategies — mix & match per combo step:
- **🤖 One-command CLI/agent setup** — `setup-*` configures 12+ coding tools; `omniroute launch` / `launch-codex` are zero-config. → [CLI Integrations](docs/guides/CLI-INTEGRATIONS.md)
- **🛰️ Remote mode** — drive a remote OmniRoute with scoped tokens (`connect` / `contexts` / `tokens`) + an `antigravity` OAuth helper for VPS installs. → [Remote Mode](docs/guides/REMOTE-MODE.md)
- **🧭 Smarter auto-routing** — `auto/:` combos, **Fusion** (model panel + judge), task-aware routing, per-request model / mode / USD-budget overrides. → [Auto-Combo](docs/routing/AUTO-COMBO.md)
-- **🗜️ Pluggable compression** — 11 composable engines + Compression Studios: LLMLingua-2, two-tier Ultra, omniglyph, per-step fidelity gate, GCF v3.2, drag-reorder editor. → [Compression](docs/compression/COMPRESSION_ENGINES.md)
+- **🗜️ Pluggable compression** — 12 composable engines + Compression Studios: LLMLingua-2, two-tier Ultra, omniglyph, per-step fidelity gate, GCF v3.2, drag-reorder editor. → [Compression](docs/compression/COMPRESSION_ENGINES.md)
- **🕵️ Transparent MITM decrypt (TPROXY)** — capture CLIs that ignore proxy env vars, with a per-SNI CA + trust-store installer. → [MITM/TPROXY](docs/security/MITM-TPROXY-DECRYPT.md)
- **💸 Cost telemetry everywhere** — `X-OmniRoute-*` cost/usage headers on every endpoint, cache-HIT savings header, per-key USD spend quotas. → [API Reference](docs/reference/API_REFERENCE.md)
- **🧠 Memory you control** — off by default, opt-in int8 vector quantization + typed decay, per-request `x-omniroute-no-memory`. → [Memory](docs/frameworks/MEMORY.md)
@@ -488,24 +509,26 @@ claude mcp add-server omniroute --type http --url http://localhost:20128/api/mcp
-> **Why use many tokens when few tokens do the trick?** Every request passes through OmniRoute's compression pipeline **transparently** — no client changes. It's now a **stack of 11 composable engines** that run in order and mix & match per routing combo — building on ideas from [RTK](https://github.com/rtk-ai/rtk), [Caveman](https://github.com/JuliusBrussee/caveman) (⭐ 90K+), [LLMLingua-2](https://github.com/microsoft/LLMLingua), and [Troglodita](https://github.com/leninejunior/troglodita) (PT-BR).
+> **Why use many tokens when few tokens do the trick?** Every request passes through OmniRoute's compression pipeline **transparently** — no client changes. It's now a **stack of 12 composable engines** that run in order and mix & match per routing combo — building on ideas from [RTK](https://github.com/rtk-ai/rtk), [Caveman](https://github.com/JuliusBrussee/caveman) (⭐ 90K+), [LLMLingua-2](https://github.com/microsoft/LLMLingua), and [Troglodita](https://github.com/leninejunior/troglodita) (PT-BR).
-### 🧱 The 11-engine stack
+### 🧱 The 12-engine stack
Engines run in pipeline order; each is independently toggleable and configurable per combo:
-| # | Engine | What it does |
-| --- | ----------------- | ----------------------------------------------------------------------------------------------------------------------- |
-| 1 | **Session-Dedup** | Drops content repeated across turns (content-addressed, cross-turn) |
-| 2 | **CCR** | Archives large blocks behind retrieve markers, fetched on demand |
-| 3 | **RTK** | Smart tool-result filtering, dedup & truncation (command-aware) |
-| 4 | **Headroom** | Lossless tabular compaction of homogeneous JSON arrays, flat or nested (~30%), via a vendored **GCF** codec (spec v3.2) |
-| 5 | **Relevance** | Extractive sentence scoring against the last user query |
-| 6 | **Caveman** | Rule-based prose compression (~65–75% on output) |
-| 7 | **LLMLingua-2** | ML semantic pruning via MobileBERT ONNX — code-safe, async |
-| 8 | **Lite** | Whitespace + image-URL trimming (latency-light baseline) |
-| 9 | **Aggressive** | Summarization + progressive aging of old turns |
-| 10 | **Ultra** | Heuristic token pruning with an optional small-model (SLM) tier |
+| # | Engine | What it does |
+| --- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
+| 1 | **Session-Dedup** | Drops content repeated across turns (content-addressed, cross-turn) |
+| 2 | **CCR** | Archives large blocks behind retrieve markers, fetched on demand |
+| 3 | **Lite** | Whitespace + image-URL trimming (latency-light baseline) |
+| 4 | **RTK** | Smart tool-result filtering, dedup & truncation (command-aware) |
+| 5 | **Responses Tool Output** | Lossless-first JSON + bounded diagnostic compression for shell/patch/search/build outputs (Responses API) |
+| 6 | **Headroom** | Lossless tabular compaction of homogeneous JSON arrays, flat or nested (~30%), via a vendored **GCF** codec (spec v3.2) |
+| 7 | **Relevance** | Extractive sentence scoring against the last user query |
+| 8 | **Caveman** | Rule-based prose compression (~65–75% on output) |
+| 9 | **Aggressive** | Summarization + progressive aging of old turns |
+| 10 | **LLMLingua-2** | ML semantic pruning via MobileBERT ONNX — code-safe, async |
+| 11 | **Ultra** | Heuristic token pruning with an optional small-model (SLM) tier |
+| 12 | **OmniGlyph** | Experimental context-as-image encoding routed to Claude Fable 5 (most aggressive; opt-in) |
Code blocks, URLs and structured data are **always preserved** byte-perfect. **One-click presets** combine the engines:
@@ -538,7 +561,7 @@ Code blocks, URLs and structured data are **always preserved** byte-perfect. **O
### 📖 How it works — pipeline, architecture & savings math
-
+
Default stacked combo runs `RTK → Caveman`. When both act on the same tool/context payload, savings compound:
diff --git a/docs/reference/ENVIRONMENT.md b/docs/reference/ENVIRONMENT.md
index d3dd45fe02..46b60e976a 100644
--- a/docs/reference/ENVIRONMENT.md
+++ b/docs/reference/ENVIRONMENT.md
@@ -1152,6 +1152,26 @@ Provider quota endpoints, network tunnels (Tailscale, Ngrok, MITM debug proxy),
| `OMNIROUTE_ROTATE_400_THRESHOLD` | `1` | `open-sse/services/rotationConfig.ts` | Number of `400` errors within `OMNIROUTE_ROTATE_400_WINDOW_SECONDS` required before the account is rotated (only consulted when `OMNIROUTE_ROTATE_ON_400=true`). |
| `OMNIROUTE_ROTATE_400_WINDOW_SECONDS` | `120` | `open-sse/services/rotationConfig.ts` | Sliding window (seconds) over which `400` errors are counted toward `OMNIROUTE_ROTATE_400_THRESHOLD`. |
+### Browser-Login VNC Sessions & Data-Dir Alias
+
+Containerized Chromium+VNC used for interactive browser-login credential capture (`/api/vnc-session`), plus a legacy `DATA_DIR` alias. All optional — the VNC defaults target the bundled `omniroute-vnc-chromium:local` image and are only overridden for a custom container image, ports, or lifecycle tuning.
+
+| Variable | Default | Source File | Description |
+| --- | --- | --- | --- |
+| `OMNIROUTE_VNC_IMAGE` | `omniroute-vnc-chromium:local` | `src/lib/vncSession/manifest.ts` | Docker image tag for the Chromium+VNC login container. Build `docker/vnc-browser/chromium` or point this at a custom image. |
+| `OMNIROUTE_DOCKER_BIN` | `docker` | `src/lib/vncSession/manifest.ts` | Container runtime binary used to launch the VNC container (e.g. set to `podman`). |
+| `OMNIROUTE_VNC_CONTAINER_VNC_PORT` | `3000` | `src/lib/vncSession/manifest.ts` | VNC/noVNC port exposed inside the container. |
+| `OMNIROUTE_VNC_CONTAINER_CDP_PORT` | `9223` | `src/lib/vncSession/manifest.ts` | Chrome DevTools Protocol port inside the container. |
+| `OMNIROUTE_VNC_CONTAINER_PROFILE_DIR` | `/config` | `src/lib/vncSession/manifest.ts` | Chromium profile directory path inside the container. |
+| `OMNIROUTE_VNC_PROFILE_DIR` | `$HOME/.omniroute/browser-login-profiles` | `src/lib/vncSession/manifest.ts` | Host directory holding persisted browser-login profiles. |
+| `OMNIROUTE_VNC_IDLE_MS` | `600000` (10 min) | `src/lib/vncSession/manifest.ts` | Idle timeout (ms) before an inactive VNC session is reaped. |
+| `OMNIROUTE_VNC_MAX_MS` | `1800000` (30 min) | `src/lib/vncSession/manifest.ts` | Hard cap (ms) on a single VNC session's lifetime. |
+| `OMNIROUTE_VNC_MAX_SESSIONS` | `4` | `src/lib/vncSession/manifest.ts` | Maximum number of concurrent VNC sessions. |
+| `OMNIROUTE_VNC_READY_MS` | `45000` | `src/lib/vncSession/manifest.ts` | Timeout (ms) waiting for the containerized browser to become CDP-ready. |
+| `OMNIROUTE_VNC_HARVEST_MS` | `20000` | `src/lib/vncSession/manifest.ts` | Timeout (ms) for harvesting the captured session/cookies after login completes. |
+| `OMNIROUTE_VNC_CHROMIUM_ARGS` | `--remote-debugging-port=9222 --no-first-run --no-default-browser-check` | `src/lib/vncSession/manifest.ts` | Extra command-line flags passed to the containerized Chromium. |
+| `VIBEPROXY_DATA_DIR` | _(unset)_ | `open-sse/services/notionThreadSessions.ts` | **Legacy alias** for `DATA_DIR`, checked only after both `DATA_DIR` and `OMNIROUTE_DATA_DIR` are unset. Locates the Notion web-thread session cache (`/notion-web-thread-sessions.json`). |
+
---
## 26. Test & E2E Harness
diff --git a/open-sse/services/autoCombo/virtualFactory.ts b/open-sse/services/autoCombo/virtualFactory.ts
index bd48f8dbdf..b81ac27d37 100644
--- a/open-sse/services/autoCombo/virtualFactory.ts
+++ b/open-sse/services/autoCombo/virtualFactory.ts
@@ -134,8 +134,19 @@ function hasUsableConnectionCredential(conn: VirtualFactoryConn): boolean {
const SYNTHETIC_NOAUTH_CONNECTION_ID = "noauth";
+// Allowlist of no-auth (keyless) providers permitted to enter the `auto`/`auto-*`
+// candidate pool. Narrowed to the backends verified to answer without any
+// configuration on our reference egress (VPS .15): `opencode` and `felo-web`
+// both return 200 there, while duckduckgo-web (429/VQD rate limit), theoldllm
+// (403 Vercel egress block), chipotle (502), aihorde (401, anon key rejected)
+// and the others are unreliable. The excluded providers stay fully usable via
+// direct `/` calls — they are just kept OUT of auto-routing until
+// re-verified. Re-add an id here to bring it back into every auto/* pool.
+const AUTO_COMBO_NOAUTH_ALLOWLIST = new Set(["opencode", "felo-web"]);
+
function isChatAutoComboNoAuthProvider(providerDef: NoAuthProviderDefinition): boolean {
if (providerDef.noAuth !== true) return false;
+ if (!AUTO_COMBO_NOAUTH_ALLOWLIST.has(providerDef.id)) return false;
if (!Array.isArray(providerDef.serviceKinds) || providerDef.serviceKinds.length === 0)
return true;
return providerDef.serviceKinds.includes("llm");
diff --git a/tests/unit/noauth-autocombo-allowlist.test.ts b/tests/unit/noauth-autocombo-allowlist.test.ts
new file mode 100644
index 0000000000..21b3ad98af
--- /dev/null
+++ b/tests/unit/noauth-autocombo-allowlist.test.ts
@@ -0,0 +1,107 @@
+/**
+ * Auto-combo no-auth allowlist — the `auto`/`auto-*` candidate pool must only
+ * pull in no-auth (keyless) providers verified to work without any credential on
+ * our reference egress. As of this change that allowlist is narrowed to
+ * `opencode` and `felo-web`: on the reference VPS (.15) they are the only no-auth
+ * backends that answer 200 with zero configuration. The other no-auth providers
+ * (duckduckgo-web, theoldllm, chipotle, aihorde) stay OUT of every auto/* pool
+ * until re-verified — they remain usable via direct `/` calls, they
+ * are just not auto-routed to.
+ *
+ * Regression guard: AUTO_COMBO_NOAUTH_ALLOWLIST in
+ * open-sse/services/autoCombo/virtualFactory.ts drives what belongs here.
+ */
+import test from "node:test";
+import assert from "node:assert/strict";
+import fs from "node:fs";
+import os from "node:os";
+import path from "node:path";
+
+const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-noauth-allowlist-"));
+const ORIGINAL_DATA_DIR = process.env.DATA_DIR;
+
+process.env.DATA_DIR = TEST_DATA_DIR;
+
+const core = await import("../../src/lib/db/core.ts");
+const virtualFactory = await import("../../open-sse/services/autoCombo/virtualFactory.ts");
+
+async function resetStorage() {
+ core.resetDbInstance();
+ fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
+ fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
+}
+
+test.beforeEach(async () => {
+ await resetStorage();
+});
+
+test.after(async () => {
+ await resetStorage();
+ fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
+
+ if (ORIGINAL_DATA_DIR === undefined) {
+ delete process.env.DATA_DIR;
+ } else {
+ process.env.DATA_DIR = ORIGINAL_DATA_DIR;
+ }
+});
+
+const ALLOWED_NOAUTH_PROVIDERS = ["opencode", "felo-web"];
+const EXCLUDED_NOAUTH_PROVIDERS = ["duckduckgo-web", "theoldllm", "chipotle", "aihorde"];
+
+test("fresh install: the allowlisted no-auth providers are present in the auto-combo pool", async () => {
+ const combo = await virtualFactory.createVirtualAutoCombo(undefined);
+
+ for (const providerId of ALLOWED_NOAUTH_PROVIDERS) {
+ const models = combo.models.filter((m: { providerId: string }) => m.providerId === providerId);
+ assert.ok(
+ models.length >= 1,
+ `allowlisted no-auth provider "${providerId}" must be present in the fresh-install ` +
+ `auto-combo pool. Pool providers: ${JSON.stringify([...new Set(combo.models.map((m: { providerId: string }) => m.providerId))])}`
+ );
+ assert.ok(
+ models.every((m: { connectionId: string }) => m.connectionId === "noauth"),
+ `all "${providerId}" models must use the synthetic noauth connection`
+ );
+ assert.ok(
+ combo.autoConfig.candidatePool.includes(providerId),
+ `"${providerId}" must be in the candidatePool`
+ );
+ }
+});
+
+test("fresh install: non-allowlisted no-auth providers are EXCLUDED from the auto-combo pool", async () => {
+ const combo = await virtualFactory.createVirtualAutoCombo(undefined);
+
+ for (const providerId of EXCLUDED_NOAUTH_PROVIDERS) {
+ const present = combo.models.some((m: { providerId: string }) => m.providerId === providerId);
+ assert.equal(
+ present,
+ false,
+ `no-auth provider "${providerId}" must NOT be auto-routed to (not in allowlist), ` +
+ `but it appeared in the pool. Pool: ${JSON.stringify(combo.models.map((m: { model: string }) => m.model))}`
+ );
+ assert.ok(
+ !combo.autoConfig.candidatePool.includes(providerId),
+ `"${providerId}" must not be in the candidatePool`
+ );
+ }
+});
+
+test("fresh install: EVERY synthetic-noauth candidate belongs to an allowlisted provider (no other keyless provider leaks in)", async () => {
+ const combo = await virtualFactory.createVirtualAutoCombo(undefined);
+
+ const noauthProviders = [
+ ...new Set(
+ combo.models
+ .filter((m: { connectionId: string }) => m.connectionId === "noauth")
+ .map((m: { providerId: string }) => m.providerId)
+ ),
+ ].sort();
+ assert.deepEqual(
+ noauthProviders,
+ [...ALLOWED_NOAUTH_PROVIDERS].sort(),
+ `only the allowlisted providers may be no-auth (connectionId="noauth") candidates on a ` +
+ `fresh install, but found: ${JSON.stringify(noauthProviders)}`
+ );
+});
diff --git a/tests/unit/virtual-auto-combo.test.ts b/tests/unit/virtual-auto-combo.test.ts
index 6383613097..4bb7e1fb49 100644
--- a/tests/unit/virtual-auto-combo.test.ts
+++ b/tests/unit/virtual-auto-combo.test.ts
@@ -203,38 +203,31 @@ test("createVirtualAutoCombo includes no-auth OpenCode Free without provider_con
assert.ok(combo.autoConfig.candidatePool.includes("opencode"));
});
-test("createVirtualAutoCombo includes all chat-capable no-auth providers without connections", async () => {
+test("createVirtualAutoCombo restricts the no-auth pool to the allowlist", async () => {
+ // Policy: the no-auth (keyless) auto-combo allowlist is narrowed to `opencode`
+ // and `felo-web` (open-sse/services/autoCombo/virtualFactory.ts::AUTO_COMBO_NOAUTH_ALLOWLIST) —
+ // the keyless backends verified to work without configuration on our reference
+ // egress. The others stay usable via direct `/` calls but must
+ // NOT be auto-routed to. Dedicated guard:
+ // tests/unit/noauth-autocombo-allowlist.test.ts.
const combo: VirtualComboResult = await virtualFactory.createVirtualAutoCombo("fast");
- // Each noAuth provider should have multiple models (not just the first)
- const ddgwModels = combo.models.filter((m) => m.providerId === "duckduckgo-web");
- assert.ok(ddgwModels.length >= 1, "duckduckgo-web should have at least one model");
- assert.ok(
- ddgwModels.every((m) => m.connectionId === "noauth"),
- "all ddgw models should use noauth connection"
- );
- assert.ok(
- ddgwModels.some((m) => m.model.startsWith("ddgw/")),
- "ddgw models should have correct prefix"
- );
+ for (const allowed of ["opencode", "felo-web"]) {
+ const models = combo.models.filter((m) => m.providerId === allowed);
+ assert.ok(models.length >= 1, `${allowed} should have at least one model`);
+ assert.ok(
+ models.every((m) => m.connectionId === "noauth"),
+ `all ${allowed} models should use noauth connection`
+ );
+ }
- const tllmModels = combo.models.filter((m) => m.providerId === "theoldllm");
- assert.ok(tllmModels.length >= 1, "theoldllm should have at least one model");
- assert.ok(
- tllmModels.every((m) => m.connectionId === "noauth"),
- "all tllm models should use noauth connection"
- );
- assert.ok(
- tllmModels.some((m) => m.model === "tllm/GPT_5_4"),
- "tllm should include GPT_5_4"
- );
-
- const chipotleModels = combo.models.filter((m) => m.providerId === "chipotle");
- assert.ok(chipotleModels.length >= 1, "chipotle should have at least one model");
- assert.ok(
- chipotleModels.every((m) => m.connectionId === "noauth"),
- "all chipotle models should use noauth connection"
- );
+ for (const excluded of ["duckduckgo-web", "theoldllm", "chipotle", "aihorde"]) {
+ assert.equal(
+ combo.models.some((model) => model.providerId === excluded),
+ false,
+ `no-auth provider "${excluded}" must be excluded from the auto-combo pool (not in allowlist)`
+ );
+ }
assert.equal(
combo.models.some((model) => model.providerId === "veoaifree-web"),