mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-15 19:32:20 +03:00
Merge release/v3.8.51 into fix/release-v3.8.51-basereds-orphans
# Conflicts: # config/quality/file-size-baseline.json # open-sse/handlers/chatCore.ts # open-sse/handlers/chatCore/providerExecutionPipeline.ts # src/lib/db/models/activeSyncedCatalog.ts # tests/unit/hard-session-lease-bypass-inventory.test.ts # tests/unit/kiro-tool-call-validation.test.ts
This commit is contained in:
@@ -73,6 +73,11 @@ docs/i18n/**
|
||||
# so without this rule these land in /app/docs and become readable through the
|
||||
# dashboard's Docs viewer at runtime.
|
||||
docs/superpowers/**
|
||||
# Operator-internal security writeups: git only, not the image or /docs catalog.
|
||||
docs/security/STEALTH_GUIDE.md
|
||||
docs/security/SOCKET_DEV_FINDINGS.md
|
||||
docs/security/MITM-TPROXY-DECRYPT.md
|
||||
docs/security/PUBLIC_CREDS.md
|
||||
docs/diagrams/**/*.png
|
||||
docs/diagrams/**/*.jpg
|
||||
docs/diagrams/**/*.jpeg
|
||||
|
||||
40
.env.example
40
.env.example
@@ -124,6 +124,21 @@ DISABLE_SQLITE_AUTO_BACKUP=false
|
||||
# Host port for the compose Redis sidecar. Default: 6379.
|
||||
# REDIS_PORT=6379
|
||||
|
||||
# Host interface docker-compose publishes the app's own ports (dashboard,
|
||||
# API, live-WS) on for the base/web/cli/host profiles and docker-compose.prod.yml.
|
||||
# Default: 127.0.0.1 (loopback only). Combined with REQUIRE_API_KEY=false
|
||||
# (the default below), an unqualified publish spec would expose the anonymous
|
||||
# /v1 LLM proxy to your whole LAN/WAN. Only set this to 0.0.0.0 once you've
|
||||
# confirmed REQUIRE_API_KEY=true, or that a reverse proxy in front of this
|
||||
# instance already enforces its own authentication. (#12568)
|
||||
# APP_BIND_HOST=127.0.0.1
|
||||
# Host interface docker-compose publishes the Qdrant memory sidecar on.
|
||||
# Default: 127.0.0.1 (loopback only). Same LAN-exposure reasoning as Redis.
|
||||
# QDRANT_BIND_HOST=127.0.0.1
|
||||
# Host interface docker-compose publishes the Bifrost router sidecar on.
|
||||
# Default: 127.0.0.1 (loopback only). Same LAN-exposure reasoning as Redis.
|
||||
# BIFROST_BIND_HOST=127.0.0.1
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 3. NETWORK & PORTS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -373,6 +388,8 @@ AUTH_COOKIE_SECURE=false
|
||||
# Require an API key for all /v1/* proxy endpoints.
|
||||
# Used by: API middleware — rejects unauthenticated requests to the proxy API.
|
||||
# Default: false | Set true for multi-user/public deployments.
|
||||
# Leaving this false is only safe when the app is reachable on loopback only
|
||||
# (see APP_BIND_HOST above) or sits behind a reverse proxy doing its own auth.
|
||||
REQUIRE_API_KEY=false
|
||||
|
||||
# Allow revealing full API key values in the Dashboard UI.
|
||||
@@ -1175,6 +1192,11 @@ CODEX_OAUTH_CLIENT_ID=app_EMoamEEZ73f0CkXaXp7hrann
|
||||
# Trae OAuth token override. Used by: open-sse/executors/trae.ts.
|
||||
# TRAE_TOKEN=
|
||||
|
||||
# Trae web client Origin/Referer override (fleet-wide bump if Trae moves hosts
|
||||
# again without a code change). Default: https://work.trae.ai.
|
||||
# Used by: open-sse/executors/trae.ts.
|
||||
# TRAE_WEB_ORIGIN=https://work.trae.ai
|
||||
|
||||
# ── Gemini / Antigravity (Google-based) ──
|
||||
# These providers ship public OAuth client_id/secret values embedded in their
|
||||
# public CLIs. Defaults are baked into the code via
|
||||
@@ -1311,7 +1333,8 @@ CLAUDE_USER_AGENT="claude-cli/2.1.258 (external, cli)"
|
||||
# stream with a misleading 400 out-of-extra-usage placeholder. Set to true to
|
||||
# forward the original names verbatim (debugging only).
|
||||
# CLAUDE_DISABLE_TOOL_NAME_CLOAK=false
|
||||
CODEX_USER_AGENT="codex-cli/0.144.1 (Windows 10.0.26200; x64)"
|
||||
# Optional override; leave unset to follow the shared Codex client version.
|
||||
# CODEX_USER_AGENT="codex-cli/0.153.4 (Windows 10.0.26200; x64)"
|
||||
GITHUB_USER_AGENT="GitHubCopilotChat/0.54.0"
|
||||
ANTIGRAVITY_USER_AGENT="antigravity/2.0.1 linux/arm64 google-api-nodejs-client/10.3.0"
|
||||
KIRO_USER_AGENT="AWS-SDK-JS/3.0.0 kiro-ide/1.0.0"
|
||||
@@ -1331,7 +1354,7 @@ CURSOR_USER_AGENT="Cursor/3.4"
|
||||
|
||||
# Override Codex client version sent in headers independently of the
|
||||
# CODEX_USER_AGENT string. Used by: open-sse/config/codexClient.ts.
|
||||
# CODEX_CLIENT_VERSION=0.144.1
|
||||
# CODEX_CLIENT_VERSION=0.153.4
|
||||
#
|
||||
# Override the advertised Claude Code client version independently of
|
||||
# CLAUDE_USER_AGENT. Anthropic gates some models (Fable 5.1) on this
|
||||
@@ -1646,6 +1669,7 @@ CURSOR_USER_AGENT="Cursor/3.4"
|
||||
|
||||
# ── TLS client (wreq-js fingerprint proxy) ──
|
||||
# TLS_CLIENT_TIMEOUT_MS=600000 # Inherits from FETCH_TIMEOUT_MS by default
|
||||
# TLS_FIRST_BYTE_WATCHDOG_MS=10000 # #12656: bounds time-to-first-byte on the wreq body (0 disables)
|
||||
|
||||
# ── API Bridge (/v1 proxy server) ──
|
||||
# API_BRIDGE_PROXY_TIMEOUT_MS=600000 # Proxy hop timeout (default: 10min)
|
||||
@@ -2077,6 +2101,13 @@ APP_LOG_TO_FILE=true
|
||||
# Management key for an externally managed instance. Embedded instances use
|
||||
# OmniRoute's encrypted service key.
|
||||
# CLIPROXYAPI_MANAGEMENT_KEY=
|
||||
# Host interface docker-compose publishes the cliproxyapi sidecar on (the
|
||||
# --profile cliproxyapi Docker service, port 8317). Default: 127.0.0.1
|
||||
# (loopback only) — its data volume holds provider OAuth/API credentials, and
|
||||
# the pinned image has no env-based data-plane api-keys override (only a
|
||||
# mounted config.yaml), so an unqualified publish spec would put a
|
||||
# credential-bearing service on your whole LAN. (#12578)
|
||||
# CLIPROXY_BIND_HOST=127.0.0.1
|
||||
|
||||
# ── Mux embedded service ──
|
||||
# Override the port where the embedded Mux (coder/mux) agent-orchestration
|
||||
@@ -2721,6 +2752,10 @@ PLAYGROUND_COMPARE_MAX_COLUMNS=4
|
||||
# MEMORY_VEC_TOP_K=20 # default top-K for vector search
|
||||
# MEMORY_RRF_K=60 # RRF k constant (sqlite-vec hybrid recipe)
|
||||
# HF_HUB_ENDPOINT=https://huggingface.co # override Hugging Face Hub base URL for static potion downloads
|
||||
# Test/diagnostic seam (src/lib/memory/vectorStore.ts) — forces getVectorStore() to
|
||||
# return null (simulates a cloud/WASM environment without sqlite-vec), degrading
|
||||
# memory retrieval to FTS5 keyword search. Default off; leave unset in production.
|
||||
# VECTOR_STORE_DISABLE_VEC=false
|
||||
# TV6 typed memory decay (OPT-IN, default off — the sweep DELETES decayed memories)
|
||||
# MEMORY_TYPED_DECAY_ENABLED=false # master switch for the destructive sweep (default off)
|
||||
# MEMORY_TYPED_DECAY_EPISODIC_DAYS=30 # episodic TTL in days; 0 = episodic immune too
|
||||
@@ -2990,6 +3025,7 @@ QUOTA_STORE_DRIVER=sqlite
|
||||
# OMNIROUTE_VNC_READY_MS=45000
|
||||
# OMNIROUTE_VNC_HARVEST_MS=20000
|
||||
# OMNIROUTE_VNC_CHROMIUM_ARGS=--remote-debugging-port=9222 --no-first-run --no-default-browser-check
|
||||
# OMNIROUTE_VNC_NETWORK=omniroute-vnc-browser-login
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Data-dir alias (optional — open-sse/services/notionThreadSessions.ts)
|
||||
|
||||
16
.github/workflows/ci.yml
vendored
16
.github/workflows/ci.yml
vendored
@@ -144,6 +144,12 @@ jobs:
|
||||
- run: npm run check:test-discovery
|
||||
- run: npm run check:radar-sentinels
|
||||
- run: npm run check:tracked-artifacts
|
||||
# A test parked in vitest.config.ts's exclude list does not run, and looks like
|
||||
# coverage to whoever reads the tree. 62 files accumulated behind a comment pointing
|
||||
# at #8618 — closed in August while the list grew to 62; 51 of them passed when
|
||||
# finally measured (#13204). This gate requires every exclusion to name a tracker and
|
||||
# to appear in config/quality/vitest-exclusions.json, so the debt stays reviewable.
|
||||
- run: npm run check:vitest-exclusions
|
||||
# (gap 30) Also lives in quality.yml's PR-only "Merge integrity" job — because the
|
||||
# CHANGELOG half of that job needs a base to diff against. This half does NOT: the
|
||||
# generator either reproduces the committed SKILL.md files or it does not.
|
||||
@@ -515,6 +521,16 @@ jobs:
|
||||
env:
|
||||
BASE_REF: ${{ github.base_ref && format('origin/{0}', github.base_ref) || '' }}
|
||||
run: node scripts/i18n/check-ui-value-drift.mjs
|
||||
# Sibling of the drift gate above. That one catches an English value that was
|
||||
# REWRITTEN; this one catches an English key that was ADDED while some locales never
|
||||
# got it. The coverage gate at the top of this job cannot: it is a percentage per
|
||||
# locale, and 11 absent keys out of ~13,000 leaves coverage at 99.9%. Incident: the
|
||||
# Phase 3 canvas keys were translated across the 42 locales that existed, then the EU
|
||||
# batch (#13044) took the repo to 51 and the nine newcomers shipped untranslated.
|
||||
- name: i18n new-key coverage (a new key must reach every locale)
|
||||
env:
|
||||
BASE_REF: ${{ github.base_ref && format('origin/{0}', github.base_ref) || '' }}
|
||||
run: node scripts/i18n/check-new-key-coverage.mjs
|
||||
|
||||
# #8038: cheap glossary/protected-terms consistency gate —
|
||||
# complements i18n-ui-coverage (key parity) and the ICU `i18n` job below
|
||||
|
||||
484
@omniroute/opencode-plugin-v2/package-lock.json
generated
484
@omniroute/opencode-plugin-v2/package-lock.json
generated
@@ -1790,490 +1790,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
|
||||
"integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/android-arm": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz",
|
||||
"integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz",
|
||||
"integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/android-x64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz",
|
||||
"integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
|
||||
"integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz",
|
||||
"integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz",
|
||||
"integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz",
|
||||
"integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz",
|
||||
"integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz",
|
||||
"integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz",
|
||||
"integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz",
|
||||
"integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz",
|
||||
"integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz",
|
||||
"integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz",
|
||||
"integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz",
|
||||
"integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
|
||||
"integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz",
|
||||
"integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz",
|
||||
"integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz",
|
||||
"integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz",
|
||||
"integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/openharmony-arm64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz",
|
||||
"integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz",
|
||||
"integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz",
|
||||
"integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz",
|
||||
"integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz",
|
||||
"integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/esbuild": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
|
||||
"integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.27.7",
|
||||
"@esbuild/android-arm": "0.27.7",
|
||||
"@esbuild/android-arm64": "0.27.7",
|
||||
"@esbuild/android-x64": "0.27.7",
|
||||
"@esbuild/darwin-arm64": "0.27.7",
|
||||
"@esbuild/darwin-x64": "0.27.7",
|
||||
"@esbuild/freebsd-arm64": "0.27.7",
|
||||
"@esbuild/freebsd-x64": "0.27.7",
|
||||
"@esbuild/linux-arm": "0.27.7",
|
||||
"@esbuild/linux-arm64": "0.27.7",
|
||||
"@esbuild/linux-ia32": "0.27.7",
|
||||
"@esbuild/linux-loong64": "0.27.7",
|
||||
"@esbuild/linux-mips64el": "0.27.7",
|
||||
"@esbuild/linux-ppc64": "0.27.7",
|
||||
"@esbuild/linux-riscv64": "0.27.7",
|
||||
"@esbuild/linux-s390x": "0.27.7",
|
||||
"@esbuild/linux-x64": "0.27.7",
|
||||
"@esbuild/netbsd-arm64": "0.27.7",
|
||||
"@esbuild/netbsd-x64": "0.27.7",
|
||||
"@esbuild/openbsd-arm64": "0.27.7",
|
||||
"@esbuild/openbsd-x64": "0.27.7",
|
||||
"@esbuild/openharmony-arm64": "0.27.7",
|
||||
"@esbuild/sunos-x64": "0.27.7",
|
||||
"@esbuild/win32-arm64": "0.27.7",
|
||||
"@esbuild/win32-ia32": "0.27.7",
|
||||
"@esbuild/win32-x64": "0.27.7"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.22.3",
|
||||
"dev": true,
|
||||
|
||||
@@ -63,5 +63,8 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@opencode-ai/plugin": ">=1.18.29 <2"
|
||||
},
|
||||
"overrides": {
|
||||
"esbuild": "^0.28.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,33 @@ interface CapturedCall {
|
||||
kind: "catalog" | "integration";
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until `read()` stops changing, then return the settled value.
|
||||
*
|
||||
* The plugin's optional tier lands asynchronously after a publish. Waiting for
|
||||
* it with a fixed `sleep(5)` raced the work: under load the tier arrived after
|
||||
* the sleep, so the *next* assertion counted its reload and read 2 where it
|
||||
* expected 1. Polling until the value holds steady for a few consecutive turns
|
||||
* ties the wait to the work instead of to the clock.
|
||||
*/
|
||||
async function settle<T>(read: () => T, quietTurns = 3, timeoutMs = 5000): Promise<T> {
|
||||
const { setTimeout: sleep } = await import("node:timers/promises");
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
let last = read();
|
||||
let stable = 0;
|
||||
while (stable < quietTurns && Date.now() < deadline) {
|
||||
await sleep(5);
|
||||
const current = read();
|
||||
if (current === last) {
|
||||
stable += 1;
|
||||
} else {
|
||||
last = current;
|
||||
stable = 0;
|
||||
}
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
interface FakeCtx {
|
||||
options: Record<string, unknown>;
|
||||
catalog: {
|
||||
@@ -163,7 +190,6 @@ describe("plugin-v2 entrypoint", () => {
|
||||
const { mkdtempSync } = await import("node:fs");
|
||||
const { tmpdir } = await import("node:os");
|
||||
const { join } = await import("node:path");
|
||||
const { setTimeout: sleep } = await import("node:timers/promises");
|
||||
const dir = mkdtempSync(join(tmpdir(), "omniroute-lazy-"));
|
||||
const prevDataDir = process.env.OPENCODE_DATA_DIR;
|
||||
process.env.OPENCODE_DATA_DIR = dir;
|
||||
@@ -222,16 +248,15 @@ describe("plugin-v2 entrypoint", () => {
|
||||
await cb(draft);
|
||||
assert.equal(reloads, 0, "the first publish sets the baseline, it does not reload");
|
||||
assert.equal(modelsCall, 1);
|
||||
await sleep(5);
|
||||
// The optional tier lands after that first publish and brings combos and
|
||||
// the overlay with it — one reload, so the picker shows them without
|
||||
// waiting for the next refresh.
|
||||
const afterFirstUpgrade = reloads;
|
||||
const afterFirstUpgrade = await settle(() => reloads);
|
||||
assert.ok(afterFirstUpgrade <= 1, `at most one reload for the first upgrade, got ${reloads}`);
|
||||
await cb(draft);
|
||||
assert.equal(reloads, afterFirstUpgrade + 1, "a new model id reloads once");
|
||||
assert.equal(modelsCall, 2);
|
||||
await sleep(5);
|
||||
await settle(() => reloads);
|
||||
await cb(draft);
|
||||
assert.equal(reloads, afterFirstUpgrade + 1, "an identical run never reloads");
|
||||
assert.equal(modelsCall, 3);
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"overrides": {
|
||||
"esbuild": "^0.28.1"
|
||||
"esbuild": "^0.28.1",
|
||||
"toml": "^4.1.2"
|
||||
}
|
||||
}
|
||||
|
||||
21
AGENTS.md
21
AGENTS.md
@@ -46,7 +46,7 @@ Repository map and Reference Documentation sections below.
|
||||
|
||||
## Project at a Glance
|
||||
|
||||
**OmniRoute** — unified AI proxy/router. One endpoint, 356 LLM providers, auto-fallback.
|
||||
**OmniRoute** — unified AI proxy/router. One endpoint, 358 LLM providers, auto-fallback.
|
||||
|
||||
| Layer | Location | Purpose |
|
||||
| ------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -56,7 +56,7 @@ Repository map and Reference Documentation sections below.
|
||||
| Translators | `open-sse/translator/` | Format conversion (OpenAI↔Claude↔Gemini) |
|
||||
| Transformer | `open-sse/transformer/` | Responses API ↔ Chat Completions |
|
||||
| Services | `open-sse/services/` | Combo routing, rate limits, caching, etc |
|
||||
| Database | `src/lib/db/` | SQLite domain modules (173 migrations) |
|
||||
| Database | `src/lib/db/` | SQLite domain modules (174 migrations) |
|
||||
| Domain/Policy | `src/domain/` | Policy engine, cost rules, fallback logic |
|
||||
| MCP Server | `open-sse/mcp-server/` | 110 tools (45 canonical + memory/skill/GitHub/pool/gamification/plugin/Notion/Obsidian/local-corpus/RTK modules), 3 transports (stdio / SSE / Streamable HTTP), 33 scopes |
|
||||
| A2A Server | `src/lib/a2a/` | JSON-RPC 2.0 agent protocol |
|
||||
@@ -578,14 +578,31 @@ own dedicated branch, and you MUST confirm the base branch with the operator bef
|
||||
# HARD LINKS (`cp -al`), never a symlink: ~5s for the whole tree and near-zero extra
|
||||
# disk (the inodes are shared), and unlike a symlink it does not break the dev server.
|
||||
cp -al "$(git -C <main_checkout> rev-parse --show-toplevel)/node_modules" node_modules
|
||||
# `.husky/_` is gitignored, so a fresh worktree does NOT have it and
|
||||
# `core.hooksPath=.husky/_` then points at a directory that does not exist —
|
||||
# every pre-commit gate goes silently mute. Copy it too.
|
||||
cp -a "$(git -C <main_checkout> rev-parse --show-toplevel)/.husky/_" .husky/_
|
||||
```
|
||||
|
||||
`scripts/dev/new-worktree.sh <branch> [base]` does all of the above (canonical path,
|
||||
hard-linked `node_modules`, `.husky/_`) and then **verifies** the hook is actually
|
||||
executable, so prefer it over running the steps by hand.
|
||||
|
||||
**Never `ln -s` node_modules.** Turbopack rejects a symlink that resolves outside the
|
||||
project root, so `npm run dev` dies with a FATAL panic (`Symlink [project]/node_modules
|
||||
is invalid, it points out of the filesystem root`) while typecheck, lint and the test
|
||||
runners all keep passing — the error names "filesystem root", not the worktree, so it
|
||||
reads like a Next/build bug and costs real time to trace (incident 2026-07-31, #9043).
|
||||
|
||||
**A worktree without `.husky/_` runs NO pre-commit gate — and says nothing.** `git`
|
||||
resolves `core.hooksPath` relative to the worktree top; when the directory is missing it
|
||||
simply finds no hook and commits. Nothing is printed, the commit succeeds, and the
|
||||
identity/lint/docs gates never ran. This is how 59 commits carrying a stale identity
|
||||
override (name of a contributor + the maintainer's e-mail) got past
|
||||
`scripts/check/check-git-identity.sh` between 2026-08-29 and 09-02 — they were all made in
|
||||
`cp -al` worktrees. Verify with `ls .husky/_/pre-commit` inside a new worktree, or just use
|
||||
`scripts/dev/new-worktree.sh`, which fails loudly when the hook is not executable.
|
||||
|
||||
3. **Work, commit, push, open the PR — all from inside the worktree.** Never `git checkout` a
|
||||
different branch inside a worktree another session might share.
|
||||
4. **Tear down only your own** worktree + branch when done, from the main checkout:
|
||||
|
||||
@@ -340,7 +340,7 @@ RUN --mount=type=cache,id=s/92ca8a61-c1ba-421f-a389-d48ac7258c2d-apt-cache,targe
|
||||
# build, not the floating `@latest`.
|
||||
RUN --mount=type=cache,id=s/92ca8a61-c1ba-421f-a389-d48ac7258c2d-npm-cache,target=/root/.npm \
|
||||
npm install -g --no-audit --no-fund \
|
||||
@openai/codex@0.153.2 \
|
||||
@openai/codex@0.153.4 \
|
||||
@anthropic-ai/claude-code@2.1.260 \
|
||||
droid@0.212.0 \
|
||||
openclaw@2026.9.1
|
||||
|
||||
@@ -1253,7 +1253,7 @@ Métricas canônicas em 2026-08-24: **1.029 vídeos únicos** · **11.132.922 vi
|
||||
<tr><td nowrap><b>Runtime</b></td><td>Node.js 22.x / 24.x LTS — <code>>=22.22.2 <23 || >=24.0.0 <27</code></td></tr>
|
||||
<tr><td nowrap><b>Language</b></td><td>TypeScript 6.0 — <b>100% TypeScript</b> across <code>src/</code> and <code>open-sse/</code> (zero <code>any</code> in core since v2.0)</td></tr>
|
||||
<tr><td nowrap><b>Framework</b></td><td>Next.js 16 + React 19 + Tailwind CSS 4</td></tr>
|
||||
<tr><td nowrap><b>Database</b></td><td>better-sqlite3 (SQLite, WAL journaling) + LowDB (JSON legacy) — 122 domain modules, 173 migrations</td></tr>
|
||||
<tr><td nowrap><b>Database</b></td><td>better-sqlite3 (SQLite, WAL journaling) + LowDB (JSON legacy) — 122 domain modules, 174 migrations</td></tr>
|
||||
<tr><td nowrap><b>Memory</b></td><td>SQLite FTS5 full-text + int8-quantized vector embeddings, typed decay</td></tr>
|
||||
<tr><td nowrap><b>Schemas</b></td><td>Zod 4 — MCP tool I/O validation + API contracts</td></tr>
|
||||
<tr><td nowrap><b>Protocols</b></td><td>MCP (stdio / HTTP / SSE) + A2A v0.3 (JSON-RPC 2.0 + SSE)</td></tr>
|
||||
|
||||
@@ -54,6 +54,34 @@ async function openBrowser(url) {
|
||||
}
|
||||
}
|
||||
|
||||
// Mirrors src/lib/oauth/providers.ts::isLoopbackHostname — used here to detect
|
||||
// when the redirect_uri the server resolved (and the authorize URL now
|
||||
// advertises) points at a loopback address the CLI never binds a listener on
|
||||
// (issue #12413). Returns false on an unparseable URI rather than throwing.
|
||||
function isLoopbackHost(uri) {
|
||||
try {
|
||||
return /^(localhost|127\.0\.0\.1|\[::1\]|::1)$/i.test(new URL(uri).hostname);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function printLoopbackRedirectWarning(providerId, redirectUri) {
|
||||
process.stdout.write(
|
||||
`Note: the authorize URL below advertises ${redirectUri}, but this CLI does not\n` +
|
||||
"listen on that port. Right after you approve, the browser is expected to\n" +
|
||||
"show a connection error (e.g. \"This site can't be reached\" / \n" +
|
||||
"ERR_CONNECTION_REFUSED) — that is normal, not a failure. Copy the full URL\n" +
|
||||
"from the address bar anyway and paste it below.\n"
|
||||
);
|
||||
if (providerId === "antigravity") {
|
||||
process.stdout.write(
|
||||
"Tip: `omniroute login antigravity` captures the code automatically and\n" +
|
||||
"avoids that error page entirely.\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function targetApiOptions(opts = {}) {
|
||||
return {
|
||||
baseUrl: opts.baseUrl,
|
||||
@@ -110,6 +138,10 @@ async function runBrowserFlow(def, opts) {
|
||||
const { codeVerifier, state, redirectUri: returnedRedirectUri } = start;
|
||||
const finalRedirectUri = returnedRedirectUri || redirectUri;
|
||||
|
||||
if (finalRedirectUri && isLoopbackHost(finalRedirectUri)) {
|
||||
printLoopbackRedirectWarning(def.id, finalRedirectUri);
|
||||
}
|
||||
|
||||
process.stdout.write(`\nOpen this URL to authorize:\n ${url}\n\n`);
|
||||
if (opts.browser !== false) await openBrowser(url);
|
||||
process.stdout.write(
|
||||
|
||||
@@ -35,16 +35,24 @@ export function resolveOpencodeTarget(opts = {}) {
|
||||
baseUrl = `http://localhost:${Number(opts.port ?? process.env.PORT ?? 20128) || 20128}`;
|
||||
}
|
||||
|
||||
// Precedence: explicit --api-key flag > OMNIROUTE_API_KEY env var > active
|
||||
// context's management token. A context's accessToken/apiKey is a CLI
|
||||
// management credential (oma_live_...) with no /v1/* inference scope — it
|
||||
// must never silently outrank a real inference key the caller supplied
|
||||
// either as a flag or via the ambient env var (mirrors the explicit >
|
||||
// ambient-env > context precedence documented in bin/cli/api.mjs's
|
||||
// buildHeaders()). Only fall back to the context token when neither an
|
||||
// explicit flag nor the env var is set.
|
||||
let apiKey = opts.apiKey ?? opts["api-key"];
|
||||
if (!apiKey) apiKey = process.env.OMNIROUTE_API_KEY || "";
|
||||
if (!apiKey) {
|
||||
try {
|
||||
const c = resolveActiveContext(opts.context ?? process.env.OMNIROUTE_CONTEXT);
|
||||
apiKey = c?.accessToken || c?.apiKey;
|
||||
apiKey = c?.accessToken || c?.apiKey || "";
|
||||
} catch {
|
||||
/* no context auth */
|
||||
}
|
||||
}
|
||||
if (!apiKey) apiKey = process.env.OMNIROUTE_API_KEY || "";
|
||||
return { baseUrl: baseUrl.replace(/\/+$/, ""), apiKey };
|
||||
}
|
||||
|
||||
@@ -177,8 +185,17 @@ export function registerSetupOpencode(program) {
|
||||
"--allow-container-write",
|
||||
"Write even when the target is inside a container and not mounted from the host"
|
||||
)
|
||||
.action(async (opts) => {
|
||||
const code = await runSetupOpencodeCommand(opts);
|
||||
.action(async (opts, cmd) => {
|
||||
// Commander parses the ancestor program's own global --api-key option
|
||||
// (bin/cli/program.mjs, bound to .env("OMNIROUTE_API_KEY")) against any
|
||||
// occurrence of the flag in argv, so it wins the value even when the
|
||||
// user typed --api-key AFTER `setup-opencode` — this local option's own
|
||||
// `opts.apiKey` never sees it. cmd.optsWithGlobals() resolves to the
|
||||
// correct value either way ("globals overwrite locals" is exactly the
|
||||
// outcome we want here, since the global option is where the value
|
||||
// always actually lands).
|
||||
const resolvedOpts = { ...opts, apiKey: cmd.optsWithGlobals().apiKey ?? opts.apiKey };
|
||||
const code = await runSetupOpencodeCommand(resolvedOpts);
|
||||
if (code !== 0) process.exit(code);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -50,6 +50,34 @@ if (isVersionFastPath(process.argv)) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Detect an unsupported Node.js runtime BEFORE the heavy `tsx/esm` import and
|
||||
// Commander's ~70-command registration chain run. That chain pulls in `ora` ->
|
||||
// the hoisted `string-width` package, whose module contains top-level ES2024
|
||||
// Unicode-set (`v` flag) regex literals. On a Node/V8 build that predates
|
||||
// `v`-flag support, those literals fail to even *parse*, throwing a bare
|
||||
// `SyntaxError: Invalid regular expression flags` deep inside a transitive
|
||||
// dependency instead of an actionable message (#12296). Skip this for the
|
||||
// same read-only invocations `shouldProvisionStorageKey` already exempts
|
||||
// (`--help`/`-h`, `help`/`completion`) — those still need the full command
|
||||
// registry to render their output, so an incompatible runtime crashing there
|
||||
// is a separate, pre-existing limitation this fix does not attempt to solve.
|
||||
if (shouldProvisionStorageKey(process.argv)) {
|
||||
const nodeSupport = getNodeRuntimeSupport();
|
||||
if (!nodeSupport.nodeCompatible) {
|
||||
const runtimeWarning = getNodeRuntimeWarning() || "Unsupported Node.js runtime detected.";
|
||||
console.error(
|
||||
`\x1b[31m✖ Node.js ${nodeSupport.nodeVersion} is not supported.\x1b[0m\n` +
|
||||
` ${runtimeWarning}\n` +
|
||||
` Supported runtimes: ${nodeSupport.supportedDisplay}\n` +
|
||||
` Recommended: Node.js ${nodeSupport.recommendedVersion}\n` +
|
||||
` If you installed OmniRoute globally, run \`node -v\` and confirm \`omniroute\` is not resolving to\n` +
|
||||
` a stale/distro-packaged \`nodejs\` binary (e.g. /usr/bin/node) instead of the version you expect —\n` +
|
||||
` that mismatch is the most common cause even when package.json's engines range is correct.`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// MCP stdio transport uses stdout exclusively for JSON-RPC messages. Redirect
|
||||
// console.log/warn to stderr before anything else runs — including the tsx/esm and
|
||||
// polyfill imports below, since those (and their transitive module graphs, e.g. DB
|
||||
|
||||
1
changelog.d/features/agnes-30-flash-catalog.md
Normal file
1
changelog.d/features/agnes-30-flash-catalog.md
Normal file
@@ -0,0 +1 @@
|
||||
- feat(providers): **list Agnes 3.0 Flash as the current free chat model, drop retired 1.5 Flash, add Image 2.0/2.5 Flash plus Video 2.5/2.5 Flash, and discover the live `/v1/models` catalog (including the CN host `api.agnes-ai.cn`).** `agnes-1.5-flash` now forwards to `agnes-3.0-flash`. Video 2.5 polls `GET /v1/videos/{id}` (not the V2.0 `/agnesapi` contract). Live `/v1/models` (2026-09-09) no longer serves 1.5; the wiki marks it deprecated. 3.0 Flash is 512K context / 65,536 max output, same window as 2.5. CN-region keys use the existing per-connection base-URL field, default stays `apihub.agnes-ai.com`.
|
||||
1
changelog.d/features/codex-gpt-6-astra.md
Normal file
1
changelog.d/features/codex-gpt-6-astra.md
Normal file
@@ -0,0 +1 @@
|
||||
- **feat(sse):** Codex and OpenAI catalogs list GPT-6 Astra with effort aliases (`-low` through `-ultra`); Codex CLI identity pins `@openai/codex@0.153.4` in lockstep with the image ([#13026](https://github.com/diegosouzapw/OmniRoute/pull/13026))
|
||||
@@ -0,0 +1 @@
|
||||
- fix(routing): stop a round-robin combo's "opencode" targets from collapsing onto the opencode-zen connection (#11912)
|
||||
@@ -0,0 +1 @@
|
||||
- fix(routing): stop the reactive-compaction debug log from lying when compression is globally disabled (#11977)
|
||||
@@ -0,0 +1 @@
|
||||
- fix(electron): relativize standalone-bundle symlink targets so Stage 8 manifest verification stops failing on Windows (#11979)
|
||||
1
changelog.d/fixes/12061-compression-studio-run-error.md
Normal file
1
changelog.d/fixes/12061-compression-studio-run-error.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(dashboard): surface a visible error when Compression Studio's combined preview run fails (#12061)
|
||||
1
changelog.d/fixes/12063-compression-profile-header.md
Normal file
1
changelog.d/fixes/12063-compression-profile-header.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(dashboard): make the compression "Effective pipeline" preview honor the active profile and warn when the master switch is off (#12063)
|
||||
1
changelog.d/fixes/12072-tinycms-dom-shim-leak.md
Normal file
1
changelog.d/fixes/12072-tinycms-dom-shim-leak.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(providers): scope TinyCMS Web signer's DOM shims to each call instead of leaking them for the process lifetime, and surface a clean HTTP status on a non-JSON interception-toggles error (#12072)
|
||||
1
changelog.d/fixes/12111-vision-bridge-model-lockout.md
Normal file
1
changelog.d/fixes/12111-vision-bridge-model-lockout.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(guardrails): stop Vision Bridge from re-selecting a model locked after a 404 (#12111)
|
||||
@@ -0,0 +1 @@
|
||||
- fix(sse): require Responses-shaped body before native OpenAI-compatible passthrough (#12129)
|
||||
1
changelog.d/fixes/12132-minimax-m3-adaptive-thinking.md
Normal file
1
changelog.d/fixes/12132-minimax-m3-adaptive-thinking.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(providers): minimax-m3 now collapses manual thinking.type:"enabled" to adaptive, preventing upstream 400 (2013) (#12132)
|
||||
1
changelog.d/fixes/12172-model-id-collision-chat-image.md
Normal file
1
changelog.d/fixes/12172-model-id-collision-chat-image.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(db): scope model visibility overrides by modality so hiding a Chat model no longer hides an identically-ID'd Image/Embeddings/etc. model (#12172)
|
||||
1
changelog.d/fixes/12173-lmstudio-multi-account.md
Normal file
1
changelog.d/fixes/12173-lmstudio-multi-account.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(db): scope local-provider apiKey dedup to matching base URL so LM Studio/Ollama-style connections support multiple accounts (#12173)
|
||||
1
changelog.d/fixes/12190-trae-referer-401.md
Normal file
1
changelog.d/fixes/12190-trae-referer-401.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(providers): refresh Trae's stale Referer/Origin and forward user timezone so imported connections stop failing with 401 (#12190)
|
||||
1
changelog.d/fixes/12196-opencode-go-gpt56luna.md
Normal file
1
changelog.d/fixes/12196-opencode-go-gpt56luna.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(providers): route opencode-go/gpt-5.6-luna to /responses instead of /chat/completions (#12196)
|
||||
1
changelog.d/fixes/12251-extra-upstream-headers-delete.md
Normal file
1
changelog.d/fixes/12251-extra-upstream-headers-delete.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(dashboard): allow deleting the last extra-upstream-header row even when invalid (#12251)
|
||||
1
changelog.d/fixes/12272-missing-i18n.md
Normal file
1
changelog.d/fixes/12272-missing-i18n.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(i18n):** translate pre-existing `__MISSING__:` keys for `combo.sort`, `requestLogger.detail` expand/collapse, `common.profile`, and `settings.resilienceCredentialHealth*` across 39 locales ([#12272](https://github.com/diegosouzapw/OmniRoute/issues/12272))
|
||||
1
changelog.d/fixes/12296-node-runtime-guard-early.md
Normal file
1
changelog.d/fixes/12296-node-runtime-guard-early.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(cli): run the Node.js runtime compatibility guard before the heavy `tsx/esm` + Commander import chain so an unsupported runtime gets a clear message instead of a raw `Invalid regular expression flags` crash (#12296)
|
||||
1
changelog.d/fixes/12298-provider-node-delete-refresh.md
Normal file
1
changelog.d/fixes/12298-provider-node-delete-refresh.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(dashboard): refresh the providers list after deleting a compatible provider node (#12298)
|
||||
1
changelog.d/fixes/12341-budget-alias-auto.md
Normal file
1
changelog.d/fixes/12341-budget-alias-auto.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(usage): fail closed on API-key budget enforcement when a provider's `auto` routing alias has no pricing row, instead of silently counting it as $0 (#12341)
|
||||
1
changelog.d/fixes/12398-claude-truly-empty-stream.md
Normal file
1
changelog.d/fixes/12398-claude-truly-empty-stream.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(sse): surface an error instead of a silent empty 200 when a Claude stream closes with zero bytes (#12398)
|
||||
@@ -0,0 +1 @@
|
||||
- fix(oauth): warn before the dead localhost:8080 redirect in antigravity/gemini `oauth start` (#12413)
|
||||
1
changelog.d/fixes/12517-devin-cli-sse-double-close.md
Normal file
1
changelog.d/fixes/12517-devin-cli-sse-double-close.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(providers): stop devin-cli spawn error from double-closing the SSE controller (#12517)
|
||||
1
changelog.d/fixes/12561-kilo-pass-i18n.md
Normal file
1
changelog.d/fixes/12561-kilo-pass-i18n.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(i18n):** backfill missing `usage.kiloPass*` strings in 39 locales and restore `featureFlagOmnirouteDisableThinkingLevelVariantsDescription` in `pt.json` ([#12561](https://github.com/diegosouzapw/OmniRoute/issues/12561)) — thanks @HouMinXi
|
||||
1
changelog.d/fixes/12568-compose-loopback-bind.md
Normal file
1
changelog.d/fixes/12568-compose-loopback-bind.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(docker): default docker-compose app ports (dashboard/API/live-WS) to loopback instead of `0.0.0.0`, closing the anonymous `/v1` LAN/WAN exposure gap left open by `REQUIRE_API_KEY=false` (#12568)
|
||||
1
changelog.d/fixes/12569-webhook-dns-rebinding-ssrf.md
Normal file
1
changelog.d/fixes/12569-webhook-dns-rebinding-ssrf.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(api): close DNS-rebinding SSRF gap in webhook outbound-URL guard (#12569)
|
||||
1
changelog.d/fixes/12571-vnc-cdp-bridge-auth.md
Normal file
1
changelog.d/fixes/12571-vnc-cdp-bridge-auth.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(docker): require a per-session token on the VNC browser CDP bridge and isolate it on a dedicated Docker network (#12571)
|
||||
@@ -0,0 +1 @@
|
||||
- fix(open-sse): write Adobe Firefly session tokens and cookie jars with 0700/0600 permissions instead of the process umask (#12572)
|
||||
1
changelog.d/fixes/12573-gemini-cors-wildcard.md
Normal file
1
changelog.d/fixes/12573-gemini-cors-wildcard.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(api): remove hardcoded wildcard CORS in openai-to-gemini-sse.ts so the centralized fail-closed CORS gate is the sole source of `Access-Control-Allow-Origin` (#12573)
|
||||
1
changelog.d/fixes/12574-elevenlabs-policy-enforcement.md
Normal file
1
changelog.d/fixes/12574-elevenlabs-policy-enforcement.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(api): enforce API key policy (budget/rate-limit/schedule/endpoint scoping) on the ElevenLabs speech-to-text, text-to-speech and voices proxy routes (#12574)
|
||||
1
changelog.d/fixes/12577-huggingchat-buffer-cap.md
Normal file
1
changelog.d/fixes/12577-huggingchat-buffer-cap.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(sse): cap HuggingChat NDJSON body size and bound the read loop with the fetch timeout so a stalled or hostile upstream cannot buffer unbounded memory (#12577)
|
||||
1
changelog.d/fixes/12578-cliproxyapi-loopback-bind.md
Normal file
1
changelog.d/fixes/12578-cliproxyapi-loopback-bind.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(docker): scope the cliproxyapi/qdrant/bifrost sidecars to loopback by default and forward `CLIPROXYAPI_MANAGEMENT_KEY` into the cliproxyapi container so its management API is not left both unauthenticated and LAN-published (#12578)
|
||||
1
changelog.d/fixes/12579-db-export-tempdir-mkdtemp.md
Normal file
1
changelog.d/fixes/12579-db-export-tempdir-mkdtemp.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(api): create DB export temp paths with `fs.mkdtempSync` instead of predictable timestamps (#12579)
|
||||
1
changelog.d/fixes/12594-cline-401-oauth.md
Normal file
1
changelog.d/fixes/12594-cline-401-oauth.md
Normal file
@@ -0,0 +1 @@
|
||||
- Cline 401 bodies that say "re-authenticate your Cline account" classify as a refreshable OAuth token, not a terminal expired key. The cooling panel no longer labels every cooldown as a 429; it shows the recorded last error instead. (#12594)
|
||||
1
changelog.d/fixes/12613-combo-openrouter-modalities.md
Normal file
1
changelog.d/fixes/12613-combo-openrouter-modalities.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(catalog):** degrade unknown combo targets instead of dropping LCD modalities, and persist OpenRouter `architecture.input_modalities` into the capability snapshot ([#12613](https://github.com/diegosouzapw/OmniRoute/issues/12613))
|
||||
@@ -0,0 +1 @@
|
||||
- fix(providers): send `x-api-key` instead of `Authorization: Bearer` for OpenCode Zen's `/v1/responses` endpoint (Muse Spark Contributor models), fixing a 401 on OmniRoute's auth header (#12633)
|
||||
@@ -0,0 +1 @@
|
||||
- fix(sse): surface the actionable "Auggie CLI not found" message when the shell reports a missing `auggie` binary via exit code instead of a spawn error (#12645)
|
||||
1
changelog.d/fixes/12656-tls-wreq-first-byte-watchdog.md
Normal file
1
changelog.d/fixes/12656-tls-wreq-first-byte-watchdog.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(sse): add first-byte watchdog to the TLS-fingerprint transport so a stalled wreq body falls back instead of hanging for minutes (#12656)
|
||||
@@ -0,0 +1 @@
|
||||
- fix(sse): exempt tiny-budget reasoning probes from combo quality failure and surface persisted-cooldown skips in ALL_TARGETS_SKIPPED diagnostics (#12659)
|
||||
@@ -0,0 +1 @@
|
||||
- fix(models): declare the real ~1M contextLength for OpenCode Zen's Muse Spark 1.2 models instead of falling back to the 200000 provider default (#12681)
|
||||
@@ -0,0 +1 @@
|
||||
- fix(oauth): align codebuddy-cn OAuth User-Agent with the chat/usage CLI version to avoid WAF false positives (#12702)
|
||||
1
changelog.d/fixes/12709-guest-import-settings.md
Normal file
1
changelog.d/fixes/12709-guest-import-settings.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(dashboard): surface an authentication-required banner instead of silently blanking database settings for a guest session (#12709)
|
||||
1
changelog.d/fixes/12734-semantic-cache-tool-choice.md
Normal file
1
changelog.d/fixes/12734-semantic-cache-tool-choice.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(cache): fold tool_choice/tools/response_format into the semantic cache signature so a cached tool_calls response can no longer be replayed for a request whose tool policy forbids it (#12734)
|
||||
1
changelog.d/fixes/12745-memory-rerank-loopback-auth.md
Normal file
1
changelog.d/fixes/12745-memory-rerank-loopback-auth.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(memory): authenticate the internal /v1/rerank loopback call so memory reranking no longer silently degrades to unranked order when REQUIRE_API_KEY=true (#12745)
|
||||
1
changelog.d/fixes/12749-ollama-cloud-usage-cookie.md
Normal file
1
changelog.d/fixes/12749-ollama-cloud-usage-cookie.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(sse): parse Ollama Cloud's current usage markup (`$X of $Y used` aria-label, nested width style) (#12749)
|
||||
@@ -0,0 +1 @@
|
||||
- fix(cli): setup-opencode no longer sends an active context's management token to `/v1/models` when `--api-key`/`OMNIROUTE_API_KEY` is supplied — an explicit flag or the env var now always outranks the context's token, and the flag itself is no longer swallowed by the parent program's global `--api-key` option (#12783)
|
||||
1
changelog.d/fixes/12784-arcee-ai-provider-registry.md
Normal file
1
changelog.d/fixes/12784-arcee-ai-provider-registry.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(sse): register Arcee AI in the executor provider registry so requests reach api.arcee.ai instead of silently falling back to OpenAI (#12784)
|
||||
1
changelog.d/fixes/12800-cliproxyapi-unknown-provider.md
Normal file
1
changelog.d/fixes/12800-cliproxyapi-unknown-provider.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(routing): recognize CLIProxyAPI's 'unknown provider for model' 400 as fallback-worthy (#12800)
|
||||
1
changelog.d/fixes/12849-nvidia-stale-synced-catalog.md
Normal file
1
changelog.d/fixes/12849-nvidia-stale-synced-catalog.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(nvidia): fail open when a synced model catalog goes stale instead of gating forever (#12849)
|
||||
1
changelog.d/fixes/12888-a2a-dashboard-auth.md
Normal file
1
changelog.d/fixes/12888-a2a-dashboard-auth.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(a2a): accept the dashboard's own session cookie on /a2a so "Run message/send" no longer fails with "Unauthorized: missing or invalid API key" (#12888)
|
||||
1
changelog.d/fixes/12960-sqljs-wasm-global-path.md
Normal file
1
changelog.d/fixes/12960-sqljs-wasm-global-path.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(db):** resolve `sql-wasm.wasm` across global npm install and hoisted layouts, ensuring OmniRoute can boot cleanly on Node 24 when native `better-sqlite3` is uncompiled.
|
||||
@@ -0,0 +1 @@
|
||||
- **fix(combo):** quota-weighted routing stops drawing on an out-of-credit connection — a 402 now invalidates the stored quota snapshot instead of leaving its stale remaining percentage in place, and a snapshot older than 10 minutes no longer counts as confident headroom for the primary pool ([#12972](https://github.com/diegosouzapw/OmniRoute/pull/12972)) — thanks @HouMinXi
|
||||
@@ -0,0 +1 @@
|
||||
- **fix(vision):** Custom Models with "Vision capable" checked no longer have image requests swapped to `glm/glm-4.6v` when the client sends the advertised alias (`vllm/path/...`) or the bare path-shaped id — Vision Bridge now matches the stored override for all three id forms ([#12758](https://github.com/diegosouzapw/OmniRoute/issues/12758))
|
||||
1
changelog.d/fixes/13011-memory-pressure-self-heal.md
Normal file
1
changelog.d/fixes/13011-memory-pressure-self-heal.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(db):** add an opt-in self-restart circuit for sustained critical memory pressure, gate post-cleanup VACUUM behind a minimum freed-rows threshold, and checkpoint the SQLite WAL every 5 minutes with a size guard that escalates to TRUNCATE, so a growing WAL can no longer stall the event loop into a full outage.
|
||||
1
changelog.d/fixes/13017-explicit-inactive-probe.md
Normal file
1
changelog.d/fixes/13017-explicit-inactive-probe.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(auth):** an explicit connection pin may probe a quota-disabled row once and re-enable it on success ([#12874](https://github.com/diegosouzapw/OmniRoute/issues/12874)) ([#13017](https://github.com/diegosouzapw/OmniRoute/pull/13017))
|
||||
1
changelog.d/fixes/13038-fallback-attempts-chat.md
Normal file
1
changelog.d/fixes/13038-fallback-attempts-chat.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(api):** thread `X-OmniRoute-Fallback-Attempts` through combo chat completions so streaming and non-streaming responses report how many prior legs were attempted ([#13038](https://github.com/diegosouzapw/OmniRoute/pull/13038))
|
||||
@@ -0,0 +1 @@
|
||||
- Restore provider failure classification and credential refresh on non-streaming requests: classify non-2xx failures to lock models on per-model quota exhaustion, update connection rate limits from headers and body, and pass credential refresh handlers to pipeline execution so 401 tokens can be refreshed and retried (#13043).
|
||||
1
changelog.d/fixes/13050-responses-websearch-sse.md
Normal file
1
changelog.d/fixes/13050-responses-websearch-sse.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(responses):** wrap forced-non-streaming web_search fallback JSON as Responses SSE so Codex still sees `response.completed` ([#13050](https://github.com/diegosouzapw/OmniRoute/pull/13050))
|
||||
1
changelog.d/fixes/13107-volcengine-console-cookie.md
Normal file
1
changelog.d/fixes/13107-volcengine-console-cookie.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(dashboard):** expose the Volcano Ark console cookie on quota scraping and unwrap connect-error objects so the dashboard shows the upstream message
|
||||
1
changelog.d/fixes/13136-docs-sensitive-catalog.md
Normal file
1
changelog.d/fixes/13136-docs-sensitive-catalog.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(docs):** drop TLS-impersonation, MITM-decrypt, supply-chain attestation, and XOR-mask writeups from the public `/docs` catalog and Docker image. Files stay in git for engineers; operators who need them open the repo, not the website.
|
||||
1
changelog.d/fixes/13195-gemini-38-output-spec.md
Normal file
1
changelog.d/fixes/13195-gemini-38-output-spec.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(models):** give discoverable Gemini 3.8 Flash ids their own 65536 output spec so Antigravity no longer clamps them to 16384 ([#13195](https://github.com/diegosouzapw/OmniRoute/pull/13195)) — thanks @HouMinXi
|
||||
1
changelog.d/fixes/13197-deprecated-provider-purge.md
Normal file
1
changelog.d/fixes/13197-deprecated-provider-purge.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(dashboard):** leftover catalog-removed provider rows (gemini-cli) can be listed and purged from the providers page ([#13067](https://github.com/diegosouzapw/OmniRoute/issues/13067)) ([#13197](https://github.com/diegosouzapw/OmniRoute/pull/13197))
|
||||
@@ -0,0 +1 @@
|
||||
- **fix(auth):** a dashboard session now requires the `authenticated: true` claim that login, OIDC and the session refresh already emit — a JWT merely signed with `JWT_SECRET` (for example the Cursor CLI passthrough token, which any API-key holder can obtain) no longer verifies as the `auth_token` cookie on any route, the WebSocket handshake or the live server; existing sessions keep working ([#13298](https://github.com/diegosouzapw/OmniRoute/issues/13298))
|
||||
1
changelog.d/fixes/combo-test-probe-timeout.md
Normal file
1
changelog.d/fixes/combo-test-probe-timeout.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(combos):** dashboard combo test uses a short prompt, serial probes, and a 60s timeout so reasoning models and rate-limited free pools do not fail the health check
|
||||
1
changelog.d/fixes/grok-cli-shared-wallet-402.md
Normal file
1
changelog.d/fixes/grok-cli-shared-wallet-402.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(grok-cli):** a 402 "Grok Build usage balance exhausted" parks that Grok login as out of credit (Grok Build CLI, grok.com cookie, and xAI OAuth share the weekly pool). Combo routing then tries the next login instead of locking the model for every account in the pool
|
||||
9
changelog.d/fixes/orchestration-fase3-eu-locales.md
Normal file
9
changelog.d/fixes/orchestration-fase3-eu-locales.md
Normal file
@@ -0,0 +1,9 @@
|
||||
- **fix(i18n):** the nine locales added with the EU-language batch (Greek, Estonian, Irish,
|
||||
Croatian, Lithuanian, Latvian, Maltese, Slovenian, Serbian) were missing the eleven
|
||||
Orchestration Canvas keys that Phase 3 introduced, so the compare-runs panel and the
|
||||
"no runs match these filters" empty state fell back to English in those languages
|
||||
(`deepMergeFallback` substitutes English for an absent key, so nothing rendered blank —
|
||||
it rendered untranslated). The coverage gate does not catch this: it enforces an 80% floor
|
||||
per locale, and eleven missing keys out of ~13,000 leaves coverage at 99.9%. Translated for
|
||||
real in each language, calibrated against the wording each file already uses for "run",
|
||||
"filter" and "skill".
|
||||
@@ -0,0 +1 @@
|
||||
- **fix(combo):** quota-aware expansion drops banned, inactive, missing, and wrong-provider connections before quota fetch or model dispatch; pins and allowlists stay selectors, not a bypass. Antigravity automatic exhaustion now requires a reported zero remaining, so a positive balance below 1% stays eligible.
|
||||
1
changelog.d/fixes/quota-weighted-test-clock.md
Normal file
1
changelog.d/fixes/quota-weighted-test-clock.md
Normal file
@@ -0,0 +1 @@
|
||||
- Fix an intermittent failure in the quota-weighted routing test suite: scores are a function of `Date.now()`, so peers with identical quota scored microseconds apart never tied and swapped order.
|
||||
@@ -0,0 +1 @@
|
||||
- Clear the `release/v3.8.51` `check:agent-skills-sync` base-red: regenerate `skills/cli-tunnel/SKILL.md` so the `tunnel create [type]` positional that #13009 taught the generator to read is reflected in the committed skill.
|
||||
@@ -0,0 +1 @@
|
||||
- Clear the `release/v3.8.51` typecheck base-red from #12731: the new `Mode pack ranking gates` candidates in `open-sse/services/autoCombo/__tests__/autoCombo.test.ts` omitted the required `provider`, `model` and `errorRate` fields of `ProviderCandidate`, failing `check:open-sse-typecheck` (and with it `Fast Quality Gates`) on every open PR.
|
||||
@@ -0,0 +1 @@
|
||||
- Clear the `release/v3.8.51` docs-sync base-red: EURouter (#13025) and GreenPT (#13024) took the live provider count to 358, leaving 7 STRICT drifts (`PROVIDER_REFERENCE.md`, 4 diagrams, 2 tier-flow images) plus the `AGENTS.md` / `llm.txt` / `package.json` count claims stale.
|
||||
@@ -0,0 +1 @@
|
||||
- **chore(quality):** raise the `imageGeneration.ts` file-size ceiling for the image-only-model guard that clears the #12945 base-red
|
||||
@@ -0,0 +1 @@
|
||||
- **chore(quality):** type the combo-test route's JSON response bodies instead of casting them to `any`, drop the now-empty suppression entry, and remove the `hasPerModelQuota` import `chatCore.ts` stopped using when the failure-classification helper was extracted
|
||||
@@ -0,0 +1 @@
|
||||
- **chore(electron):** the desktop bundle no longer ships the root-level files of every translated docs mirror (`README.md`, `llm.txt`, `CLAUDE.md`, `GEMINI.md`, `CONTRIBUTING.md`, `SECURITY.md`, `CODE_OF_CONDUCT.md`) — the packaged app only reads `docs/i18n/<locale>/docs/**`, which stays. Saves ~11 MB on top of the translated CHANGELOGs already pruned. (#0000)
|
||||
@@ -0,0 +1 @@
|
||||
- **chore(quality):** retighten the `src/sse/handlers/chat.ts` unused-vars suppression count to the 8 violations that actually remain
|
||||
8
changelog.d/maintenance/gates-for-silent-debt.md
Normal file
8
changelog.d/maintenance/gates-for-silent-debt.md
Normal file
@@ -0,0 +1,8 @@
|
||||
- **chore(ci):** two gates that close the blind spots behind the exclusions above.
|
||||
`check:vitest-exclusions` requires every Vitest exclusion to name a tracking issue and to
|
||||
appear in `config/quality/vitest-exclusions.json` — the previous list grew to 62 files behind
|
||||
a comment pointing at an issue that had been closed for a month. `check-new-key-coverage`
|
||||
requires a key newly added to `en.json` to reach every locale; the existing coverage gate is a
|
||||
percentage floor per locale, so eleven absent keys out of ~13,000 left it at 99.9% while a
|
||||
whole feature shipped untranslated in nine languages. Both are diff-aware, so pre-existing
|
||||
debt stays frozen and neither needed a migration to turn on.
|
||||
1
changelog.d/maintenance/layout-no-google-fonts.md
Normal file
1
changelog.d/maintenance/layout-no-google-fonts.md
Normal file
@@ -0,0 +1 @@
|
||||
- **build:** root layout no longer loads Inter from `next/font/google`, so a production image build does not need fonts.googleapis.com ([#13026](https://github.com/diegosouzapw/OmniRoute/pull/13026))
|
||||
6
changelog.d/maintenance/revive-excluded-vitest-tests.md
Normal file
6
changelog.d/maintenance/revive-excluded-vitest-tests.md
Normal file
@@ -0,0 +1,6 @@
|
||||
- **chore(tests):** 51 test files that had been excluded from Vitest are running again, restoring
|
||||
roughly 350 assertions to the blocking suite. Each was measured individually first: of the 62
|
||||
files parked behind the `// #8618 — pre-existing failure` comment, 51 pass against the current
|
||||
tree with no source change, so the exclusions had outlived the failures they were added for.
|
||||
The 11 that genuinely still fail stay excluded, but now point at a live tracker (#13204) rather
|
||||
than at #8618, which was closed in August while the list it tracked kept growing.
|
||||
@@ -0,0 +1 @@
|
||||
- **chore(quality):** drop two ESLint suppression entries whose violations no longer exist, so `eslint --suppressions-location` stops rejecting every commit that touches the surrounding files
|
||||
@@ -568,11 +568,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"open-sse/services/imageCombo.ts": {
|
||||
"@typescript-eslint/no-unused-vars": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"open-sse/services/inAppLoginService.ts": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 2
|
||||
@@ -1359,11 +1354,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/api/webhooks/[id]/test/route.ts": {
|
||||
"@typescript-eslint/no-unused-vars": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/login/page.tsx": {
|
||||
"@typescript-eslint/no-unused-vars": {
|
||||
"count": 3
|
||||
@@ -2085,7 +2075,7 @@
|
||||
},
|
||||
"src/sse/handlers/chat.ts": {
|
||||
"@typescript-eslint/no-unused-vars": {
|
||||
"count": 9
|
||||
"count": 8
|
||||
}
|
||||
},
|
||||
"src/sse/handlers/chatHelpers.ts": {
|
||||
@@ -2806,11 +2796,6 @@
|
||||
"count": 10
|
||||
}
|
||||
},
|
||||
"tests/unit/chat-helpers.test.ts": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 13
|
||||
}
|
||||
},
|
||||
"tests/unit/chat-rate-limit-body-lock.test.ts": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 2
|
||||
@@ -3378,11 +3363,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"tests/unit/combo-test-route.test.ts": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 10
|
||||
}
|
||||
},
|
||||
"tests/unit/combos-duplicate-resolution-audit.test.ts": {
|
||||
"@typescript-eslint/no-unused-vars": {
|
||||
"count": 1
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"_rebaseline_2026_09_11_12732_catalog_timeout_pin": "+1 in tests/unit/models-catalog-route.test.ts (1652->1653) for a single line: process.env.CATALOG_BUILD_TIMEOUT_MS. #12627 bounds a cold catalog build at 8s; beforeEach resets the catalog cache so every case in this file pays a cold build, and a tsx runner needs 10-13s under load — the file returned catalog_build_timeout instead of rows and oscillated between 1 and 10 failures per run, reddening the whole PR queue (base-red #12732). The bound itself stays covered by tests/unit/12627-catalog-inflight-timeout.test.ts. The file is already at its frozen ceiling, so the pin cannot be absorbed; structural shrink tracked in #3501.",
|
||||
"_rebaseline_2026_09_11_12945_image_only_model_guard": "PR #12945 own growth: open-sse/handlers/imageGeneration.ts 3259->3293 (+35/-1). The image-only-model guard the PR adds to clear its base-red: the handler now recognises a model that only serves image generation and answers before the chat path can mis-route it. Irreducible at this call site; the predicate itself lives outside the file. Landed as its own PR rather than on #12945 because that branch has a live worktree in another session and pushing to it would pull the branch out from under whoever is working it. Covered by the batch run: 203/208 with the 5 remaining failures reproducing on the pure tip.",
|
||||
"_rebaseline_2026_09_11_mergebatch_v3851_diego": "/merge-batch 2026-09-11 (v3.8.51), owner batch. open-sse/handlers/chatCore.ts 6144->6146 (+2): #13278 requires a Responses-shaped body before the native OpenAI-compatible passthrough (+1) and #13276 stops the reactive-compaction log from claiming a compaction when compression is disabled (+2/-1). Both are guard conditions at existing call sites, no new branching structure. open-sse/utils/stream.ts is deliberately NOT rebaselined: already 3115 > 3098 on the pure tip with zero contribution from this batch (base-red #12732, owned by /sweep-reds). Covered by 256 assertions across the batch's test files (246 node:test + 10 vitest).",
|
||||
"_rebaseline_2026_09_11_mergebatch_v3851_houminxi": "/merge-batch 2026-09-11 (v3.8.51), batch by HouMinXi. Final combined values, set on the first PR merged so every intermediate state is covered. open-sse/handlers/chatCore.ts 6036->6144: #13069 routes the non-streaming leg through the same provider-failure classification, model lockout and credential-refresh path the streaming leg already used (+443/-340 = +103 net; it extracts applyProviderFailureClassification and wires both legs to it, which is what #13043 reported missing), plus #13050 stamping that the client asked for SSE before the web_search fallback flips stream off (+6) and #13038 threading the dispatched target index (+3). src/sse/services/auth.ts 3488->3542: #13017 adds the explicit-pin one-shot probe for a recoverable inactive row with its 60s storm gate (+42 net) and #13061 makes a grok-cli 402 a connection-wide shared-wallet signal instead of a per-model billing miss (+12 net). src/sse/handlers/chat.ts 2458->2462: #13038 (+5). open-sse/services/combo/executeTargetAttempt.ts 1205->1212: #13006 feeds the 402 it already classified into the quota cache instead of dropping it (+7). open-sse/services/accountFallback.ts 2468->2469: #13060 adds the Cline re-auth phrase to OAUTH_INVALID_TOKEN_SIGNALS (+1). open-sse/utils/stream.ts is deliberately NOT rebaselined: already 3115 > 3098 on the pure tip with zero contribution from this batch (base-red #12732, owned by /sweep-reds). The file also carried \"open-sse/handlers/chatCore.ts\" twice (6026 and 6036); JSON keeps the last, so the first was dead weight any writer could have picked instead. Collapsed to one entry at the live value. Covered by 531 focused assertions across the batch's 46 test files.",
|
||||
"_rebaseline_2026_09_11_12358_chat_pipeline_custom_node": "PR #12358 own test growth: tests/integration/chat-pipeline.test.ts 1648->1736 (+88). One new integration case, \"#11884 chat pipeline sends a custom node's edited Chat API type upstream\": it seeds a custom OpenAI-compatible node with an edited Chat/Responses API type, stubs fetch, drives handleChatCore and asserts the upstream request carries the live connection setting rather than the format baked into the node id at creation. Irreducible at this layer — the point of the test is the full route-to-upstream path, which is what #11884 regressed. Nothing else in the file changed. Covered by the case itself plus tests/unit/chat-helpers.test.ts (28/28).",
|
||||
"_rebaseline_2026_09_10_12975_rotation_correlation_id": "PR #12975 own growth: open-sse/executors/base.ts 1751->1753 (+2) and open-sse/handlers/chatCore.ts 6021->6024 (+3). The opencode rotation lines carry the request correlationId: one optional ExecuteInput field and one correlationId argument at each of the three executor.execute call sites in handleChatCore. Irreducible plumbing at existing call sites; the rotation logic itself lives in open-sse/executors/opencode.ts and the new leaf predicates (under cap). Covered by tests/unit/opencode-transient-rotation.test.ts and tests/unit/chat-correlation-id-exhaustion.test.ts.",
|
||||
"_rebaseline_2026_09_11_mergebatch_v3851_maxmad_opencode": "/merge-batch 2026-09-11 (v3.8.51), PRs #13141, #13146 and #12975 by maxmad64bis. src/sse/services/auth.ts 3450->3488 (+38): #13146 adds the narrow ruleScope===model branch to markAccountUnavailable (gated on status 400; every other status keeps its path) plus the HONORS_RULE_LOCK_SCOPE_PROVIDERS opencode entry, taking it to 3464; #12975 then adds buildExhaustionOptions so the exhaustion log lines carry the request correlationId (+24). open-sse/services/accountFallback.ts 2467->2468 (+1): #13141 routes hasFutureRateLimitUntil through the tolerant epoch normalizer; #13146 is net zero there (+16/-16). open-sse/executors/base.ts 1751->1753 (+2): #12975 adds the optional ExecuteInput.correlationId field with its doc comment. src/sse/handlers/chat.ts is NOT rebaselined: #12975 threads correlationId through the three executor call sites (+2) but the file lands at 2452, still under its existing 2458 freeze. open-sse/utils/stream.ts is deliberately NOT rebaselined either: it is already 3115 > 3098 on the pure tip with zero contribution from this batch (base-red #12732, owned by /sweep-reds). No new branching beyond the two guarded branches named above. Covered by tests/unit/combo-predicates-epoch-cooldown.test.ts, opencode-400-model-unavailable.test.ts, agentrouter-error-rules.test.ts, opencode-transient-rotation.test.ts and chat-correlation-id-exhaustion.test.ts.",
|
||||
@@ -431,16 +434,15 @@
|
||||
"open-sse/executors/codex.ts": 1505,
|
||||
"open-sse/executors/cursor.ts": 1759,
|
||||
"open-sse/executors/muse-spark-web.ts": 1405,
|
||||
"open-sse/handlers/chatCore.ts": 6026,
|
||||
"open-sse/handlers/chatCore.ts": 6036,
|
||||
"open-sse/handlers/imageGeneration.ts": 3259,
|
||||
"open-sse/handlers/chatCore.ts": 6146,
|
||||
"open-sse/handlers/imageGeneration.ts": 3293,
|
||||
"open-sse/handlers/search.ts": 1789,
|
||||
"open-sse/mcp-server/schemas/tools.ts": 1621,
|
||||
"open-sse/mcp-server/server.ts": 1572,
|
||||
"open-sse/services/accountFallback.ts": 2468,
|
||||
"open-sse/services/accountFallback.ts": 2469,
|
||||
"open-sse/services/adobeFireflyBrowserLogin.ts": 1401,
|
||||
"open-sse/services/combo.ts": 4080,
|
||||
"open-sse/services/combo/executeTargetAttempt.ts": 1205,
|
||||
"open-sse/services/combo/executeTargetAttempt.ts": 1212,
|
||||
"open-sse/translator/response/openai-responses.ts": 1466,
|
||||
"open-sse/utils/cursorAgentProtobuf.ts": 1547,
|
||||
"open-sse/utils/proxyFetch.ts": 1271,
|
||||
@@ -472,8 +474,8 @@
|
||||
"src/shared/components/RequestLoggerV2.tsx": 1718,
|
||||
"src/shared/constants/providers/apikey/gateways.ts": 1502,
|
||||
"src/shared/services/cliRuntime.ts": 1296,
|
||||
"src/sse/handlers/chat.ts": 2458,
|
||||
"src/sse/services/auth.ts": 3488,
|
||||
"src/sse/handlers/chat.ts": 2462,
|
||||
"src/sse/services/auth.ts": 3542,
|
||||
"tests/unit/account-fallback-service.test.ts": 2453,
|
||||
"tests/unit/provider-validation-specialty.test.ts": 4656,
|
||||
"open-sse/services/autoCombo/virtualFactory.ts": 1219,
|
||||
@@ -665,5 +667,6 @@
|
||||
"_rebaseline_2026_09_07_chatcore_nonstreaming_regression_fixes": "Own growth: open-sse/handlers/chatCore.ts 5984->6021 (+37). Two of my own PRs on top of #12867: #12963 pins the ok variant of the non-streaming leg result in its own binding (the discriminated-union narrowing was lost across the tool-loop reassignment, 13 TS2339 under tsconfig.typecheck-api.json), and #12990 restores four behaviours the same refactor dropped — abort classification through isLocalStreamLifecycleError, the omitted synthetic clientResponse, the claudePromptCacheLogMeta rebuild on the leg path, and the lazy fail-closed fence identity. Irreducible at the existing chokepoints: each edit sits where chatCore already owns the decision, and the helpers themselves (nonStreamingProviderLeg.ts, serverOwnedToolLoopWire.ts) are under cap. Covered by tests/unit/chatcore-translation-paths.test.ts (72/74; the 2 open are issue #13043).",
|
||||
"_rebaseline_2026_09_07_virtualfactory_crosses_the_new_file_cap": "open-sse/services/autoCombo/virtualFactory.ts crosses the 1200 new-file cap for the first time (1187 on the pre-wave tip, 1219 after the wave). Growth is spread across the routing/free-tier wave, not one extractable block: #12794 feeds observed breaker state and model quality into snapshot scoring instead of neutral constants, #12792 adds the reliability factor the snapshot path was still ignoring and the pooled-latency bootstrap, and #12744 tightens the free-model predicate the factory consumes, and #12795 records which filter stage emptied an auto/* pool. FROZEN RATHER THAN SPLIT, deliberately, and this is debt: two cohesive extraction candidates are ready when someone owns the move — computeSnapshotWeights (~85 lines) and the credential-eligibility group hasUsableOAuthToken/hasProviderSpecificSessionData/isKeylessEligibleConnection/hasUsableConnectionCredential (~70 lines). Either alone clears 1200 from here. Splitting three contributors' just-merged work mid-batch was the larger risk.",
|
||||
"_rebaseline_2026_09_07_streaming_wave": "Stacked growth from the SSE/streaming wave. open-sse/handlers/chatCore.ts 6021->6026 (+5): #12854 seeds the in-memory pending continuation state synchronously, before saveCallLogOperation's first await, closing the window where resolvePreviousResponseState finds nothing because the artifact write has not landed yet. open-sse/utils/stream.ts 3080->3098 (+18): #12828 emits the trailing usage-estimate chunk on the translate flush (#12151 had only covered passthrough, so translate-mode clients never saw token counts) and #12718 stops rebuilding a truncated summary from the collector's cap-dropped event array. Irreducible at the existing chokepoints — both are the flush/finalization points themselves. Covered by the continuation-store, translate-usage and collector-truncation suites.",
|
||||
"_rebaseline_2026_09_07_roundrobin_crosses_new_file_cap": "open-sse/services/combo/roundRobinCombo.ts 1198->1205, crossing the 1200 new-file cap. #12884 wires the quota-skip diagnostics into the round-robin attempt path so an ALL_TARGETS_SKIPPED 503 names which windows were exhausted instead of returning an opaque skip. The file was already at 1198 when #12811 lifted it out of combo.ts, so seven lines cross it; the diagnostics themselves live in quotaSkipDiagnostics.ts, under cap. Frozen rather than split: the natural next extraction is the attempt-loop body, which #12746/#12811 just moved and should settle before being cut again."
|
||||
"_rebaseline_2026_09_07_roundrobin_crosses_new_file_cap": "open-sse/services/combo/roundRobinCombo.ts 1198->1205, crossing the 1200 new-file cap. #12884 wires the quota-skip diagnostics into the round-robin attempt path so an ALL_TARGETS_SKIPPED 503 names which windows were exhausted instead of returning an opaque skip. The file was already at 1198 when #12811 lifted it out of combo.ts, so seven lines cross it; the diagnostics themselves live in quotaSkipDiagnostics.ts, under cap. Frozen rather than split: the natural next extraction is the attempt-loop body, which #12746/#12811 just moved and should settle before being cut again.",
|
||||
"_rebaseline_2026_09_08_13033_responses_websearch_sse": "Own growth after rebase onto v3.8.51 tip af49d4972: open-sse/handlers/chatCore.ts 6036->6035 (-1, check-file-size split-newline). Branch stamps clientRequestedResponsesStream before web_search fallback forces stream:false, then wraps JSON via synthesizeOpenAiSseFromJson. Call-site wiring next to the existing web_search non-stream fallback; no new god-file. Covered tests/unit/responses-websearch-sse-13033.test.ts."
|
||||
}
|
||||
|
||||
72
config/quality/vitest-exclusions.json
Normal file
72
config/quality/vitest-exclusions.json
Normal file
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"_comment": "Inventário dos arquivos de teste excluídos do Vitest. Toda entrada precisa de uma issue de rastreio ABERTA. Gate: npm run check:vitest-exclusions. Contexto: #13204.",
|
||||
"_measured": "2026-09-10 — cada arquivo rodado isoladamente com as exclusões removidas",
|
||||
"excluded": [
|
||||
{
|
||||
"file": "tests/unit/ui/request-logger-autorefresh-visibility-3972.test.tsx",
|
||||
"issue": "#13204",
|
||||
"measured": "2026-09-10",
|
||||
"status": "Tests 1 failed | 5 passed (6)"
|
||||
},
|
||||
{
|
||||
"file": "src/app/(dashboard)/dashboard/webhooks/__tests__/webhook-wizard.test.tsx",
|
||||
"issue": "#13204",
|
||||
"measured": "2026-09-10",
|
||||
"status": "Tests 1 failed | 6 passed (7)"
|
||||
},
|
||||
{
|
||||
"file": "tests/unit/ui/logs-page-detail-modal-reopen-on-close.test.tsx",
|
||||
"issue": "#13204",
|
||||
"measured": "2026-09-10",
|
||||
"status": "Tests 2 failed (2)"
|
||||
},
|
||||
{
|
||||
"file": "tests/unit/ui/agent-card.test.tsx",
|
||||
"issue": "#13204",
|
||||
"measured": "2026-09-10",
|
||||
"status": "Tests 3 failed | 1 passed (4)"
|
||||
},
|
||||
{
|
||||
"file": "src/app/(dashboard)/dashboard/endpoint/__tests__/ApiEndpointsTab.test.tsx",
|
||||
"issue": "#13204",
|
||||
"measured": "2026-09-10",
|
||||
"status": "Tests 2 failed | 1 passed (3)"
|
||||
},
|
||||
{
|
||||
"file": "src/app/(dashboard)/dashboard/cache/__tests__/CacheTrends.test.tsx",
|
||||
"issue": "#13204",
|
||||
"measured": "2026-09-10",
|
||||
"status": "Tests 7 failed | 6 passed (13)"
|
||||
},
|
||||
{
|
||||
"file": "src/app/(dashboard)/dashboard/cache/__tests__/IdempotencyLayer.test.tsx",
|
||||
"issue": "#13204",
|
||||
"measured": "2026-09-10",
|
||||
"status": "Tests 9 failed | 4 passed (13)"
|
||||
},
|
||||
{
|
||||
"file": "src/app/(dashboard)/dashboard/cache/__tests__/CachePerformance.test.tsx",
|
||||
"issue": "#13204",
|
||||
"measured": "2026-09-10",
|
||||
"status": "Tests 10 failed | 4 passed (14)"
|
||||
},
|
||||
{
|
||||
"file": "src/app/(dashboard)/dashboard/discovery/__tests__/DiscoveryPageClient.test.tsx",
|
||||
"issue": "#13204",
|
||||
"measured": "2026-09-10",
|
||||
"status": "Tests 2 failed | 1 passed (3)"
|
||||
},
|
||||
{
|
||||
"file": "tests/unit/ui/combos-page-smoke.test.tsx",
|
||||
"issue": "#13204",
|
||||
"measured": "2026-09-10",
|
||||
"status": "Tests 1 failed (1)"
|
||||
},
|
||||
{
|
||||
"file": "tests/unit/ui/evals-tab-smoke.test.tsx",
|
||||
"issue": "#13204",
|
||||
"measured": "2026-09-10",
|
||||
"status": "Tests 1 failed (1)"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -63,17 +63,22 @@ services:
|
||||
- DASHBOARD_PORT=${DASHBOARD_PORT:-${PORT:-20128}}
|
||||
- API_PORT=${API_PORT:-20129}
|
||||
- LIVE_WS_PORT=${LIVE_WS_PORT:-20132}
|
||||
- LIVE_WS_HOST=${LIVE_WS_HOST:-0.0.0.0}
|
||||
- LIVE_WS_HOST=${LIVE_WS_HOST:-127.0.0.1}
|
||||
- LIVE_WS_ALLOWED_ORIGINS=${LIVE_WS_ALLOWED_ORIGINS:-http://localhost:${PROD_DASHBOARD_PORT:-20130},http://127.0.0.1:${PROD_DASHBOARD_PORT:-20130}}
|
||||
- API_HOST=${API_HOST:-0.0.0.0}
|
||||
- HOSTNAME=0.0.0.0
|
||||
- API_HOST=${API_HOST:-127.0.0.1}
|
||||
# HOSTNAME intentionally not hardcoded to 0.0.0.0 (#12568) — let the
|
||||
# app's own loopback-first default apply unless the operator sets it.
|
||||
- DATA_DIR=/app/data
|
||||
- OMNIROUTE_BASE_PATH=${OMNIROUTE_BASE_PATH:-}
|
||||
- CHATGPT_WEB_CODEX_CDP_URL=http://chatgpt-web-codex-browser:9223
|
||||
ports:
|
||||
- "${PROD_DASHBOARD_PORT:-20130}:${DASHBOARD_PORT:-${PORT:-20128}}"
|
||||
- "${PROD_API_PORT:-20131}:${API_PORT:-20129}"
|
||||
- "${PROD_LIVE_WS_PORT:-20132}:${LIVE_WS_PORT:-20132}"
|
||||
# Loopback-only by default (#12568) — see docker-compose.yml's
|
||||
# APP_BIND_HOST comment for the rationale. Override for a LAN/WAN prod
|
||||
# deployment only once REQUIRE_API_KEY=true or a reverse proxy in front
|
||||
# of this instance is confirmed to enforce its own auth.
|
||||
- "${APP_BIND_HOST:-127.0.0.1}:${PROD_DASHBOARD_PORT:-20130}:${DASHBOARD_PORT:-${PORT:-20128}}"
|
||||
- "${APP_BIND_HOST:-127.0.0.1}:${PROD_API_PORT:-20131}:${API_PORT:-20129}"
|
||||
- "${APP_BIND_HOST:-127.0.0.1}:${PROD_LIVE_WS_PORT:-20132}:${LIVE_WS_PORT:-20132}"
|
||||
volumes:
|
||||
- omniroute-prod-data:/app/data
|
||||
healthcheck:
|
||||
|
||||
@@ -37,9 +37,9 @@ x-common: &common
|
||||
- PORT=${PORT:-20128}
|
||||
- DASHBOARD_PORT=${DASHBOARD_PORT:-20128}
|
||||
- API_PORT=${API_PORT:-20129}
|
||||
- API_HOST=${API_HOST:-0.0.0.0}
|
||||
- API_HOST=${API_HOST:-127.0.0.1}
|
||||
- LIVE_WS_PORT=${LIVE_WS_PORT:-20132}
|
||||
- LIVE_WS_HOST=${LIVE_WS_HOST:-0.0.0.0}
|
||||
- LIVE_WS_HOST=${LIVE_WS_HOST:-127.0.0.1}
|
||||
- LIVE_WS_ALLOWED_ORIGINS=${LIVE_WS_ALLOWED_ORIGINS:-http://localhost:20128,http://127.0.0.1:20128}
|
||||
- REDIS_URL=${REDIS_URL:-redis://redis:6379}
|
||||
- NODE_OPTIONS=--max-old-space-size=2048
|
||||
@@ -99,9 +99,14 @@ services:
|
||||
OMNIROUTE_BASE_PATH: ${OMNIROUTE_BASE_PATH:-}
|
||||
image: omniroute:base
|
||||
ports:
|
||||
- "${DASHBOARD_PORT:-20128}:${DASHBOARD_PORT:-20128}"
|
||||
- "${API_PORT:-20129}:${API_PORT:-20129}"
|
||||
- "${LIVE_WS_PORT:-20132}:${LIVE_WS_PORT:-20132}"
|
||||
# Loopback-only by default (#12568): with REQUIRE_API_KEY=false shipping
|
||||
# as the .env.example default, an unqualified publish spec here binds
|
||||
# 0.0.0.0 and exposes the anonymous /v1 LLM proxy on every LAN/WAN
|
||||
# interface. Set APP_BIND_HOST=0.0.0.0 only once you've confirmed
|
||||
# REQUIRE_API_KEY=true or an upstream reverse proxy enforces its own auth.
|
||||
- "${APP_BIND_HOST:-127.0.0.1}:${DASHBOARD_PORT:-20128}:${DASHBOARD_PORT:-20128}"
|
||||
- "${APP_BIND_HOST:-127.0.0.1}:${API_PORT:-20129}:${API_PORT:-20129}"
|
||||
- "${APP_BIND_HOST:-127.0.0.1}:${LIVE_WS_PORT:-20132}:${LIVE_WS_PORT:-20132}"
|
||||
profiles:
|
||||
- base
|
||||
|
||||
@@ -126,17 +131,17 @@ services:
|
||||
- PORT=${PORT:-20128}
|
||||
- DASHBOARD_PORT=${DASHBOARD_PORT:-20128}
|
||||
- API_PORT=${API_PORT:-20129}
|
||||
- API_HOST=${API_HOST:-0.0.0.0}
|
||||
- API_HOST=${API_HOST:-127.0.0.1}
|
||||
- LIVE_WS_PORT=${LIVE_WS_PORT:-20132}
|
||||
- LIVE_WS_HOST=${LIVE_WS_HOST:-0.0.0.0}
|
||||
- LIVE_WS_HOST=${LIVE_WS_HOST:-127.0.0.1}
|
||||
- LIVE_WS_ALLOWED_ORIGINS=${LIVE_WS_ALLOWED_ORIGINS:-http://localhost:20128,http://127.0.0.1:20128}
|
||||
- REDIS_URL=${REDIS_URL:-redis://redis:6379}
|
||||
- OMNIROUTE_BASE_PATH=${OMNIROUTE_BASE_PATH:-}
|
||||
- CHATGPT_WEB_CODEX_CDP_URL=http://chatgpt-web-codex-browser:9223
|
||||
ports:
|
||||
- "${DASHBOARD_PORT:-20128}:${DASHBOARD_PORT:-20128}"
|
||||
- "${API_PORT:-20129}:${API_PORT:-20129}"
|
||||
- "${LIVE_WS_PORT:-20132}:${LIVE_WS_PORT:-20132}"
|
||||
- "${APP_BIND_HOST:-127.0.0.1}:${DASHBOARD_PORT:-20128}:${DASHBOARD_PORT:-20128}"
|
||||
- "${APP_BIND_HOST:-127.0.0.1}:${API_PORT:-20129}:${API_PORT:-20129}"
|
||||
- "${APP_BIND_HOST:-127.0.0.1}:${LIVE_WS_PORT:-20132}:${LIVE_WS_PORT:-20132}"
|
||||
profiles:
|
||||
- web
|
||||
|
||||
@@ -165,9 +170,9 @@ services:
|
||||
OMNIROUTE_BASE_PATH: ${OMNIROUTE_BASE_PATH:-}
|
||||
image: omniroute:cli
|
||||
ports:
|
||||
- "${DASHBOARD_PORT:-20128}:${DASHBOARD_PORT:-20128}"
|
||||
- "${API_PORT:-20129}:${API_PORT:-20129}"
|
||||
- "${LIVE_WS_PORT:-20132}:${LIVE_WS_PORT:-20132}"
|
||||
- "${APP_BIND_HOST:-127.0.0.1}:${DASHBOARD_PORT:-20128}:${DASHBOARD_PORT:-20128}"
|
||||
- "${APP_BIND_HOST:-127.0.0.1}:${API_PORT:-20129}:${API_PORT:-20129}"
|
||||
- "${APP_BIND_HOST:-127.0.0.1}:${LIVE_WS_PORT:-20132}:${LIVE_WS_PORT:-20132}"
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
# SECURITY: mounting the host Docker socket gives this container full
|
||||
@@ -194,17 +199,17 @@ services:
|
||||
OMNIROUTE_BASE_PATH: ${OMNIROUTE_BASE_PATH:-}
|
||||
image: omniroute:base
|
||||
ports:
|
||||
- "${DASHBOARD_PORT:-20128}:${DASHBOARD_PORT:-20128}"
|
||||
- "${API_PORT:-20129}:${API_PORT:-20129}"
|
||||
- "${LIVE_WS_PORT:-20132}:${LIVE_WS_PORT:-20132}"
|
||||
- "${APP_BIND_HOST:-127.0.0.1}:${DASHBOARD_PORT:-20128}:${DASHBOARD_PORT:-20128}"
|
||||
- "${APP_BIND_HOST:-127.0.0.1}:${API_PORT:-20129}:${API_PORT:-20129}"
|
||||
- "${APP_BIND_HOST:-127.0.0.1}:${LIVE_WS_PORT:-20132}:${LIVE_WS_PORT:-20132}"
|
||||
environment:
|
||||
- DATA_DIR=/app/data
|
||||
- PORT=${PORT:-20128}
|
||||
- DASHBOARD_PORT=${DASHBOARD_PORT:-20128}
|
||||
- API_PORT=${API_PORT:-20129}
|
||||
- API_HOST=${API_HOST:-0.0.0.0}
|
||||
- API_HOST=${API_HOST:-127.0.0.1}
|
||||
- LIVE_WS_PORT=${LIVE_WS_PORT:-20132}
|
||||
- LIVE_WS_HOST=${LIVE_WS_HOST:-0.0.0.0}
|
||||
- LIVE_WS_HOST=${LIVE_WS_HOST:-127.0.0.1}
|
||||
- LIVE_WS_ALLOWED_ORIGINS=${LIVE_WS_ALLOWED_ORIGINS:-http://localhost:20128,http://127.0.0.1:20128}
|
||||
- CLI_MODE=host
|
||||
- CLI_EXTRA_PATHS=/host-local/bin:/host-node/bin
|
||||
@@ -243,8 +248,8 @@ services:
|
||||
container_name: omniroute-qdrant
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${QDRANT_PORT:-6333}:6333"
|
||||
- "${QDRANT_GRPC_PORT:-6334}:6334"
|
||||
- "${QDRANT_BIND_HOST:-127.0.0.1}:${QDRANT_PORT:-6333}:6333"
|
||||
- "${QDRANT_BIND_HOST:-127.0.0.1}:${QDRANT_GRPC_PORT:-6334}:6334"
|
||||
volumes:
|
||||
- qdrant-data:/qdrant/storage
|
||||
environment:
|
||||
@@ -271,7 +276,7 @@ services:
|
||||
container_name: omniroute-bifrost
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${BIFROST_PORT:-8080}:8080"
|
||||
- "${BIFROST_BIND_HOST:-127.0.0.1}:${BIFROST_PORT:-8080}:8080"
|
||||
volumes:
|
||||
- bifrost-data:/data
|
||||
environment:
|
||||
@@ -294,12 +299,22 @@ services:
|
||||
image: docker.io/eceasy/cli-proxy-api:v6.9.7
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${CLIPROXYAPI_PORT:-8317}:${CLIPROXYAPI_PORT:-8317}"
|
||||
# Loopback-only by default: this sidecar's data volume
|
||||
# (cliproxiapi-data:/root/.cli-proxy-api) holds provider OAuth/API
|
||||
# credentials, and the pinned image only reads api-keys from a mounted
|
||||
# config.yaml (not env vars), so an unqualified "8317:8317" publish spec
|
||||
# would put a credential-bearing service with no compose-configured
|
||||
# data-plane auth on every LAN interface. Same reasoning as Redis above.
|
||||
- "${CLIPROXY_BIND_HOST:-127.0.0.1}:${CLIPROXYAPI_PORT:-8317}:${CLIPROXYAPI_PORT:-8317}"
|
||||
volumes:
|
||||
- cliproxyapi-data:/root/.cli-proxy-api
|
||||
environment:
|
||||
- PORT=${CLIPROXYAPI_PORT:-8317}
|
||||
- HOST=0.0.0.0
|
||||
# Forwards to the one auth-related env var the pinned binary actually
|
||||
# reads (MANAGEMENT_PASSWORD) — secures the management API only; the
|
||||
# data-plane completions endpoints have no env-based override upstream.
|
||||
- MANAGEMENT_PASSWORD=${CLIPROXYAPI_MANAGEMENT_KEY:-}
|
||||
healthcheck:
|
||||
test:
|
||||
["CMD", "wget", "--spider", "-q", "http://127.0.0.1:${CLIPROXYAPI_PORT:-8317}/v1/models"]
|
||||
|
||||
@@ -8,6 +8,14 @@
|
||||
# Chrome 150 ignores --remote-debugging-address and binds loopback only.
|
||||
# The OmniRoute server harvests cookies over the host-mapped 9223.
|
||||
#
|
||||
# SECURITY (#12571): 9223 is gated by a per-session shared secret
|
||||
# (CDP_BRIDGE_TOKEN, injected via `-e` by src/lib/vncSession/service.ts) that
|
||||
# every caller must present as an `X-Omni-Cdp-Token` header before the bridge
|
||||
# forwards a single byte to Chromium — see cdp-bridge.py for the check. The
|
||||
# container also runs on a dedicated Docker network (not the default bridge)
|
||||
# so sibling containers can't reach 9223 either. Do not remove either control
|
||||
# or the CDP bridge reverts to an unauthenticated, full-session-takeover proxy.
|
||||
#
|
||||
# Alpine/Debian package mirrors are unreachable from the build sandbox, so we
|
||||
# extend a prebuilt image rather than apt/apk-installing anything.
|
||||
FROM linuxserver/chromium:latest
|
||||
|
||||
@@ -5,19 +5,64 @@ Chrome binds DevTools to 127.0.0.1 only and ignores --remote-debugging-address
|
||||
on recent versions, so the host can't reach it via `docker -p 9222:9222`. This
|
||||
tiny TCP bridge (run inside the container) exposes the same CDP on all
|
||||
interfaces so the OmniRoute server's VNC harvester can connect from the host.
|
||||
|
||||
SECURITY (#12571): 9223 is reachable by any sibling container on the same
|
||||
Docker bridge network, not just the host, and CDP grants full control over a
|
||||
live, credential-bearing browser session (Runtime.evaluate, cookie theft,
|
||||
etc). Every connection MUST present the shared secret in CDP_BRIDGE_TOKEN
|
||||
(env, injected per-session by src/lib/vncSession/service.ts) as an
|
||||
`X-Omni-Cdp-Token: <token>` header on its first HTTP request/WS-upgrade
|
||||
before a single byte is forwarded upstream. A missing/invalid token gets the
|
||||
connection closed immediately with no response, so probing gives no signal.
|
||||
"""
|
||||
import socket, threading, sys
|
||||
import os, socket, threading, sys
|
||||
|
||||
SRC_HOST, SRC_PORT = "127.0.0.1", 9222
|
||||
PUB_HOST, PUB_PORT = "0.0.0.0", 9223
|
||||
TOKEN = os.environ.get("CDP_BRIDGE_TOKEN", "")
|
||||
TOKEN_HEADER = f"x-omni-cdp-token: {TOKEN}".lower()
|
||||
PEEK_TIMEOUT_S = 5
|
||||
MAX_PEEK_BYTES = 8192
|
||||
|
||||
|
||||
def has_valid_token(initial_chunk: bytes) -> bool:
|
||||
"""Check whether the client's first bytes carry the configured secret.
|
||||
|
||||
A missing/empty TOKEN always fails closed (no caller can present a valid
|
||||
empty header line the way this check is written).
|
||||
"""
|
||||
if not TOKEN:
|
||||
return False
|
||||
try:
|
||||
text = initial_chunk.decode("latin-1", errors="ignore").lower()
|
||||
except (UnicodeDecodeError, LookupError):
|
||||
return False
|
||||
return TOKEN_HEADER in text
|
||||
|
||||
|
||||
def read_initial_chunk(client):
|
||||
client.settimeout(PEEK_TIMEOUT_S)
|
||||
try:
|
||||
return client.recv(MAX_PEEK_BYTES)
|
||||
except OSError:
|
||||
return b""
|
||||
finally:
|
||||
client.settimeout(None)
|
||||
|
||||
|
||||
def bridge(client, target_addr):
|
||||
initial = read_initial_chunk(client)
|
||||
if not has_valid_token(initial):
|
||||
client.close()
|
||||
return
|
||||
|
||||
try:
|
||||
upstream = socket.create_connection(target_addr, timeout=10)
|
||||
upstream.sendall(initial)
|
||||
except OSError:
|
||||
client.close()
|
||||
return
|
||||
|
||||
a = threading.Thread(target=pipe, args=(client, upstream), daemon=True)
|
||||
b = threading.Thread(target=pipe, args=(upstream, client), daemon=True)
|
||||
a.start(); b.start()
|
||||
|
||||
@@ -494,7 +494,7 @@ the global circuit breaker / connection cooldown / model lockout layers:
|
||||
- Claude Code obfuscation: `open-sse/services/claudeCodeObfuscation.ts`
|
||||
|
||||
For the full stealth playbook and operational guidance, see
|
||||
[`docs/security/STEALTH_GUIDE.md`](../security/STEALTH_GUIDE.md).
|
||||
`docs/security/STEALTH_GUIDE.md` (git; not compiled into `/docs`).
|
||||
|
||||
### H. Webhooks, Reasoning Cache, Read Cache
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user