Revert "fix(config): externalize ws for copilot-m365-web executor (#6098, closes #6062)"

This reverts commit e61b75f007.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-03 16:40:11 -03:00
parent e61b75f007
commit 604afeacf4
510 changed files with 6988 additions and 31652 deletions

View File

@@ -71,7 +71,6 @@ Lookup material — API surface, environment variables, CLI flags, provider cata
- [API_REFERENCE.md](reference/API_REFERENCE.md) — REST API endpoints and shapes.
- [PROVIDER_REFERENCE.md](reference/PROVIDER_REFERENCE.md) — auto-generated provider catalog (do not edit by hand).
- [PROVIDER_PLUGIN_MANIFEST.md](reference/PROVIDER_PLUGIN_MANIFEST.md) — sidecar-safe provider plugin contract for Bifrost and CLIProxyAPI migration.
- [openapi.yaml](openapi.yaml) — OpenAPI spec for the public API.
- [ENVIRONMENT.md](reference/ENVIRONMENT.md) — environment variables reference.
- [FEATURE_FLAGS.md](reference/FEATURE_FLAGS.md) — feature flags and their defaults.

View File

@@ -1,7 +1,7 @@
---
title: "Extending the Compression Pipeline"
version: 3.8.44
lastUpdated: 2026-07-02
version: 3.8.40
lastUpdated: 2026-06-28
---
# Extending the Compression Pipeline
@@ -512,62 +512,6 @@ To drive it from config, set `mode: "stacked"` and provide the step array under
---
## Upstream Sync Policy
OmniRoute's compression engines credit several upstream projects in the README
("inspired by RTK, Caveman, LLMLingua-2, Troglodita"). A common contributor
question is: **when upstream RTK adds a new tool filter or Caveman adds a rule
pack, how does that reach OmniRoute?** This section is the authoritative answer.
### Vendored copies vs. independent implementations
| Engine | Relationship to upstream | Location |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| **RTK** | **Independent reimplementation** (inspired-by, not a copy) | `open-sse/services/compression/engines/rtk/` |
| **Caveman** | **Independent reimplementation** (inspired-by) | `open-sse/services/compression/engines/cavemanAdapter.ts` |
| **Headroom** | Mostly internal; only the `gcf/` codec is **genuinely vendored** from `gcf-typescript` (MIT, SPDX-marked, generic profile only) | `open-sse/services/compression/engines/headroom/gcf/` |
| **LLMLingua-2 / Troglodita** | Inspired-by (drive the `llmlingua` + `session-dedup` engines) | `open-sse/services/compression/engines/llmlingua/`, `session-dedup` |
Key point: **RTK and Caveman are clean-room TypeScript implementations of the
_ideas_ (filter rules, rule packs), not vendored source trees.** There is no
upstream copy to `git pull` from — which is exactly why the README says
"inspired by" rather than "bundled".
### How upstream improvements are merged
There is **no automated upstream-release tracking and no `compression-sync`
label** — by design. Because the engines are reimplementations, an upstream RTK
filter or Caveman rule pack is not merged as code; it is **re-expressed as a new
rule/filter in OmniRoute's own format** (see
[COMPRESSION_RULES_FORMAT.md](./COMPRESSION_RULES_FORMAT.md)) and lands ad-hoc via
a normal PR. The extension points above (custom engine, language pack, RTK filter)
are the sanctioned way to contribute one.
Recent examples of exactly this flow:
- RTK filters for Gradle & `dotnet` build output (v3.8.42)
- RTK filters for kubectl / docker-build / composer / gh (#2824)
- Caveman Indonesian language pack (#3975), plus German / French / Japanese / Chinese packs
### Headroom (input-compression proxy)
Headroom is **fully internal** — a pinned vendored `gcf` codec snapshot plus
OmniRoute's own `smartcrusher` / `toon` / `tabular` layers. There is no live
upstream to track beyond the vendored copy; updates to `gcf` are refreshed
manually when the codec changes and re-validated against the compression budget
gate (`check:compression-budget`).
### Proposing an upstream-inspired improvement
1. **Don't vendor** — re-express the upstream rule/filter in OmniRoute's format.
2. Add it via the matching extension point below (language pack, RTK filter, or
custom engine).
3. Reference the upstream project in the PR description (attribution), not by
copying its license-bearing source.
4. Include tests and confirm the `check:compression-budget` gate still passes.
---
## Best Practices
### Engine Development

View File

@@ -1,13 +1,13 @@
---
title: "Embedded Services"
description: "Reference for 9Router, CLIProxyAPI, Mux, and Bifrost"
description: "Reference for 9Router and CLIProxyAPI"
---
# Embedded Services
> **Version:** v3.8.44
> **Last updated:** 2026-07-03
> **Audience:** Engineers adding, maintaining, or debugging embedded services (9Router, CLIProxyAPI, Mux, Bifrost).
> **Version:** v3.8.4
> **Last updated:** 2026-06-28
> **Audience:** Engineers adding, maintaining, or debugging embedded services (9Router, CLIProxyAPI).
Embedded services are locally-installed process sidecar tools that OmniRoute installs, supervises, and
exposes as first-class routing targets. Unlike external providers (which are reached over the internet
@@ -32,20 +32,18 @@ via API keys), embedded services run on the same machine as OmniRoute and commun
### Why embedded services?
Four services are embedded as of v3.8.44:
Two services are embedded as of v3.8.4:
| Service | npm package | Default port | Purpose |
| --------------- | ----------------------------------------------- | :----------: | ------------------------------------------------------------------------------------------------------------------ |
| **9Router** | `9router` | 20130 | AI router that OmniRoute can use as a sub-provider. Models exposed as `9router/{sub}/{model}` |
| **CLIProxyAPI** | `@anthropic/cli-proxy` (via `cliproxy` binary) | auto | Local proxy adapter for Anthropic CLI auth flows. Provides fallback routing when OAuth tokens expire |
| **Mux** | `mux` (headless `mux server`) | 8322 | Local agent-orchestration daemon (coder/mux). Lifecycle-managed only — not a routing target (no LLM proxying). |
| **Bifrost** | `@maximhq/bifrost` | 8080 | Go AI-gateway relay backend. When running, auto-selected by the relay route (`/v1/relay/`) |
| Service | npm package | Default port | Purpose |
| --------------- | ---------------------------------------------- | :----------: | ---------------------------------------------------------------------------------------------------- |
| **9Router** | `9router` | 20130 | AI router that OmniRoute can use as a sub-provider. Models exposed as `9router/{sub}/{model}` |
| **CLIProxyAPI** | `@anthropic/cli-proxy` (via `cliproxy` binary) | auto | Local proxy adapter for Anthropic CLI auth flows. Provides fallback routing when OAuth tokens expire |
All four follow the same supervisory model:
Both follow the same supervisory model:
- OmniRoute installs them under `DATA_DIR/services/{name}/` (isolated from OmniRoute's own `package.json`)
- OmniRoute spawns and monitors them as child processes
- OmniRoute injects an ephemeral API key into the child's environment and rotates it without downtime (where applicable)
- OmniRoute injects an ephemeral API key into the child's environment and rotates it without downtime
- All management routes (`/api/services/*`) are **LOCAL_ONLY** — accessible only from loopback (hard rule #17)
### Key decisions (from design plan)
@@ -56,7 +54,7 @@ All four follow the same supervisory model:
| Installation mechanism | `npm install {package}` via `execFile` (no shell interpolation) |
| Consumption mode | Provider registered as `9router/{sub}/{model}` in routing engine |
| API key management | OmniRoute generates, encrypts at-rest (AES-256-GCM), and injects via env |
| Dashboard location | `/dashboard/providers/services` (three tabs) |
| Dashboard location | `/dashboard/providers/services` (two tabs) |
| Auto-start | Toggle per service, default OFF |
---
@@ -66,13 +64,12 @@ All four follow the same supervisory model:
```
┌────────────────────────────────────────────────────────────────────┐
│ Layer 1 — UI │
│ /dashboard/providers/services (tabs: CLIProxyAPI | 9Router | Mux)
│ /dashboard/providers/services (tabs: CLIProxyAPI | 9Router)
│ Logs live (SSE), Start/Stop/Restart/Update, Settings, Install │
│ │
│ src/app/(dashboard)/dashboard/providers/services/ │
│ ├── page.tsx Shell + tab routing by ?tab= │
│ ├── tabs/ CliproxyServiceTab, NinerouterServiceTab,
│ │ MuxServiceTab │
│ ├── tabs/ CliproxyServiceTab, NinerouterServiceTab│
│ └── components/ ServiceStatusCard, ServiceLifecycleButtons,│
│ ServiceLogsPanel, ApiKeyCard, ... │
└──────────────────────┬─────────────────────────────────────────────┘
@@ -84,8 +81,6 @@ All four follow the same supervisory model:
│ rotate-key|status|auto-start|logs} │
│ /api/services/cliproxy/{install|start|stop|restart|update| │
│ status|auto-start|logs} │
│ /api/services/mux/{install|start|stop|restart|update| │
│ status|auto-start|logs} │
│ /dashboard/providers/services/9router/embed/[...path] │
│ (reverse HTTP + WebSocket proxy → 9Router upstream) │
│ │
@@ -111,8 +106,7 @@ All four follow the same supervisory model:
│ modelSync.ts Periodic GET /v1/models → service_models table │
│ ringBuffer.ts Circular log buffer (5 MB per service) │
│ healthCheck.ts Polling HTTP health probe │
│ installers/ ninerouter.ts, cliproxy.ts, mux.ts
│ (installer adapters) │
│ installers/ ninerouter.ts, cliproxy.ts (installer adapters)
└──────────────────────┬─────────────────────────────────────────────┘
│ OpenAI-compatible HTTP (loopback)
┌──────────────────────▼─────────────────────────────────────────────┐
@@ -129,10 +123,6 @@ All four follow the same supervisory model:
│ open-sse/config/providerRegistry.ts │
│ Models stored as "9router/{sub}/{model}" (prefixed). │
│ Synced every 5 min by modelSync.ts. │
│ │
│ Mux is lifecycle-managed ONLY (Layers 1-3) — it is an agent- │
│ orchestration daemon, not an LLM proxy, so it has no Layer 4 │
│ executor/provider entry and is never a routing target. │
└────────────────────────────────────────────────────────────────────┘
```
@@ -149,7 +139,6 @@ All four follow the same supervisory model:
| `src/lib/services/healthCheck.ts` | HTTP health probe (configurable interval) |
| `src/lib/services/installers/ninerouter.ts` | npm install/update/uninstall for 9Router |
| `src/lib/services/installers/cliproxy.ts` | npm install/update/uninstall for CLIProxyAPI |
| `src/lib/services/installers/mux.ts` | npm install/update/uninstall for Mux |
| `src/app/api/services/9router/_lib.ts` | `getOrInitSupervisor()` helper |
| `src/app/api/services/[name]/logs/route.ts` | Shared SSE logs endpoint |
| `open-sse/executors/ninerouter.ts` | Provider executor (Layer 4) |
@@ -445,56 +434,12 @@ config) and `status` includes fewer fields.
| `GET` | `/api/services/cliproxy/status` | Live + DB status (no `apiKeyMasked`) |
| `POST` | `/api/services/cliproxy/auto-start` | Toggle auto-start |
The shared `GET /api/services/{name}/logs` endpoint (see §4.1) works for all
four services using the `[name]` dynamic segment.
The shared `GET /api/services/{name}/logs` endpoint (see §4.1) works for both
services using the `[name]` dynamic segment.
---
### 4.3 Mux endpoints (7 routes)
Mux has the same endpoint shape as CLIProxyAPI — no `rotate-key` route in the API
surface (the bearer token is generated the same way as 9Router's via
`getOrCreateApiKey("mux")` and injected via the `MUX_SERVER_AUTH_TOKEN` env var, but
there is no dedicated rotation endpoint yet). Mux is lifecycle-managed only: unlike
9Router, it has no Layer 4 executor and is never registered as a routing provider.
| Method | Path | Description |
| ------ | -------------------------------- | ------------------------------------- |
| `POST` | `/api/services/mux/install` | Install Mux from npm (`npm i mux`) |
| `POST` | `/api/services/mux/start` | Start Mux (`mux server`) |
| `POST` | `/api/services/mux/stop` | Stop Mux |
| `POST` | `/api/services/mux/restart` | Restart Mux |
| `POST` | `/api/services/mux/update` | Update to newer npm version |
| `GET` | `/api/services/mux/status` | Live + DB status |
| `POST` | `/api/services/mux/auto-start` | Toggle auto-start |
---
### 4.4 Bifrost endpoints (7 routes)
Bifrost is a Go AI-gateway relay backend (`@maximhq/bifrost`). It uses the same
endpoint shape as CLIProxyAPI (no `rotate-key` — Bifrost manages its own provider
keys in `config.json` under its `-app-dir`).
| Method | Path | Description |
| ------ | ---------------------------------- | ------------------------------------------------------ |
| `POST` | `/api/services/bifrost/install` | Install Bifrost from npm (`@maximhq/bifrost`) |
| `POST` | `/api/services/bifrost/start` | Start Bifrost on port 8080 (default) |
| `POST` | `/api/services/bifrost/stop` | Stop Bifrost |
| `POST` | `/api/services/bifrost/restart` | Restart Bifrost |
| `POST` | `/api/services/bifrost/update` | Update to newer version |
| `GET` | `/api/services/bifrost/status` | Live + DB status |
| `POST` | `/api/services/bifrost/auto-start` | Toggle auto-start |
| `GET` | `/api/services/bifrost/logs` | SSE log tail (via shared `[name]/logs` dynamic route) |
**Routing wiring:** When `BIFROST_BASE_URL` is unset and the supervised Bifrost
instance is running, `getBifrostRoutingConfig()` (in `routingBackend.ts`) automatically
uses `http://127.0.0.1:{port}` as the relay base URL. Explicit `BIFROST_BASE_URL` env
always takes precedence.
---
### 4.4 Reverse proxy (9Router dashboard embed)
### 4.3 Reverse proxy (9Router dashboard embed)
The dashboard embeds the 9Router web UI inside an iframe via an internal reverse
proxy at:
@@ -541,20 +486,14 @@ matrix.
### API key injection
9Router and Mux require an API key/bearer token for their own HTTP endpoints.
OmniRoute:
9Router requires an API key for its own HTTP endpoints. OmniRoute:
1. Generates a key via `crypto.randomBytes(32).toString("base64url")` with a
service-specific prefix (`nr_` for 9Router, `mx_` for Mux).
service-specific prefix (`nr_` for 9Router).
2. Encrypts it at-rest using AES-256-GCM (same cipher used for provider credentials).
3. Decrypts and injects it as an environment variable at spawn time
`NINEROUTER_API_KEY` for 9Router, `MUX_SERVER_AUTH_TOKEN` for Mux (never a CLI
flag, so the token never appears in `ps`/process listings).
3. Decrypts and injects it as `NINEROUTER_API_KEY` environment variable at spawn time.
4. Never returns the plaintext key in any HTTP response.
CLIProxyAPI does not require an injected key (it authenticates via the host's
existing CLI config).
### SSRF defense
The reverse HTTP proxy (`/dashboard/.../embed/[...path]`) is hardcoded to forward

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -6,24 +6,6 @@
## [3.8.31] — 2026-06-20
## [3.8.44] — TBD
### ✨ New Features
_TBD_
### 🔧 Bug Fixes
_TBD_
### 📝 Maintenance
- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw)
- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw)
---
## [3.8.43] — 2026-07-02
### ✨ New Features

View File

@@ -1,7 +1,7 @@
openapi: 3.1.0
info:
title: OmniRoute API
version: 3.8.44
version: 3.8.43
description: |
OmniRoute is a local-first AI API proxy router. It provides an OpenAI-compatible
endpoint that routes requests to multiple AI providers with load balancing,
@@ -3444,276 +3444,6 @@ paths:
"400":
description: Invalid request body
/api/services/mux/install:
post:
tags: [Embedded Services]
summary: Install Mux from npm
description: >-
Installs the `mux` npm package (coder/mux — local agent-orchestration
daemon) under DATA_DIR/services/mux/. **LOCAL_ONLY** — loopback only.
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
version:
type: string
default: latest
responses:
"200":
description: Install succeeded
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
installedVersion:
type: string
"400":
description: Invalid request body
"500":
description: npm install failed
/api/services/mux/start:
post:
tags: [Embedded Services]
summary: Start Mux
description: >-
Spawns `mux server --host 127.0.0.1 --port <port>`. Idempotent if
already running. **LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service started
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatus"
"409":
description: Mux is not installed
"503":
description: Start failed
/api/services/mux/stop:
post:
tags: [Embedded Services]
summary: Stop Mux
description: >-
Gracefully stops Mux. Idempotent.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service stopped
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatus"
/api/services/mux/restart:
post:
tags: [Embedded Services]
summary: Restart Mux
description: >-
stop() then start() under the operation lock.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service restarted
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatus"
/api/services/mux/update:
post:
tags: [Embedded Services]
summary: Update Mux to a newer npm version
description: >-
Stops, installs newer version, restarts.
**LOCAL_ONLY** — loopback only.
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
version:
type: string
default: latest
responses:
"200":
description: Update succeeded
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
installedVersion:
type: string
"500":
description: Update failed
/api/services/mux/status:
get:
tags: [Embedded Services]
summary: Get Mux status
description: >-
Returns live supervisor state and DB metadata.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Status response
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatus"
/api/services/mux/auto-start:
post:
tags: [Embedded Services]
summary: Toggle Mux auto-start
description: >-
When enabled, Mux starts automatically on the next OmniRoute boot.
**LOCAL_ONLY** — loopback only.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [enabled]
properties:
enabled:
type: boolean
responses:
"200":
description: Auto-start flag updated
content:
application/json:
schema:
type: object
properties:
autoStart:
type: boolean
"400":
description: Invalid request body
/api/services/bifrost/install:
post:
tags: [Embedded Services]
summary: Install Bifrost
description: >-
Installs the `@maximhq/bifrost` npm package under DATA_DIR/services/bifrost/.
The package downloads the Go binary on first run. Accepts an optional `version`
field (semver or `latest`). **LOCAL_ONLY** — loopback only.
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
version:
type: string
default: latest
responses:
"200":
description: Installation result
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
installedVersion:
type: string
installPath:
type: string
durationMs:
type: number
/api/services/bifrost/start:
post:
tags: [Embedded Services]
summary: Start Bifrost
description: Starts the supervised Bifrost process. **LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service status after start
"409":
description: Bifrost is not installed
/api/services/bifrost/stop:
post:
tags: [Embedded Services]
summary: Stop Bifrost
description: Stops the supervised Bifrost process. **LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service status after stop
/api/services/bifrost/restart:
post:
tags: [Embedded Services]
summary: Restart Bifrost
description: Restarts the supervised Bifrost process. **LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service status after restart
"409":
description: Bifrost is not installed
/api/services/bifrost/update:
post:
tags: [Embedded Services]
summary: Update Bifrost
description: >-
Updates Bifrost to the latest npm version. Stops the running process,
installs the new version, and restarts if it was previously running.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Update result
/api/services/bifrost/status:
get:
tags: [Embedded Services]
summary: Get Bifrost status
description: Returns live and DB status for the supervised Bifrost service. **LOCAL_ONLY** — loopback only.
responses:
"200":
description: Bifrost service status
/api/services/bifrost/auto-start:
post:
tags: [Embedded Services]
summary: Toggle Bifrost auto-start
description: >-
When enabled, Bifrost starts automatically on the next OmniRoute boot.
**LOCAL_ONLY** — loopback only.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [enabled]
properties:
enabled:
type: boolean
responses:
"204":
description: Auto-start flag updated
"400":
description: Invalid request body
/api/services/{name}/logs:
get:
tags: [Embedded Services]
@@ -5534,214 +5264,6 @@ paths:
$ref: "#/components/responses/InternalError"
"503":
description: Generator module not available
/api/v1/ocr:
post:
tags:
- Images
summary: Document OCR
description: >-
Mistral OCRcompatible document OCR endpoint. Accepts a JSON body
referencing a document/image and returns extracted text. Success
responses carry the `X-OmniRoute-*` cost-telemetry headers.
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
model:
type: string
document:
type: object
responses:
"200":
description: OCR result with extracted text.
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/audio/translations:
post:
tags:
- Audio
summary: Translate audio to English
description: >-
OpenAI Whispercompatible audio translation (multipart/form-data).
Unlike `/api/v1/audio/transcriptions`, output is always English
regardless of the source language. Success responses carry the
`X-OmniRoute-*` cost-telemetry headers.
security:
- BearerAuth: []
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required:
- file
properties:
file:
type: string
format: binary
model:
type: string
responses:
"200":
description: English translation of the audio.
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/providers/suggested-models:
get:
tags:
- Providers
summary: Suggested media models
description: >-
Read-only server-side proxy to the public HuggingFace Hub models search
API, used by the dashboard to suggest models for a media provider kind
without exposing an HF token client-side. Never accepts or returns
credentials.
parameters:
- name: type
in: query
schema:
type: string
description: Media kind to search for (e.g. `image`, `audio`, `video`).
responses:
"200":
description: List of suggested HuggingFace Hub models.
"500":
$ref: "#/components/responses/InternalError"
/api/v1/provider-plugin-manifest:
get:
tags:
- Providers
summary: Provider plugin manifest
description: Returns the manifest describing installed provider plugins.
responses:
"200":
description: Provider plugin manifest.
"500":
$ref: "#/components/responses/InternalError"
/api/keys/{id}/devices:
get:
tags:
- API Keys
summary: List devices for an API key
description: >-
Lists the distinct devices (masked IP + User-Agent fingerprints)
tracked for an API key by the in-memory device tracker. IPs are masked
before storage; the route never sees the raw client IP.
x-internal: true
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"200":
description: Distinct devices seen for the API key.
"401":
$ref: "#/components/responses/Unauthorized"
"404":
$ref: "#/components/responses/NotFound"
/api/settings/purge-usage-history:
post:
tags:
- Settings
summary: Purge usage history
description: Dashboard-only. Purges stored usage-history records.
x-internal: true
responses:
"200":
description: Usage history purged.
"401":
$ref: "#/components/responses/Unauthorized"
/api/oauth/codex/import-token:
post:
tags:
- OAuth
summary: Import a Codex connection from a bare access token
description: >-
Dashboard-only. Creates a Codex (ChatGPT/OpenAI) connection from a raw
access token with no refresh token (authType `access_token`).
x-internal: true
responses:
"200":
description: Connection imported.
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
/api/cli-tools/crush-settings:
get:
tags:
- CLI Tools
summary: Read Crush CLI OmniRoute config
description: Local-only. Reads the OmniRoute provider block in Crush's config.
x-internal: true
responses:
"200":
description: Current Crush config state.
post:
tags:
- CLI Tools
summary: Write Crush CLI OmniRoute config
description: Local-only. Registers OmniRoute as an `openai-compat` provider in Crush's config.
x-internal: true
responses:
"200":
description: Crush config updated.
delete:
tags:
- CLI Tools
summary: Remove OmniRoute from Crush CLI config
description: Local-only. Removes the OmniRoute provider block from Crush's config.
x-internal: true
responses:
"200":
description: Crush config entry removed.
/api/cli-tools/codewhale-settings:
get:
tags:
- CLI Tools
summary: Read CodeWhale CLI OmniRoute config
description: >-
Local-only. Reads the OmniRoute config block from
`~/.codewhale/config.toml` (with `~/.deepseek/config.toml` legacy
fallback).
x-internal: true
responses:
"200":
description: Current CodeWhale config state.
post:
tags:
- CLI Tools
summary: Write CodeWhale CLI OmniRoute config
description: Local-only. Writes the OmniRoute config block in CodeWhale TOML format.
x-internal: true
responses:
"200":
description: CodeWhale config updated.
delete:
tags:
- CLI Tools
summary: Remove OmniRoute from CodeWhale CLI config
description: Local-only. Removes the OmniRoute config block from CodeWhale's config.
x-internal: true
responses:
"200":
description: CodeWhale config entry removed.
components:
securitySchemes:

View File

@@ -18,7 +18,6 @@ Complete reference for all OmniRoute API endpoints.
- [Embeddings](#embeddings)
- [Image Generation](#image-generation)
- [List Models](#list-models)
- [Provider Plugin Manifest](#provider-plugin-manifest)
- [Compatibility Endpoints](#compatibility-endpoints)
- [Files API](#files-api)
- [Batches API](#batches-api)
@@ -179,22 +178,6 @@ Selecting this id (e.g. in a Claude Code config that always attaches a `thinking
---
## Provider Plugin Manifest
```bash
GET /api/v1/provider-plugin-manifest
```
Returns the JSON-safe provider plugin manifest used by Bifrost, CLIProxyAPI, and
future sidecar routers. The response is generated from the TypeScript provider
registry and intentionally excludes OAuth client secrets, runtime environment
resolution, executor functions, request headers, and account data.
Use this endpoint when a sidecar runs out-of-process and cannot import
`open-sse/config/providerPluginManifestRegistry.ts` directly.
---
## Compatibility Endpoints
| Method | Path | Format |

View File

@@ -12,7 +12,7 @@ OmniRoute integrates with three categories of CLI tools spread across three dedi
| Page | Route | Concept | Count |
| -------------- | ----------------------- | ------------------------------------------------------------------------- | ------------ |
| **CLI Code's** | `/dashboard/cli-code` | Coding tools you point at OmniRoute (Client → CLI → OmniRoute → Provider) | 20 |
| **CLI Code's** | `/dashboard/cli-code` | Coding tools you point at OmniRoute (Client → CLI → OmniRoute → Provider) | 19 |
| **CLI Agents** | `/dashboard/cli-agents` | Autonomous agents you point at OmniRoute (same flow, broader scope) | 6 |
| **ACP Agents** | `/dashboard/acp-agents` | CLIs that OmniRoute spawns as backend via stdio/ACP (reverse flow) | see registry |
@@ -90,7 +90,7 @@ Entries with `baseUrlSupport: "none"` are **not shown** in the dashboard pages
---
## 1. CLI Code's Catalog (20 tools)
## 1. CLI Code's Catalog (19 tools)
Tools that support custom base URL and appear in `/dashboard/cli-code`:
@@ -107,7 +107,6 @@ Tools that support custom base URL and appear in `/dashboard/cli-code`:
| forge | ForgeCode | Antinomy HQ | full | custom | true |
| jcode | jcode | 1jehuang (OSS) | full | custom | false |
| deepseek-tui | DeepSeek TUI | Hunter Bown (OSS) | full | custom | false |
| codewhale | CodeWhale | Hmbown (OSS) | full | custom | false |
| opencode | OpenCode | Anomaly (ex-SST) | full | guide | true |
| droid | Factory Droid | Factory AI | partial | guide | false |
| copilot | GitHub Copilot CLI | GitHub/MS | full | custom | false |
@@ -199,8 +198,7 @@ New tools with `configType: "custom"` have dedicated settings API routes:
| ------------------------------------------- | ------------------------------ |
| `POST /api/cli-tools/forge-settings` | ForgeCode (.forge.toml) |
| `POST /api/cli-tools/jcode-settings` | jcode (--base-url flag) |
| `POST /api/cli-tools/deepseek-tui-settings` | DeepSeek TUI (OPENAI_BASE_URL, legacy) |
| `POST /api/cli-tools/codewhale-settings` | CodeWhale (OPENAI_BASE_URL, primary + legacy `~/.deepseek` sync) |
| `POST /api/cli-tools/deepseek-tui-settings` | DeepSeek TUI (OPENAI_BASE_URL) |
| `POST /api/cli-tools/smelt-settings` | Smelt |
| `POST /api/cli-tools/pi-settings` | Pi coding agent |

View File

@@ -117,7 +117,6 @@ OmniRoute uses **SQLite** (via `better-sqlite3`) for all persistence. These vari
| Variable | Default | Source File | Description |
| ------------------------------------------- | ------------------------------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PORT` | `20128` | `src/lib/runtime/ports.ts` | Primary port for both Dashboard UI and API endpoints (single-port mode). |
| `OMNIROUTE_BASE_PATH` | _(empty = root)_ | `next.config.mjs` | URL subpath for serving OmniRoute behind a reverse proxy under a subpath (sets Next.js `basePath`; auth redirects are basePath-aware). E.g. `/omniroute`. |
| `API_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | When set, serves the `/v1/*` proxy API on this separate port. |
| `API_HOST` | `0.0.0.0` | `src/lib/runtime/ports.ts` | Bind address for the API port. |
| `DASHBOARD_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | When set, serves the Dashboard UI on this separate port. |
@@ -265,8 +264,6 @@ OmniRoute provides a two-layer defense: request-side injection scanning and resp
| `NEXT_PUBLIC_CLOUD_URL` | _(empty)_ | Client-side | Client-side mirror of `CLOUD_URL`. |
| `NEXT_PUBLIC_APP_URL` | _(unset)_ | `src/shared/services/cloudSyncScheduler.ts` | Legacy fallback for `NEXT_PUBLIC_BASE_URL`. |
| `OMNIROUTE_PUBLIC_BASE_URL` | _(unset)_ | Public-origin resolver, image URLs | Highest-priority browser-facing OmniRoute origin used for public URL generation and non-dashboard browser-origin validation (for example `/v1/chatgpt-web/image/<id>`). Set this when OpenWebUI or another relay reaches OmniRoute by an internal URL but the user's browser must fetch images from a LAN, tunnel, or public origin. Do **not** include `/v1`. |
| `OMNIROUTE_PROVIDER_MANIFEST_URL` | _(unset)_ | `open-sse/config/providerPluginManifestUrl.ts` | Absolute provider plugin manifest URL advertised to sidecar clients. When unset, OmniRoute derives `/api/v1/provider-plugin-manifest` from request origin or HOST/PORT. |
| `OMNIROUTE_PUBLIC_PROTOCOL` | `http` | `open-sse/config/providerPluginManifestUrl.ts` | Protocol used when deriving the provider plugin manifest URL from HOST/PORT without a request origin. Set to `https` behind a TLS-terminating public proxy when no explicit `OMNIROUTE_PROVIDER_MANIFEST_URL` is set. |
| `OMNIROUTE_TRUST_PROXY` | _(unset)_ | `src/server/origin/publicOrigin.ts` | Optional trust mode for forwarded public-origin headers. Unset = do not trust `Forwarded` / `X-Forwarded-*` for security decisions. `true` / `loopback` trusts forwarded host/proto only from a token-stamped loopback proxy. `private` / `lan` also trusts private-LAN proxy peers. Prefer explicit `NEXT_PUBLIC_BASE_URL` in production. |
| `OMNIROUTE_CGPT_WEB_IMAGE_TIMEOUT_MS` | `180000` (3 min) | `open-sse/executors/chatgpt-web.ts` | Max wait time for an async chatgpt-web image to land via the celsius WebSocket. Increase during upstream queue-deep windows. |
| `OMNIROUTE_CGPT_WEB_IMAGE_CACHE_MAX_MB` | `256` | `open-sse/services/chatgptImageCache.ts` | Total in-memory byte budget (MB) for the chatgpt-web image cache serving `/v1/chatgpt-web/image/<id>`. Lower on memory-constrained hosts; raise if image generation is heavy and clients race the 30-minute TTL. |
@@ -356,8 +353,6 @@ Controls how OmniRoute discovers and launches CLI sidecars (Claude Code, Codex,
| `CLI_QODER_BIN` | `qoder` | `src/shared/services/cliRuntime.ts` | Custom path to Qoder CLI binary. |
| `CLI_QWEN_BIN` | `qwen` | `src/shared/services/cliRuntime.ts` | Custom path to the Qwen Code CLI binary. |
| `CLI_DEVIN_BIN` | `devin` | `open-sse/executors/devin-cli.ts` | Custom path to the Devin CLI binary (v3.8.0). Used by the Windsurf/Devin executor. |
| `AUGGIE_BIN` | `auggie` | `open-sse/executors/auggie.ts` | Absolute-path override for the Augment (Auggie) CLI binary used by the local `auggie` provider. Falls back to `CLI_AUGGIE_BIN`, then a PATH lookup. |
| `CLI_AUGGIE_BIN` | `auggie` | `open-sse/executors/auggie.ts` | Alias override for the Augment (Auggie) CLI binary path (checked after `AUGGIE_BIN`). |
| `HERMES_HOME` | `~/.hermes` | `src/lib/cli-helper/config-generator/hermesHome.ts` | Hermes Agent home directory where OmniRoute reads/writes the Hermes CLI config. Matches the env var the Hermes PowerShell installer sets on Windows (`%LOCALAPPDATA%\hermes`). |
### CLI Profile Auto-Sync
@@ -480,7 +475,6 @@ Built-in credentials for **localhost development**. For remote deployments, regi
| `QODER_PERSONAL_ACCESS_TOKEN` | Qoder | Direct API key fallback (bypasses OAuth). |
| `QODER_CLI_WORKSPACE` | Qoder | Workspace ID for Qoder CLI. |
| `OMNIROUTE_QODER_WORKSPACE` | Qoder | Alias for `QODER_CLI_WORKSPACE`. |
| `QODER_CLI_CONFIG_DIR` | Qoder | Override the Qoder CLI config dir (isolated PAT session, avoids clobbering a browser login). |
| `BLACKBOX_WEB_VALIDATED_TOKEN` | Blackbox Web | Frontend `tk` token to send as `validated` on `/api/chat`. Required when Blackbox enforces token matching; otherwise OmniRoute falls back to a random UUID. See issue #2252. |
| `VISION_BRIDGE_BASE_URL` | Vision Bridge guardrail | OpenAI-compatible base URL for non-Anthropic vision-bridge calls. Defaults to the legacy OpenAI URL env or api.openai.com. Point at OmniRoute's `/v1` self-loop or any OpenAI-compat endpoint (Gemini OpenAI-compat, OpenRouter). Issue #2232. |
| `VISION_BRIDGE_API_KEY` | Vision Bridge guardrail | API key for the URL above. Overrides per-provider OpenAI / Google env vars for non-Anthropic vision-bridge calls. Anthropic models keep their dedicated Anthropic key path. Issue #2232. |
@@ -624,8 +618,6 @@ REQUEST_TIMEOUT_MS (global override)
| `FETCH_KEEPALIVE_TIMEOUT_MS` | `4000` | Keep-alive socket idle timeout. |
| `TLS_CLIENT_TIMEOUT_MS` | = `FETCH_TIMEOUT_MS` | TLS fingerprint proxy (wreq-js) timeout. |
| `API_BRIDGE_PROXY_TIMEOUT_MS` | `30000` | Proxy hop timeout for `/v1` bridge requests. |
| `FIRECRAWL_BASE_URL` | `https://api.firecrawl.dev` | Point the Firecrawl web-fetch executor at a self-hosted instance (API key optional off-cloud). |
| `FIRECRAWL_TIMEOUT_MS` | `30000` | Per-request timeout for the Firecrawl web-fetch executor. |
| `API_BRIDGE_SERVER_REQUEST_TIMEOUT_MS` | `300000` | Overall server request timeout for the bridge. |
| `API_BRIDGE_SERVER_HEADERS_TIMEOUT_MS` | `60000` | Time to send response headers via the bridge. |
| `API_BRIDGE_SERVER_KEEPALIVE_TIMEOUT_MS` | `5000` | Bridge keep-alive idle timeout. |
@@ -814,7 +806,6 @@ Automatic model pricing data synchronization from external sources.
| `CLIPROXYAPI_HOST` | `127.0.0.1` | `open-sse/executors/cliproxyapi.ts` | CLIProxyAPI bridge host (legacy integration). |
| `CLIPROXYAPI_PORT` | `5544` | `open-sse/executors/cliproxyapi.ts` | CLIProxyAPI bridge port. |
| `CLIPROXYAPI_CONFIG_DIR` | `~/.cli-proxy-api` | `src/lib/versionManager/processManager.ts` | CLIProxyAPI config directory. |
| `MUX_SERVICE_PORT` | `8322` | `src/lib/services/bootstrap.ts` | Override the port where the embedded Mux (coder/mux) agent-orchestration daemon listens (always 127.0.0.1). |
| `LOCAL_HOSTNAMES` | _(empty)_ | `open-sse/config/providerRegistry.ts` | Comma-separated additional hostnames treated as "local" (Docker service names, etc.). |
`ENABLE_CC_COMPATIBLE_PROVIDER` is only for third-party relays that accept Claude Code clients
@@ -831,11 +822,6 @@ Anthropic-compatible provider instead.
| `PROXY_FAST_FAIL_TIMEOUT_MS` | `2000` | `src/lib/proxyHealth.ts` | Fast-fail health check timeout. |
| `PROXY_HEALTH_CACHE_TTL_MS` | `30000` | `src/lib/proxyHealth.ts` | Health check result cache TTL. |
| `PROXY_HEALTH_UNHEALTHY_CACHE_TTL_MS` | `2000` | `src/lib/proxyHealth.ts` | Cache TTL for failed proxy health probes. Keep this shorter than `PROXY_HEALTH_CACHE_TTL_MS` so transient proxy timeouts under high concurrency retry quickly without disabling fast-fail for truly dead proxies. |
| `PROXY_HEALTH_ENABLED` | `true` | `src/lib/proxyHealth/scheduler.ts` | Set `false` to disable the background proxy health scheduler that periodically probes registered proxies. |
| `PROXY_HEALTH_INTERVAL_MS` | `600000` | `src/lib/proxyHealth/scheduler.ts` | Background health-scheduler sweep interval in ms (minimum `60000`). |
| `PROXY_HEALTH_TEST_URL` | `https://httpbin.org/ip` | `src/lib/proxyHealth/scheduler.ts` | Reachability probe target used by the scheduler and the `/api/settings/proxies/auto-test` endpoint. Point it at an internal/self-hosted URL to avoid the public default. |
| `PROXY_AUTO_REMOVE` | `false` | `src/lib/proxyHealth/scheduler.ts` | Set `true` to let the scheduler auto-remove proxies after repeated consecutive failures. |
| `PROXY_AUTO_REMOVE_AFTER` | `3` | `src/lib/proxyHealth/scheduler.ts` | Consecutive failures before the scheduler auto-removes a proxy (when `PROXY_AUTO_REMOVE=true`). |
| `OMNIROUTE_CONTROL_PLANE_PROXY_DIRECT_FALLBACK` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Allow OAuth and provider validation flows to bypass a pinned proxy and connect directly when proxy reachability pre-checks fail. Effective precedence is Feature Flags DB override > env var > default. |
| `RATE_LIMIT_MAX_WAIT_MS` | `120000` (2 min) | `open-sse/services/rateLimitManager.ts` | Max time to wait on a 429 before failing the request. |
| `RATE_LIMIT_AUTO_ENABLE` | _(unset)_ | `open-sse/services/rateLimitManager.ts` | Force the auto-enable rate limit safety net on/off regardless of the persisted Dashboard setting. Accepts `true`/`1`/`on` to force on, `false`/`0`/`off` to force off. |
@@ -1019,10 +1005,6 @@ Provider quota endpoints, network tunnels (Tailscale, Ngrok, MITM debug proxy),
| `FREE_PROXY_PROXIFLY_ANONYMITY` | `elite` | `src/lib/freeProxyProviders/proxifly.ts` | Anonymity level filter for Proxifly (`elite`, `anonymous`, `transparent`). |
| `FREE_PROXY_IPLOCATE_ENABLED` | `false` | `src/lib/freeProxyProviders/iplocate.ts` | Enable the IPLocate free proxy source. Opt-in only. |
| `FREE_PROXY_IPLOCATE_BASE_URL` | `https://raw.githubusercontent.com/iplocate/free-proxy-list/main/protocols` | `src/lib/freeProxyProviders/iplocate.ts` | IPLocate proxy list base URL override. |
| `FREE_PROXY_WEBSHARE_ENABLED` | `true` | `src/lib/freeProxyProviders/webshare.ts` | Enable the Webshare proxy pool source. Set to `false` to disable; also requires `FREE_PROXY_WEBSHARE_API_KEY` to be set. |
| `FREE_PROXY_WEBSHARE_API_KEY` | _(none)_ | `src/lib/freeProxyProviders/webshare.ts` | Webshare account API token (`Authorization: Token <key>`). Required — the provider stays disabled without it. |
| `FREE_PROXY_WEBSHARE_API_URL` | `https://proxy.webshare.io/api/v2/proxy/list/` | `src/lib/freeProxyProviders/webshare.ts` | Webshare proxy list API URL override. |
| `FREE_PROXY_WEBSHARE_MAX` | `500` | `src/lib/freeProxyProviders/webshare.ts` | Maximum proxies imported per Webshare sync. |
| `NEXT_PUBLIC_VERCEL_RELAY_ENABLED` | `true` | `src/app/(dashboard)/…/ProxyPoolTab.tsx` | Show/hide the Deploy Vercel Relay button in the Proxy Pool tab. |
| `VERCEL_API_BASE` | `https://api.vercel.com` | `src/app/api/settings/proxy/vercel-deploy/route.ts` | Vercel API base URL override (for testing). |
| `NEXT_PUBLIC_VERCEL_RELAY_DEFAULT_PROJECT` | `omniroute-relay` | `src/app/(dashboard)/…/VercelRelayModal.tsx` | Default project name pre-filled in the Vercel Relay deploy modal. |
@@ -1056,7 +1038,6 @@ Provider quota endpoints, network tunnels (Tailscale, Ngrok, MITM debug proxy),
| `PLAYGROUND_IMPROVE_PROMPT_DEFAULT_MODEL` | _(unset)_ | `src/app/(dashboard)/dashboard/playground/` | Default model for the Playground 'improve prompt' action (falls back to the active model when unset). |
| `BIFROST_ENABLED` | `1` | `src/app/api/v1/relay/chat/completions/bifrost/route.ts` | Master kill switch for the bifrost sidecar proxy. When set to `0`, the route returns 503 with the `X-Bifrost-Killswitch` header and the operator is bounced to the TS path. Use to disable the sidecar without redeploying (tier-1 router incident, key rotation). |
| `BIFROST_BASE_URL` | _(unset)_ | `src/app/api/v1/relay/chat/completions/bifrost/route.ts` | When set, the Bifrost sidecar proxy route forwards `/v1/chat/completions` traffic to this Go gateway instead of the TS relay handler. Unset → 503-with-fallback. Trailing slash is stripped. |
| `BIFROST_PORT` | `8080` | `src/lib/services/bootstrap.ts` | Port the supervised Bifrost embedded service binds to (`127.0.0.1:<port>`) when OmniRoute manages the Bifrost sidecar lifecycle. Defaults to `8080`. |
| `BIFROST_API_KEY` | _(unset)_ | `src/app/api/v1/relay/chat/completions/bifrost/route.ts` | API key for the Bifrost gateway (sent as `Authorization: Bearer ...`). If unset, the route expects the request to carry a valid OmniRoute API key; this key is for gateway-side auth only. |
| `BIFROST_STREAMING_ENABLED` | `true` | `src/app/api/v1/relay/chat/completions/bifrost/route.ts` | When true, the Bifrost sidecar route streams responses back via SSE through the gateway rather than the TS streaming executor. Set to `0` to force non-streaming JSON responses through the gateway. |
| `BIFROST_TIMEOUT_MS` | `30000` | `src/app/api/v1/relay/chat/completions/bifrost/route.ts` | Per-request timeout when proxying to the Bifrost gateway (ms). On timeout the route returns the TS relay path via the `X-Bifrost-Fallback` header. |

View File

@@ -1,81 +0,0 @@
---
title: "Provider Plugin Manifest"
version: 3.8.42
lastUpdated: 2026-07-01
---
# Provider Plugin Manifest
`open-sse/config/providerPluginManifest.ts` defines the JSON-safe provider
plugin contract. `open-sse/config/providerPluginManifestRegistry.ts` binds that
contract to the current provider registry for sidecars such as Bifrost,
CLIProxyAPI, or a future Go/Rust router. The TypeScript registry remains the
source of truth, but sidecars can consume the manifest without importing
executor code, OAuth defaults, headers, or process environment state.
The same manifest is available over HTTP at
`GET /api/v1/provider-plugin-manifest` for sidecars that run out-of-process.
OmniRoute advertises that URL to Bifrost and CLIProxyAPI via the
`X-OmniRoute-Provider-Manifest-Url` request header. Set
`OMNIROUTE_PROVIDER_MANIFEST_URL` when the sidecar needs a public or container
network URL instead of the local request origin.
## Goal
Move provider metadata toward a plugin contract so the hot request path can
eventually be owned by a lower-latency sidecar while OmniRoute keeps the
TypeScript route as the policy gate and fallback. The manifest is additive: it
does not change request routing by itself.
## Contract
The manifest contains:
- provider id and alias
- upstream format and executor name
- auth type, auth header, and optional auth prefix
- static endpoint metadata
- sidecar eligibility and explicit reasons when a provider should stay on TS
- JSON-safe model metadata such as context length, vision/reasoning flags, and
unsupported params
- capability tags including `apikey`, `oauth`, `custom-executor`,
`passthrough-models`, `responses`, and `sidecar-candidate`
The manifest intentionally excludes:
- OAuth client secrets and default secret values
- runtime environment resolution
- request headers and public credential helpers
- dynamic URL builders
- executor functions
- session pool internals
## Sidecar Use
Sidecars should treat `sidecar.eligible` as a conservative candidate signal, not
as an unconditional routing decision. The first import target should be
API-key, static-endpoint providers using the default executor. Providers with
custom web executors, OAuth/session flows, dynamic URL builders, or pool config
stay on the TypeScript fallback path until a sidecar implements equivalent
behavior and telemetry proves parity.
Suggested migration phases:
1. Generate and validate the provider plugin manifest from the TS registry.
2. Teach Bifrost or CLIProxyAPI to import the manifest for API-key/static
providers.
3. Route eligible providers through the sidecar behind `OMNIROUTE_RELAY_BACKEND`
while keeping TS fallback enabled.
4. Promote providers only when success rate, p99 latency, streaming behavior,
and unsupported-param handling match the TS path.
5. Add sidecar-native plugins for custom executors one provider family at a
time.
## Why Not Embed Providers Directly In Next
The Next frontend should not own provider execution. It should call the API
boundary. The backend can then decide whether to use the TypeScript executor,
Bifrost, CLIProxyAPI, or a future native sidecar. This keeps request signing,
allowlist checks, DB policy, and fallback behavior centralized before any
sidecar handoff.

View File

@@ -75,10 +75,3 @@ For sustained high RPM/RPS and strict success SLO:
- `BIFROST_ENABLED=1`
- Keep API keys, allowlist, sanitizer, and rate-limit checks enabled in route handlers (they always run before downstream forwarding).
- Export fallback metrics from your reverse proxy and request logs so sidecar outages are visible within one minute.
## Provider plugin contract
Sidecars should import provider metadata through the JSON-safe provider plugin
manifest instead of depending on TypeScript executor internals. See
[Provider Plugin Manifest](./PROVIDER_PLUGIN_MANIFEST.md) for the sidecar
eligibility contract and migration phases.

View File

@@ -1,162 +0,0 @@
---
title: CORS Configuration & Security
---
# CORS Configuration & Security
OmniRoute controls which **browser origins** may read cross-origin responses
from a single, centralized allowlist. The model is **fail-closed by default**:
no origin is allowed until you opt one in. This page documents how the allowlist
resolves, what `CORS_ALLOW_ALL=true` actually exposes (and, importantly, what it
does **not**), how to configure dev vs production safely, and the runtime warning
the dashboard shows when a wildcard is live.
**Source of truth:** `src/server/cors/origins.ts` (`resolveAllowedOrigin`,
`applyCorsHeaders`, `getCorsStatus`). The allowlist is applied once, in the
middleware (`src/server/authz/pipeline.ts`) — per-route handlers do not set
`Access-Control-Allow-Origin` themselves.
## How an origin is resolved
For each request the middleware computes the `Access-Control-Allow-Origin` value
in this order:
1. **`CORS_ALLOW_ALL=true`** (or the legacy `CORS_ORIGIN=*`) → echo the caller's
`Origin` back (or `*` when there is no `Origin` header), with `Vary: Origin`
so caches stay correct.
2. Otherwise, the request `Origin` is normalized (lower-cased, trailing slash
stripped) and matched against the **merged allowlist**:
- env **`CORS_ALLOWED_ORIGINS`** — comma-separated list, and
- the runtime **`corsOrigins`** setting (Dashboard → Security → _CORS Allowed
Origins_), injected via `setRuntimeAllowedOrigins()` from
`src/lib/config/runtimeSettings.ts`.
3. No match → **no `Access-Control-Allow-Origin` header is emitted**. The browser
blocks the cross-origin read. This is the intended fail-closed default.
| Env var | Meaning |
| ---------------------- | ------------------------------------------------------------------------------------ |
| `CORS_ALLOWED_ORIGINS` | CSV of exact origins to allow (recommended). |
| `CORS_ALLOW_ALL` | `true`/`1` → echo any origin (wildcard). Dev only. |
| `CORS_ORIGIN` | Legacy. `*` behaves like `CORS_ALLOW_ALL`; a single value is added to the allowlist. |
## Threat model — what `CORS_ALLOW_ALL=true` really exposes
The generic OWASP warning ("wildcard CORS = any site can call your API") is worth
taking seriously, but OmniRoute's exposure is **narrower than the generic case**,
because of one concrete implementation fact:
> **The central `applyCorsHeaders()` never emits
> `Access-Control-Allow-Credentials`.** A browser will not expose a _credentialed_
> (cookie-bearing) cross-origin response unless the server sends
> `Access-Control-Allow-Credentials: true`. OmniRoute's shared CORS path never
> does.
What that means per surface, even with `CORS_ALLOW_ALL=true`:
| Surface | Auth mechanism | Effect of wildcard CORS |
| ----------------------------------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Dashboard / MANAGEMENT `/api/*` | Cookie session | Origin is echoed, but **without `Allow-Credentials`** the browser **blocks** the credentialed read. A malicious cross-origin site **cannot read** your authenticated dashboard responses, and the session cookie is not exposed. |
| Client API `/v1/*`, `/v1beta/*` | Bearer / `x-api-key` header | Already permissive **by design** (`relaxForTokenAuth`): browsers never auto-attach `Authorization`/`x-api-key`, so an attacker's page cannot supply your key. `CORS_ALLOW_ALL` does not widen this. |
| Public read-only (`/api/health`, …) | None | Non-sensitive; wildcard is harmless. |
So the **residual** exposure of `CORS_ALLOW_ALL=true` is limited to: (a)
non-credentialed cross-origin **reads** of already-unauthenticated data, and (b)
letting CORS **preflight pass** on management routes — which still require auth
that a cross-origin page cannot provide. It is **not** a session-hijack or
credential-theft vector on the shared CORS path.
### One genuine exception — `/api/v1/agents/`
The Cloud-Agent routes (`/api/v1/agents/{health,credentials,tasks,tasks/[id]}`) set
their **own** CORS headers
(`src/lib/cloudAgent/api.ts`, `getCloudAgentCorsHeaders`) and **do** emit
`Access-Control-Allow-Origin: <origin>|*` together with
`Access-Control-Allow-Credentials: true`. This is the single surface where
origin-echo and credentials coexist, and it is **independent of
`CORS_ALLOW_ALL`**. These routes are management-authenticated
(`requireManagementAuth`); operators who expose the dashboard off-host should be
aware that this is the one place a cross-origin credentialed read is permitted by
the response headers. Tightening it to an explicit allowlist is tracked
separately from this CORS guidance.
## Production checklist
- **Never set `CORS_ALLOW_ALL=true` in production.** Leave it unset.
- Set an **explicit** origin list — either the env var or the Security-tab field:
```bash
CORS_ALLOWED_ORIGINS="https://app.example.com, https://admin.example.com"
```
- If OmniRoute runs behind a reverse proxy / tunnel (nginx, Caddy, Cloudflare
Tunnel, Tailscale), CORS is **not** your only control — the loopback route
guard still protects spawn-capable routes (see
[ROUTE_GUARD_TIERS](./ROUTE_GUARD_TIERS.md)). Do not forge
`X-Forwarded-For: 127.0.0.1` to "fix" a 403; that re-opens the RCE class the
route guard closes.
- Confirm the runtime state: the dashboard shows a **persistent amber banner**
under Dashboard → Security → Authorization Inventory whenever
`CORS_ALLOW_ALL=true` is live, and `/api/settings/authz-inventory` returns a
`cors: { allowAll, allowedOrigins }` envelope monitoring tools can poll.
## Development convenience — allow specific local origins
You rarely need the wildcard even in dev. Allow just the dev servers you use:
```bash
# Vite (5173) + Next.js (3000) dev servers calling a local OmniRoute
CORS_ALLOWED_ORIGINS="http://localhost:5173, http://localhost:3000"
```
Origins are matched case-insensitively with the trailing slash ignored, so
`http://localhost:3000` and `http://localhost:3000/` are equivalent. The same CSV
can be set at runtime in **Dashboard → Security → CORS Allowed Origins** without a
restart.
## API keys vs cookie sessions
- **Bearer / `x-api-key` (the `/v1/*` inference surface):** browsers never attach
these automatically. CORS is not a meaningful barrier here — the API key is the
barrier — which is why that surface is intentionally permissive so browser and
Electron clients can read responses they are already entitled to.
- **Cookie session (the dashboard):** protected by the fail-closed default **and**
by the absence of `Access-Control-Allow-Credentials` on the shared path. Keep
management/dashboard origins out of any permissive config; they must stay exactly
fail-closed.
## Example: reverse proxy in front of OmniRoute
CORS is enforced by OmniRoute itself, so the proxy generally should **not** add or
rewrite `Access-Control-*` headers (double headers break browsers). Terminate TLS
and forward — let OmniRoute answer preflight:
```nginx
# nginx — forward to OmniRoute; do NOT inject Access-Control-* here
location / {
proxy_pass http://127.0.0.1:20128;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
# Do NOT set X-Forwarded-For to 127.0.0.1 — it defeats the loopback route guard.
}
```
Set the allowed browser origins in OmniRoute (`CORS_ALLOWED_ORIGINS` or the
Security tab), not in the proxy.
## Source files
| Concern | File |
| ----------------------------------------------- | -------------------------------------------------------------------- |
| Allowlist resolution + `getCorsStatus()` | `src/server/cors/origins.ts` |
| Middleware application (single source of truth) | `src/server/authz/pipeline.ts` |
| Settings → runtime origin injection | `src/lib/config/runtimeSettings.ts` |
| Runtime status for the dashboard | `src/app/api/settings/authz-inventory/route.ts` |
| Dashboard warning banner | `src/app/(dashboard)/dashboard/settings/components/AuthzSection.tsx` |
| CORS Allowed Origins field | `src/app/(dashboard)/dashboard/settings/components/SecurityTab.tsx` |
| Cloud-Agent per-route CORS (the exception) | `src/lib/cloudAgent/api.ts` |
## See also
- [Route Guard Tiers](./ROUTE_GUARD_TIERS.md) — loopback enforcement for
spawn-capable routes (a separate, complementary control).
- [Authorization Guide](../architecture/AUTHZ_GUIDE.md) — the full auth pipeline.