From 204e15628c3ef64bfb4c6595819d117caff0c665 Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Wed, 13 May 2026 02:15:31 -0300 Subject: [PATCH] chore(release): prepare v3.8.0 docs and coverage ratchet Refresh the v3.8.0 documentation set across API, setup, Docker, environment, Fly.io, VM deployment, and troubleshooting guides. Document the updated budget payload, management-auth requirements, WebSocket bridge secret, Compose profiles, production deployment details, and expanded Node support. Also update the OpenAPI catalog, raise coverage thresholds to match the current baseline, and align cloud-agent task route typing and schemas with the stricter runtime behavior. --- docs/API_REFERENCE.md | 13 +- docs/COVERAGE_PLAN.md | 97 ++++++------ docs/DOCKER_GUIDE.md | 115 +++++++++++++- docs/ENVIRONMENT.md | 223 ++++++++++++++++----------- docs/FLY_IO_DEPLOYMENT_GUIDE.md | 70 +++++++-- docs/SETUP_GUIDE.md | 35 ++++- docs/TROUBLESHOOTING.md | 126 ++++++++++++++- docs/VM_DEPLOYMENT_GUIDE.md | 14 +- docs/openapi.yaml | 184 +++++++++++++++++++--- package.json | 2 +- src/app/api/v1/agents/tasks/route.ts | 10 +- src/lib/cloudAgent/types.ts | 4 +- tests/unit/cloudAgent-types.test.ts | 2 +- 13 files changed, 700 insertions(+), 195 deletions(-) diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md index ea93b8c320..539ba70857 100644 --- a/docs/API_REFERENCE.md +++ b/docs/API_REFERENCE.md @@ -457,12 +457,17 @@ POST /api/usage/budget Content-Type: application/json { - "keyId": "key-123", - "limit": 50.00, - "period": "monthly" + "apiKeyId": "key-123", + "dailyLimitUsd": 5.00, + "weeklyLimitUsd": 30.00, + "monthlyLimitUsd": 100.00, + "warningThreshold": 0.8, + "resetInterval": "monthly" } ``` +> **Schema notes** (`setBudgetSchema`): `apiKeyId` is required; at least one of `dailyLimitUsd`, `weeklyLimitUsd`, or `monthlyLimitUsd` must be greater than zero. Optional fields: `warningThreshold` (0–1), `resetInterval` (`daily` | `weekly` | `monthly`), `resetTime` (`HH:MM`). The legacy `{keyId, limit, period}` shape returns `400 Bad Request`. + ## Request Processing 1. Client sends request to `/v1/*` @@ -486,3 +491,5 @@ Full architecture reference: [`ARCHITECTURE.md`](ARCHITECTURE.md) - Login uses saved password hash; fallback to `INITIAL_PASSWORD` - `requireLogin` toggleable via `/api/settings/require-login` - `/v1/*` routes optionally require Bearer API key when `REQUIRE_API_KEY=true` + +> **Breaking change (v3.8.0)** — `/api/v1/agents/tasks/*` and the cooldown management endpoints now require **management auth** (dashboard `auth_token` cookie or a management-scoped API key). Clients that previously called these routes unauthenticated will receive `401 Unauthorized`. See commit `588a0333` (`fix(auth): require management auth for agent and cooldown APIs`). diff --git a/docs/COVERAGE_PLAN.md b/docs/COVERAGE_PLAN.md index 2c639045fe..7829d8d1c3 100644 --- a/docs/COVERAGE_PLAN.md +++ b/docs/COVERAGE_PLAN.md @@ -1,6 +1,8 @@ # Test Coverage Plan -Last updated: 2026-03-28 +Last updated: 2026-05-13 + +> Status measured on 2026-05-13: lines 82.58%, statements 82.58%, functions 84.23%, branches 75.22%. Phases 1-5 are complete. Current focus is Phase 6 (>=85%) and Phase 7 (>=90%). ## Baseline @@ -10,7 +12,7 @@ There are multiple coverage numbers depending on how the report is computed. For | -------------------- | ----------------------------------------------------- | -----------------: | -------: | --------: | --------------------------------------------------- | | Legacy | Old `npm run test:coverage` | 79.42% | 75.15% | 67.94% | Inflated: counts test files and excludes `open-sse` | | Diagnostic | Source-only, excluding tests and excluding `open-sse` | 68.16% | 63.55% | 64.06% | Useful only to isolate `src/**` | -| Recommended baseline | Source-only, excluding tests and including `open-sse` | 56.95% | 66.05% | 57.80% | This is the project-wide baseline to improve | +| Recommended baseline | Source-only, excluding tests and including `open-sse` | 82.58% | 75.22% | 84.23% | This is the project-wide baseline to improve | The recommended baseline is the number to optimize against. @@ -34,50 +36,51 @@ The recommended baseline is the number to optimize against. ## Milestones -| Phase | Target | Focus | -| ------- | ---------------------: | ------------------------------------------------- | -| Phase 1 | 60% statements / lines | Quick wins and low-risk utility coverage | -| Phase 2 | 65% statements / lines | DB and route foundations | -| Phase 3 | 70% statements / lines | Provider validation and usage analytics | -| Phase 4 | 75% statements / lines | `open-sse` translators and helpers | -| Phase 5 | 80% statements / lines | `open-sse` handlers and executor branches | -| Phase 6 | 85% statements / lines | Harder edge cases, branch debt, regression suites | -| Phase 7 | 90% statements / lines | Final sweep, gap closure, strict ratchet | +| Phase | Target | Focus | Status | +| ------- | ---------------------: | ------------------------------------------------- | ---------- | +| Phase 1 | 60% statements / lines | Quick wins and low-risk utility coverage | ✅ Done | +| Phase 2 | 65% statements / lines | DB and route foundations | ✅ Done | +| Phase 3 | 70% statements / lines | Provider validation and usage analytics | ✅ Done | +| Phase 4 | 75% statements / lines | `open-sse` translators and helpers | ✅ Done | +| Phase 5 | 80% statements / lines | `open-sse` handlers and executor branches | ✅ Done | +| Phase 6 | 85% statements / lines | Harder edge cases, branch debt, regression suites | In progress | +| Phase 7 | 90% statements / lines | Final sweep, gap closure, strict ratchet | Pending | Branches and functions should ratchet upward with each phase, but the primary hard target is statements / lines. ## Priority hotspots -These files or areas offer the best return for the next phases: +These files have the lowest line coverage today (< 60%) and offer the best return for Phases 6-7. Generated from `coverage/coverage-summary.json` on 2026-05-13: -1. `open-sse/handlers` - - `chatCore.ts` at 7.57% - - Overall directory at 29.07% -2. `open-sse/translator/request` - - Overall directory at 36.39% - - Many translators are still near single-digit coverage -3. `open-sse/translator/response` - - Overall directory at 8.07% -4. `open-sse/executors` - - Overall directory at 36.62% -5. `src/lib/db` - - `models.ts` at 20.66% - - `registeredKeys.ts` at 34.46% - - `modelComboMappings.ts` at 36.25% - - `settings.ts` at 46.40% - - `webhooks.ts` at 33.33% -6. `src/lib/usage` - - `usageHistory.ts` at 21.12% - - `usageStats.ts` at 9.56% - - `costCalculator.ts` at 30.00% -7. `src/lib/providers` - - `validation.ts` at 41.16% -8. Low-risk utility and API files for early gains - - `src/shared/utils/upstreamError.ts` - - `src/shared/utils/apiAuth.ts` - - `src/lib/api/errorResponse.ts` - - `src/app/api/settings/require-login/route.ts` - - `src/app/api/providers/[id]/models/route.ts` +| # | File | Lines % | +| --- | ----------------------------------------------------------------- | ------: | +| 1 | `open-sse/services/compression/validation.ts` | 7.87% | +| 2 | `src/app/api/v1/batches/route.ts` | 9.67% | +| 3 | `src/app/docs/components/FeedbackWidget.tsx` | 9.80% | +| 4 | `open-sse/services/compression/toolResultCompressor.ts` | 10.00% | +| 5 | `src/app/docs/components/DocCodeBlocks.tsx` | 10.63% | +| 6 | `open-sse/services/compression/engines/rtk/lineFilter.ts` | 10.96% | +| 7 | `open-sse/services/specificityRules.ts` | 11.28% | +| 8 | `src/mitm/systemCommands.ts` | 12.19% | +| 9 | `open-sse/services/compression/aggressive.ts` | 12.77% | +| 10 | `src/app/api/v1/batches/[id]/cancel/route.ts` | 12.98% | +| 11 | `open-sse/services/compression/progressiveAging.ts` | 13.26% | +| 12 | `open-sse/services/compression/engines/rtk/smartTruncate.ts` | 13.43% | +| 13 | `open-sse/services/compression/engines/rtk/deduplicator.ts` | 13.51% | +| 14 | `src/lib/cloudAgent/agents/jules.ts` | 13.52% | +| 15 | `open-sse/services/compression/lite.ts` | 14.46% | +| 16 | `src/app/api/v1/rerank/route.ts` | 14.94% | +| 17 | `open-sse/services/compression/preservation.ts` | 15.07% | +| 18 | `src/lib/cloudAgent/agents/codex.ts` | 15.54% | +| 19 | `open-sse/services/tierResolver.ts` | 16.66% | +| 20 | `src/app/docs/components/DocsLazyWrapper.tsx` | 16.66% | + +Themes for Phases 6-7: + +- `open-sse/services/compression/**` is the densest cluster of low coverage and dominates the remaining gap. +- Batch and rerank API routes (`src/app/api/v1/batches/**`, `src/app/api/v1/rerank/route.ts`) need handler-level tests. +- Cloud agent adapters (`src/lib/cloudAgent/agents/jules.ts`, `codex.ts`) and `tierResolver.ts` need scenario tests. +- Docs UI components and `src/mitm/systemCommands.ts` are lower priority but cheap branch wins. ## Execution checklist @@ -148,18 +151,26 @@ These files or areas offer the best return for the next phases: Update `npm run test:coverage` thresholds only after the project actually exceeds the next milestone with a comfortable buffer. -Recommended ratchet sequence: +**Current gate (as of 2026-05-13):** `npm run test:coverage` enforces **75 statements / 75 lines / 75 functions / 70 branches**. This is the conservative ratchet against the measured baseline (82.58% / 82.58% / 84.23% / 75.22%) and preserves headroom for transient flakiness. + +For ad-hoc threshold checks against the latest report use: + +```bash +node scripts/test-report-summary.mjs --threshold 75 +``` + +Recommended ratchet sequence (order is `statements-lines / branches / functions`): 1. 55/60/55 2. 60/62/58 3. 65/64/62 4. 70/66/66 -5. 75/70/72 +5. 75/70/72 <-- current gate (75/70/75) 6. 80/75/78 7. 85/80/84 8. 90/85/88 -Order is `statements-lines / branches / functions`. +Next ratchet target is `80/75/78` once branch coverage holds above 78% for two consecutive runs. ## Known gap diff --git a/docs/DOCKER_GUIDE.md b/docs/DOCKER_GUIDE.md index 771cf819d4..6af1872d8b 100644 --- a/docs/DOCKER_GUIDE.md +++ b/docs/DOCKER_GUIDE.md @@ -7,6 +7,11 @@ - [Quick Run](#quick-run) - [With Environment File](#with-environment-file) - [Docker Compose](#docker-compose) +- [Available Profiles](#available-profiles) +- [Redis Sidecar](#redis-sidecar) +- [Production Compose](#production-compose) +- [Dockerfile Stages](#dockerfile-stages) +- [Critical Environment Variables](#critical-environment-variables) - [Docker Compose with Caddy (HTTPS)](#docker-compose-with-caddy-https-auto-tls) - [Cloudflare Quick Tunnel](#cloudflare-quick-tunnel) - [Image Tags](#image-tags) @@ -50,8 +55,114 @@ docker compose --profile base up -d # CLI profile (Claude Code, Codex, OpenClaw built-in) docker compose --profile cli up -d + +# Host profile (Linux-first; mounts host CLI binaries read-only) +docker compose --profile host up -d + +# Combine CLI + CLIProxyAPI sidecar +docker compose --profile cli --profile cliproxyapi up -d ``` +## Available Profiles + +OmniRoute ships four Compose profiles. Pick the one that matches your environment. + +| Profile | Service | When to use | Command | +| ---------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | +| `base` (default) | `omniroute-base` | Headless server / minimal runtime, no provider CLIs bundled | `docker compose --profile base up -d` | +| `cli` | `omniroute-cli` | Agentic workflows that call `omniroute providers/setup/doctor` and bundled CLIs (Codex, Claude Code, Droid, OpenClaw) | `docker compose --profile cli up -d` | +| `host` | `omniroute-host` | Linux hosts that want `network_mode`-like access to host CLIs by mounting `~/.local/bin`, `~/.codex`, `~/.claude`, etc. read-only | `docker compose --profile host up -d` | +| `cliproxyapi` | `cliproxyapi` | Run the [CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI) sidecar on port `8317` for upstream CLI proxying | `docker compose --profile cliproxyapi up -d` | + +> Multiple profiles can be combined: `docker compose --profile cli --profile cliproxyapi up -d`. + +## Redis Sidecar + +OmniRoute relies on Redis to back the distributed rate limiter and shared cache. The `redis` service is **always defined** in `docker-compose.yml` (it has no profile gate) and starts alongside any other profile. + +| Detail | Value | +| -------------------- | --------------------------------- | +| Image | `redis:7-alpine` | +| Container name | `omniroute-redis` | +| Internal port | `6379` | +| Host port (override) | `REDIS_PORT` (defaults to `6379`) | +| Volume | `omniroute-redis-data` → `/data` | +| Healthcheck | `redis-cli ping` (10s interval) | + +Related environment variables: + +- `REDIS_URL` — connection string injected into the app (`redis://redis:6379` by default). +- `REDIS_PORT` — host-side port mapping for the Redis container. + +**Disabling Redis** is not recommended (rate limiter will degrade to in-memory fallback). If you must, either remove/comment the `redis:` service block in `docker-compose.yml` or scale it to zero: + +```bash +docker compose up -d --scale redis=0 +``` + +## Production Compose + +For an isolated production snapshot running alongside dev, use `docker-compose.prod.yml`. + +| Detail | Value | +| ---------------------- | ---------------------------------------------------------------------------------- | +| File | `docker-compose.prod.yml` | +| Default dashboard port | `PROD_DASHBOARD_PORT=20130` (mapped to internal `${DASHBOARD_PORT:-20128}`) | +| Default API port | `PROD_API_PORT=20131` | +| Image | `omniroute:prod` (built from `runner-cli` target) | +| Redis container | `omniroute-redis-prod` (`redis:8.6.2`, dedicated `redis-prod-data` volume) | +| Data volume | `omniroute-prod-data` (named, persisted across rebuilds) | +| Healthchecks | `node healthcheck.mjs` + `redis-cli ping`, with `depends_on` gated on Redis health | + +How to use: + +```bash +# Build & start the production stack +docker compose -f docker-compose.prod.yml up -d --build + +# Stream logs +docker compose -f docker-compose.prod.yml logs -f + +# Tear down (keep volumes) +docker compose -f docker-compose.prod.yml down +``` + +The prod stack runs in parallel with the dev compose (different container names, ports, and volumes), so you can keep iterating locally while production stays up. + +## Dockerfile Stages + +The repository ships a multi-stage Dockerfile (`Dockerfile`). Three stages are exposed; pick the right `target` for your use case. + +| Stage | Base image | Purpose | +| ------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `builder` | `node:24.15.0-trixie-slim` | Installs deps (`npm ci --legacy-peer-deps`) and runs `npm run build -- --webpack` | +| `runner-base` | `node:24.15.0-trixie-slim` | Production runtime with the Next.js standalone output. **No provider CLIs bundled.** | +| `runner-cli` | `runner-base` | Adds `git`, `docker.io`, `docker-compose` and global CLIs: `@openai/codex`, `@anthropic-ai/claude-code`, `droid`, `openclaw`. **Pick this for agentic workflows.** | + +Build a specific target manually: + +```bash +docker build --target runner-base -t omniroute:base . +docker build --target runner-cli -t omniroute:cli . +``` + +Defaults exported by `runner-base`: `PORT=20128`, `HOSTNAME=0.0.0.0`, `NODE_OPTIONS=--max-old-space-size=256`, `DATA_DIR=/app/data`, `OMNIROUTE_MIGRATIONS_DIR=/app/migrations`. + +## Critical Environment Variables + +Beyond the defaults documented in [ENVIRONMENT.md](ENVIRONMENT.md), the following variables matter most when running under Docker: + +| Variable | Purpose | Default | +| ----------------------------- | --------------------------------------------------------------------------------------------------- | ------------------------- | +| `OMNIROUTE_WS_BRIDGE_SECRET` | Shared secret for the WebSocket bridge. **Required in production** — set to a strong random string. | unset (must be provided) | +| `REDIS_URL` | Connection string for the rate limiter / cache backend | `redis://redis:6379` | +| `REDIS_PORT` | Host-side port for the bundled Redis container | `6379` | +| `AUTO_UPDATE_HOST_REPO_DIR` | Host path mounted into `cli` profile at `/workspace/omniroute` for self-update workflows | `.` (current directory) | +| `OMNIROUTE_MEMORY_MB` | Node heap ceiling (`NODE_OPTIONS=--max-old-space-size`) baked into the image | `256` (set in Dockerfile) | +| `DASHBOARD_PORT` / `API_PORT` | Override exposed ports for dashboard (20128) and API (20129) | `20128` / `20129` | +| `PROD_DASHBOARD_PORT` | Host-side dashboard port for `docker-compose.prod.yml` | `20130` | +| `CLIPROXYAPI_PORT` | Host-side port for the `cliproxyapi` sidecar | `8317` | + ## Docker Compose with Caddy (HTTPS Auto-TLS) OmniRoute can be securely exposed using Caddy's automatic SSL provisioning. Ensure your domain's DNS A record points to your server's IP. @@ -101,9 +212,9 @@ Endpoint tunnel panels (Cloudflare, Tailscale, ngrok) can be shown or hidden fro | Image | Tag | Size | Description | | ------------------------ | -------- | ------ | --------------------- | | `diegosouzapw/omniroute` | `latest` | ~250MB | Latest stable release | -| `diegosouzapw/omniroute` | `3.7.8` | ~250MB | Current version | +| `diegosouzapw/omniroute` | `3.8.0` | ~250MB | Current version | -Multi-platform: AMD64 + ARM64 native (Apple Silicon, AWS Graviton, Raspberry Pi). +Multi-platform manifest: `linux/amd64` + `linux/arm64` native (Apple Silicon, AWS Graviton, Raspberry Pi). Docker selects the matching architecture automatically; pass `--platform linux/amd64` if you need to force AMD64 emulation on ARM hosts. ## Important Notes diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index 066d800705..0087bb2ee0 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -30,6 +30,7 @@ - [21. Proxy Health](#21-proxy-health) - [22. Debugging](#22-debugging) - [23. GitHub Integration](#23-github-integration) +- [24. Skills Sandbox (v3.8.0+)](#24-skills-sandbox-v380) - [Deployment Scenarios](#deployment-scenarios) - [Audit: Removed / Dead Variables](#audit-removed--dead-variables) @@ -39,19 +40,21 @@ These **must** be set before the first run. Without them, the application will either refuse to start or operate with insecure defaults. -| Variable | Required | Default | Source File | Description | -| ------------------ | -------- | -------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Yes** | _(none)_ | `src/lib/auth` | Signs/verifies all dashboard session cookies (JWT). Generate with `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Yes** | _(none)_ | `src/lib/db/apiKeys.ts` | AES encryption key for API key values at rest in SQLite. Generate with `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Yes** | `123456` | Bootstrap script | Sets the initial admin dashboard password. **Change before first use.** After login, change via Dashboard → Settings → Security. | +| Variable | Required | Default | Source File | Description | +| ---------------------------- | -------------------- | ---------- | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Yes** | _(none)_ | `src/lib/auth` | Signs/verifies all dashboard session cookies (JWT). Generate with `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Yes** | _(none)_ | `src/lib/db/apiKeys.ts` | AES encryption key for API key values at rest in SQLite. Generate with `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | **Yes** | `CHANGEME` | Bootstrap script | Sets the initial admin dashboard password (matches `.env.example` default — kept obviously insecure to force a change). **Change before first use.** After login, change via Dashboard → Settings → Security. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Yes** (production) | _(unset)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Shared secret for the internal Codex Responses WebSocket bridge. Authenticates bridge requests between the Electron/browser WS relay and OmniRoute. ⚠️ **REQUIRED in production — when unset, all WS bridge requests are rejected.** Generate with `openssl rand -base64 32`. | ### Generation Commands ```bash -# Generate all three secrets at once: +# Generate all four secrets at once: echo "JWT_SECRET=$(openssl rand -base64 48)" echo "API_KEY_SECRET=$(openssl rand -hex 32)" echo "INITIAL_PASSWORD=$(openssl rand -base64 16)" +echo "OMNIROUTE_WS_BRIDGE_SECRET=$(openssl rand -base64 32)" ``` > [!CAUTION] @@ -63,14 +66,19 @@ echo "INITIAL_PASSWORD=$(openssl rand -base64 16)" OmniRoute uses **SQLite** (via `better-sqlite3`) for all persistence. These variables control data location, encryption, and lifecycle. -| Variable | Default | Source File | Description | -| -------------------------------- | -------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | -| `DATA_DIR` | `~/.omniroute/` | `src/lib/db/core.ts` | Root directory for SQLite DB, backups, and data files. Override for Docker volumes or custom paths. | -| `STORAGE_ENCRYPTION_KEY` | _(empty = disabled)_ | `src/lib/db/encryption.ts` | AES key for full SQLite database encryption at rest. Generate with `openssl rand -hex 32`. | -| `STORAGE_ENCRYPTION_KEY_VERSION` | `v1` | `scripts/bootstrap-env.mjs`, `electron/main.js` | Version label for the encryption key. Increment when performing key rotation to support decryption of old backups. | -| `DISABLE_SQLITE_AUTO_BACKUP` | `false` | `src/lib/db/backup.ts` | When `true`, skips the automatic database backup that runs before migrations on every startup. | -| `OMNIROUTE_CRYPT_KEY` | _(unset)_ | `src/lib/db/encryption.ts` | **Legacy alias** for `STORAGE_ENCRYPTION_KEY`. Accepted as a fallback when the primary variable is absent. | -| `OMNIROUTE_API_KEY_BASE64` | _(unset)_ | `src/lib/db/encryption.ts` | **Legacy alias** (Base64-encoded form) accepted as a fallback. Decoded automatically before use. | +| Variable | Default | Source File | Description | +| -------------------------------------- | -------------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `DATA_DIR` | `~/.omniroute/` | `src/lib/db/core.ts` | Root directory for SQLite DB, backups, and data files. Override for Docker volumes or custom paths. | +| `STORAGE_ENCRYPTION_KEY` | _(empty = disabled)_ | `src/lib/db/encryption.ts` | AES key for full SQLite database encryption at rest. Generate with `openssl rand -hex 32`. | +| `STORAGE_ENCRYPTION_KEY_VERSION` | `v1` | `scripts/bootstrap-env.mjs`, `electron/main.js` | Version label for the encryption key. Increment when performing key rotation to support decryption of old backups. | +| `DISABLE_SQLITE_AUTO_BACKUP` | `false` | `src/lib/db/backup.ts` | When `true`, skips the automatic database backup that runs before migrations on every startup. | +| `OMNIROUTE_CRYPT_KEY` | _(unset)_ | `src/lib/db/encryption.ts` | **Legacy alias** for `STORAGE_ENCRYPTION_KEY`. Accepted as a fallback when the primary variable is absent. | +| `OMNIROUTE_API_KEY_BASE64` | _(unset)_ | `src/lib/db/encryption.ts` | **Legacy alias** (Base64-encoded form) accepted as a fallback. Decoded automatically before use. | +| `OMNIROUTE_DB_HEALTHCHECK_INTERVAL_MS` | _(unset)_ | `src/lib/db/core.ts` | Override the periodic SQLite healthcheck interval (ms). When unset, defaults are derived from `NODE_ENV`. | +| `OMNIROUTE_FORCE_DB_HEALTHCHECK` | `0` | `src/lib/db/core.ts` | Set to `1` to force the DB healthcheck loop on, even when it would normally be skipped (e.g., short-lived tasks). | +| `OMNIROUTE_MIGRATIONS_DIR` | _(auto-detect)_ | `src/lib/db/migrationRunner.ts` | Override the directory that the migration runner scans. Useful when shipping bundled migrations in custom builds. | +| `OMNIROUTE_SPEND_FLUSH_INTERVAL_MS` | _(default in code)_ | `src/lib/spend/batchWriter.ts` | Flush interval (ms) for the batched spend/cost writer. Lower values reduce write coalescing; higher values reduce DB contention. | +| `OMNIROUTE_SPEND_MAX_BUFFER_SIZE` | _(default in code)_ | `src/lib/spend/batchWriter.ts` | Max buffered spend entries before a forced flush. Raise on high-QPS deployments; lower when bounded memory matters more. | ### Scenarios @@ -85,16 +93,17 @@ OmniRoute uses **SQLite** (via `better-sqlite3`) for all persistence. These vari ## 3. Network & Ports -| Variable | Default | Source File | Description | -| --------------------- | ------------ | -------------------------- | -------------------------------------------------------------------------------------- | -| `PORT` | `20128` | `src/lib/runtime/ports.ts` | Primary port for both Dashboard UI and API endpoints (single-port mode). | -| `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. | -| `PROD_DASHBOARD_PORT` | `20130` | `docker-compose.prod.yml` | Host-side published port for the Dashboard in Docker production mode. | -| `PROD_API_PORT` | `20131` | `docker-compose.prod.yml` | Host-side published port for the API in Docker production mode. | -| `OMNIROUTE_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | Takes precedence over `PORT` when running inside Electron or other wrappers. | -| `NODE_ENV` | `production` | Next.js core | Controls logging verbosity, caching, error detail exposure, and Next.js optimizations. | +| Variable | Default | Source File | Description | +| ------------------------- | ------------------------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `PORT` | `20128` | `src/lib/runtime/ports.ts` | Primary port for both Dashboard UI and API endpoints (single-port mode). | +| `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. | +| `PROD_DASHBOARD_PORT` | `20130` | `docker-compose.prod.yml` | Host-side published port for the Dashboard in Docker production mode. | +| `PROD_API_PORT` | `20131` | `docker-compose.prod.yml` | Host-side published port for the API in Docker production mode. | +| `OMNIROUTE_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | Takes precedence over `PORT` when running inside Electron or other wrappers. | +| `NODE_ENV` | `production` | Next.js core | Controls logging verbosity, caching, error detail exposure, and Next.js optimizations. | +| `OMNIROUTE_USE_TURBOPACK` | `1` (default in `.env.example`) | `package.json` / Next.js 16 | Toggles the Next.js 16 Turbopack bundler in `npm run dev` and `npm run build`. Set to `0` on Windows or when running into native binding incompatibilities. | ### Port Modes @@ -124,16 +133,17 @@ OmniRoute uses **SQLite** (via `better-sqlite3`) for all persistence. These vari ## 4. Security & Authentication -| Variable | Default | Source File | Description | -| ----------------------------- | --------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt combined with hardware identifiers for machine fingerprinting. Change per-deployment for isolation. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Sets the `Secure` flag on session cookies. **Must be `true`** when running behind HTTPS. | -| `REQUIRE_API_KEY` | `false` | API middleware | When `true`, all `/v1/*` proxy requests must include a valid API key. | -| `ALLOW_API_KEY_REVEAL` | `false` | Dashboard providers page | Allows revealing full API key values in the Dashboard UI. Security risk on shared instances. | -| `NO_LOG_API_KEY_IDS` | _(empty)_ | `src/lib/compliance/index.ts` | Comma-separated API key IDs that bypass request logging (GDPR compliance). | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Maximum allowed request body size. Rejects payloads exceeding this limit. | -| `CORS_ORIGIN` | `*` | Next.js middleware | CORS `Access-Control-Allow-Origin` value. Restrict for production. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Block provider calls targeting private/loopback/link-local IP ranges. Disable only in isolated test envs. | +| Variable | Default | Source File | Description | +| --------------------------------------- | --------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt combined with hardware identifiers for machine fingerprinting. Change per-deployment for isolation. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Sets the `Secure` flag on session cookies. **Must be `true`** when running behind HTTPS. | +| `REQUIRE_API_KEY` | `false` | API middleware | When `true`, all `/v1/*` proxy requests must include a valid API key. | +| `ALLOW_API_KEY_REVEAL` | `false` | Dashboard providers page | Allows revealing full API key values in the Dashboard UI. Security risk on shared instances. | +| `NO_LOG_API_KEY_IDS` | _(empty)_ | `src/lib/compliance/index.ts` | Comma-separated API key IDs that bypass request logging (GDPR compliance). | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Maximum allowed request body size. Rejects payloads exceeding this limit. | +| `CORS_ORIGIN` | `*` | Next.js middleware | CORS `Access-Control-Allow-Origin` value. Restrict for production. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Block provider calls targeting private/loopback/link-local IP ranges. Disable only in isolated test envs. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Allow provider URLs pointing to private/local networks (localhost, 192.168.x.x, 10.x.x.x, etc.). **REQUIRED for self-hosted providers** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). When `false`, the dashboard rejects validation of local URLs. | ### Hardening Checklist @@ -188,14 +198,17 @@ OmniRoute provides a two-layer defense: request-side injection scanning and resp ## 7. URLs & Cloud Sync -| Variable | Default | Source File | Description | -| ----------------------- | ------------------------ | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | -| `BASE_URL` | `http://localhost:20128` | `src/lib/cloudSync.ts` | Server-side URL for internal sync jobs to call `/api/sync/cloud`. | -| `CLOUD_URL` | _(empty)_ | `src/lib/cloudSync.ts` | Cloud relay endpoint URL (premium feature). | -| `CLOUD_SYNC_TIMEOUT_MS` | `12000` | `src/lib/cloudSync.ts` | HTTP timeout for cloud sync requests. | -| `NEXT_PUBLIC_BASE_URL` | `http://localhost:20128` | OAuth, Dashboard, sync | Public-facing URL for OAuth redirect_uri, Dashboard links. **Must match your public URL behind reverse proxy.** | -| `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`. | +| Variable | Default | Source File | Description | +| --------------------------------------- | ------------------------ | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `BASE_URL` | `http://localhost:20128` | `src/lib/cloudSync.ts` | Server-side URL for internal sync jobs to call `/api/sync/cloud`. | +| `CLOUD_URL` | _(empty)_ | `src/lib/cloudSync.ts` | Cloud relay endpoint URL (premium feature). | +| `CLOUD_SYNC_TIMEOUT_MS` | `12000` | `src/lib/cloudSync.ts` | HTTP timeout for cloud sync requests. | +| `NEXT_PUBLIC_BASE_URL` | `http://localhost:20128` | OAuth, Dashboard, sync | Public-facing URL for OAuth redirect_uri, Dashboard links. **Must match your public URL behind reverse proxy.** | +| `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)_ | `open-sse/executors/chatgpt-web.ts` | Browser-facing OmniRoute origin used for image URLs in API responses (e.g., `/v1/chatgpt-web/image/`). 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_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/`. Lower on memory-constrained hosts; raise if image generation is heavy and clients race the 30-minute TTL. | > [!IMPORTANT] > When deploying behind a reverse proxy (nginx, Caddy), `NEXT_PUBLIC_BASE_URL` **must** be set to your public URL (e.g., `https://omniroute.example.com`). Without this, OAuth callbacks will fail because the redirect_uri won't match. @@ -230,20 +243,21 @@ Route upstream LLM provider calls through an HTTP or SOCKS5 proxy for egress con Controls how OmniRoute discovers and launches CLI sidecars (Claude Code, Codex, etc.). -| Variable | Default | Source File | Description | -| ------------------------- | ---------- | ----------------------------------- | -------------------------------------------------------------------------- | -| `CLI_MODE` | `auto` | `src/shared/services/cliRuntime.ts` | `auto` = search system PATH; `manual` = use explicit paths only. | -| `CLI_EXTRA_PATHS` | _(unset)_ | `src/shared/services/cliRuntime.ts` | Additional PATH entries for CLI binary discovery (colon-separated). | -| `CLI_CONFIG_HOME` | _(unset)_ | `src/shared/services/cliRuntime.ts` | Override home directory for reading CLI configs (`~/.claude`, `~/.codex`). | -| `CLI_ALLOW_CONFIG_WRITES` | `false` | `src/shared/services/cliRuntime.ts` | Allow OmniRoute to write CLI config files (token refresh, session data). | -| `CLI_CLAUDE_BIN` | `claude` | `src/shared/services/cliRuntime.ts` | Custom path to Claude CLI binary. | -| `CLI_CODEX_BIN` | `codex` | `src/shared/services/cliRuntime.ts` | Custom path to Codex CLI binary. | -| `CLI_DROID_BIN` | `droid` | `src/shared/services/cliRuntime.ts` | Custom path to Droid CLI binary. | -| `CLI_OPENCLAW_BIN` | `openclaw` | `src/shared/services/cliRuntime.ts` | Custom path to OpenClaw CLI binary. | -| `CLI_CURSOR_BIN` | `agent` | `src/shared/services/cliRuntime.ts` | Custom path to Cursor agent binary. | -| `CLI_CLINE_BIN` | `cline` | `src/shared/services/cliRuntime.ts` | Custom path to Cline CLI binary. | -| `CLI_CONTINUE_BIN` | `cn` | `src/shared/services/cliRuntime.ts` | Custom path to Continue CLI binary. | -| `CLI_QODER_BIN` | `qoder` | `src/shared/services/cliRuntime.ts` | Custom path to Qoder CLI binary. | +| Variable | Default | Source File | Description | +| ------------------------- | ---------- | ----------------------------------- | ---------------------------------------------------------------------------------- | +| `CLI_MODE` | `auto` | `src/shared/services/cliRuntime.ts` | `auto` = search system PATH; `manual` = use explicit paths only. | +| `CLI_EXTRA_PATHS` | _(unset)_ | `src/shared/services/cliRuntime.ts` | Additional PATH entries for CLI binary discovery (colon-separated). | +| `CLI_CONFIG_HOME` | _(unset)_ | `src/shared/services/cliRuntime.ts` | Override home directory for reading CLI configs (`~/.claude`, `~/.codex`). | +| `CLI_ALLOW_CONFIG_WRITES` | `false` | `src/shared/services/cliRuntime.ts` | Allow OmniRoute to write CLI config files (token refresh, session data). | +| `CLI_CLAUDE_BIN` | `claude` | `src/shared/services/cliRuntime.ts` | Custom path to Claude CLI binary. | +| `CLI_CODEX_BIN` | `codex` | `src/shared/services/cliRuntime.ts` | Custom path to Codex CLI binary. | +| `CLI_DROID_BIN` | `droid` | `src/shared/services/cliRuntime.ts` | Custom path to Droid CLI binary. | +| `CLI_OPENCLAW_BIN` | `openclaw` | `src/shared/services/cliRuntime.ts` | Custom path to OpenClaw CLI binary. | +| `CLI_CURSOR_BIN` | `agent` | `src/shared/services/cliRuntime.ts` | Custom path to Cursor agent binary. | +| `CLI_CLINE_BIN` | `cline` | `src/shared/services/cliRuntime.ts` | Custom path to Cline CLI binary. | +| `CLI_CONTINUE_BIN` | `cn` | `src/shared/services/cliRuntime.ts` | Custom path to Continue CLI binary. | +| `CLI_QODER_BIN` | `qoder` | `src/shared/services/cliRuntime.ts` | Custom path to Qoder 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. | ### Docker Example @@ -290,28 +304,34 @@ CLI_CLAUDE_BIN=/host-cli/bin/claude Built-in credentials for **localhost development**. For remote deployments, register your own at each provider's developer console. -| Variable | Provider | Notes | -| --------------------------------- | ----------------------- | --------------------------------------------------------------------------------- | -| `CLAUDE_OAUTH_CLIENT_ID` | Claude Code (Anthropic) | Public client — no secret needed. | -| `CLAUDE_CODE_REDIRECT_URI` | Claude Code | Override redirect URI. Default: `https://platform.claude.com/oauth/code/callback` | -| `CODEX_OAUTH_CLIENT_ID` | Codex / OpenAI | Public client. | -| `GEMINI_OAUTH_CLIENT_ID` | Gemini (Google) | Requires matching `_SECRET`. | -| `GEMINI_OAUTH_CLIENT_SECRET` | Gemini (Google) | — | -| `GEMINI_CLI_OAUTH_CLIENT_ID` | Gemini CLI | Usually same as Gemini. | -| `GEMINI_CLI_OAUTH_CLIENT_SECRET` | Gemini CLI | — | -| `QWEN_OAUTH_CLIENT_ID` | Qwen (Alibaba) | Public client. | -| `KIMI_CODING_OAUTH_CLIENT_ID` | Kimi Coding (Moonshot) | Public client. | -| `ANTIGRAVITY_OAUTH_CLIENT_ID` | Antigravity (Google) | Requires matching `_SECRET`. | -| `ANTIGRAVITY_OAUTH_CLIENT_SECRET` | Antigravity (Google) | — | -| `GITHUB_OAUTH_CLIENT_ID` | GitHub Copilot | Public client. | -| `QODER_OAUTH_CLIENT_SECRET` | Qoder | — | -| `QODER_OAUTH_AUTHORIZE_URL` | Qoder | Set to enable Qoder OAuth. | -| `QODER_OAUTH_TOKEN_URL` | Qoder | — | -| `QODER_OAUTH_USERINFO_URL` | Qoder | — | -| `QODER_OAUTH_CLIENT_ID` | Qoder | — | -| `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`. | +| Variable | Provider | Notes | +| --------------------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `CLAUDE_OAUTH_CLIENT_ID` | Claude Code (Anthropic) | Public client — no secret needed. | +| `CLAUDE_CODE_REDIRECT_URI` | Claude Code | Override redirect URI. Default: `https://platform.claude.com/oauth/code/callback` | +| `CODEX_OAUTH_CLIENT_ID` | Codex / OpenAI | Public client. | +| `GEMINI_OAUTH_CLIENT_ID` | Gemini (Google) | Requires matching `_SECRET`. | +| `GEMINI_OAUTH_CLIENT_SECRET` | Gemini (Google) | — | +| `GEMINI_CLI_OAUTH_CLIENT_ID` | Gemini CLI | Usually same as Gemini. | +| `GEMINI_CLI_OAUTH_CLIENT_SECRET` | Gemini CLI | — | +| `QWEN_OAUTH_CLIENT_ID` | Qwen (Alibaba) | Public client. | +| `KIMI_CODING_OAUTH_CLIENT_ID` | Kimi Coding (Moonshot) | Public client. | +| `ANTIGRAVITY_OAUTH_CLIENT_ID` | Antigravity (Google) | Requires matching `_SECRET`. | +| `ANTIGRAVITY_OAUTH_CLIENT_SECRET` | Antigravity (Google) | — | +| `GITHUB_OAUTH_CLIENT_ID` | GitHub Copilot | Public client. | +| `WINDSURF_FIREBASE_API_KEY` | Windsurf / Devin (v3.8) | Public Firebase Web API key used by Windsurf's Secure Token Service to refresh short-lived browser-flow tokens. Client-side credential (not a secret). Long-lived import tokens skip this entirely. Source: extracted from Devin CLI binary. | +| `WINDSURF_API_KEY` | Windsurf / Devin (v3.8) | API key fallback used by `open-sse/executors/devin-cli.ts` when no per-connection credential is available. Optional. | +| `CLI_DEVIN_BIN` | Devin CLI (v3.8) | Custom path to the Devin CLI binary (`devin`). Resolved by `open-sse/executors/devin-cli.ts`. | +| `GITLAB_DUO_OAUTH_CLIENT_ID` | GitLab Duo (v3.8) | OAuth client ID for GitLab Duo. Register an app at `https://gitlab.com/-/profile/applications` with redirect URI `/callback` and scopes `api, read_user, openid, profile, email`. Falls back to `GITLAB_OAUTH_CLIENT_ID`. | +| `GITLAB_DUO_OAUTH_CLIENT_SECRET` | GitLab Duo (v3.8) | OAuth client secret for GitLab Duo. Optional — PKCE flow does not require a secret. Falls back to `GITLAB_OAUTH_CLIENT_SECRET`. | +| `GITLAB_DUO_BASE_URL` | GitLab Duo (v3.8) | Override GitLab base URL (self-hosted GitLab). Defaults to `https://gitlab.com`. Falls back to `GITLAB_BASE_URL`. | +| `QODER_OAUTH_CLIENT_SECRET` | Qoder | — | +| `QODER_OAUTH_AUTHORIZE_URL` | Qoder | Set to enable Qoder OAuth. | +| `QODER_OAUTH_TOKEN_URL` | Qoder | — | +| `QODER_OAUTH_USERINFO_URL` | Qoder | — | +| `QODER_OAUTH_CLIENT_ID` | Qoder | — | +| `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`. | > [!WARNING] > **Google OAuth** (Antigravity, Gemini CLI) credentials **only work on localhost**. For remote servers: @@ -335,15 +355,15 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.121 (external, cli)` | When Anthropic releases a new CLI version | -| `CODEX_USER_AGENT` | `codex-cli/0.125.0 (Windows 10.0.26100; x64)` | When OpenAI updates the Codex CLI | -| `CODEX_CLIENT_VERSION` | `0.125.0` | Override Codex client version independently of full UA string | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.137 (external, cli)` | When Anthropic releases a new CLI version | +| `CODEX_USER_AGENT` | `codex-cli/0.130.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | +| `CODEX_CLIENT_VERSION` | `0.130.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | -| `ANTIGRAVITY_USER_AGENT` | `antigravity/1.107.0 darwin/arm64` | When Antigravity IDE updates | +| `ANTIGRAVITY_USER_AGENT` | `antigravity/1.23.2 darwin/arm64` | When Antigravity IDE updates | | `KIRO_USER_AGENT` | `AWS-SDK-JS/3.0.0 kiro-ide/1.0.0` | When Kiro IDE updates | | `QODER_USER_AGENT` | `Qoder-Cli` | When Qoder CLI updates | -| `QWEN_USER_AGENT` | `QwenCode/0.15.3 (linux; x64)` | When Qwen Code updates | -| `CURSOR_USER_AGENT` | `connect-es/1.6.1` | When Cursor updates | +| `QWEN_USER_AGENT` | `QwenCode/0.15.9 (linux; x64)` | When Qwen Code updates | +| `CURSOR_USER_AGENT` | `Cursor/3.3` | When Cursor updates | | `GEMINI_CLI_USER_AGENT` | `google-api-nodejs-client/10.3.0` | When Google API client updates | > [!TIP] @@ -562,13 +582,14 @@ Anthropic-compatible provider instead. ## 21. Proxy Health -| Variable | Default | Source File | Description | -| ---------------------------- | ---------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -| `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. | -| `RATE_LIMIT_MAX_WAIT_MS` | `120000` (2 min) | `open-sse/services/rateLimitManager.ts` | Max time to wait on a 429 before failing the request. | -| `REQUEST_RETRY` | `2` | `src/sse/services/cooldownAwareRetry.ts` | Number of automatic retries on model-scoped cooldown responses before returning error to client. | -| `MAX_RETRY_INTERVAL_SEC` | `30` | `src/sse/services/cooldownAwareRetry.ts` | Max backoff interval (seconds) between cooldown retries. Capped by this value regardless of upstream `Retry-After`. | +| Variable | Default | Source File | Description | +| ---------------------------- | ---------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `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. | +| `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` | `false` | `open-sse/services/rateLimitManager.ts` | When `true`/`1`, automatically engages the rate-limit manager on first observed upstream 429 (without requiring manual toggle). Accepts `true`/`1`/`yes`. | +| `REQUEST_RETRY` | `2` | `src/sse/services/cooldownAwareRetry.ts` | Number of automatic retries on model-scoped cooldown responses before returning error to client. | +| `MAX_RETRY_INTERVAL_SEC` | `30` | `src/sse/services/cooldownAwareRetry.ts` | Max backoff interval (seconds) between cooldown retries. Capped by this value regardless of upstream `Retry-After`. | --- @@ -657,6 +678,26 @@ CLI_COMPAT_ALL=1 --- +## 24. Skills Sandbox (v3.8.0+) + +Limits and safety knobs applied when the Skills framework (`src/lib/skills/`) executes user-defined automations in a sandboxed environment. + +| Variable | Default | Source File | Description | +| --------------------------------- | --------------------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------ | +| `SKILLS_SANDBOX_TIMEOUT_MS` | `10000` (10 s) | `src/lib/skills/builtins.ts` | Per-execution wall-clock timeout for sandboxed skill code. Hard cap; anything longer is killed. | +| `SKILLS_EXECUTION_TIMEOUT_MS` | _(falls back to `SKILLS_SANDBOX_TIMEOUT_MS`)_ | `src/lib/skills/` | High-level skill orchestration timeout. Set higher than `SKILLS_SANDBOX_TIMEOUT_MS` to allow multi-step workflows. | +| `SKILLS_MAX_FILE_BYTES` | `1048576` (1 MB) | `src/lib/skills/builtins.ts` | Max bytes a skill may read from any single sandboxed file. | +| `SKILLS_MAX_HTTP_RESPONSE_BYTES` | `256000` (250 KB) | `src/lib/skills/builtins.ts` | Max bytes captured from any single HTTP response inside a skill. | +| `SKILLS_MAX_SANDBOX_OUTPUT_CHARS` | `100000` | `src/lib/skills/builtins.ts` | Hard cap on stdout/stderr characters returned from a sandbox invocation. | +| `SKILLS_SANDBOX_NETWORK_ENABLED` | `false` | `src/lib/skills/builtins.ts` | Set `1`/`true` to allow outbound network from inside the sandbox. Defaults to **isolated** for safety. | +| `SKILLS_ALLOWED_SANDBOX_IMAGES` | _(empty)_ | `src/lib/skills/builtins.ts` | Comma-separated allowlist of container images permitted for sandbox execution. Empty means built-in default only. | +| `SKILLS_SANDBOX_DOCKER_IMAGE` | _(built-in default)_ | `src/lib/skills/` | Container image used when spawning a Docker-backed sandbox. Override to pin a custom hardened base image. | + +> [!CAUTION] +> Enabling `SKILLS_SANDBOX_NETWORK_ENABLED=true` opens an egress path from arbitrary skill code. Pair with `OUTBOUND_SSRF_GUARD_ENABLED=true` and a strict `CORS_ORIGIN`/proxy policy in shared deployments. + +--- + ## Audit: Removed / Dead Variables The following variables appeared in previous versions of `.env.example` but have **no runtime references** in the current codebase. They have been removed: diff --git a/docs/FLY_IO_DEPLOYMENT_GUIDE.md b/docs/FLY_IO_DEPLOYMENT_GUIDE.md index 5d48c42093..9144cd8ea9 100644 --- a/docs/FLY_IO_DEPLOYMENT_GUIDE.md +++ b/docs/FLY_IO_DEPLOYMENT_GUIDE.md @@ -85,7 +85,7 @@ flyctl version ### 4.1 获取代码并进入目录 ```powershell -git clone https://github.com/xiaoge1688/OmniRoute.git +git clone https://github.com/diegosouzapw/OmniRoute.git cd OmniRoute ``` @@ -139,6 +139,7 @@ flyctl deploy - `JWT_SECRET` - `MACHINE_ID_SALT` - `NEXT_PUBLIC_BASE_URL` +- `OMNIROUTE_WS_BRIDGE_SECRET` (生产环境必需 / required in production / obrigatório em produção — 用于 WebSocket 桥接鉴权 / used for WebSocket bridge authentication) - `STORAGE_ENCRYPTION_KEY` ### 5.2 关于 `INITIAL_PASSWORD` @@ -162,20 +163,21 @@ flyctl deploy 建议放入 Fly Secrets: -| 变量名 | 是否推荐 | 说明 | -| --- | --- | --- | -| `API_KEY_SECRET` | 必需 | API Key 生成与校验使用 | -| `JWT_SECRET` | 必需 | 登录态和 JWT 签名使用 | -| `STORAGE_ENCRYPTION_KEY` | 强烈推荐 | 加密存储敏感连接信息 | -| `MACHINE_ID_SALT` | 推荐 | 生成稳定机器标识 | -| `INITIAL_PASSWORD` | 可选 | 首次部署时直接指定后台初始密码 | -| OAuth/API 私密凭证 | 按需 | 各类外部平台鉴权配置 | +| 变量名 | 是否推荐 | 说明 | +| ---------------------------- | --------------------------------- | ----------------------------------------------------------------------------------------- | +| `API_KEY_SECRET` | 必需 | API Key 生成与校验使用 | +| `JWT_SECRET` | 必需 | 登录态和 JWT 签名使用 | +| `OMNIROUTE_WS_BRIDGE_SECRET` | 生产必需 (required / obrigatório) | WebSocket 桥接鉴权密钥 (WebSocket bridge auth / chave de autenticação da ponte WebSocket) | +| `STORAGE_ENCRYPTION_KEY` | 强烈推荐 | 加密存储敏感连接信息 | +| `MACHINE_ID_SALT` | 推荐 | 生成稳定机器标识 | +| `INITIAL_PASSWORD` | 可选 | 首次部署时直接指定后台初始密码 | +| OAuth/API 私密凭证 | 按需 | 各类外部平台鉴权配置 | ### 6.2 当前项目推荐值 -| 变量名 | 推荐值 | -| --- | --- | -| `DATA_DIR` | `/data` | +| 变量名 | 推荐值 | +| ---------------------- | --------------------------- | +| `DATA_DIR` | `/data` | | `NEXT_PUBLIC_BASE_URL` | `https://omniroute.fly.dev` | 说明: @@ -183,6 +185,34 @@ flyctl deploy - `DATA_DIR=/data` 非常关键,必须与 Fly Volume 挂载点一致 - `NEXT_PUBLIC_BASE_URL` 用于调度器和前端回调等场景 +### 6.3 OAuth 回调地址配置 (OAuth callback URL / URL de callback OAuth) + +如果你需要在 Fly.io 部署上启用 OAuth 登录类的 provider(例如 Antigravity、Gemini、Cursor 等),必须确保以下两点: +(If you need to enable OAuth-based providers — e.g. Antigravity, Gemini, Cursor — on the Fly.io deployment, make sure of the following two points. / Se precisar habilitar providers via OAuth — p.ex. Antigravity, Gemini, Cursor — na implantação Fly.io, garanta os dois pontos abaixo.) + +1. **设置 `NEXT_PUBLIC_BASE_URL` 指向你公开的 HTTPS 域名 (set `NEXT_PUBLIC_BASE_URL` to the public HTTPS domain / defina `NEXT_PUBLIC_BASE_URL` para o domínio HTTPS público)** + + ```powershell + flyctl secrets set NEXT_PUBLIC_BASE_URL=https://omniroute.fly.dev -a omniroute + ``` + + 如果你使用了自定义域名 (if using a custom domain / se usar um domínio personalizado),请替换为对应域名 (e.g. `https://omniroute.yourdomain.com`)。 + +2. **在 provider 控制台配置回调 URL (configure the callback URL on the provider console / configure a URL de callback no painel do provider)** + + 通常格式为 (typical format / formato típico): + + ```text + /api/oauth//callback + ``` + + 例如 (e.g. / p.ex.): + - `https://omniroute.fly.dev/api/oauth/gemini/callback` + - `https://omniroute.fly.dev/api/oauth/antigravity/callback` + - `https://omniroute.fly.dev/api/oauth/cursor/callback` + + 如果 `NEXT_PUBLIC_BASE_URL` 与 provider 控制台中注册的回调 URL 不一致,OAuth 流程会在浏览器回跳阶段失败 (mismatch between `NEXT_PUBLIC_BASE_URL` and the registered callback URL will cause OAuth to fail at the browser redirect step / divergência entre `NEXT_PUBLIC_BASE_URL` e a URL de callback registrada quebra o OAuth no redirect do navegador)。 + --- ## 7. 一键设置参数 @@ -199,17 +229,29 @@ $apiKeySecret = [Convert]::ToHexString((1..32 | ForEach-Object { Get-Random -Min $jwtSecret = [Convert]::ToHexString((1..64 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })).ToLower() $machineIdSalt = [Convert]::ToHexString((1..32 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })).ToLower() $storageKey = [Convert]::ToHexString((1..32 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })).ToLower() +$wsBridgeSecret = [Convert]::ToHexString((1..32 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })).ToLower() flyctl secrets set ` API_KEY_SECRET=$apiKeySecret ` JWT_SECRET=$jwtSecret ` MACHINE_ID_SALT=$machineIdSalt ` STORAGE_ENCRYPTION_KEY=$storageKey ` + OMNIROUTE_WS_BRIDGE_SECRET=$wsBridgeSecret ` DATA_DIR=/data ` NEXT_PUBLIC_BASE_URL=https://omniroute.fly.dev ` -a omniroute ``` +在 Linux / macOS 上,也可以直接用 `openssl rand -hex 32` 生成 (on Linux / macOS, you can also use `openssl rand -hex 32` / em Linux / macOS, também é possível usar `openssl rand -hex 32`): + +```bash +flyctl secrets set OMNIROUTE_WS_BRIDGE_SECRET=$(openssl rand -hex 32) -a omniroute +``` + +说明 (notes / observações): + +- `OMNIROUTE_WS_BRIDGE_SECRET` 在生产环境必需,缺失会导致 WebSocket 桥接握手失败 (required in production; missing it breaks WebSocket bridge handshake / obrigatório em produção; sem ele o handshake da ponte WebSocket falha) + 如果你还要加初始密码: ```powershell @@ -282,6 +324,8 @@ git describe --tags --always git show --no-patch --oneline v3.4.7 ``` +> 注 (note / nota):当前项目版本为 `v3.8.0` (current project version is `v3.8.0` / a versão atual do projeto é `v3.8.0`)。下文中的 `v3.4.7` 仅为历史示例 (the `v3.4.7` references below are kept as historical examples only / as referências a `v3.4.7` abaixo são apenas exemplos históricos);实际发布时请使用 `:latest` 或当前版本标签 (e.g. `:v3.8.0`) (use `:latest` or the current version tag — e.g. `:v3.8.0` — for actual releases / use `:latest` ou a tag da versão atual — p.ex. `:v3.8.0` — em releases reais)。 + 如果你想合并上游最新 `main`,并强制保留 fork 当前的 `fly.toml`,可按下面流程执行: ```powershell @@ -319,7 +363,7 @@ git merge-base --is-ancestor v3.4.7 upstream/main 6. `flyctl status -a omniroute` 7. `flyctl logs --no-tail -a omniroute` -这就是当前项目升级到 `v3.4.7` 时使用的实际流程。 +这就是当前项目升级到 `v3.4.7` 时使用的实际流程 (示例为历史版本,当前实际版本是 `v3.8.0` / example refers to a historical version; the current actual version is `v3.8.0` / o exemplo refere-se a uma versão histórica; a versão atual é `v3.8.0`)。 --- diff --git a/docs/SETUP_GUIDE.md b/docs/SETUP_GUIDE.md index b4f20d7213..fa03745643 100644 --- a/docs/SETUP_GUIDE.md +++ b/docs/SETUP_GUIDE.md @@ -47,15 +47,42 @@ The [AUR package](https://aur.archlinux.org/packages/omniroute-bin) installs Omn ### From Source ```bash -cp .env.example .env npm install PORT=20128 DASHBOARD_PORT=20129 NEXT_PUBLIC_BASE_URL=http://localhost:20129 npm run dev ``` +> **Note:** `npm install` auto-generates `.env` from `.env.example` on first run. Subsequent installs will not overwrite an existing `.env`, so customizations are preserved. To re-seed, delete `.env` before re-running. + ### Docker See the [Docker Guide](DOCKER_GUIDE.md) for complete Docker setup including Compose profiles and Caddy HTTPS. +### Desktop App (Electron) + +OmniRoute ships a desktop wrapper built on Electron 41 + electron-builder 26.10. Available scripts (workspace root): + +```bash +npm run electron:dev # Run desktop with hot-reload +npm run electron:build # Build for current OS (auto-detected) +npm run electron:build:win # Windows installer (NSIS + portable) +npm run electron:build:mac # macOS (dmg + zip, arm64+x64) +npm run electron:build:linux # Linux (AppImage + deb + rpm) +npm run electron:smoke:packaged # Smoke-test packaged build +``` + +Releases of the desktop installers are attached to GitHub Releases. For the full Electron deep-dive (signing, IPC bridge, distros), see [`ELECTRON_GUIDE.md`](./ELECTRON_GUIDE.md) _(criado em fase posterior)_. + +### Headless server (CI/automation) + +For unattended setups (Docker, Kubernetes, CI), use: + +```bash +omniroute setup --non-interactive +omniroute providers test-batch +``` + +Combined with env vars (`INITIAL_PASSWORD`, `OMNIROUTE_WS_BRIDGE_SECRET`, etc.), this lets you spin up an OmniRoute instance fully scriptable. + ### CLI Options | Command | Description | @@ -253,7 +280,7 @@ For Void Linux users, you can build a native package using `xbps-src`. Save this ```bash # Template file for 'omniroute' pkgname=omniroute -version=3.4.1 +version=3.8.0 revision=1 hostmakedepends="nodejs python3 make" depends="openssl" @@ -262,7 +289,9 @@ maintainer="zenobit " license="MIT" homepage="https://github.com/diegosouzapw/OmniRoute" distfiles="https://github.com/diegosouzapw/OmniRoute/archive/refs/tags/v${version}.tar.gz" -checksum=009400afee90a9f32599d8fe734145cfd84098140b7287990183dde45ae2245b +# Regenerate the checksum for each release with: +# curl -L -o /tmp/omniroute.tar.gz "https://github.com/diegosouzapw/OmniRoute/archive/refs/tags/v${version}.tar.gz" && sha256sum /tmp/omniroute.tar.gz +checksum=PLACEHOLDER_REGENERATE_PER_RELEASE system_accounts="_omniroute" omniroute_homedir="/var/lib/omniroute" export NODE_ENV=production diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index 5a46315fba..c0b3577a73 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -14,7 +14,7 @@ Common problems and solutions for OmniRoute. | Dashboard opens on wrong port | Set `PORT=20128` and `NEXT_PUBLIC_BASE_URL=http://localhost:20128` | | No logs written to disk | Set `APP_LOG_TO_FILE=true` and verify call log capture is enabled | | EACCES: permission denied | Set `DATA_DIR=/path/to/writable/dir` to override `~/.omniroute` | -| Routing strategy not saving | Update to v1.4.11+ (Zod schema fix for settings persistence) | +| Routing strategy not saving | Update to the latest v3.x release (Zod schema fix for settings persistence shipped in earlier versions) | | Login crash / blank page | Check Node.js version — see [Node.js Compatibility](#nodejs-compatibility) below | | `dlopen` / `slice is not valid mach-o file` (macOS) | Run `cd $(npm root -g)/omniroute/app && npm rebuild better-sqlite3 && omniroute` — see [macOS native module rebuild](#macos-native-module-rebuild) below | | Proxy "fetch failed" | Ensure proxy config is set at the correct level — see [Proxy Issues](#proxy-issues) below | @@ -46,7 +46,7 @@ Common problems and solutions for OmniRoute. 3. Reinstall OmniRoute: `npm install -g omniroute` 4. Restart: `omniroute` -> **Supported secure versions:** `>=20.20.2 <21`, `>=22.22.2 <23`, or `>=24.0.0 <25`. Node.js 24.x LTS (Krypton) is fully supported. +> **Supported secure versions:** `>=20.20.2 <21`, `>=22.22.2 <23`, or `>=24.0.0 <27`. Node.js 24.x LTS (Krypton) and Node.js 26 are fully supported. ### macOS: `dlopen` / "slice is not valid mach-o file" @@ -72,7 +72,7 @@ npm rebuild better-sqlite3 omniroute ``` -> **Note:** This recompiles the native binding against your local Node.js version and CPU architecture, resolving the binary mismatch. The officially supported range is **`>=20.20.2 <21`, `>=22.22.2 <23`, or `>=24.0.0 <25`** (`engines` field in `package.json`). Node.js 24.x LTS (Krypton) is fully supported with `better-sqlite3` v12.x. +> **Note:** This recompiles the native binding against your local Node.js version and CPU architecture, resolving the binary mismatch. The officially supported range is **`>=20.20.2 <21`, `>=22.22.2 <23`, or `>=24.0.0 <27`** (`engines` field in `package.json`). Node.js 24.x LTS (Krypton) and Node.js 26 are fully supported with `better-sqlite3` v12.x. --- @@ -268,10 +268,10 @@ Use **Dashboard → Translator** to debug format translation issues: - **Thinking tags not appearing** — Check if the target provider supports thinking and the thinking budget setting - **Tool calls dropping** — Some format translations may strip unsupported fields; verify in Playground mode - **System prompt missing** — Claude and Gemini handle system prompts differently; check translation output -- **SDK returns raw string instead of object** — Fixed in v1.1.0: response sanitizer now strips non-standard fields (`x_groq`, `usage_breakdown`, etc.) that cause OpenAI SDK Pydantic validation failures -- **GLM/ERNIE rejects `system` role** — Fixed in v1.1.0: role normalizer automatically merges system messages into user messages for incompatible models -- **`developer` role not recognized** — Fixed in v1.1.0: automatically converted to `system` for non-OpenAI providers -- **`json_schema` not working with Gemini** — Fixed in v1.1.0: `response_format` is now converted to Gemini's `responseMimeType` + `responseSchema` +- **SDK returns raw string instead of object** — Resolved in v1.x; response sanitizer strips non-standard fields (`x_groq`, `usage_breakdown`, etc.) that cause OpenAI SDK Pydantic validation failures. If you still see this on v3.x+, please file an issue. +- **GLM/ERNIE rejects `system` role** — Resolved in v1.x; role normalizer automatically merges system messages into user messages for incompatible models. If you still see this on v3.x+, please file an issue. +- **`developer` role not recognized** — Resolved in v1.x; automatically converted to `system` for non-OpenAI providers. If you still see this on v3.x+, please file an issue. +- **`json_schema` not working with Gemini** — Resolved in v1.x; `response_format` is now converted to Gemini's `responseMimeType` + `responseSchema`. If you still see this on v3.x+, please file an issue. --- @@ -332,6 +332,118 @@ You can ignore this section if you do not run RAG or agent pipelines behind Omni --- +## v3.8.0 Known Issues + +Issues specific to the v3.8.0 release and their current workarounds. If a fix lands in a later patch, the entry will be updated or removed. + +### Windsurf OAuth flow fails with 401 + +**Symptoms:** + +- "401 unauthorized" while completing the Windsurf OAuth flow from the dashboard +- Windsurf provider card stays in "needs reconnection" state after the callback + +**Causes:** + +- `WINDSURF_FIREBASE_API_KEY` env var missing or empty +- `WINDSURF_API_KEY` misconfigured or pointing at a stale token +- Local firewall/proxy blocking the OAuth callback + +**Fix:** + +1. Verify both `WINDSURF_FIREBASE_API_KEY` and `WINDSURF_API_KEY` are set in `.env` +2. Restart OmniRoute so the new env values are picked up +3. Re-run the OAuth flow from **Dashboard → Providers → Windsurf → Reconnect** + +### Devin CLI auth failures + +**Symptoms:** + +- "Devin CLI not found" or "auth failed" when invoking Devin-backed tools +- CLI runtime check reports `installed=false` + +**Causes:** + +- `CLI_DEVIN_BIN` points to a path that does not exist +- Devin CLI is not installed on the host + +**Fix:** + +1. Install the Devin CLI for your platform +2. Set `CLI_DEVIN_BIN=/usr/local/bin/devin` (or the real path) in `.env` +3. Restart OmniRoute and re-test from **Dashboard → CLI Tools** + +### Model cooldown stuck (manual reset) + +**Symptoms:** + +- A model stays listed in cooldown even after the expiration time has passed +- Requests still skip the model in combo routing despite the timestamp being in the past + +**Manual reset:** + +- **Dashboard:** **Settings → Model Cooldowns** → click **Re-enable** on the affected card +- **API:** `DELETE /api/resilience/model-cooldowns` with management auth headers + +### Command Code provider connection fails with 403 + +**Symptoms:** + +- 403 when testing the Command Code provider connection +- The provider card shows "unauthorized" after a fresh add + +**Cause:** The OAuth flow did not complete (callback not received or token not persisted). + +**Fix:** + +- Run `omniroute providers` from the CLI to re-trigger the OAuth flow, or +- Re-run OAuth from **Dashboard → Providers → Command Code → Reconnect** + +### ModelScope returns aggressive 429 cooldowns + +**Symptoms:** + +- Very short or immediate cooldowns on ModelScope after a small burst of requests +- Combo routing skips ModelScope earlier than expected + +**Cause:** ModelScope emits provider-specific `Retry-After` headers. v3.8.0 ships dedicated handling for those headers, so older versions misread them as generic rate-limit hints. + +**Fix:** + +- Ensure you are on v3.8.0 or later +- Verify the `useUpstream429BreakerHints` toggle is enabled under **Settings → Resilience** + +### OMNIROUTE_WS_BRIDGE_SECRET missing in production + +**Symptoms:** + +- 401 on every Codex/Responses WebSocket bridge request when running on a remote production host +- WebSocket bridge handshake closes immediately after connect + +**Cause:** The `OMNIROUTE_WS_BRIDGE_SECRET` env var is missing from the production environment. + +**Fix:** + +1. Generate a random secret: `openssl rand -hex 32` +2. Set `OMNIROUTE_WS_BRIDGE_SECRET=` in the production server env (and any client that talks to the bridge) +3. Restart OmniRoute + +### Responses API: background mode degraded to synchronous + +**Symptoms:** + +- Warning logged: `background mode degraded to synchronous` +- A `background: true` request returns a normal synchronous response instead of a background job handle + +**Cause:** v3.8.0 intentionally degrades `background: true` on the Responses API to synchronous execution while emitting a warning. Full async background execution is a future deliverable. + +**Fix:** + +- Adjust the client to call without `background`, or +- Wait for a later release that ships full async background mode (track the changelog) + +--- + ## Still Stuck? - **GitHub Issues**: [github.com/diegosouzapw/OmniRoute/issues](https://github.com/diegosouzapw/OmniRoute/issues) diff --git a/docs/VM_DEPLOYMENT_GUIDE.md b/docs/VM_DEPLOYMENT_GUIDE.md index 7e968f90ee..19dc21f150 100644 --- a/docs/VM_DEPLOYMENT_GUIDE.md +++ b/docs/VM_DEPLOYMENT_GUIDE.md @@ -54,7 +54,7 @@ apt install -y ca-certificates curl gnupg install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg chmod a+r /etc/apt/keyrings/docker.gpg -echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $ (. /etc/os-release && echo “$VERSION_CODENAME”) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null +echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $ (. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null apt update apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin ``` @@ -91,7 +91,7 @@ mkdir -p /opt/omniroute ### 2.2 Create environment variables file ```bash -cat > /opt/omniroute/.env << ‘EOF’ +cat > /opt/omniroute/.env << 'EOF' # === Security === JWT_SECRET=CHANGE-TO-A-UNIQUE-64-CHAR-SECRET-KEY INITIAL_PASSWORD=YourSecurePassword123! @@ -99,13 +99,13 @@ API_KEY_SECRET=REPLACE-WITH-ANOTHER-SECRET-KEY STORAGE_ENCRYPTION_KEY=REPLACE-WITH-THIRD-SECRET-KEY STORAGE_ENCRYPTION_KEY_VERSION=v1 MACHINE_ID_SALT=CHANGE-TO-A-UNIQUE-SALT +OMNIROUTE_WS_BRIDGE_SECRET=REPLACE-WITH-WS-BRIDGE-SECRET # REQUIRED em produção: usado pelo Codex Responses WS bridge # === App === PORT=20128 NODE_ENV=production HOSTNAME=0.0.0.0 DATA_DIR=/app/data -STORAGE_DRIVER=sqlite APP_LOG_TO_FILE=true AUTH_COOKIE_SECURE=false REQUIRE_API_KEY=false @@ -173,7 +173,7 @@ chmod 600 /etc/nginx/ssl/origin.key ### 3.2 Nginx Configuration ```bash -cat > /etc/nginx/sites-available/omniroute << ‘NGINX’ +cat > /etc/nginx/sites-available/omniroute << 'NGINX' # Default server — blocks direct access via IP server { listen 80 default_server; @@ -208,7 +208,7 @@ server { # WebSocket support proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection “upgrade”; + proxy_set_header Connection "upgrade"; # SSE (Server-Sent Events) — streaming AI responses proxy_buffering off; @@ -315,7 +315,7 @@ docker run --rm -v omniroute-data:/data -v $(pwd):/backup \ ```bash docker stop omniroute docker run --rm -v omniroute-data:/data -v $(pwd):/backup \ - alpine sh -c “rm -rf /data/* && tar xzf /backup/omniroute-data-YYYY-MM-DD.tar.gz -C /” + alpine sh -c "rm -rf /data/* && tar xzf /backup/omniroute-data-YYYY-MM-DD.tar.gz -C /" docker start omniroute ``` @@ -326,7 +326,7 @@ docker start omniroute ### Restrict nginx to Cloudflare IPs ```bash -cat > /etc/nginx/cloudflare-ips.conf << ‘CF’ +cat > /etc/nginx/cloudflare-ips.conf << 'CF' # Cloudflare IPv4 ranges — update periodically # https://www.cloudflare.com/ips-v4/ set_real_ip_from 173.245.48.0/20; diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 46dca73813..4f43468f53 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -1433,22 +1433,6 @@ paths: "200": description: Translation history entries - /api/translator/load: - get: - tags: [Translator] - summary: Load saved translation template - responses: - "200": - description: Template data - - /api/translator/save: - post: - tags: [Translator] - summary: Save translation template - responses: - "200": - description: Template saved - # ─── CLI Tools ───────────────────────────────────────────────── /api/cli-tools/backups: @@ -2269,6 +2253,73 @@ paths: "200": description: Generated content + # ─── OpenAPI Spec ────────────────────────────────────────────── + + /api/openapi/spec: + get: + tags: [System] + summary: Get OpenAPI specification catalog + description: >- + Returns a structured JSON catalog parsed from this `openapi.yaml`, + including info, servers, tags, schemas, and a flat list of endpoints + (method, path, tags, summary, security, parameters, responses). + Used by the in-app API explorer. + responses: + "200": + description: Parsed OpenAPI catalog + content: + application/json: + schema: + type: object + properties: + info: + type: object + servers: + type: array + items: + type: object + tags: + type: array + items: + type: object + endpoints: + type: array + items: + type: object + properties: + method: + type: string + path: + type: string + tags: + type: array + items: + type: string + summary: + type: string + description: + type: string + security: + type: boolean + parameters: + type: array + items: + type: object + requestBody: + type: boolean + responses: + type: array + items: + type: string + schemas: + type: array + items: + type: string + "404": + description: openapi.yaml file not found on disk + "500": + description: Failed to parse OpenAPI spec + components: securitySchemes: BearerAuth: @@ -2476,13 +2527,35 @@ components: type: array items: type: object - required: [role, content] + required: [role] properties: role: type: string - enum: [system, user, assistant] + description: >- + Message role. The proxy accepts any non-empty string; common values + include system, user, assistant, tool, function, and developer. + example: user content: + description: >- + Message content. May be a plain string, an array of content parts + for multimodal inputs (text, image, audio, etc.), or null when the + message only carries tool/function calls. + oneOf: + - type: string + - type: array + items: + type: object + - type: "null" + name: type: string + tool_call_id: + type: string + tool_calls: + type: array + items: + type: object + function_call: + type: object stream: type: boolean default: false @@ -2492,6 +2565,65 @@ components: maximum: 2 max_tokens: type: integer + top_p: + type: number + minimum: 0 + maximum: 1 + n: + type: integer + minimum: 1 + default: 1 + stop: + description: Up to 4 stop sequences (string or array of strings). + oneOf: + - type: string + - type: array + items: + type: string + maxItems: 4 + frequency_penalty: + type: number + minimum: -2 + maximum: 2 + presence_penalty: + type: number + minimum: -2 + maximum: 2 + seed: + type: integer + logprobs: + type: boolean + top_logprobs: + type: integer + minimum: 0 + maximum: 20 + response_format: + type: object + description: Output format constraint (e.g. JSON mode or JSON Schema). + properties: + type: + type: string + example: json_object + tools: + type: array + description: Tool definitions available to the model. + items: + type: object + tool_choice: + description: Controls which tool (if any) is invoked by the model. + oneOf: + - type: string + example: auto + - type: object + parallel_tool_calls: + type: boolean + default: true + service_tier: + type: string + example: auto + user: + type: string + description: Stable end-user identifier for abuse monitoring. ChatCompletionResponse: type: object @@ -2637,7 +2769,21 @@ components: type: string strategy: type: string - enum: [priority, weighted, round-robin, random, least-used, cost-optimized] + enum: + - priority + - weighted + - round-robin + - context-relay + - fill-first + - p2c + - random + - least-used + - cost-optimized + - reset-aware + - strict-random + - auto + - lkgp + - context-optimized default: priority nodes: type: array diff --git a/package.json b/package.json index dfcb228a07..eb500cf118 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "test:protocols:e2e": "node scripts/run-protocol-clients-tests.mjs", "test:vitest": "vitest run --config vitest.mcp.config.ts", "test:ecosystem": "node scripts/run-ecosystem-tests.mjs", - "test:coverage": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true c8 --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 60 --lines 60 --functions 60 --branches 60 node --import tsx/esm --test --test-concurrency=1 tests/unit/*.test.ts", + "test:coverage": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true c8 --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 75 --lines 75 --functions 75 --branches 70 node --import tsx/esm --test --test-concurrency=1 tests/unit/*.test.ts", "test:coverage:legacy": "c8 --output-dir coverage --exclude=open-sse --check-coverage --lines 50 --functions 50 --branches 50 node --import tsx/esm --test tests/unit/*.test.ts", "coverage:report": "c8 report --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov", "coverage:summary": "node scripts/test-report-summary.mjs --input coverage/coverage-summary.json --output coverage/coverage-report.md --threshold 60", diff --git a/src/app/api/v1/agents/tasks/route.ts b/src/app/api/v1/agents/tasks/route.ts index f17d73ba51..2a93439f61 100644 --- a/src/app/api/v1/agents/tasks/route.ts +++ b/src/app/api/v1/agents/tasks/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from "next/server"; import { getAgent } from "@/lib/cloudAgent/registry"; +import type { CloudAgentTaskRow } from "@/lib/cloudAgent/db"; import { createCloudAgentTaskTable, insertCloudAgentTask, @@ -19,8 +20,6 @@ import pino from "pino"; const logger = pino({ name: "cloud-agents-api" }); -createCloudAgentTaskTable(); - function getLimit(value: string | null): number { const parsed = Number.parseInt(value || "50", 10); if (!Number.isFinite(parsed)) return 50; @@ -36,12 +35,14 @@ export async function GET(request: NextRequest) { const authError = await requireCloudAgentManagementAuth(request); if (authError) return authError; + createCloudAgentTaskTable(); + const { searchParams } = new URL(request.url); const providerId = searchParams.get("provider"); const status = searchParams.get("status"); const limit = getLimit(searchParams.get("limit")); - let tasks; + let tasks: CloudAgentTaskRow[]; if (providerId) { tasks = getCloudAgentTasksByProvider(providerId, limit); } else if (status) { @@ -107,6 +108,7 @@ export async function POST(request: NextRequest) { credentials ); + createCloudAgentTaskTable(); insertCloudAgentTask({ id: task.id, provider_id: task.providerId, @@ -152,6 +154,8 @@ export async function DELETE(request: NextRequest) { const authError = await requireCloudAgentManagementAuth(request); if (authError) return authError; + createCloudAgentTaskTable(); + const { searchParams } = new URL(request.url); const taskId = searchParams.get("id"); diff --git a/src/lib/cloudAgent/types.ts b/src/lib/cloudAgent/types.ts index 13d7d398b0..45c7a65406 100644 --- a/src/lib/cloudAgent/types.ts +++ b/src/lib/cloudAgent/types.ts @@ -85,13 +85,13 @@ export const CloudAgentActivitySchema = z.object({ type: z.enum(["plan", "command", "code_change", "message", "error", "completion"]), content: z.string(), timestamp: z.string().datetime(), - metadata: z.record(z.unknown()).optional(), + metadata: z.record(z.string(), z.unknown()).optional(), }); export const CloudAgentTaskOptionsSchema = z.object({ autoCreatePr: z.boolean().optional(), planApprovalRequired: z.boolean().optional(), - environment: z.record(z.string()).optional(), + environment: z.record(z.string(), z.string()).optional(), }); export const CreateCloudAgentTaskSchema = z.object({ diff --git a/tests/unit/cloudAgent-types.test.ts b/tests/unit/cloudAgent-types.test.ts index f1d8dcb620..6bc9b74fed 100644 --- a/tests/unit/cloudAgent-types.test.ts +++ b/tests/unit/cloudAgent-types.test.ts @@ -1,7 +1,7 @@ import test from "node:test"; import assert from "node:assert/strict"; -const { CLOUD_AGENT_STATUS, CloudAgentStatusSchema, CloudAgentTask } = +const { CLOUD_AGENT_STATUS, CloudAgentStatusSchema } = await import("../../src/lib/cloudAgent/types.ts"); const { CreateCloudAgentTaskSchema, UpdateCloudAgentTaskSchema } = await import("../../src/lib/cloudAgent/types.ts");