mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-08 08:12:20 +03:00
Compare commits
1 Commits
fix/codex-
...
fix/deps-m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9233a9483c |
197
.cbmignore
197
.cbmignore
@@ -1,197 +0,0 @@
|
||||
# codebase-memory-mcp ignore list
|
||||
#
|
||||
# Padrão gitignore-style. Linhas começando com `#` são comentários.
|
||||
# Barra final (`/`) = só diretório. Sem barra = casa arquivo OU diretório.
|
||||
#
|
||||
# O CBM também lê `.gitignore` automaticamente — esta lista deixa explícito o que
|
||||
# os hooks do CBM vão pular. Se uma regra entrar em conflito entre os dois arquivos,
|
||||
# vale a união. Editar este arquivo é mais barato do que confiar na herança implícita.
|
||||
#
|
||||
# Última reconciliação: 2026-07-31, status `ready` (513k nodes / 689k edges),
|
||||
# `auto_index_limit=50000`, total indexável medido ≈11.546 arquivos (folga 4,3×).
|
||||
#
|
||||
# Fontes cruzadas:
|
||||
# - `codebase-memory-mcp cli index_status --project home-diegosouzapw-dev-proxys-OmniRoute`
|
||||
# → `not_indexed.dirs` (27) + `not_indexed.files` (336), todos `BY DESIGN`.
|
||||
# - `.gitignore` deste repo (5.691 B) — fonte canônica secundária.
|
||||
#
|
||||
# Como auditar mudanças: depois de editar este arquivo, rodar `index_repository`
|
||||
# (ou esperar `auto_watch` re-indexar) e re-checar `cli index_status` → comparar
|
||||
# contagens em `not_indexed.dirs_count` e `not_indexed.files_count`.
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 1. Diretorios de runtime / pacote — nao sao codigo-fonte
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
node_modules/
|
||||
node_modules
|
||||
|
||||
# Builds e artefatos reproduziveis (Layer 1 Next.js / Electron)
|
||||
.build/
|
||||
dist/
|
||||
.next/
|
||||
out/
|
||||
|
||||
# Electron especifico
|
||||
electron/dist-electron/
|
||||
electron/node_modules/
|
||||
icon.iconset/
|
||||
|
||||
# Workspaces internos que tem proprio node_modules
|
||||
@omniroute/opencode-plugin/dist/
|
||||
@omniroute/opencode-plugin/node_modules/
|
||||
@omniroute/opencode-provider/dist/
|
||||
@omniroute/opencode-provider/node_modules/
|
||||
|
||||
# Recursos nativos compilados (C/JNI/wasm)
|
||||
src/mitm/tproxy/native/build/
|
||||
|
||||
# Artefatos locais do Stryker / Playwright / coverage
|
||||
.stryker-tmp/
|
||||
reports/mutation/
|
||||
stryker-output-*.json
|
||||
.playwright-mcp/
|
||||
test-results/
|
||||
playwright-report/
|
||||
blob-report/
|
||||
|
||||
# Analise / linters / caches
|
||||
.analysis/
|
||||
.sisyphus/
|
||||
.plans/
|
||||
.gitnexus
|
||||
.worktrees
|
||||
.codegraph/
|
||||
|
||||
# Quality artifacts (gerados por npm run lint --cache etc)
|
||||
.eslintcache
|
||||
.eslintcache-complexity
|
||||
|
||||
# Claude Code local state
|
||||
.claude/scheduled_tasks.lock
|
||||
.claude/scheduled_tasks/
|
||||
.claude/sessions/
|
||||
.claude/state.json
|
||||
.claude/settings.local.json
|
||||
|
||||
# Serena / Antigravity / outras tools locais
|
||||
.serena/
|
||||
.antigravitycli/
|
||||
.gemini/
|
||||
.config/
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 2. Diretorios com prefixo `_` — locais / privados (regra global do .gitignore)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
_*/
|
||||
_artifacts/
|
||||
_cache/
|
||||
_mono_repo/
|
||||
_references/
|
||||
_tasks/
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 3. Diretorios de tooling IA (state local, nao codigo)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
.agents/
|
||||
.claude/
|
||||
.vscode/
|
||||
.idea/
|
||||
.junie/
|
||||
.omc/
|
||||
.data/
|
||||
.data-dev/
|
||||
.local-data/
|
||||
.logs/
|
||||
.artifacts/
|
||||
.source/
|
||||
.superpowers/
|
||||
.claude-flow/
|
||||
.omnivscodeagent/
|
||||
omnirouteCloud/
|
||||
omnirouteSite/
|
||||
.omniroute/
|
||||
.stent/
|
||||
|
||||
# Subpaths especificos do Claude Code que nao estao em .claude/ (criados sob repo)
|
||||
.claude/worktrees/
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 4. Diretorios de dados / runtime locais (storage, env, secrets, scratch)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
data/
|
||||
src/lib/env/
|
||||
src/app/api/agent-skills/coverage/
|
||||
src/app/api/cloud/
|
||||
src/app/api/sync/cloud/
|
||||
src/app/api/system/env/
|
||||
tests/golden-set/data/
|
||||
|
||||
# Logs e saida de teste
|
||||
logs/*
|
||||
test_output.log
|
||||
home-diegosouzapw-dev-automacoes-*.txt
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 5. Diretorios do monorepo por subprojeto (nao fazem parte do app principal)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
security-analysis/
|
||||
vscode-extension/
|
||||
obsidian-plugin/node_modules/
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 6. Diretorios de documentacao interna / workflow
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
docs/superpowers/
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 7. Arquivos especificos (nao diretorios inteiros)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
# Segredos e env — NUNCA indexar
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.homolog.example
|
||||
|
||||
# TypeScript build info e next env declaration
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
typescript
|
||||
|
||||
# SQLite transient files (WAL/SHM/journal)
|
||||
*.sqlite-shm
|
||||
*.sqlite-wal
|
||||
*.sqlite-journal
|
||||
|
||||
# Mapas e source maps
|
||||
*.map
|
||||
|
||||
# Bun / npm lockfiles ruidosos
|
||||
bun.lock
|
||||
|
||||
# `cheaper-inference-gateway.svg` e arquivos de midia na raiz/asset ja cobertos
|
||||
# pelos `ignored-suffix` do indexador (svg/png/jpg/ico/etc >50kB ou >500linhas);
|
||||
# manter a regra explicita aqui ajuda a auditar:
|
||||
cheaper-inference-gateway.svg
|
||||
cheaper-inference-gateway-*.svg
|
||||
|
||||
# Husky internals
|
||||
.husky/_/
|
||||
|
||||
# CI / quality metric artifacts
|
||||
config/quality/quality-metrics.json
|
||||
config/quality/test-impact-map.json
|
||||
audit-report.json
|
||||
.gh-discussions.json
|
||||
|
||||
# i18n audit (gerado por npm run scripts)
|
||||
scripts/i18n/_audit.json
|
||||
scripts/i18n/_pending-keys.json
|
||||
|
||||
# Cli binario local (scratch)
|
||||
bin/omniroute.mjs
|
||||
|
||||
# Deploy / docker backups
|
||||
deploy.sh
|
||||
docker-compose.yml.bak
|
||||
docker-compose.minimal.yml
|
||||
@@ -1414,6 +1414,10 @@ APP_LOG_TO_FILE=true
|
||||
# Default: ~/.omniroute/plugins/ Override in dev/CI to point at a local plugin tree.
|
||||
# OMNIROUTE_PLUGIN_PATH=
|
||||
|
||||
# Allow plugins to request the 'exec' permission (spawn child processes from the
|
||||
# plugin worker sandbox). Disabled by default; set to 1 to enable (local operator only).
|
||||
# OMNIROUTE_PLUGINS_ALLOW_EXEC=0
|
||||
|
||||
# ── Prompt cache (system prompt deduplication) ──
|
||||
# Used by: open-sse/services — caches identical system prompts across requests.
|
||||
# PROMPT_CACHE_MAX_SIZE=50 # Max cached entries (default: 50)
|
||||
|
||||
11
.github/pull_request_template.md
vendored
11
.github/pull_request_template.md
vendored
@@ -9,14 +9,11 @@
|
||||
|
||||
## Validation
|
||||
|
||||
Choose the change type and focused loop from the
|
||||
[Contribution Golden Path](../docs/dev/CONTRIBUTION_GOLDEN_PATH.md). The full unit suite,
|
||||
Vitest, the 60% coverage gate, and the production build all run in CI on this PR (#8329):
|
||||
Run only the focused loop for what you changed — the full unit suite, Vitest, the
|
||||
60% coverage gate, and the production build all run in CI on this PR (#8329):
|
||||
|
||||
- [ ] Change type: provider / routing / UI / i18n / CLI / DB / build-deploy / other
|
||||
- [ ] Focused tests and category gates from the golden path
|
||||
- [ ] Focused tests for the change: `node --import tsx/esm --test tests/unit/<file>.test.ts`
|
||||
- [ ] `npm run lint`
|
||||
- [ ] Reconciled with the current active release base; focused checks rerun afterward
|
||||
- [ ] Production-code changes include a new or updated automated test in this PR
|
||||
- [ ] SonarQube PR analysis is green or any remaining issues are explicitly documented below
|
||||
|
||||
@@ -32,4 +29,4 @@ Vitest, the 60% coverage gate, and the production build all run in CI on this PR
|
||||
|
||||
## Reviewer Notes
|
||||
|
||||
- Call out any risky areas, migrations, feature flags, or manual validation that reviewers should know about.
|
||||
- Call out any risky areas, migrations, feature flags, or manual validation that reviewers should know about.
|
||||
71
.github/workflows/build-fork.yml
vendored
Normal file
71
.github/workflows/build-fork.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
name: Publish Fork Image to GHCR
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
|
||||
# Least-privilege default: read-only at the top level; the build job that pushes to
|
||||
# GHCR grants packages: write itself (Scorecard TokenPermissions).
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
IMAGE_NAME: ghcr.io/kang-heewon/omniroute
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and Push Fork Image
|
||||
if: github.repository == 'kang-heewon/OmniRoute'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: ${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
type=sha,prefix=sha-
|
||||
type=ref,event=tag
|
||||
labels: |
|
||||
org.opencontainers.image.title=omniroute
|
||||
org.opencontainers.image.description=Unified AI proxy/router — fork image
|
||||
org.opencontainers.image.url=https://github.com/kang-heewon/OmniRoute
|
||||
org.opencontainers.image.source=https://github.com/kang-heewon/OmniRoute
|
||||
org.opencontainers.image.licenses=MIT
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
target: runner-base
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
39
.github/workflows/build-rinseaid-image.yml
vendored
Normal file
39
.github/workflows/build-rinseaid-image.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
name: Build Rinseaid OmniRoute image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [build-k3-reasoning-image]
|
||||
paths:
|
||||
- Dockerfile
|
||||
- package-lock.json
|
||||
- package.json
|
||||
- open-sse/**
|
||||
- scripts/build/**
|
||||
- .github/workflows/build-rinseaid-image.yml
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
target: runner-base
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ghcr.io/rinseaid/omniroute:k3-reasoning-${{ github.sha }}
|
||||
76
.github/workflows/ci.yml
vendored
76
.github/workflows/ci.yml
vendored
@@ -42,18 +42,6 @@ jobs:
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: ${{ env.CI_NODE_VERSION }}
|
||||
# Refuse a PR that targets its own head branch before spending anything on it. #8912 has
|
||||
# head == base == release/v3.8.50: no diff, can never merge, and it sits in the queue with
|
||||
# a full check board attached on every push to that branch. One field comparison.
|
||||
- name: Reject a PR that targets its own branch
|
||||
if: github.event_name == 'pull_request'
|
||||
env:
|
||||
HEAD_REF: ${{ github.head_ref }}
|
||||
BASE_REF: ${{ github.base_ref }}
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
run: node scripts/check/check-pr-self-target.mjs
|
||||
|
||||
- id: classify
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
@@ -137,17 +125,6 @@ jobs:
|
||||
- run: npm run check:route-guard-membership
|
||||
- run: npm run check:test-discovery
|
||||
- run: npm run check:tracked-artifacts
|
||||
# (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.
|
||||
#
|
||||
# Keeping it PR-only left a real hole. This cycle's merge trains landed in batches with
|
||||
# `--admin`, which bypasses required checks, so three SKILL.md files drifted from the route
|
||||
# catalog, rode the release squash into `main`, and the next cycle's sync-back turned them
|
||||
# into a base-red that blocked EVERY PR into release/v3.8.50 until #8954. Running it here
|
||||
# means a push to `main` catches the drift at the source instead of the next cycle
|
||||
# inheriting it.
|
||||
- run: npm run check:agent-skills-sync
|
||||
# WS1.7 (v3.8.49 plan): Dockerfile lint (hadolint, pinned by digest).
|
||||
# failure-threshold=error keeps the 5 pre-existing warnings (DL3008/DL3003/
|
||||
# DL3016 version pinning / WORKDIR) visible without blocking; any ERROR fails.
|
||||
@@ -353,16 +330,8 @@ jobs:
|
||||
install -m 0755 /tmp/osv/*linux_amd64 "$HOME/.local/bin/osv-scanner"
|
||||
# actionlint — official download script
|
||||
bash <(curl -fsSL https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) latest "$HOME/.local/bin"
|
||||
# zizmor — PyPI (pipx preferred, pip --user fallback); lands in ~/.local/bin.
|
||||
# PINNED on purpose. Unpinned, the runner installed whatever PyPI served that day and
|
||||
# measured 1 finding MORE than the devbox on the identical commit (190 vs 189) during
|
||||
# the v3.8.49 cycle — which cost a second rebaseline push per release, chasing a
|
||||
# number that was never the code's. The ratchet compares counts across machines, so
|
||||
# the auditor version has to be the same on both. Bump this deliberately, and
|
||||
# rebaseline in the same commit: check-workflows.mjs now prints `zizmorVersion=` next
|
||||
# to the count so the new number is traceable to the tool that produced it.
|
||||
ZIZMOR_VERSION=1.25.2
|
||||
pipx install "zizmor==$ZIZMOR_VERSION" || pip install --user "zizmor==$ZIZMOR_VERSION"
|
||||
# zizmor — PyPI (pipx preferred, pip --user fallback); lands in ~/.local/bin
|
||||
pipx install zizmor || pip install --user zizmor
|
||||
# oasdiff — download latest linux amd64 tarball via gh (authed), extract binary
|
||||
rm -rf /tmp/oasd && mkdir -p /tmp/oasd
|
||||
gh release download --repo oasdiff/oasdiff --pattern '*linux_amd64.tar.gz' --dir /tmp/oasd
|
||||
@@ -751,13 +720,7 @@ jobs:
|
||||
test-unit:
|
||||
name: Unit Tests (${{ matrix.shard }}/8)
|
||||
# Same dynamic-runner rule as Build (own-origin only; fallback ubuntu-latest).
|
||||
# PINNED to hosted, deliberately not on the USE_VPS_RUNNER switch (gap 19). One variable
|
||||
# governed the build and the test jobs, which want OPPOSITE machines: the build needs the
|
||||
# .113's RAM, the tests need the hosted runner's link. Measured on 2026-07-29 —
|
||||
# actions/setup-node took 20m06s on .113 with 4 concurrent runners versus 16s hosted (npm
|
||||
# cache restore saturating the link), while the tests themselves tied, 2m54 vs 2m31. So
|
||||
# self-hosted is strictly worse here and there is nothing to configure.
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)) && fromJSON('["self-hosted","omni-release"]') || 'ubuntu-latest' }}
|
||||
timeout-minutes: 25
|
||||
# needs: changes (not build) — this job never downloads the next-build artifact;
|
||||
# gating it on Build only serialized ~20min of wall-clock for nothing. Jobs that
|
||||
@@ -829,13 +792,7 @@ jobs:
|
||||
test-vitest:
|
||||
name: Vitest (MCP / autoCombo / UI components)
|
||||
# Same dynamic-runner rule as Build (own-origin only; fallback ubuntu-latest).
|
||||
# PINNED to hosted, deliberately not on the USE_VPS_RUNNER switch (gap 19). One variable
|
||||
# governed the build and the test jobs, which want OPPOSITE machines: the build needs the
|
||||
# .113's RAM, the tests need the hosted runner's link. Measured on 2026-07-29 —
|
||||
# actions/setup-node took 20m06s on .113 with 4 concurrent runners versus 16s hosted (npm
|
||||
# cache restore saturating the link), while the tests themselves tied, 2m54 vs 2m31. So
|
||||
# self-hosted is strictly worse here and there is nothing to configure.
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)) && fromJSON('["self-hosted","omni-release"]') || 'ubuntu-latest' }}
|
||||
timeout-minutes: 15
|
||||
# needs: changes (not build) — no artifact consumed; see test-unit note.
|
||||
needs: changes
|
||||
@@ -1272,8 +1229,6 @@ jobs:
|
||||
- name: Generate dashboard
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
# Workflow-controlled data (job results), not user input — safe to read here.
|
||||
NEEDS_JSON: ${{ toJSON(needs) }}
|
||||
run: |
|
||||
status() {
|
||||
case "$1" in
|
||||
@@ -1288,29 +1243,6 @@ jobs:
|
||||
echo "# 🚀 CI Dashboard" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
# (gap 12) A cancelled job never reported a verdict, and in a long table that reads the
|
||||
# same as a green one. `cancel-in-progress` plus incremental fixing cancels jobs on every
|
||||
# push, and this cycle the Vitest job was cancelled in rounds 1, 2 and 3 — it only ran to
|
||||
# completion in round 4, where it revealed a suite that had been broken the whole cycle
|
||||
# plus two production bugs. A gate that never finishes is indistinguishable from one that
|
||||
# passes, so name them at the TOP instead of leaving them to be spotted mid-table.
|
||||
CANCELLED_JOBS=$(printf '%s' "$NEEDS_JSON" \
|
||||
| jq -r 'to_entries | map(select(.value.result == "cancelled")) | .[].key' 2>/dev/null \
|
||||
| sort | paste -sd", " -) || CANCELLED_JOBS=""
|
||||
if [ -n "$CANCELLED_JOBS" ]; then
|
||||
{
|
||||
echo "> ### ⚫ Cancelled — no verdict was reported"
|
||||
echo ">"
|
||||
echo "> \`$CANCELLED_JOBS\`"
|
||||
echo ">"
|
||||
echo "> These did not fail; they never finished, so nothing was checked. Treat this"
|
||||
echo "> run as INCOMPLETE for those gates. If the cancellation came from"
|
||||
echo "> \`cancel-in-progress\` on a newer push, the newer run covers it — otherwise"
|
||||
echo "> re-run them before reading this dashboard as green."
|
||||
echo ""
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
|
||||
echo "## 🧱 Core Checks" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "| Job | Status |" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "|-----|--------|" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
89
.github/workflows/electron-release.yml
vendored
89
.github/workflows/electron-release.yml
vendored
@@ -120,18 +120,6 @@ jobs:
|
||||
env:
|
||||
JWT_SECRET: ci-build-secret-with-sufficient-length-for-validation
|
||||
NODE_OPTIONS: "--max_old_space_size=6144"
|
||||
# Linux builds with webpack, not Turbopack. Turbopack's production build
|
||||
# allocates natively (Rust, off the V8 heap), so --max_old_space_size does
|
||||
# not bound it, and on this module graph it peaks above what the hosted
|
||||
# runner can give — the VM is reclaimed mid-compile with "The runner has
|
||||
# received a shutdown signal", no exit code. That is what silently took the
|
||||
# whole desktop channel out of v3.8.49: the linux leg died, `release` was
|
||||
# skipped, and the release shipped with ZERO assets. Measured on a 32 GB
|
||||
# box the same build passes and peaks past 14 GB. The webpack fallback is
|
||||
# the project's documented escape hatch for RAM-constrained machines
|
||||
# (docs/reference/ENVIRONMENT.md, #6409) and is the same remedy already
|
||||
# applied to nightly-compat's Node 26 build (#8090).
|
||||
OMNIROUTE_USE_TURBOPACK: ${{ matrix.platform == 'linux' && '0' || '1' }}
|
||||
run: npm run build
|
||||
|
||||
- name: Sync version in electron/package.json
|
||||
@@ -229,16 +217,6 @@ jobs:
|
||||
release:
|
||||
name: Create Release
|
||||
needs: [validate, build]
|
||||
# Fail-partial, not fail-closed. `build` is a 4-leg matrix with `fail-fast: false`,
|
||||
# so the legs that succeed still upload their artifacts — but a default `needs:`
|
||||
# gate skips this job the moment ANY leg fails, discarding all of them. That is
|
||||
# exactly what happened to v3.8.49: the linux leg died and the release shipped with
|
||||
# ZERO assets, throwing away 1.7 GB of good Windows/macOS installers **and** the
|
||||
# source archives + SBOM, which do not depend on a build at all. The result was
|
||||
# indistinguishable from "this version has no desktop channel".
|
||||
# Now: attach everything that did build, then fail the job loudly (see the last
|
||||
# step) so an incomplete channel is visible instead of silent.
|
||||
if: ${{ !cancelled() && needs.validate.result == 'success' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write # softprops/action-gh-release creates the GitHub Release
|
||||
@@ -249,33 +227,11 @@ jobs:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
# `merge-multiple` is deliberately OFF. It resolves same-name collisions by ARRIVAL
|
||||
# ORDER, and the two macOS jobs each emit their own `latest-mac.yml` listing only their
|
||||
# own dmg (measured: 338 and 350 bytes, different content, identical name). One silently
|
||||
# overwrote the other — arm64 won in the published v3.8.48, and since the Intel dmg
|
||||
# carries no arch suffix in its name, electron-updater's
|
||||
# `files.find(url includes process.arch) ?? files.shift()` sends every Intel Mac to the
|
||||
# ARM dmg. Downloading into per-artifact subdirectories keeps both, so they can be
|
||||
# merged on purpose instead of by luck.
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
# Writes release-assets/latest-mac.yml with BOTH dmgs, un-suffixed entry first (that is
|
||||
# the one electron-updater can only reach through its fallback). Refuses to write when the
|
||||
# inputs disagree on version — a manifest stitched from two builds is worse than none.
|
||||
- name: Merge the per-arch macOS updater manifests
|
||||
run: node scripts/release/merge-mac-update-manifest.mjs artifacts release-assets
|
||||
|
||||
# Everything else moves across as-is. The partial latest-mac.yml files are excluded so
|
||||
# they cannot clobber the merged one; -n is a second belt on the same braces.
|
||||
- name: Collect the remaining artifacts
|
||||
run: |
|
||||
mkdir -p release-assets
|
||||
find artifacts -type f ! -name latest-mac.yml -exec cp -n {} release-assets/ \;
|
||||
echo "release-assets:"
|
||||
ls -la release-assets/
|
||||
path: release-assets
|
||||
merge-multiple: true
|
||||
|
||||
- name: Create source archives
|
||||
env:
|
||||
@@ -319,47 +275,6 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
verify-desktop-assets:
|
||||
name: Verify desktop assets landed
|
||||
needs: [validate, release]
|
||||
# Deliberately a SEPARATE job, not a final step of `release`: failing inside
|
||||
# `release` would cascade into `publish-npm` (which gates on `needs: release`) and
|
||||
# block the npm channel over a desktop-only gap. Here the assets are attached, npm
|
||||
# still publishes, and an incomplete desktop channel shows up as a red job instead
|
||||
# of passing unnoticed — the v3.8.49 release had ZERO assets and every gate was
|
||||
# green, because nothing ever asserted the release HAS binaries.
|
||||
if: ${{ !cancelled() && needs.release.result == 'success' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Assert every platform is present on the release
|
||||
env:
|
||||
# Regex-validated (^v[0-9]+\.[0-9]+\.[0-9]+$) in the `validate` job, and
|
||||
# passed via env rather than interpolated into the script body.
|
||||
VERSION: ${{ needs.validate.outputs.version }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
names=$(gh release view "$VERSION" --repo "$GITHUB_REPOSITORY" \
|
||||
--json assets --jq '.assets[].name')
|
||||
echo "Assets on $VERSION:"
|
||||
echo "$names" | sed 's/^/ /'
|
||||
|
||||
missing=""
|
||||
# `[ ... ] && missing=...` as the last command in a branch returns 1 and
|
||||
# would abort the whole script under Actions' default `set -e`. Use if/fi.
|
||||
for want in '\.exe$' '\.dmg$' '\.AppImage$' '\.deb$' '^latest.*\.yml$' '\.source\.tar\.gz$'; do
|
||||
if ! echo "$names" | grep -qE "$want"; then
|
||||
missing="$missing $want"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -n "$missing" ]; then
|
||||
echo "::error::Desktop channel incomplete on $VERSION — no asset matching:$missing"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ every platform present on $VERSION"
|
||||
|
||||
publish-npm:
|
||||
name: Publish to npm
|
||||
needs: [validate, release]
|
||||
|
||||
57
.github/workflows/npm-publish.yml
vendored
57
.github/workflows/npm-publish.yml
vendored
@@ -159,17 +159,6 @@ jobs:
|
||||
# `head_sha` is the tree-equality guarantee: same commit, same tree.
|
||||
# Best-effort by design (retention is 1 day): every miss falls through to the build
|
||||
# step below, which is why the dynamic runner above matters as the backstop.
|
||||
#
|
||||
# The `head_repository.full_name == env.REPO` clause is a supply-chain guard, not a
|
||||
# filter refinement. This artifact becomes the published npm tarball. `pull_request`
|
||||
# runs from forks execute in THIS repository's context and upload their own
|
||||
# `next-build` built from fork-controlled source, and the runs API returns them for a
|
||||
# matching `head_sha` — 57 such runs exist in this repo today. Without the clause,
|
||||
# anything that made a fork's head commit coincide with the publish commit could put
|
||||
# attacker-built bytes on npm. Requiring the run to originate from this repository
|
||||
# excludes every fork run while keeping the fast path intact (verified: the same
|
||||
# single run is selected either way for the current tip).
|
||||
# CodeQL: actions/artifact-poisoning/critical.
|
||||
- name: Reuse CI's next-build artifact (skips the heavy rebuild)
|
||||
if: steps.resolve.outputs.skip != 'true'
|
||||
continue-on-error: true
|
||||
@@ -179,36 +168,14 @@ jobs:
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
set -uo pipefail
|
||||
# The question is "which run HAS the artifact", not "which run passed" (gap 16).
|
||||
# Requiring `conclusion == "success"` on the whole run discarded a perfectly good tree
|
||||
# whenever any unrelated shard went red — one flaky test then pushed the publish into
|
||||
# the 40-minute build this step exists to avoid. The artifact is only uploaded if the
|
||||
# Build job itself succeeded, so its PRESENCE is the accurate signal; the run's overall
|
||||
# conclusion is noise from jobs that have nothing to do with the tree.
|
||||
#
|
||||
# `head_repository.full_name == env.REPO` stays, and it is not a filter refinement:
|
||||
# this tree becomes the published npm tarball, and fork `pull_request` runs execute in
|
||||
# THIS repository's context uploading their own next-build. That clause is the
|
||||
# supply-chain guard (CodeQL actions/artifact-poisoning).
|
||||
CANDIDATES=$(gh api "repos/$REPO/actions/runs?head_sha=$HEAD_SHA&per_page=100" \
|
||||
--jq '[.workflow_runs[]
|
||||
| select(.name == "CI"
|
||||
and .head_repository.full_name == env.REPO)]
|
||||
| sort_by(.run_started_at) | reverse | .[0:5] | .[].id') || CANDIDATES=""
|
||||
if [ -z "$CANDIDATES" ]; then
|
||||
echo "::notice::no CI run from this repository for $HEAD_SHA — falling back to a full build"
|
||||
RUN=$(gh api "repos/$REPO/actions/runs?head_sha=$HEAD_SHA&per_page=100" \
|
||||
--jq '[.workflow_runs[] | select(.name == "CI" and .conclusion == "success")] | .[0].id // empty') || RUN=""
|
||||
if [ -z "$RUN" ]; then
|
||||
echo "::notice::no successful CI run for $HEAD_SHA — falling back to a full build"
|
||||
exit 0
|
||||
fi
|
||||
RUN=""
|
||||
for candidate in $CANDIDATES; do
|
||||
if gh run download "$candidate" --repo "$REPO" --name next-build --dir /tmp/next-build 2>/dev/null; then
|
||||
RUN="$candidate"
|
||||
break
|
||||
fi
|
||||
echo " run $candidate carries no usable next-build — trying the next"
|
||||
done
|
||||
if [ -z "$RUN" ]; then
|
||||
echo "::notice::none of the candidate runs still carries next-build (1-day retention) — falling back to a full build"
|
||||
if ! gh run download "$RUN" --repo "$REPO" --name next-build --dir /tmp/next-build; then
|
||||
echo "::notice::next-build artifact unavailable for run $RUN (expired?) — falling back to a full build"
|
||||
exit 0
|
||||
fi
|
||||
tar -xzf /tmp/next-build/e2e-build.tar.gz -C .
|
||||
@@ -256,18 +223,6 @@ jobs:
|
||||
if: steps.resolve.outputs.skip != 'true'
|
||||
run: npm run check:pack-boot
|
||||
|
||||
# The boot-smoke above proves a CLEAN install boots. It does not prove the path that
|
||||
# actually broke us: installing over an existing version, where ~110 SQLite migrations
|
||||
# run against a populated database. v3.8.48 shipped as a hotfix because the published
|
||||
# 3.8.47 crashed on boot, and the v3.8.49 upgrade path was first exercised end-to-end
|
||||
# by hand on a real 3.8.48 box (VPS .16) — after publishing, which is exactly backwards.
|
||||
# Runs BEFORE `npm stage publish` so a broken upgrade never reaches the registry at all;
|
||||
# a staged package that is never approved simply expires, with no `npm deprecate` needed.
|
||||
- name: Prove clean-install AND upgrade-over-previous both boot
|
||||
if: steps.resolve.outputs.skip != 'true'
|
||||
timeout-minutes: 30
|
||||
run: npm run check:install-upgrade
|
||||
|
||||
# WS1.3 (D2, v3.8.49 plan): STAGED publishing by default — `npm stage publish`
|
||||
# parks the exact bytes on the registry WITHOUT making them installable; the
|
||||
# owner then verifies and approves with 2FA (`npm stage approve`), moving the
|
||||
|
||||
135
.github/workflows/quality.yml
vendored
135
.github/workflows/quality.yml
vendored
@@ -112,20 +112,7 @@ jobs:
|
||||
# release captain has USE_VPS_RUNNER=true AND this is not a fork PR (own-origin
|
||||
# branches only — a fork PR must never execute on the LAN runner). Var unset/false
|
||||
# or a fork PR falls back to ubuntu-latest, so this is inert until the flag flips.
|
||||
# PINNED to hosted (gap 19). This job carried the USE_VPS_RUNNER expression, and that
|
||||
# expression was DEAD CONFIGURATION: across 160 quality.yml runs the job never once landed on
|
||||
# a self-hosted runner — every non-skipped sample is `GitHub Actions NNNN`. The classifier is
|
||||
# not at fault: in the same window ci.yml's Build demonstrably ran on omniroute-113-7 and
|
||||
# omniroute-113-6, so self-hosted runs are visible when they happen.
|
||||
#
|
||||
# And if it ever HAD fired it would have inherited the measured penalty, because this job's
|
||||
# first two steps are exactly the bottleneck: actions/setup-node + npm ci took 20m06s on .113
|
||||
# with 4 concurrent runners versus 16s hosted (npm cache restore saturating the link). Median
|
||||
# here is 5.6 min hosted across 72 successful runs.
|
||||
#
|
||||
# With this pinned, USE_VPS_RUNNER governs ONLY build-like jobs — one variable, one coherent
|
||||
# purpose. That is what gap 19 asked for; a second variable turned out to be unnecessary.
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)) && fromJSON('["self-hosted","omni-release"]') || 'ubuntu-latest' }}
|
||||
# tsx gates (known-symbols, route-guard-membership) import modules that open
|
||||
# SQLite on load; provide DB env so a fresh CI DB initializes cleanly.
|
||||
env:
|
||||
@@ -179,81 +166,6 @@ jobs:
|
||||
# Complexity + cognitive-complexity: ONE ESLint walk (both baselines still
|
||||
# enforced separately by ruleId). Avoids two cold tree walks on fast-path.
|
||||
- run: npm run check:complexity-ratchets
|
||||
# ── G0 (trilho .50): gates do trilho A que faltavam no trilho B ──────────────
|
||||
# The god-file refactor happens in PRs→release/**; without these, the release
|
||||
# rail never sees a new import cycle, dead code, duplication or a security
|
||||
# regression until the release PR to main. Deliberately NOT brought here:
|
||||
# bundle-size (self-skips without a build — this rail's build job is advisory
|
||||
# and uploads nothing, so it would be dead configuration) and the coverage
|
||||
# run (fast-unit already runs the full suite; the coverage ratchet stays on
|
||||
# the main rail via --allow-missing in lint-guard).
|
||||
- run: npm run check:cycles
|
||||
- run: npm run check:lockfile
|
||||
- name: Duplication ratchet
|
||||
run: npm run check:duplication
|
||||
- name: Dead-code ratchet (knip)
|
||||
run: npm run check:dead-code
|
||||
- name: Type coverage ratchet
|
||||
run: npm run check:type-coverage
|
||||
- name: Compression budget ratchet
|
||||
run: npm run check:compression-budget
|
||||
# Security scanners — same hardened install as ci.yml quality-extended
|
||||
# (gh release download = authenticated, 5000 req/hr; curl to api.github.com
|
||||
# is rate-limited to 60/hr and silently no-ops when throttled). The blocking
|
||||
# gates below SKIP (exit 0) when their binary is absent — only a measured
|
||||
# regression vs config/quality/quality-baseline.json blocks.
|
||||
- name: Install security scanners (gitleaks/osv/actionlint/zizmor/oasdiff)
|
||||
continue-on-error: true
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set +e
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
# Ratchets compare scanner COUNTS across runs. Pin every auditor: a rule-set
|
||||
# update must be an explicit PR that re-measures/rebaselines, never a random
|
||||
# red (or green) caused by whatever "latest" served that morning.
|
||||
GITLEAKS_VERSION=v8.30.1
|
||||
OSV_SCANNER_VERSION=v2.3.8
|
||||
ACTIONLINT_VERSION=v1.7.12
|
||||
ZIZMOR_VERSION=1.25.2
|
||||
OASDIFF_VERSION=v1.19.1
|
||||
# gitleaks — pinned linux x64 tarball via gh (authed), extract binary
|
||||
rm -rf /tmp/gl && mkdir -p /tmp/gl
|
||||
gh release download "$GITLEAKS_VERSION" --repo gitleaks/gitleaks --pattern '*linux_x64.tar.gz' --dir /tmp/gl
|
||||
tar -xzf /tmp/gl/*linux_x64.tar.gz -C "$HOME/.local/bin" gitleaks
|
||||
# osv-scanner — pinned linux amd64 bare binary via gh (authed)
|
||||
rm -rf /tmp/osv && mkdir -p /tmp/osv
|
||||
gh release download "$OSV_SCANNER_VERSION" --repo google/osv-scanner --pattern '*linux_amd64' --dir /tmp/osv
|
||||
install -m 0755 /tmp/osv/*linux_amd64 "$HOME/.local/bin/osv-scanner"
|
||||
# actionlint — official installer from a pinned release tag (never main)
|
||||
bash <(curl -fsSL "https://raw.githubusercontent.com/rhysd/actionlint/${ACTIONLINT_VERSION}/scripts/download-actionlint.bash") "$ACTIONLINT_VERSION" "$HOME/.local/bin"
|
||||
# zizmor — pinned PyPI package (same version as ci.yml quality-extended)
|
||||
pipx install "zizmor==$ZIZMOR_VERSION" || pip install --user "zizmor==$ZIZMOR_VERSION"
|
||||
# oasdiff — pinned linux amd64 tarball via gh (authed), extract binary
|
||||
rm -rf /tmp/oasd && mkdir -p /tmp/oasd
|
||||
gh release download "$OASDIFF_VERSION" --repo Tufin/oasdiff --pattern '*linux_amd64.tar.gz' --dir /tmp/oasd
|
||||
tar -xzf /tmp/oasd/*linux_amd64.tar.gz -C "$HOME/.local/bin" oasdiff
|
||||
# ALWAYS export the bin dir (even if any step above failed)
|
||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
||||
"$HOME/.local/bin/gitleaks" version || true
|
||||
"$HOME/.local/bin/actionlint" -version || true
|
||||
"$HOME/.local/bin/osv-scanner" --version || true
|
||||
"$HOME/.local/bin/oasdiff" --version || true
|
||||
zizmor --version || true
|
||||
- name: Secret scan (gitleaks, ratchet, blocking)
|
||||
run: npm run check:secrets -- --ratchet
|
||||
- name: Vulnerability ratchet (osv-scanner, ratchet, blocking)
|
||||
run: npm run check:vuln-ratchet -- --ratchet
|
||||
- name: Workflow lint (actionlint+zizmor, ratchet, blocking)
|
||||
run: npm run check:workflows -- --ratchet
|
||||
# BASE_REF is read by the script from the env (never interpolated into a
|
||||
# shell body) — workflow-injection-safe. actions/checkout fetches remote
|
||||
# refs, not a local branch named github.base_ref, so prefix origin/ or this
|
||||
# gate self-skips every PR with reason=base-unresolved.
|
||||
- name: OpenAPI breaking-change (oasdiff, ratchet, blocking)
|
||||
env:
|
||||
BASE_REF: ${{ github.base_ref && format('origin/{0}', github.base_ref) || '' }}
|
||||
run: npm run check:openapi-breaking -- --ratchet
|
||||
- name: Typecheck (core)
|
||||
run: npm run typecheck:core
|
||||
# #7033: dashboard-scoped typecheck gate — src/app/(dashboard) TSX is not
|
||||
@@ -348,13 +260,7 @@ jobs:
|
||||
needs: changes
|
||||
if: ${{ github.event_name != 'pull_request' || ((github.event.pull_request.draft == false || startsWith(github.head_ref, 'mergify/merge-queue/')) && needs.changes.outputs.code == 'true') }}
|
||||
# Dynamic runner — see fast-gates (own-origin + flag; fork/unset → ubuntu-latest).
|
||||
# PINNED to hosted, deliberately not on the USE_VPS_RUNNER switch (gap 19). One variable
|
||||
# governed the build and the test jobs, which want OPPOSITE machines: the build needs the
|
||||
# .113's RAM, the tests need the hosted runner's link. Measured on 2026-07-29 —
|
||||
# actions/setup-node took 20m06s on .113 with 4 concurrent runners versus 16s hosted (npm
|
||||
# cache restore saturating the link), while the tests themselves tied, 2m54 vs 2m31. So
|
||||
# self-hosted is strictly worse here and there is nothing to configure.
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)) && fromJSON('["self-hosted","omni-release"]') || 'ubuntu-latest' }}
|
||||
env:
|
||||
JWT_SECRET: ci-lint-secret-with-sufficient-length-for-validation
|
||||
API_KEY_SECRET: ci-lint-api-key-secret-long
|
||||
@@ -390,13 +296,7 @@ jobs:
|
||||
# critical path again (~8.5min → ~4.5min on ubuntu-latest; ~2min on the 8-slot
|
||||
# runner box). Node's native --test-shard=N/total takes any denominator — only
|
||||
# this matrix and the TEST_SHARD env below encode the shard count.
|
||||
# PINNED to hosted, deliberately not on the USE_VPS_RUNNER switch (gap 19). One variable
|
||||
# governed the build and the test jobs, which want OPPOSITE machines: the build needs the
|
||||
# .113's RAM, the tests need the hosted runner's link. Measured on 2026-07-29 —
|
||||
# actions/setup-node took 20m06s on .113 with 4 concurrent runners versus 16s hosted (npm
|
||||
# cache restore saturating the link), while the tests themselves tied, 2m54 vs 2m31. So
|
||||
# self-hosted is strictly worse here and there is nothing to configure.
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)) && fromJSON('["self-hosted","omni-release"]') || 'ubuntu-latest' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -439,12 +339,6 @@ jobs:
|
||||
if: ${{ github.event_name != 'pull_request' || ((github.event.pull_request.draft == false || startsWith(github.head_ref, 'mergify/merge-queue/')) && needs.changes.outputs.code == 'true') }}
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }}
|
||||
# G0 (trilho .50): security-events:read lets the CodeQL ratchet below read open
|
||||
# code-scanning alerts via `gh api .../code-scanning/alerts` (same as ci.yml's
|
||||
# quality-gate job). contents: read keeps checkout working.
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: read
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
@@ -466,29 +360,6 @@ jobs:
|
||||
- name: ESLint (baseline congelado — warning novo = vermelho)
|
||||
# lint:json writes the report; --max-warnings 0 keeps no-new-warnings policy.
|
||||
run: npm run lint:json -- --max-warnings 0
|
||||
# ── G0 (trilho .50): motor de ratchet também no trilho B ─────────────────────
|
||||
# This job just wrote .artifacts/eslint-results.json — collect-metrics prefers
|
||||
# that file, so the ratchet engine lands here at ZERO extra ESLint cost (one
|
||||
# inventory, two consumers; same reason ci.yml chains lint → quality-gate).
|
||||
# The coverage-report artifact does not exist on this rail, so both ratchet
|
||||
# invocations run --allow-missing: coverage.* metrics skip gracefully while
|
||||
# the deterministic ones (eslint / openapi-coverage / i18n-ui) stay BLOCKING.
|
||||
# Coverage authority remains on the main rail (ci.yml test-coverage → quality-gate).
|
||||
- run: npm run quality:collect
|
||||
- name: Ratchet check (blocking)
|
||||
run: node scripts/quality/check-quality-ratchet.mjs --allow-missing --summary .artifacts/quality-ratchet.md
|
||||
- name: Require-tighten (blocking)
|
||||
run: node scripts/quality/check-quality-ratchet.mjs --allow-missing --require-tighten
|
||||
# CodeQL alerts ratchet — same semantics as ci.yml quality-gate: exits 1 ONLY
|
||||
# on a real regression (open alerts > baseline in quality-baseline.json);
|
||||
# a measurement failure (gh/auth/api) self-skips with exit 0.
|
||||
- name: CodeQL alerts ratchet (blocking)
|
||||
run: npm run check:codeql-ratchet
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Append ratchet summary
|
||||
if: always()
|
||||
run: cat .artifacts/quality-ratchet.md >> "$GITHUB_STEP_SUMMARY" || true
|
||||
|
||||
# Merge-integrity: pega no PR os dois vazamentos crônicos de merge que hoje só
|
||||
# explodem na release-PR. (1) CHANGELOG-eat — o auto-resolve do merge come
|
||||
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -171,6 +171,7 @@ config/quality/test-impact-map.json
|
||||
# GitNexus local index
|
||||
.gitnexus
|
||||
.worktrees
|
||||
bin/omniroute.mjs
|
||||
|
||||
# Consistent with .dockerignore / .npmignore
|
||||
.omc/
|
||||
@@ -200,10 +201,7 @@ scripts/i18n/_pending-keys.json
|
||||
.codegraph/
|
||||
|
||||
# Fumadocs generated source
|
||||
/.source/
|
||||
|
||||
# Temporary local worktrees used to build unpublished npm tarballs
|
||||
/.deploy-build-*/
|
||||
.source/
|
||||
|
||||
# AI agent local settings and configs
|
||||
.agents/
|
||||
@@ -252,4 +250,3 @@ tests/homolog/.auth/
|
||||
tests/homolog/ui/.auth/
|
||||
homolog-report/
|
||||
docker-compose.yml.bak
|
||||
.playwright-cli/
|
||||
|
||||
8
.source/dynamic.ts
Normal file
8
.source/dynamic.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
// @ts-nocheck
|
||||
import { dynamic } from 'fumadocs-mdx/runtime/dynamic';
|
||||
import * as Config from '../source.config';
|
||||
|
||||
const create = await dynamic<typeof Config, import("fumadocs-mdx/runtime/types").InternalTypeConfig & {
|
||||
DocData: {
|
||||
}
|
||||
}>(Config, {"configPath":"source.config.ts","environment":"next","outDir":".source"}, {"doc":{"passthroughs":["extractedReferences"]}});
|
||||
22
.source/source.config.mjs
Normal file
22
.source/source.config.mjs
Normal file
@@ -0,0 +1,22 @@
|
||||
// source.config.ts
|
||||
import { defineDocs, defineConfig } from "fumadocs-mdx/config";
|
||||
var docs = defineDocs({
|
||||
dir: "docs",
|
||||
docs: {
|
||||
files: [
|
||||
"./architecture/**/*.md",
|
||||
"./guides/**/*.md",
|
||||
"./reference/**/*.md",
|
||||
"./frameworks/**/*.md",
|
||||
"./routing/**/*.md",
|
||||
"./security/**/*.md",
|
||||
"./compression/**/*.md",
|
||||
"./ops/**/*.md"
|
||||
]
|
||||
}
|
||||
});
|
||||
var source_config_default = defineConfig();
|
||||
export {
|
||||
source_config_default as default,
|
||||
docs
|
||||
};
|
||||
16
CHANGELOG.md
16
CHANGELOG.md
@@ -8,18 +8,6 @@
|
||||
|
||||
---
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1440,6 +1428,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
18
CLAUDE.md
18
CLAUDE.md
@@ -45,7 +45,7 @@ For full test matrix, see `CONTRIBUTING.md` → "Running Tests". For deep archit
|
||||
| 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 (130 migrations) |
|
||||
| Database | `src/lib/db/` | SQLite domain modules (95 files, 110 migrations) |
|
||||
| Domain/Policy | `src/domain/` | Policy engine, cost rules, fallback logic |
|
||||
| MCP Server | `open-sse/mcp-server/` | 104 tools (42 base + memory/skill/agentSkill/pool/notion/obsidian/gamification/plugin modules), 3 transports (stdio / SSE / Streamable HTTP), 31 scopes |
|
||||
| A2A Server | `src/lib/a2a/` | JSON-RPC 2.0 agent protocol |
|
||||
@@ -72,7 +72,7 @@ Client → /v1/chat/completions (Next.js route)
|
||||
|
||||
API routes follow a consistent pattern: `Route → CORS preflight → Zod body validation → Optional auth (extractApiKey/isValidApiKey) → API key policy enforcement → Handler delegation (open-sse)`. No global Next.js middleware — interception is route-specific.
|
||||
|
||||
**Combo routing** (`open-sse/services/combo.ts`): 19 public strategies (priority, weighted, fill-first, round-robin, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline). Each target calls `handleSingleModel()` which wraps `handleChatCore()` with per-target error handling and circuit breaker checks. The `fusion` strategy is the exception: it fans out to a panel of models in parallel, then a judge model synthesizes one final answer (`open-sse/services/fusion.ts`). See `docs/routing/AUTO-COMBO.md` for the 13-factor Auto-Combo scoring + the full strategy table and `docs/architecture/RESILIENCE_GUIDE.md` for the 3 resilience layers.
|
||||
**Combo routing** (`open-sse/services/combo.ts`): 18 strategies (priority, weighted, fill-first, round-robin, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline). Each target calls `handleSingleModel()` which wraps `handleChatCore()` with per-target error handling and circuit breaker checks. The `fusion` strategy is the exception: it fans out to a panel of models in parallel, then a judge model synthesizes one final answer (`open-sse/services/fusion.ts`). See `docs/routing/AUTO-COMBO.md` for the 12-factor Auto-Combo scoring + the full strategy table and `docs/architecture/RESILIENCE_GUIDE.md` for the 3 resilience layers.
|
||||
|
||||
---
|
||||
|
||||
@@ -332,7 +332,7 @@ For any non-trivial change, read the matching deep-dive first:
|
||||
| Repo navigation | `docs/architecture/REPOSITORY_MAP.md` |
|
||||
| Architecture | `docs/architecture/ARCHITECTURE.md` |
|
||||
| Engineering reference | `docs/architecture/CODEBASE_DOCUMENTATION.md` |
|
||||
| Auto-Combo (13-factor scoring, 19 strategies) | `docs/routing/AUTO-COMBO.md` |
|
||||
| Auto-Combo (12-factor scoring, 18 strategies) | `docs/routing/AUTO-COMBO.md` |
|
||||
| Resilience (3 mechanisms) | `docs/architecture/RESILIENCE_GUIDE.md` |
|
||||
| Reasoning replay | `docs/routing/REASONING_REPLAY.md` |
|
||||
| Skills framework | `docs/frameworks/SKILLS.md` |
|
||||
@@ -461,18 +461,10 @@ own dedicated branch, and you MUST confirm the base branch with the operator bef
|
||||
git fetch origin "$BASE_BRANCH"
|
||||
git worktree add ".claude/worktrees/${TASK##*/}" -b "$TASK" "origin/$BASE_BRANCH"
|
||||
cd ".claude/worktrees/${TASK##*/}"
|
||||
# Reuse the main checkout's node_modules to skip a per-worktree npm install.
|
||||
# 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
|
||||
# symlink node_modules from the main checkout to skip a per-worktree npm install:
|
||||
ln -s "$(git -C <main_checkout> rev-parse --show-toplevel)/node_modules" node_modules
|
||||
```
|
||||
|
||||
**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).
|
||||
|
||||
In Claude Code prefer the native `EnterWorktree` tool (it already creates worktrees under
|
||||
`.claude/worktrees/`): create the worktree with the command above, then call `EnterWorktree`
|
||||
with its `path`.
|
||||
|
||||
@@ -2,11 +2,6 @@
|
||||
|
||||
Thank you for your interest in contributing! This guide covers everything you need to get started.
|
||||
|
||||
For the official per-change workflow, start with the
|
||||
[Contribution Golden Path](docs/dev/CONTRIBUTION_GOLDEN_PATH.md). It maps provider, routing,
|
||||
UI/UX, i18n, CLI, database, and build/deploy changes to their contracts, focused tests, CI
|
||||
coverage, and reconciliation steps.
|
||||
|
||||
---
|
||||
|
||||
## Development Setup
|
||||
@@ -203,11 +198,10 @@ Coverage notes:
|
||||
|
||||
### Pull Request Requirements
|
||||
|
||||
Before opening a PR, use the
|
||||
[Contribution Golden Path](docs/dev/CONTRIBUTION_GOLDEN_PATH.md) to run the focused loop for
|
||||
what you changed. The full unit suite (4 CI shards), Vitest, the **60%+** coverage gate, and
|
||||
the production build are CI's responsibility — running them locally adds no signal the PR
|
||||
checks will not already give you, and on smaller machines it can saturate the host (#8084):
|
||||
Before opening a PR, run the focused loop for what you changed. The full unit suite
|
||||
(4 CI shards), Vitest, the **60%+** coverage gate, and the production build are CI's
|
||||
responsibility — running them locally adds no signal the PR checks will not already
|
||||
give you, and on smaller machines it can saturate the host (#8084):
|
||||
|
||||
- Run the test files that cover your change: `node --import tsx/esm --test tests/unit/<file>.test.ts`
|
||||
- Run `npm run lint`
|
||||
@@ -277,7 +271,7 @@ src/ # TypeScript (.ts / .tsx)
|
||||
│ ├── a2a/ # Agent-to-Agent v0.3 protocol server
|
||||
│ ├── acp/ # Agent Communication Protocol registry
|
||||
│ ├── compliance/ # Compliance policy engine
|
||||
│ ├── db/ # SQLite domain modules + 130 migrations
|
||||
│ ├── db/ # SQLite database layer (21 modules + 16 migrations)
|
||||
│ ├── memory/ # Persistent conversational memory
|
||||
│ ├── oauth/ # OAuth providers, services, and utilities
|
||||
│ ├── skills/ # Extensible skill framework
|
||||
@@ -287,7 +281,7 @@ src/ # TypeScript (.ts / .tsx)
|
||||
├── mitm/ # MITM proxy (cert, DNS, target routing)
|
||||
├── shared/
|
||||
│ ├── components/ # React components (.tsx)
|
||||
│ ├── constants/ # Provider definitions (290), MCP scopes, 19 routing strategies
|
||||
│ ├── constants/ # Provider definitions (177), MCP scopes, 14 routing strategies
|
||||
│ ├── utils/ # Circuit breaker, sanitizer, auth helpers
|
||||
│ └── validation/ # Zod v4 schemas
|
||||
└── sse/ # SSE proxy pipeline
|
||||
@@ -295,7 +289,7 @@ src/ # TypeScript (.ts / .tsx)
|
||||
open-sse/ # @omniroute/open-sse workspace
|
||||
├── executors/ # 14 provider-specific request executors
|
||||
├── handlers/ # 11 request handlers (chat, responses, embeddings, images, etc.)
|
||||
├── mcp-server/ # MCP server (104 tools, 3 transports, 31 scopes)
|
||||
├── mcp-server/ # MCP server (25 tools, 3 transports, 10 scopes)
|
||||
├── services/ # 36+ services (combo, autoCombo, rateLimitManager, etc.)
|
||||
├── translator/ # Format translators (OpenAI ↔ Claude ↔ Gemini ↔ Responses ↔ Ollama)
|
||||
├── transformer/ # Responses API transformer
|
||||
|
||||
136
README.md
136
README.md
@@ -67,46 +67,32 @@
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="right"><b>🚀 Start</b></td>
|
||||
<td align="center"><a href="#-quick-start">🚀 Quick Start</a></td>
|
||||
<td align="center"><a href="#-more-install-methods--docker-source-pnpm-arch">📦 Install</a></td>
|
||||
<td align="center"><a href="#-works-the-second-you-install-it--no-keys-no-config">🆓 Zero-config</a></td>
|
||||
<td align="center"><a href="#-quick-start"><b>🚀 Quick Start</b></a></td>
|
||||
<td align="center"><a href="#-combos--the-flagship"><b>🎯 Combos</b></a></td>
|
||||
<td align="center"><a href="#-290-ai-providers--90-free"><b>🌐 Providers</b></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right"><b>💡 Learn</b></td>
|
||||
<td align="center"><a href="#-full-cli--a2a--mcp"><b>🔌 CLI & MCP</b></a></td>
|
||||
<td align="center"><a href="#%EF%B8%8F-save-1595-tokens--automatically"><b>🗜️ Compression</b></a></td>
|
||||
<td align="center"><a href="https://omniroute.online"><b>🌍 Website</b></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><a href="#-the-promise">💥 The Promise</a></td>
|
||||
<td align="center"><a href="#-why-omniroute">🤔 Why OmniRoute</a></td>
|
||||
<td align="center"><a href="#-why-omniroute">🤔 Why</a></td>
|
||||
<td align="center"><a href="#-what-sets-omniroute-apart">🏆 What Sets Apart</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right"><b>⚙️ Features</b></td>
|
||||
<td align="center"><a href="#-combos--the-flagship">🎯 Combos</a></td>
|
||||
<td align="center"><a href="#-290-ai-providers--90-free">🌐 Providers</a></td>
|
||||
<td align="center"><a href="#-full-cli--a2a--mcp">🔌 CLI & MCP</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right"></td>
|
||||
<td align="center"><a href="#%EF%B8%8F-save-1595-tokens--automatically">🗜️ Compression</a></td>
|
||||
<td align="center"><a href="#-compatible-clis--coding-agents">🤖 Compatible CLIs</a></td>
|
||||
<td align="center"><a href="#%EF%B8%8F-where-omniroute-runs--anywhere">🖥️ Where It Runs</a></td>
|
||||
<td align="center"><a href="#-private--local-first">🔒 Private</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right"><b>👀 See it</b></td>
|
||||
<td align="center"><a href="#-omniroute-in-action">🎬 In Action</a></td>
|
||||
<td align="center"><a href="#-whats-new">✨ What's New</a></td>
|
||||
<td align="center"><a href="#-compatible-clis--coding-agents">🤖 Compatible CLIs</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right"><b>💚 Support</b></td>
|
||||
<td align="center"><a href="#-support-omniroute">💚 Support / Donate</a></td>
|
||||
<td align="center"><a href="#-community--help">💬 Community</a></td>
|
||||
<td align="center"><a href="#-sponsors">💖 Sponsors</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right"><b>📦 Project</b></td>
|
||||
<td align="center"><a href="#%EF%B8%8F-tech-stack">🛠️ Tech Stack</a></td>
|
||||
<td align="center"><a href="#-documentation">📖 Docs</a></td>
|
||||
<td align="center"><a href="#-500-contributors">👥 Contributors</a></td>
|
||||
<td align="center"><a href="#-dashboard-screenshots">📸 Screenshots</a></td>
|
||||
<td align="center"><a href="#-support--community">📧 Support</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -241,53 +227,12 @@ curl http://localhost:20128/v1/chat/completions \
|
||||
<b>What Kimi's support powers:</b> Kimi's API credits power OmniRoute's AI-validated release pipeline — the <i>merge validation powered by Kimi K3</i> stage that reviews every pull request before it ships — plus day-to-day feature development. First-class Kimi support ships on both rails: the direct <a href="https://platform.kimi.ai?aff=omniroute">Kimi API</a> (<code>kimi-k3</code>) and the <a href="https://www.kimi.com/code?aff=omniroute">Kimi Code coding plan</a> (OAuth and API key). OmniRoute is also the first Brazilian open-source project in Kimi's support program. <a href="https://platform.kimi.ai?aff=omniroute"><b>Get a Kimi API key →</b></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" width="150">
|
||||
<a href="https://cheaperinference.com/?utm_source=omniroute">
|
||||
<img src="public/providers/cheaperinference.svg" width="64" alt="Cheaper Inference"/>
|
||||
</a>
|
||||
<br/><b>Cheaper Inference</b><br/><sub>cheaperinference.com</sub><br/><br/>
|
||||
<img src="https://img.shields.io/badge/Open_Source_Friend-31f889?style=flat-square&labelColor=04170d" alt="Open Source Friend"/>
|
||||
</td>
|
||||
<td>
|
||||
Thanks to <b>Cheaper Inference</b>, an OmniRoute Open Source Friend, for backing this project! Cheaper Inference is a cost-ranked gateway that resells 42 frontier models — Claude, GPT-5.x, Gemini, Kimi K3, GLM, DeepSeek, Grok and MiniMax — behind one OpenAI-compatible endpoint, routing each request to the cheapest eligible provider without ever charging above the model maker's list price.
|
||||
<br/><br/>
|
||||
<b>First-class support in OmniRoute:</b> Chat Completions, the native <code>/v1/responses</code> endpoint, vision, tool calling and 3 image models (<code>grok-imagine</code>, <code>nano-banana-pro</code>, <code>nano-banana-2</code>, reachable as <code>cheaperinference/<model></code>). <a href="https://cheaperinference.com/?utm_source=omniroute"><b>Get an API key →</b></a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<sub>Links tagged <code>aff=omniroute</code> are partner links. They fund the project at no extra cost to you.</sub>
|
||||
|
||||
<br/>
|
||||
|
||||
<details open>
|
||||
<summary><sub><b>🎟️ Affiliates Promo</b> — free signup coupons from providers we don't sponsor (click to expand)</sub></summary>
|
||||
|
||||
<sub><i>This section is for referral/coupon codes only. Sponsored partnerships live in <b>🤝 Supported by our Open Source Friends</b> above. OmniRoute has no sponsorship or partnership with the providers listed here — these are public coupons anyone can use.</i></sub>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="120">
|
||||
<a href="https://agentrouter.org/register?aff=70LM">
|
||||
<img src="public/providers/agentrouter.png" width="32" alt="AgentRouter"/>
|
||||
</a>
|
||||
<br/><sub><b>AgentRouter</b></sub><br/><sub>agentrouter.org</sub>
|
||||
</td>
|
||||
<td>
|
||||
<sub><b><a href="https://agentrouter.org/register?aff=70LM">AgentRouter</a></b> — affiliate signup · <b>$100 free credits</b> on signup (free server, expect higher latency — best for testing, not production). First-class support in OmniRoute since <b>v3.8.50</b>: Chat Completions, the Anthropic-compatible wire format and the OpenAI-compatible path. Available models include <code>claude-opus-4-8</code>, <code>claude-opus-5</code>, <code>gpt-5.6-sol</code> and more. <b><a href="https://agentrouter.org/register?aff=70LM">Grab your $100 →</a></b></sub>
|
||||
<br/><br/>
|
||||
<sub>⚠️ <i>Affiliate link — OmniRoute has no sponsorship or partnership with this provider.</i></sub>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<sub>Know another provider with a generous free signup coupon that benefits OmniRoute users? Open an issue and we'll add it here.</sub>
|
||||
|
||||
</details>
|
||||
|
||||
<br/>
|
||||
|
||||
<div align="center">
|
||||
|
||||
## 🎯 Combos — The Flagship
|
||||
@@ -445,50 +390,13 @@ All **19** strategies — mix & match per combo step:
|
||||
|
||||
<br/>
|
||||
|
||||
## 💚 Support OmniRoute
|
||||
## ❤️ Support
|
||||
|
||||
OmniRoute is MIT-licensed and maintained in the open. If it saves you time or money, here's how to keep it independent — pick whatever fits you. Sponsorship never affects routing priority; it buys visibility, not ranking.
|
||||
OmniRoute is free and open source, built and maintained in the open. If it saves you time or money, consider supporting development:
|
||||
|
||||
<table>
|
||||
<tr><td nowrap>⭐ <b>Star the repo</b></td><td>Free — genuinely helps visibility</td><td><a href="https://github.com/diegosouzapw/OmniRoute">Star OmniRoute</a></td></tr>
|
||||
<tr><td nowrap>🐙 <b>GitHub Sponsors</b></td><td>One-off or monthly · zero platform fee</td><td><a href="https://github.com/sponsors/diegosouzapw">github.com/sponsors/diegosouzapw</a></td></tr>
|
||||
<tr><td nowrap>🏢 <b>Open Collective</b></td><td><b>Companies</b> — issues an invoice/receipt · transparent books</td><td><a href="https://opencollective.com/omniroute">opencollective.com/omniroute</a></td></tr>
|
||||
<tr><td nowrap>☕ <b>Ko-fi</b></td><td>Quick one-off tip, no signup for the donor</td><td><a href="https://ko-fi.com/diegosouzapw">ko-fi.com/diegosouzapw</a></td></tr>
|
||||
<tr><td nowrap>🧋 <b>Buy Me a Coffee</b></td><td>Small, informal gesture</td><td><a href="https://www.buymeacoffee.com/diegosouzapw">buymeacoffee.com/diegosouzapw</a></td></tr>
|
||||
<tr><td nowrap>🖐 <b>Liberapay</b></td><td>Recurring · non-profit · open source</td><td><a href="https://liberapay.com/diegosouzapw">liberapay.com/diegosouzapw</a></td></tr>
|
||||
<tr><td nowrap>🇧🇷 <b>PIX</b> (Brazil)</td><td>Instant, no fees</td><td>key & QR below</td></tr>
|
||||
<tr><td nowrap>₿ <b>Crypto</b></td><td>BTC · ETH · USDT-TRC20 · USDC-Solana</td><td>addresses below</td></tr>
|
||||
</table>
|
||||
|
||||
**🇧🇷 PIX** — instant, no fees (Brazil)
|
||||
|
||||
<img src="docs/assets/pix-qr.png" width="140" align="right" alt="OmniRoute PIX QR code"/>
|
||||
|
||||
Key (random): `5d865059-bc44-483a-962d-43ceb80126eb`
|
||||
|
||||
Pix copia-e-cola:
|
||||
|
||||
```
|
||||
00020101021126580014br.gov.bcb.pix01365d865059-bc44-483a-962d-43ceb80126eb5204000053039865802BR5922OMNIROUTE CONTRIBUICAO6006BRASIL62070503***630475DD
|
||||
```
|
||||
|
||||
<br clear="right"/>
|
||||
|
||||
<details>
|
||||
<summary><b>₿ Crypto</b> — BTC · ETH · USDT-TRC20 · USDC-Solana (click to expand)</summary>
|
||||
|
||||
<table>
|
||||
<tr><td nowrap><b>₿ BTC</b></td><td nowrap>Bitcoin (SegWit)</td><td><code>bc1qh00smz004sy85wyl28v77tenkt3ckl6eaep7fd</code></td></tr>
|
||||
<tr><td nowrap><b>Ξ ETH</b></td><td nowrap>Ethereum (ERC20)</td><td><code>0x64Cf6B68A6Ff34288e89172950a2d00102337a84</code></td></tr>
|
||||
<tr><td nowrap><b>₮ USDT</b></td><td nowrap>Tron (TRC20)</td><td><code>TKAF41JpuQrHbKTnsQa9svJE2T192Hvsc2</code></td></tr>
|
||||
<tr><td nowrap><b>$ USDC</b></td><td nowrap>Solana</td><td><code>2emNNZzVVWQc3FQ2wk9M6qXUQmW8AKdjjL174fXR28Tu</code></td></tr>
|
||||
</table>
|
||||
|
||||
<sub>⚠️ Send each coin only on the network shown — sending on the wrong network can lose the funds.</sub>
|
||||
|
||||
</details>
|
||||
|
||||
🐛 Found a bug or have feedback? Open a [Discussion](https://github.com/diegosouzapw/OmniRoute/discussions).
|
||||
- ⭐ **Star the repo** — it genuinely helps visibility
|
||||
- 💖 **[GitHub Sponsors](https://github.com/sponsors/diegosouzapw)** — fund ongoing maintenance and new providers
|
||||
- 🐛 **Report bugs and share feedback** in [Discussions](https://github.com/diegosouzapw/OmniRoute/discussions)
|
||||
|
||||
<br/>
|
||||
|
||||
@@ -997,7 +905,7 @@ same process on one port, so there is no separate CLI-only package today.
|
||||
|
||||
<div align="center">
|
||||
|
||||
# 📧 Community & Help
|
||||
# 📧 Support & Community
|
||||
|
||||
> Everything in one place — follow the maintainer, chat with the community, or open an issue.
|
||||
|
||||
@@ -1013,7 +921,7 @@ same process on one port, so there is no separate CLI-only package today.
|
||||
| 📦 **Source code** | [github.com/diegosouzapw/OmniRoute](https://github.com/diegosouzapw/OmniRoute) |
|
||||
| 🐛 **Report a bug** | [open an issue](https://github.com/diegosouzapw/OmniRoute/issues) — attach `npm run system-info` output |
|
||||
| 🤝 **Contribute** | [CONTRIBUTING.md](CONTRIBUTING.md) · [Branching & Release Model](docs/ops/BRANCHING_MODEL.md) · pick a `good first issue` |
|
||||
| 💚 **Support the project** | [Ways to support ↑](#-support-omniroute) · [GitHub Sponsors](https://github.com/sponsors/diegosouzapw) |
|
||||
| ⭐ **Support the project** | [Star the repo](https://github.com/diegosouzapw/OmniRoute) · [GitHub Sponsors](https://github.com/sponsors/diegosouzapw) |
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
- **feat(sse):** deprecated the legacy `gemini-cli` **upstream provider**. It was not routable (no registry entry, no executor), yet the scheduler kept refreshing its token against Google — maintaining a credential that could never serve a request. A stored connection now becomes terminal with a legible reason and a working migration path: re-add the account under `gemini`, which uses the same Google OAuth client. The `gemini-cli` **client identity** (requests arriving *from* the Gemini CLI, issue #7034) is untouched ([#8980](https://github.com/diegosouzapw/OmniRoute/pull/8980))
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(ci):** the reconciliation helper no longer bounds its scan with `git describe --tags` — releases squash-merge, so that range re-listed 1361 commits instead of the cycle's real 22, which is how ~200 PRs once slipped through without a changelog bullet. The base is now resolved from the commit that opened the cycle, and a new `sweep:stale-fragments` gate removes `changelog.d/` fragments that a back-merge from `main` resurrected after they had already been folded in ([#8985](https://github.com/diegosouzapw/OmniRoute/pull/8985))
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(ci):** fixed a live auto-update defect where **Intel Macs downloaded the ARM dmg** — the two macOS jobs each emitted their own `latest-mac.yml` and `merge-multiple` let one silently overwrite the other by arrival order, leaving `electron-updater`'s arch fallback pointing at the wrong build. The manifests are now merged deliberately, un-suffixed entry first. Also: test jobs pinned to hosted runners (`setup-node` measured 20m06s self-hosted vs 16s hosted), the npm publish no longer discards a valid build artifact because an unrelated shard was flaky, the agent-skills gate now runs on pushes to `main` instead of PRs only, and the CI summary names every job that ended cancelled ([#8988](https://github.com/diegosouzapw/OmniRoute/pull/8988))
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(translator):** Honor configured Chat targets for Responses-shaped clients while preserving native Responses providers and outbound token fields ([#9161](https://github.com/diegosouzapw/OmniRoute/pull/9161)) — thanks @Zartharas
|
||||
@@ -1 +0,0 @@
|
||||
- **chore(sse):** dropped the leftover `iflow` entry from the token-refresh TTL map — the provider was removed from the product but its 24-hour refresh lead outlived it, and the identifier had exactly two occurrences left repo-wide ([#8966](https://github.com/diegosouzapw/OmniRoute/pull/8966))
|
||||
@@ -1 +0,0 @@
|
||||
- **chore(ci):** removed two fork-owned image-publish workflows that had ridden into the repo as unrelated extra files in on-topic PRs — `build-fork.yml` (`ghcr.io/kang-heewon`, job-level guard, so it instantiated a skipped run on every push to main and every tag) and `build-rinseaid-image.yml` (`ghcr.io/rinseaid`, no guard, never fired). Neither could authenticate against this repository's token; a new policy guard now fails CI on any workflow targeting a foreign registry namespace ([#8967](https://github.com/diegosouzapw/OmniRoute/pull/8967))
|
||||
@@ -1 +0,0 @@
|
||||
- **test(ci):** fixed the intermittent `spawnSync bash EPIPE` failure in the `:latest` promotion guard — the script exits on a pre-release version before reading stdin, so the harness's pipe-backed `input:` raced that exit; stdin is now file-backed, which makes the race structurally impossible ([#8977](https://github.com/diegosouzapw/OmniRoute/pull/8977))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -177,7 +177,7 @@
|
||||
"tests/unit/account-fallback-service.test.ts": 1563,
|
||||
"tests/unit/batch_api.test.ts": 1324,
|
||||
"tests/unit/cc-compatible-provider.test.ts": 1217,
|
||||
"tests/unit/chatcore-translation-paths.test.ts": 2776,
|
||||
"tests/unit/chatcore-translation-paths.test.ts": 2769,
|
||||
"tests/unit/chatgpt-web.test.ts": 3148,
|
||||
"tests/unit/combo-routing-engine.test.ts": 3449,
|
||||
"tests/unit/db-migration-runner.test.ts": 1499,
|
||||
@@ -342,7 +342,7 @@
|
||||
"_rebaseline_pr1043_minimax_tts": "Upstream port decolua/9router#1043 (toanalien) own growth: audioSpeech.ts 965->1061 (+96). Adds MiniMax T2A v2 TTS dispatch (handleMinimaxSpeech + hexToBytes helper) — provider entry was already in audioRegistry (format: minimax-tts) but no handler existed, falling through to the OpenAI-compatible default that fails (T2A has custom shape + hex-encoded audio + base_resp envelope). New branch sits next to the other inline provider branches (xiaomi-mimo, coqui, tortoise, aws-polly) — extracting would just create indirection. Covered by tests/unit/minimax-tts-1043.test.ts (3 tests, GREEN: success, base_resp error, invalid-hex).",
|
||||
"_rebaseline_pr4592_exclude_exhausted_auto": "Reconcile #4592 already-merged growth: combo.ts 2991->3036 (+45, terminal-status quota-cutoff exclusion in buildAutoCandidates + opt-in gate). Fast-gate PR->release does not run check:file-size.",
|
||||
"open-sse/executors/antigravity.ts": 1528,
|
||||
"open-sse/executors/base.ts": 1578,
|
||||
"open-sse/executors/base.ts": 1562,
|
||||
"open-sse/executors/chatgpt-web.ts": 3241,
|
||||
"open-sse/executors/codex.ts": 1534,
|
||||
"open-sse/executors/cursor.ts": 1560,
|
||||
@@ -365,7 +365,7 @@
|
||||
"open-sse/services/rateLimitManager.ts": 1060,
|
||||
"open-sse/translator/response/openai-responses.ts": 1174,
|
||||
"open-sse/utils/cursorAgentProtobuf.ts": 1505,
|
||||
"open-sse/utils/stream.ts": 2889,
|
||||
"open-sse/utils/stream.ts": 2887,
|
||||
"src/app/(dashboard)/dashboard/HomePageClient.tsx": 1381,
|
||||
"src/app/(dashboard)/dashboard/analytics/ComboHealthTab.tsx": 1031,
|
||||
"src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx": 3117,
|
||||
@@ -413,6 +413,5 @@
|
||||
"_rebaseline_2026_07_28_8860_tokenrefresh_projectid": "PR #8860 (fix/antigravity-projectid-centralized) own test growth: tests/unit/token-refresh-service.test.ts 1311->1378 (+67 = 4 cases covering projectId discovery on the tokenRefresh.ts path — the Dashboard/health-check refresh route, which #8842 did not reach since that fixed the executor path). Covered by the same file.",
|
||||
"_rebaseline_2026_07_28_8861_xiaomi_token_plan": "PR #8861 (feat/xiaomi-token-plan-protocol-selector) own growth: EditConnectionModal.tsx 1283->1316 (+33 = the per-connection API-protocol selector field) and open-sse/executors/base.ts 1540->1562 (+22 = alternate-format resolution at the existing buildUrl/headers chokepoint). Both are irreducible wiring at existing call sites.",
|
||||
"_rebaseline_2026_07_28_8863_firefly_detail_level": "PR #8863 (fix/adobe-firefly-gpt-detail-level-max) own growth: adobeFireflyClient.ts 2317->2322 (+5 = gpt-image detailLevel defaulting to maximal at the existing payload-build site). Covered by tests/unit/adobe-firefly.test.ts.",
|
||||
"_rebaseline_2026_07_29_8281_home_quickstart_prefetch": "Release v3.8.49 base-red fix (no PR — captain sweep): src/app/(dashboard)/dashboard/HomePageClient.tsx 1377->1381 (+4). #8292 added prefetch={false} to the sidebar but left /home's five quick-start Links prefetching, so first paint still fired 12 speculative RSC requests — caught by navigation.spec.ts only after the e2e helper bug (APP_ROUTE_PATTERN missing /home) was repaired in the same cycle. Growth is the five prefetch attributes; it was offset first by extracting the repeated className literals (INLINE_LINK x4, DOCS_LINK x1), which collapsed five wrapped <Link> blocks back to one line each — a naive fix measured 1391. Guard: tests/unit/sidebar-prefetch-policy-8281.test.ts.",
|
||||
"_rebaseline_2026_08_02_v3850_agentrouter_responses": "Release v3.8.50 AgentRouter/Codex compatibility reconciliation. open-sse/executors/base.ts 1562->1578: #9190 wires AgentRouter's selected Claude/OpenAI/Responses protocol through the existing executor URL, auth, identity-header and fingerprint chokepoints; the reusable alternate resolver remains outside base.ts. open-sse/utils/stream.ts 2887->2889: #9213 evaluates Responses ID and usage normalization independently so response.completed always receives finite usage.total_tokens instead of short-circuiting after an ID rewrite. tests/unit/chatcore-translation-paths.test.ts 2769->2776: #9191 updates the existing Claude-Code bridge assertions for the dynamic AgentRouter wire image. PR #9224 offsets its own chatCore growth by extracting the AgentRouter protocol decisions into chatCore/agentRouterProtocol.ts, leaving chatCore below its frozen ceiling. Covered by agentrouter executor/chatCore protocol tests, chatcore translation-path tests, and responses-commentary-passthrough tests."
|
||||
"_rebaseline_2026_07_29_8281_home_quickstart_prefetch": "Release v3.8.49 base-red fix (no PR — captain sweep): src/app/(dashboard)/dashboard/HomePageClient.tsx 1377->1381 (+4). #8292 added prefetch={false} to the sidebar but left /home's five quick-start Links prefetching, so first paint still fired 12 speculative RSC requests — caught by navigation.spec.ts only after the e2e helper bug (APP_ROUTE_PATTERN missing /home) was repaired in the same cycle. Growth is the five prefetch attributes; it was offset first by extracting the repeated className literals (INLINE_LINK x4, DOCS_LINK x1), which collapsed five wrapped <Link> blocks back to one line each — a naive fix measured 1391. Guard: tests/unit/sidebar-prefetch-policy-8281.test.ts."
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"_doc": "Tables that exist ONLY in databases upgraded from an older version — residue whose CREATE left the migration set in some past cycle but survives where it already existed. Harmless (nothing references them), but recorded here so check-install-upgrade.mjs can still fail on a NEW divergence. The opposite direction (a table a clean install creates but an upgrade does not) is NEVER allowlisted: it means every existing user is missing structure the code expects.",
|
||||
"residualTables": {
|
||||
"cache_metrics": "Measured 2026-07-30 on a real 3.8.48 install upgraded to 3.8.49 (VPS .16, 165 MB database, 114 → 117 tables). Present in upgraded databases, absent from clean installs. No code path referenced it during the upgrade (zero `no such table` in 150 log lines, both installs healthy). Left in place rather than dropped: a DROP migration on a table we cannot prove is unused everywhere is the riskier change. Revisit when the cache subsystem is next touched."
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 895 B |
@@ -1,229 +0,0 @@
|
||||
---
|
||||
title: "Contribution Golden Path"
|
||||
---
|
||||
|
||||
# Contribution Golden Path
|
||||
|
||||
Use this guide to choose the smallest reliable development loop for a pull request. It does not
|
||||
replace the area-specific architecture and security documents linked below; it connects each common
|
||||
change type to its contracts, focused checks, and CI coverage.
|
||||
|
||||
## The path every change follows
|
||||
|
||||
1. **Choose the base before editing.** Find the highest active `release/v*` branch and branch from
|
||||
its tip. Target that branch, not `main`. If a release freeze is active, do not target the frozen
|
||||
branch; use the next active cycle described in
|
||||
[Branching & Release Model](../ops/BRANCHING_MODEL.md).
|
||||
2. **Name the contracts.** Identify every catalog, schema, generated artifact, public API, or user
|
||||
interface that the change affects. The table below gives the minimum starting set.
|
||||
3. **Write or update focused tests.** Production changes in `src/`, `open-sse/`, `electron/`, or
|
||||
`bin/` require an automated test in the same PR. Run the smallest test files that prove the
|
||||
behavior, then the listed focused gates.
|
||||
4. **Let CI run the broad matrix.** The complete unit shards, Vitest, coverage ratchet, and
|
||||
production build run on the PR. Run a broad suite locally only when a focused failure points to
|
||||
wider impact or when the change spans several subsystems.
|
||||
5. **Reconcile before review.** Fetch the active base, inspect its new commits and your diff against
|
||||
it, then rebase or merge the base according to the contributor workflow. Resolve generated-file
|
||||
and catalog conflicts from their source, regenerate them, rerun the focused loop, and confirm the
|
||||
PR still targets the active release branch.
|
||||
6. **Record evidence.** In the PR template, list the commands run, every test file added or changed,
|
||||
migrations or feature flags, and any CI-only validation still pending.
|
||||
|
||||
## Golden paths by change type
|
||||
|
||||
Commands below are minimum focused checks, not permission to skip a test that directly covers the
|
||||
behavior you changed.
|
||||
|
||||
### Provider
|
||||
|
||||
**Contracts**
|
||||
|
||||
- Provider definition in `src/shared/constants/providers/` and its composition in
|
||||
`src/shared/constants/providers.ts`.
|
||||
- Models and capabilities in `open-sse/config/providerRegistry.ts` or its extracted registry files.
|
||||
- Executor/translator selection, OAuth or API-key configuration, dashboard assets, and generated
|
||||
provider reference when applicable.
|
||||
- Public credentials must use `resolvePublicCred()`; error responses must use the shared sanitized
|
||||
error helpers. See [Public Credentials](../security/PUBLIC_CREDS.md) and
|
||||
[Error Sanitization](../security/ERROR_SANITIZATION.md).
|
||||
|
||||
**Focused loop**
|
||||
|
||||
```bash
|
||||
npm run check:provider-consistency
|
||||
npm run check:provider-assets
|
||||
node --import tsx/esm --test tests/unit/provider-translate-path-golden.test.ts
|
||||
node --import tsx/esm --test tests/unit/<provider-or-executor>.test.ts
|
||||
npm run gen:provider-reference # when the catalog changes; commit the generated diff
|
||||
npm run lint
|
||||
```
|
||||
|
||||
Also test every affected request family: chat, Responses, images, embeddings, audio, or video.
|
||||
Review generated catalog and golden diffs as contract changes; do not accept them blindly.
|
||||
|
||||
### Routing
|
||||
|
||||
**Contracts**
|
||||
|
||||
- Public strategy values and UI metadata in `src/shared/constants/routingStrategies.ts`.
|
||||
- Dispatch and ordering under `open-sse/services/combo.ts` and `open-sse/services/combo/`.
|
||||
- Combo schemas, persistence, resilience state, model capabilities, and API/UI controls.
|
||||
- [Auto-Combo Engine](../routing/AUTO-COMBO.md) and resilience documentation when behavior changes.
|
||||
|
||||
**Focused loop**
|
||||
|
||||
```bash
|
||||
node --import tsx/esm --test tests/unit/combo-<behavior>.test.ts
|
||||
npm run test:combo:matrix # strategy or dispatch changes
|
||||
npm run check:known-symbols # strategy registration changes
|
||||
npm run lint
|
||||
```
|
||||
|
||||
Use deterministic mocked-upstream tests locally. Live combo smokes require credentials and are
|
||||
manual, not CI substitutes.
|
||||
|
||||
### UI / UX
|
||||
|
||||
**Contracts**
|
||||
|
||||
- Next.js route/page and shared component boundaries under `src/app/` and
|
||||
`src/shared/components/`.
|
||||
- API response shapes, loading/empty/error states, keyboard and screen-reader behavior,
|
||||
responsive layout, theming, and locale expansion.
|
||||
- English UI source strings in `src/i18n/messages/en.json`; do not hard-code new user-facing copy.
|
||||
|
||||
**Focused loop**
|
||||
|
||||
```bash
|
||||
node --import tsx --test tests/unit/dashboard/<feature>.test.ts
|
||||
npx vitest run --config vitest.config.ts tests/unit/ui/<component>.test.tsx
|
||||
npm run check:dashboard-typecheck
|
||||
npm run lint
|
||||
```
|
||||
|
||||
Run the app for interaction or visual changes and check both narrow and wide viewports. CI runs the
|
||||
production build and broader suites; visual behavior still needs a focused component, Playwright,
|
||||
or documented manual check appropriate to the change.
|
||||
|
||||
### i18n
|
||||
|
||||
**Contracts**
|
||||
|
||||
- `src/i18n/messages/en.json` is the UI source; `config/i18n.json` is the locale source.
|
||||
- CLI catalogs live separately under `bin/cli/locales/`.
|
||||
- Preserve ICU placeholders and tags exactly. Do not translate product/provider/model names,
|
||||
protocol and header names, commands, code/JSON identifiers, URLs, environment variables, or
|
||||
protected terms such as `OmniRoute`, `OAuth`, `MCP`, and `A2A`. The current source list is
|
||||
`scripts/i18n/glossary/protected-terms.json`.
|
||||
|
||||
**Focused loop**
|
||||
|
||||
```bash
|
||||
npm run i18n:sync-ui:dry
|
||||
npm run i18n:check-ui-coverage
|
||||
npm run i18n:check-value-drift
|
||||
npm run i18n:check-glossary
|
||||
npm run check:cli-i18n # when CLI strings/catalogs change
|
||||
npm run lint
|
||||
```
|
||||
|
||||
This is guidance for the existing system, not an invitation to expand its tooling or key model.
|
||||
Keep i18n patches surgical while the replacement system is being designed. Do not run translation
|
||||
commands that call external services unless the task explicitly requires generated translations and
|
||||
you have reviewed the resulting diff.
|
||||
|
||||
### CLI
|
||||
|
||||
**Contracts**
|
||||
|
||||
- Public commands and flags in `bin/cli/`, generated API commands, exit codes, stdout/stderr and
|
||||
JSON output shapes, config/environment behavior, and packaged files.
|
||||
- CLI user-facing strings must use the CLI i18n layer and keep `en`/`pt-BR` catalogs aligned.
|
||||
- Preserve Node as the supported runtime and the published binary contract.
|
||||
|
||||
**Focused loop**
|
||||
|
||||
```bash
|
||||
node --import tsx/esm --test tests/unit/cli/<command>.test.ts
|
||||
npm run check:cli-i18n
|
||||
npm run build:cli # generated/bundled CLI changes
|
||||
npm run check:pack-policy # package-surface changes
|
||||
npm run lint
|
||||
```
|
||||
|
||||
Use the exact command in a temporary data directory when behavior depends on parsing, files, or exit
|
||||
status. CI performs the broader package artifact and ecosystem checks.
|
||||
|
||||
### Database
|
||||
|
||||
**Contracts**
|
||||
|
||||
- Domain modules under `src/lib/db/`; `src/lib/localDb.ts` remains a re-export layer only.
|
||||
- Numbered, idempotent SQL migrations under `src/lib/db/migrations/`, transaction safety, upgrade
|
||||
behavior, indexes, and every caller affected by the schema.
|
||||
- Routes and handlers never issue raw SQL directly.
|
||||
|
||||
**Focused loop**
|
||||
|
||||
```bash
|
||||
npm run check:migration-numbering
|
||||
npm run check:db-rules
|
||||
node --import tsx/esm --test tests/unit/db/<domain>.test.ts
|
||||
node --import tsx/esm --test tests/unit/db/migration-<number>.test.ts
|
||||
npm run lint
|
||||
```
|
||||
|
||||
Test both a fresh database and upgrade from the prior schema when adding a migration. Database tests
|
||||
must close handles and call `resetDbInstance()` during cleanup. Run `npm run test:bun:db` only when
|
||||
the best-effort Bun adapter path changes; Node remains authoritative.
|
||||
|
||||
### Build / deploy
|
||||
|
||||
**Contracts**
|
||||
|
||||
- Root and workspace manifests/lockfile, `scripts/build/`, Next.js standalone assembly, `dist/`
|
||||
package contents, Electron platform metadata, CI workflows, and deployment sentinels.
|
||||
- Supported Node ranges and the allow-listed Bun use in `CLAUDE.md` must remain intact.
|
||||
- Build artifacts stay untracked; dependency, license, workflow, and package policies apply.
|
||||
|
||||
**Focused loop**
|
||||
|
||||
```bash
|
||||
node --import tsx/esm --test tests/unit/build/<behavior>.test.ts
|
||||
npm run check:build-scope
|
||||
npm run check:lockfile # dependency or lockfile changes
|
||||
npm run check:pack-policy # published package surface changes
|
||||
npm run lint
|
||||
```
|
||||
|
||||
Use `npm run build` locally only when the change affects compilation, standalone assembly, assets,
|
||||
or runtime bundling. Use `npm run build:release` only for release/deploy validation. CI's build is
|
||||
the final cross-platform signal; platform-specific Electron changes need the matching focused build
|
||||
or smoke evidence.
|
||||
|
||||
## Local loop versus CI
|
||||
|
||||
| Run locally for each patch | CI supplies the broad signal |
|
||||
| ----------------------------------------------------------------------------- | ----------------------------------------------------------------- |
|
||||
| Direct behavior tests and category gates above | Sharded full unit suite and serial tests |
|
||||
| `npm run lint` | Vitest suites and coverage/quality ratchets |
|
||||
| Typecheck or build only when the affected contract calls for it | Production build, security, docs, dependency, and PR-policy gates |
|
||||
| Manual interaction/live checks only when automation cannot prove the behavior | Cross-job integration and platform checks configured by workflow |
|
||||
|
||||
A green focused loop is evidence about the changed contract, not proof that unrelated CI checks
|
||||
will pass. Conversely, do not make every local edit wait for the full repository matrix.
|
||||
|
||||
## Reconciliation checklist
|
||||
|
||||
Before requesting review:
|
||||
|
||||
- Confirm the PR base is still the highest active `release/v*` branch.
|
||||
- Fetch that base and review commits that landed since you branched.
|
||||
- Review `git diff <active-base>...HEAD` for accidental or generated churn.
|
||||
- Resolve catalog and generated-document conflicts by updating the source and regenerating output.
|
||||
- Rerun every focused test/gate listed in the PR description after reconciliation.
|
||||
- Never weaken assertions or drop required tests merely to match a moved base.
|
||||
|
||||
For release-freeze and retargeting rules, use
|
||||
[Branching & Release Model](../ops/BRANCHING_MODEL.md). For the complete CI inventory, use
|
||||
[Quality Gates Reference](../architecture/QUALITY_GATES.md).
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -8,18 +8,6 @@
|
||||
|
||||
---
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1154,7 +1142,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1440,6 +1427,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -461,18 +461,10 @@ own dedicated branch, and you MUST confirm the base branch with the operator bef
|
||||
git fetch origin "$BASE_BRANCH"
|
||||
git worktree add ".claude/worktrees/${TASK##*/}" -b "$TASK" "origin/$BASE_BRANCH"
|
||||
cd ".claude/worktrees/${TASK##*/}"
|
||||
# Reuse the main checkout's node_modules to skip a per-worktree npm install.
|
||||
# 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
|
||||
# symlink node_modules from the main checkout to skip a per-worktree npm install:
|
||||
ln -s "$(git -C <main_checkout> rev-parse --show-toplevel)/node_modules" node_modules
|
||||
```
|
||||
|
||||
**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).
|
||||
|
||||
In Claude Code prefer the native `EnterWorktree` tool (it already creates worktrees under
|
||||
`.claude/worktrees/`): create the worktree with the command above, then call `EnterWorktree`
|
||||
with its `path`.
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -415,18 +415,10 @@ git push -u origin feat/your-feature
|
||||
git fetch origin "$BASE_BRANCH"
|
||||
git worktree add ".claude/worktrees/${TASK##*/}" -b "$TASK" "origin/$BASE_BRANCH"
|
||||
cd ".claude/worktrees/${TASK##*/}"
|
||||
# 复用主工作区的 node_modules,省去每个 worktree 的 npm install。
|
||||
# 必须用硬链接(`cp -al`),绝不能用符号链接:整棵树约 5 秒,几乎不占额外磁盘
|
||||
# (inode 是共享的),而且与符号链接不同,它不会破坏开发服务器。
|
||||
cp -al "$(git -C <main_checkout> rev-parse --show-toplevel)/node_modules" node_modules
|
||||
# 从主工作区符号链接 node_modules,省去每个 worktree 的 npm install:
|
||||
ln -s "$(git -C <main_checkout> rev-parse --show-toplevel)/node_modules" node_modules
|
||||
```
|
||||
|
||||
**绝不要对 node_modules 使用 `ln -s`。** Turbopack 会拒绝解析到项目根目录之外的符号链接,
|
||||
因此 `npm run dev` 会以 FATAL panic 崩溃(`Symlink [project]/node_modules is invalid, it
|
||||
points out of the filesystem root`),而 typecheck、lint 和测试运行器却都照常通过 —— 错误信息
|
||||
提到的是 "filesystem root" 而不是 worktree,看起来像 Next/构建的 bug,排查会浪费大量时间
|
||||
(事故 2026-07-31,#9043)。
|
||||
|
||||
在 Claude Code 中优先使用原生的 `EnterWorktree` 工具(它已经在 `.claude/worktrees/` 下创建 worktree):先用上述命令创建 worktree,然后用其 `path` 调用 `EnterWorktree`。
|
||||
|
||||
3. **工作、提交、推送、发起 PR — 全部在 worktree 内部完成。** 绝不在另一个会话可能共享的 worktree 内 `git checkout` 不同分支。
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
@@ -1152,7 +1140,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- **fix(api):** stop JSON.stringify-ing messages before estimateTokens (restores #8368 image estimate) [recovered from #8599] ([#8740](https://github.com/diegosouzapw/OmniRoute/pull/8740))
|
||||
- **fix(quality):** let --update remove baseline entries that already fit the cap ([#8741](https://github.com/diegosouzapw/OmniRoute/pull/8741)) — thanks @MumuTW
|
||||
- **fix:** escape angle brackets in i18n messages to prevent next-intl INVALID_MESSAGE errors ([#8747](https://github.com/diegosouzapw/OmniRoute/pull/8747)) — thanks @SteeleHu
|
||||
- **fix(dashboard):** the /home quick-start cards no longer prefetch — #8292 opted the sidebar out of automatic route prefetch but left the landing page's own five links untouched, so first paint still fired 12 speculative RSC requests. The e2e guard shipped in that same PR could not catch it: it hung in the auth helper and never reached its assertion until this release's CI.
|
||||
- **fix(dashboard):** Request Logs detail no longer crashes on a structured error object — #7920 introduced `formatErrorForDisplay` for exactly this, but the combo-503 / cooldown checks added by #8213 read the raw field and called `.toLowerCase()` on it. Both paths now share the helper.
|
||||
- **fix(dashboard):** the logs detail modal stops reopening on first close again — #6830 fixed it by reading the deep-link id once, and the #8354 page rewrite regressed it by reading the live `searchParams` on every render, flipping the prop mid-session.
|
||||
### 📚 Docs
|
||||
@@ -1438,6 +1425,10 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
Thanks to everyone whose work landed in v3.8.49:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: OmniRoute API
|
||||
version: 3.8.50
|
||||
version: 3.8.49
|
||||
description: |
|
||||
OmniRoute is a local-first AI API proxy router. It provides an OpenAI-compatible
|
||||
endpoint that routes requests to multiple AI providers with load balancing,
|
||||
|
||||
@@ -414,6 +414,7 @@ detection above).
|
||||
| `OMNIROUTE_HTTP_TIMEOUT_MS` | `30000` | `bin/cli/api.mjs` | Per-attempt HTTP timeout (ms) for CLI → server requests. |
|
||||
| `OMNIROUTE_VERBOSE` | `0` | `bin/cli/api.mjs` | Set to `1` to print retry/backoff diagnostics to stderr during CLI commands. |
|
||||
| `OMNIROUTE_PLUGIN_PATH` | _(unset)_ | `bin/cli/plugins.mjs` | Custom directory for CLI plugin discovery (`omniroute-cmd-*` packages). Defaults to `~/.omniroute/plugins/` when unset. |
|
||||
| `OMNIROUTE_PLUGINS_ALLOW_EXEC` | `0` | `src/lib/plugins/pluginWorker.ts` | Set to `1` to allow plugins to request the `exec` permission (spawn child processes from the worker sandbox). Local operator only. |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
---
|
||||
title: "Provider Reference"
|
||||
version: 3.8.50
|
||||
lastUpdated: 2026-07-30
|
||||
version: 3.8.49
|
||||
lastUpdated: 2026-07-28
|
||||
---
|
||||
|
||||
# Provider Reference
|
||||
|
||||
> **Auto-generated** from `src/shared/constants/providers.ts` — do not edit by hand.
|
||||
> Regenerate with: `npm run gen:provider-reference`
|
||||
> **Last generated:** 2026-07-30
|
||||
> **Last generated:** 2026-07-28
|
||||
|
||||
Total providers: **290**. See category breakdown below.
|
||||
|
||||
|
||||
@@ -167,21 +167,21 @@ The Auto-Combo Engine dynamically selects the best provider/model for each reque
|
||||
|
||||
> Source: [diagrams/auto-combo-12factor.mmd](../diagrams/auto-combo-12factor.mmd) (regenerate via `npm run docs:render-diagrams`). Diagram/filename predate the `cacheAffinity` factor added by #8008 and still show 12 factors.
|
||||
|
||||
| Factor | Default Weight | Description |
|
||||
| :-------------------- | :------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `health` | 0.20 | Health score from circuit breaker (CLOSED=1.0, HALF_OPEN=0.5, OPEN=0.0) |
|
||||
| `quota` | 0.15 | Remaining quota / rate-limit headroom [0..1] |
|
||||
| `costInv` | 0.15 | Inverse **blended** cost (60% input + 40% output token price, normalized) — cheaper = higher score |
|
||||
| `latencyInv` | 0.12 | Inverse p95 latency normalized to pool — faster = higher score |
|
||||
| `taskFit` | 0.08 | Task-type fitness (coding, review, planning, analysis, debugging, docs) |
|
||||
| `stability` | 0.05 | Variance-based stability (low latency stdDev / error rate) |
|
||||
| `tierPriority` | 0.05 | Account-tier priority — Ultra=1.0, Pro=0.67, Standard=0.33, Free=0.0 |
|
||||
| `tierAffinity` | 0.05 | Affinity between the candidate's tier and the manifest-recommended tier |
|
||||
| `specificityMatch` | 0.05 | Match between request specificity (manifest hint) and model tier |
|
||||
| `contextAffinity` | 0.05 | Affinity between the request's context-window need and the model's context window |
|
||||
| `connectionDensity` | 0.05 | Spreads load across connections of the same provider (anti-concentration) |
|
||||
| Factor | Default Weight | Description |
|
||||
| :-------------------- | :------------- | :------------------------------------------------------------------------------------------------- |
|
||||
| `health` | 0.20 | Health score from circuit breaker (CLOSED=1.0, HALF_OPEN=0.5, OPEN=0.0) |
|
||||
| `quota` | 0.15 | Remaining quota / rate-limit headroom [0..1] |
|
||||
| `costInv` | 0.15 | Inverse **blended** cost (60% input + 40% output token price, normalized) — cheaper = higher score |
|
||||
| `latencyInv` | 0.12 | Inverse p95 latency normalized to pool — faster = higher score |
|
||||
| `taskFit` | 0.08 | Task-type fitness (coding, review, planning, analysis, debugging, docs) |
|
||||
| `stability` | 0.05 | Variance-based stability (low latency stdDev / error rate) |
|
||||
| `tierPriority` | 0.05 | Account-tier priority — Ultra=1.0, Pro=0.67, Standard=0.33, Free=0.0 |
|
||||
| `tierAffinity` | 0.05 | Affinity between the candidate's tier and the manifest-recommended tier |
|
||||
| `specificityMatch` | 0.05 | Match between request specificity (manifest hint) and model tier |
|
||||
| `contextAffinity` | 0.05 | Affinity between the request's context-window need and the model's context window |
|
||||
| `connectionDensity` | 0.05 | Spreads load across connections of the same provider (anti-concentration) |
|
||||
| `cacheAffinity` | 0.00 | Rendezvous-hash affinity toward the connection likeliest to already hold this request's prompt-cache prefix (`open-sse/services/combo/promptCacheAffinity.ts`); disabled by default (#8008) |
|
||||
| `resetWindowAffinity` | 0.00 | Bias toward connections whose quota reset window is favorable (disabled by default) |
|
||||
| `resetWindowAffinity` | 0.00 | Bias toward connections whose quota reset window is favorable (disabled by default) |
|
||||
|
||||
**Sum:** `0.20 + 0.15 + 0.15 + 0.12 + 0.08 + 0.05 + 0.05 + 0.05 + 0.05 + 0.05 + 0.05 + 0.00 + 0.00 = 1.0` (validated by `validateWeights()`).
|
||||
|
||||
@@ -215,11 +215,11 @@ combo's stored config. These apply only to the `auto` strategy and only for the
|
||||
that carries them; the combo's saved `modePack`/`budgetCap`/`budgetFallback` are used
|
||||
when the header is absent.
|
||||
|
||||
| Header | Accepts | Effect |
|
||||
| :---------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `X-OmniRoute-Mode` | a preset alias (`fast`, `balanced`, `quality`, `cheap`, `reliable`, `offline`) or a raw pack name (`ship-fast`, `cost-saver`, `quality-first`, `offline-friendly`, `reliability-first`) | Overrides the scoring weights for this request. `balanced`/`default` force the default weights (no pack). Unknown values are ignored (config preserved). |
|
||||
| `X-OmniRoute-Budget` | a positive number (max USD per request) | Hard cost ceiling: candidates whose estimated cost exceeds it are filtered before selection. What happens when **every** candidate exceeds it is controlled by `X-OmniRoute-Budget-Fallback` below. |
|
||||
| `X-OmniRoute-Budget-Fallback` | `cheapest` (default, aliases: `cheapest-viable`, `soft`) or `strict` (aliases: `block`, `hard`) | `cheapest`: falls back to the globally cheapest candidate even though it still exceeds the cap (legacy behavior). `strict`: refuses to select — the request fails fast with `HTTP 402` instead of silently overspending. Unknown values are ignored. |
|
||||
| Header | Accepts | Effect |
|
||||
| :----------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `X-OmniRoute-Mode` | a preset alias (`fast`, `balanced`, `quality`, `cheap`, `reliable`, `offline`) or a raw pack name (`ship-fast`, `cost-saver`, `quality-first`, `offline-friendly`, `reliability-first`) | Overrides the scoring weights for this request. `balanced`/`default` force the default weights (no pack). Unknown values are ignored (config preserved). |
|
||||
| `X-OmniRoute-Budget` | a positive number (max USD per request) | Hard cost ceiling: candidates whose estimated cost exceeds it are filtered before selection. What happens when **every** candidate exceeds it is controlled by `X-OmniRoute-Budget-Fallback` below. |
|
||||
| `X-OmniRoute-Budget-Fallback` | `cheapest` (default, aliases: `cheapest-viable`, `soft`) or `strict` (aliases: `block`, `hard`) | `cheapest`: falls back to the globally cheapest candidate even though it still exceeds the cap (legacy behavior). `strict`: refuses to select — the request fails fast with `HTTP 402` instead of silently overspending. Unknown values are ignored. |
|
||||
|
||||
```bash
|
||||
# Force the fastest profile, cap this request at $0.05, and hard-block instead of overspending
|
||||
@@ -240,27 +240,27 @@ resolved values feed the engine's existing `config.modePack` / `config.budgetCap
|
||||
|
||||
OmniRoute's combo engine supports **19 routing strategies** (declared in `src/shared/constants/routingStrategies.ts` → `ROUTING_STRATEGY_VALUES`). The Auto Combo engine itself is exposed under the `auto` strategy; the others are available for persisted combos.
|
||||
|
||||
| Strategy | Description |
|
||||
| :------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `priority` | First-target ordered list with explicit priority |
|
||||
| `weighted` | Weighted random by per-target weight |
|
||||
| `round-robin` | Cycle through targets in order |
|
||||
| `context-relay` | Hand off context across targets (long conversations) |
|
||||
| `fill-first` | Fill each target's quota before moving to next |
|
||||
| `p2c` | Power-of-2-choices random load balancing |
|
||||
| `random` | Uniform random selection |
|
||||
| `least-used` | Pick target with lowest current load |
|
||||
| `cost-optimized` | Minimize $ per request given catalog pricing |
|
||||
| `reset-aware` ⭐ | Prioritize by quota reset time — short reset windows ranked higher |
|
||||
| `reset-window` | Prefer targets whose quota window resets soonest |
|
||||
| `headroom` | Pick the target with the most remaining quota headroom |
|
||||
| `strict-random` | Random without deduplication of repeats |
|
||||
| `auto` | Use Auto Combo scoring (9-factor) — **recommended** |
|
||||
| `lkgp` | Last-Known-Good Path (sticky route to last successful target) |
|
||||
| `context-optimized` | Pick target with best fit for current context size |
|
||||
| Strategy | Description |
|
||||
| :------------------ | :--------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `priority` | First-target ordered list with explicit priority |
|
||||
| `weighted` | Weighted random by per-target weight |
|
||||
| `round-robin` | Cycle through targets in order |
|
||||
| `context-relay` | Hand off context across targets (long conversations) |
|
||||
| `fill-first` | Fill each target's quota before moving to next |
|
||||
| `p2c` | Power-of-2-choices random load balancing |
|
||||
| `random` | Uniform random selection |
|
||||
| `least-used` | Pick target with lowest current load |
|
||||
| `cost-optimized` | Minimize $ per request given catalog pricing |
|
||||
| `reset-aware` ⭐ | Prioritize by quota reset time — short reset windows ranked higher |
|
||||
| `reset-window` | Prefer targets whose quota window resets soonest |
|
||||
| `headroom` | Pick the target with the most remaining quota headroom |
|
||||
| `strict-random` | Random without deduplication of repeats |
|
||||
| `auto` | Use Auto Combo scoring (9-factor) — **recommended** |
|
||||
| `lkgp` | Last-Known-Good Path (sticky route to last successful target) |
|
||||
| `context-optimized` | Pick target with best fit for current context size |
|
||||
| `cache-optimized` | Reorder targets by prompt-cache affinity — the connection likeliest to already hold this request's cached prefix is tried first (`open-sse/services/combo/promptCacheAffinity.ts`, #8008) |
|
||||
| `fusion` 🧬 | Fan out to a panel of models in parallel, then synthesize one answer via a judge (see below) |
|
||||
| `pipeline` | Run targets sequentially, threading each step's output into the next step's input; only the final answer is returned (#6396) |
|
||||
| `fusion` 🧬 | Fan out to a panel of models in parallel, then synthesize one answer via a judge (see below) |
|
||||
| `pipeline` | Run targets sequentially, threading each step's output into the next step's input; only the final answer is returned (#6396) |
|
||||
|
||||
⭐ = New in v3.8.0 · 🧬 = New in v3.8.36
|
||||
|
||||
@@ -679,11 +679,11 @@ See `docs/marketing/TIERS.md` for tier definitions and provider classification.
|
||||
|
||||
### Deterministic routing-decision matrix (`npm run test:combo:matrix`)
|
||||
|
||||
`tests/integration/combo-matrix/*.test.ts` proves the routing **decision** of all 19
|
||||
`tests/integration/combo-matrix/*.test.ts` proves the routing **decision** of all 18
|
||||
public strategies end-to-end through the real combo pipeline with a mocked upstream.
|
||||
Coverage includes:
|
||||
|
||||
- All 19 `ROUTING_STRATEGY_VALUES` strategies (ordered, weighted, cost, context, fusion, …).
|
||||
- All 18 `ROUTING_STRATEGY_VALUES` strategies (ordered, weighted, cost, context, fusion, …).
|
||||
- `quota-share` (internal) end-to-end: DRR fairness + saturation deprioritization via the
|
||||
real `selectQuotaShareTarget` seam (`registerQuotaFetcher` / `setLKGP` /
|
||||
`__setHeadroomSaturationFetcherForTests`).
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
# agentrouter.org WAF (Web Application Firewall)
|
||||
|
||||
The `agentrouter` upstream gateway runs a keyword-based content filter on
|
||||
`messages[].content`. The filter is partially deterministic (always blocks
|
||||
certain phrases) and partially probabilistic (burst-sensitive — becomes
|
||||
more aggressive after rapid requests, recovers after a cooldown).
|
||||
|
||||
When the WAF blocks a request it returns:
|
||||
|
||||
```
|
||||
HTTP/1.1 400 Bad Request
|
||||
{"error":{"code":"content-blocked","message":"content-blocked (request id: ...)","param":"","type":"agent_router_api_error"}}
|
||||
```
|
||||
|
||||
## Scope of the filter
|
||||
|
||||
The WAF inspects `messages[].content` only. It does **not** inspect:
|
||||
|
||||
- The `system` prompt
|
||||
- Structured content blocks (`tool_result`, `tool_use`, `thinking`, `image`)
|
||||
- Tool `description` and `input_schema` fields
|
||||
- Request metadata, headers, or model id
|
||||
|
||||
## Always-blocked patterns (case-insensitive)
|
||||
|
||||
| Pattern | Notes |
|
||||
|-------------------------------|----------------------------------------|
|
||||
| Any `Lorem ipsum` variant | Full Latin lorem vocabulary is blocked |
|
||||
| `language model` (alone) | "the language model" and "large language model" pass |
|
||||
| `virtual assistant` | "AI assistant" passes |
|
||||
| `I'm here to help` | "here to help" alone also blocks |
|
||||
| `Claude, made by Anthropic` | Full phrase only |
|
||||
|
||||
## Almost-always-blocked patterns
|
||||
|
||||
| Pattern | Notes |
|
||||
|-------------------|---------------------------------------------------------|
|
||||
| `placeholder` | When it stands alone (not as a parameter name, etc.) |
|
||||
| `dummy data` | Common seed phrase for fixtures |
|
||||
| `foo bar baz` | Canonical placeholder phrase |
|
||||
| Repeated short tokens (`AAA BBB CCC`, `test test test`) | Detector for keyword stuffing |
|
||||
|
||||
## Behavior under load
|
||||
|
||||
After ~5 rapid requests in a short window, the WAF begins blocking content
|
||||
that would normally pass. The bucket relaxes after ~5–10 seconds of idle
|
||||
time. This is the same IP-and-key-bound rate limiter that causes
|
||||
intermittent `400 content-blocked` errors when Claude Code or Codex CLI
|
||||
makes multiple tool-use / message-send calls in quick succession.
|
||||
|
||||
## Mitigations already applied in OmniRoute
|
||||
|
||||
1. **`open-sse/services/wafRateLimit.ts`** — burst guard that enforces a
|
||||
500 ms minimum gap between outbound requests to any `agentrouter:*`
|
||||
URL. The gap is well below human perception of latency and prevents
|
||||
the WAF from activating on normal traffic.
|
||||
|
||||
2. **`BaseExecutor.WAF_RETRY_CONFIG`** — when an upstream returns
|
||||
`400 content-blocked`, the executor retries the same URL with
|
||||
exponential backoff (1.5 s, 3.0 s, max 2 attempts). After the backoff
|
||||
the WAF usually relaxes and the retry succeeds.
|
||||
|
||||
3. **`tests/unit/compression/harness.test.ts`** — the test fixture
|
||||
`longInput` was changed from `"lorem ipsum dolor sit amet ".repeat(40)`
|
||||
to `"example content for testing purposes ".repeat(40)` so that when
|
||||
Claude Code reads this file via the `Read` tool, the file contents
|
||||
do not flow back through a `tool_result` block and trip the WAF.
|
||||
|
||||
## Guidance for prompts and tool output
|
||||
|
||||
If a Claude Code or Codex CLI session repeatedly hits
|
||||
`400 content-blocked`, check the most recent user message and the most
|
||||
recent tool result for any of the patterns above and rephrase. Common
|
||||
workarounds:
|
||||
|
||||
- Replace `Lorem ipsum …` with `example text …` or the actual content
|
||||
the test or fixture is trying to model.
|
||||
- Replace `placeholder` (when standing alone) with `example value`,
|
||||
`sample value`, or the real value.
|
||||
- Replace `language model` with `large language model` or `the model`.
|
||||
- Replace `dummy data` with `sample data` or realistic seed values.
|
||||
- Replace `I'm here to help` / `here to help` with a more specific
|
||||
opener (e.g. "I'll review the file you mentioned").
|
||||
|
||||
## Reporting the false positives upstream
|
||||
|
||||
The current filter is overly aggressive — it blocks "Lorem ipsum" in
|
||||
`tool_result` blocks even though the operator clearly did not intend to
|
||||
inject a prompt. Operators who want this fixed at the source should
|
||||
contact `agentrouter.org` to report the false positives. The blocklist
|
||||
above is the empirical result of probing the upstream as of 2026-08-03.
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "omniroute-desktop",
|
||||
"version": "3.8.50",
|
||||
"version": "3.8.49",
|
||||
"description": "OmniRoute Desktop Application",
|
||||
"main": "main.js",
|
||||
"author": {
|
||||
|
||||
@@ -13,29 +13,6 @@ const TO_NUMBER_RESTRICTION = {
|
||||
"canonical coercion shape and the `toNumberOrNull`/`toNumberArray` variants.",
|
||||
};
|
||||
|
||||
const LOCAL_DB_IMPORT_RESTRICTION = {
|
||||
regex: "^(?:@/lib/localDb(?:\\.ts)?|(?:\\.\\.?/)+(?:lib/)?localDb(?:\\.ts)?)$",
|
||||
message:
|
||||
"The localDb compatibility barrel is restricted — import the owning domain module " +
|
||||
"from `@/lib/db/` instead.",
|
||||
};
|
||||
|
||||
const EXECUTOR_IMPORT_RESTRICTION = {
|
||||
regex: "^(?:@omniroute/)?open-sse/executors(?:/|$)",
|
||||
message:
|
||||
"Executor implementations must stay behind an open-sse handler or service boundary.",
|
||||
};
|
||||
|
||||
const PROP_TYPES_RESTRICTION = {
|
||||
name: "prop-types",
|
||||
message: "PropTypes are deprecated. Use TypeScript types/interfaces instead.",
|
||||
};
|
||||
|
||||
const IMPORT_BOUNDARY_RESTRICTIONS = {
|
||||
paths: [PROP_TYPES_RESTRICTION],
|
||||
patterns: [LOCAL_DB_IMPORT_RESTRICTION],
|
||||
};
|
||||
|
||||
/** @type {import("eslint").Linter.Config[]} */
|
||||
const eslintConfig = [
|
||||
...nextVitals,
|
||||
@@ -62,32 +39,15 @@ const eslintConfig = [
|
||||
"no-eval": "error",
|
||||
"no-implied-eval": "error",
|
||||
"no-new-func": "error",
|
||||
"no-restricted-imports": ["error", IMPORT_BOUNDARY_RESTRICTIONS],
|
||||
},
|
||||
},
|
||||
// G14: DB internals may use the compatibility barrel while it is decomposed; all
|
||||
// other source files must import the owning src/lib/db domain module directly.
|
||||
{
|
||||
files: ["src/lib/db/**/*.{ts,tsx,js,jsx}"],
|
||||
rules: {
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
paths: [PROP_TYPES_RESTRICTION],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// G14: App routes/components must delegate provider execution through handlers or
|
||||
// services instead of reaching into executor implementations.
|
||||
{
|
||||
files: ["src/app/**/*.{ts,tsx,js,jsx}"],
|
||||
rules: {
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
...IMPORT_BOUNDARY_RESTRICTIONS,
|
||||
patterns: [LOCAL_DB_IMPORT_RESTRICTION, EXECUTOR_IMPORT_RESTRICTION],
|
||||
paths: [
|
||||
{
|
||||
name: "prop-types",
|
||||
message: "PropTypes are deprecated. Use TypeScript types/interfaces instead.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
DEFAULT_CODEX_CLIENT_VERSION,
|
||||
getCodexCliRsHeaders as buildCodexCliRsHeaders,
|
||||
} from "@/shared/constants/codexClient";
|
||||
|
||||
export { DEFAULT_CODEX_CLIENT_VERSION } from "@/shared/constants/codexClient";
|
||||
const DEFAULT_CODEX_CLIENT_VERSION = "0.144.1";
|
||||
const DEFAULT_CODEX_USER_AGENT_PLATFORM = "Windows 10.0.26200";
|
||||
const DEFAULT_CODEX_USER_AGENT_ARCH = "x64";
|
||||
const CODEX_VERSION_OVERRIDE_ENV = "CODEX_CLIENT_VERSION";
|
||||
@@ -47,10 +42,6 @@ export function getCodexDefaultHeaders(): Record<string, string> {
|
||||
};
|
||||
}
|
||||
|
||||
export function getCodexCliRsHeaders(): Record<string, string> {
|
||||
return buildCodexCliRsHeaders(getCodexClientVersion());
|
||||
}
|
||||
|
||||
export function normalizeCodexSessionId(value: unknown): string | null {
|
||||
if (typeof value !== "string") return null;
|
||||
const normalized = value.trim();
|
||||
|
||||
@@ -19,9 +19,10 @@ import type { FreeModelBudget } from "./freeModelCatalog.ts";
|
||||
export const FREE_CATALOG_CURATED_AT = "2026-07-22";
|
||||
|
||||
export const FREE_MODEL_BUDGETS: FreeModelBudget[] = [
|
||||
{ provider: "agentrouter", modelId: "claude-opus-4-8", displayName: "Claude Opus 4.8", monthlyTokens: 0, creditTokens: 200000000, freeType: "one-time-initial", poolKey: "agentrouter", tos: "caution" },
|
||||
{ provider: "agentrouter", modelId: "claude-opus-5", displayName: "Claude Opus 5", monthlyTokens: 0, creditTokens: 200000000, freeType: "one-time-initial", poolKey: "agentrouter", tos: "caution" },
|
||||
{ provider: "agentrouter", modelId: "gpt-5.6-sol", displayName: "GPT-5.6 Sol", monthlyTokens: 0, creditTokens: 200000000, freeType: "one-time-initial", poolKey: "agentrouter", tos: "caution" },
|
||||
{ provider: "agentrouter", modelId: "claude-opus-4-6", displayName: "Claude 4.6 Opus", monthlyTokens: 0, creditTokens: 200000000, freeType: "one-time-initial", poolKey: "agentrouter", tos: "caution" },
|
||||
{ provider: "agentrouter", modelId: "claude-haiku-4-5-20251001", displayName: "Claude 4.5 Haiku", monthlyTokens: 0, creditTokens: 200000000, freeType: "one-time-initial", poolKey: "agentrouter", tos: "caution" },
|
||||
{ provider: "agentrouter", modelId: "glm-5.1", displayName: "GLM 5.1", monthlyTokens: 0, creditTokens: 200000000, freeType: "one-time-initial", poolKey: "agentrouter", tos: "caution" },
|
||||
{ provider: "agentrouter", modelId: "deepseek-v3.2", displayName: "DeepSeek V3.2", monthlyTokens: 0, creditTokens: 200000000, freeType: "one-time-initial", poolKey: "agentrouter", tos: "caution" },
|
||||
{ provider: "agy", modelId: "claude-opus-4-6-thinking", displayName: "Claude Opus 4.6 (Thinking)", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "agy", tos: "avoid" },
|
||||
{ provider: "agy", modelId: "claude-sonnet-4-6", displayName: "Claude Sonnet 4.6 (Thinking)", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "agy", tos: "avoid" },
|
||||
{ provider: "agy", modelId: "gemini-3.1-pro-low", displayName: "Gemini 3.1 Pro (Low)", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "agy", tos: "avoid" },
|
||||
|
||||
@@ -11,7 +11,6 @@ import { KIE_IMAGE_MODELS } from "./providers/registry/kie/imageModels.ts";
|
||||
import { FREEPIK_IMAGE_PROVIDER } from "./providers/registry/freepik/index.ts";
|
||||
import { STABILITY_AI_IMAGE_MODELS } from "./providers/registry/stability-ai/imageModels.ts";
|
||||
import { GEMINI_IMAGEN_PROVIDER } from "./providers/registry/gemini/imageModels.ts";
|
||||
import { CHEAPERINFERENCE_IMAGE_PROVIDER } from "./providers/registry/cheaperinference/imageModels.ts";
|
||||
|
||||
interface ImageModelEntry {
|
||||
id: string;
|
||||
@@ -715,13 +714,6 @@ export const IMAGE_PROVIDERS: Record<string, ImageProviderConfig> = {
|
||||
supportedSizes: ["1:1", "16:9", "9:16", "4:3", "3:4", "1024x1024", "1792x1024", "1024x1792"],
|
||||
},
|
||||
|
||||
// Cheaper Inference (OSS-sponsor gateway). Declared AFTER adobe-firefly on
|
||||
// purpose: it shares the nano-banana-pro / nano-banana-2 ids, and parseImageModel
|
||||
// resolves a bare id by first-match over this object's iteration order, so
|
||||
// Firefly keeps the bare ids and these are prefix-only. See the module for the
|
||||
// full collision note.
|
||||
cheaperinference: CHEAPERINFERENCE_IMAGE_PROVIDER,
|
||||
|
||||
// Keep Bailian Coding Plan after existing duplicate model owners so adding
|
||||
// explicit `bailian-coding-plan/` and `bcp/` routes does not change
|
||||
// historical bare-model routing.
|
||||
|
||||
@@ -117,7 +117,6 @@ import { uncloseaiProvider } from "./registry/uncloseai/index.ts";
|
||||
import { nscaleProvider } from "./registry/nscale/index.ts";
|
||||
import { chatgpt_webProvider } from "./registry/chatgpt-web/index.ts";
|
||||
import { openrouterProvider } from "./registry/openrouter/index.ts";
|
||||
import { cheaperinferenceProvider } from "./registry/cheaperinference/index.ts";
|
||||
import { openvectaProvider } from "./registry/openvecta/index.ts";
|
||||
import { orcarouterProvider } from "./registry/orcarouter/index.ts";
|
||||
import { copilot_webProvider } from "./registry/copilot-web/index.ts";
|
||||
@@ -338,7 +337,6 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
nscale: nscaleProvider,
|
||||
"chatgpt-web": chatgpt_webProvider,
|
||||
openrouter: openrouterProvider,
|
||||
cheaperinference: cheaperinferenceProvider,
|
||||
openvecta: openvectaProvider,
|
||||
orcarouter: orcarouterProvider,
|
||||
"copilot-web": copilot_webProvider,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { getCodexCliRsHeaders } from "../../../codexClient.ts";
|
||||
|
||||
export const agentrouterProvider: RegistryEntry = {
|
||||
id: "agentrouter",
|
||||
@@ -9,22 +8,6 @@ export const agentrouterProvider: RegistryEntry = {
|
||||
baseUrl: "https://agentrouter.org/v1/messages",
|
||||
authType: "apikey",
|
||||
authHeader: "x-api-key",
|
||||
alternateFormats: [
|
||||
{
|
||||
format: "openai",
|
||||
baseUrl: "https://agentrouter.org/v1/chat/completions",
|
||||
authHeader: "bearer",
|
||||
headers: getCodexCliRsHeaders(),
|
||||
label: "OpenAI-compatible (Codex)",
|
||||
},
|
||||
{
|
||||
format: "openai-responses",
|
||||
baseUrl: "https://agentrouter.org/v1/responses",
|
||||
authHeader: "bearer",
|
||||
headers: getCodexCliRsHeaders(),
|
||||
label: "OpenAI Responses (Codex)",
|
||||
},
|
||||
],
|
||||
defaultContextLength: 128000,
|
||||
// No static `headers` here: agentrouter now adopts the DYNAMIC Claude-Code
|
||||
// wire image via CC_WIRE_IMAGE_BUILTINS (#6056) — the fingerprint/headers are
|
||||
@@ -32,9 +15,10 @@ export const agentrouterProvider: RegistryEntry = {
|
||||
// own baseUrl + x-api-key auth. A static fingerprint here would drift and
|
||||
// trip AgentRouter's WAF ("unauthorized client detected").
|
||||
models: [
|
||||
{ id: "claude-opus-4-8", name: "Claude Opus 4.8" },
|
||||
{ id: "claude-opus-5", name: "Claude Opus 5" },
|
||||
{ id: "gpt-5.6-sol", name: "GPT-5.6 Sol" },
|
||||
{ id: "claude-opus-4-6", name: "Claude 4.6 Opus" },
|
||||
{ id: "claude-haiku-4-5-20251001", name: "Claude 4.5 Haiku" },
|
||||
{ id: "glm-5.1", name: "GLM 5.1" },
|
||||
{ id: "deepseek-v3.2", name: "DeepSeek V3.2" },
|
||||
],
|
||||
passthroughModels: true,
|
||||
};
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
/**
|
||||
* Cheaper Inference image provider registry entry.
|
||||
* Extracted into its own module to keep open-sse/config/imageRegistry.ts
|
||||
* under the file-size cap (god-file decomposition; semantic split) — same
|
||||
* pattern as FREEPIK_IMAGE_PROVIDER / SEGMIND_IMAGE_PROVIDER.
|
||||
*
|
||||
* 3 image models measured from GET /v1/models?type=image on 2026-07-31.
|
||||
*
|
||||
* COLLISION NOTE: nano-banana-pro and nano-banana-2 are ALSO adobe-firefly model
|
||||
* ids. parseImageModel() resolves a bare id by first-match over IMAGE_PROVIDERS
|
||||
* iteration order, so this entry is spread into that object AFTER adobe-firefly:
|
||||
* bare `nano-banana-2` keeps routing to Firefly (pre-existing behaviour) and these
|
||||
* models are reachable only as `cheaperinference/<id>` / `cinf/<id>`. Do NOT add
|
||||
* IMAGE_MODEL_ALIASES entries for them — that would silently re-route Firefly
|
||||
* users. Guarded by tests/unit/cheaperinference-image-models.test.ts.
|
||||
*
|
||||
* The endpoint ignores `response_format:"url"` and always returns `b64_json`
|
||||
* (measured twice). The OpenAI image path already handles b64_json; this is not a
|
||||
* bug to "fix". /v1/images/edits returns 404 upstream, so no edit support.
|
||||
*/
|
||||
export const CHEAPERINFERENCE_IMAGE_PROVIDER = {
|
||||
id: "cheaperinference",
|
||||
alias: "cinf",
|
||||
baseUrl: "https://api.cheaperinference.com/v1/images/generations",
|
||||
authType: "apikey",
|
||||
authHeader: "bearer",
|
||||
format: "openai",
|
||||
models: [
|
||||
{ id: "grok-imagine", name: "Grok Imagine (Cheaper Inference)" },
|
||||
{ id: "nano-banana-pro", name: "Nano Banana Pro (Cheaper Inference)" },
|
||||
{ id: "nano-banana-2", name: "Nano Banana 2 (Cheaper Inference)" },
|
||||
],
|
||||
supportedSizes: ["1024x1024", "2048x2048", "4096x4096"],
|
||||
};
|
||||
@@ -1,247 +0,0 @@
|
||||
import type { RegistryEntry, RegistryModel } from "../../shared.ts";
|
||||
|
||||
/**
|
||||
* Cheaper Inference (https://api.cheaperinference.com) — cost-ranked OpenAI-compatible
|
||||
* gateway, OmniRoute Open Source Friend.
|
||||
*
|
||||
* Catalog captured from a live `GET /v1/models` on 2026-07-31 (42 entries: these 39
|
||||
* `type:"text"` models plus 3 `type:"image"` models that live in imageRegistry.ts —
|
||||
* sending an image model here returns HTTP 400 "Use POST /v1/images/generations").
|
||||
* `supportsVision`/`supportsReasoning` mirror each entry's `capabilities` object
|
||||
* verbatim; they are not inferred from the model name.
|
||||
*
|
||||
* The gateway also serves a native `/v1/responses` endpoint (`responsesBaseUrl`).
|
||||
* It is stateless and REQUIRES `store:false` — see executors/cheaperinference.ts,
|
||||
* which injects it and resolves the URL from the per-model `targetFormat` tag.
|
||||
*/
|
||||
export const CHEAPERINFERENCE_MODELS: RegistryModel[] = [
|
||||
{ id: "aion-labs.aion-2-0", name: "Aion 2.0", supportsReasoning: true, toolCalling: true },
|
||||
{
|
||||
id: "claude-fable-5",
|
||||
name: "Claude Fable 5",
|
||||
supportsVision: true,
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "claude-haiku-4.5",
|
||||
name: "Claude Haiku 4.5",
|
||||
supportsVision: true,
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "claude-opus-4-7-fast",
|
||||
name: "Claude Opus 4.7 Fast",
|
||||
supportsVision: true,
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "claude-opus-4-8-fast",
|
||||
name: "Claude Opus 4.8 Fast",
|
||||
supportsVision: true,
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "claude-opus-4.5",
|
||||
name: "Claude Opus 4.5",
|
||||
supportsVision: true,
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "claude-opus-4.6",
|
||||
name: "Claude Opus 4.6",
|
||||
supportsVision: true,
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "claude-opus-4.7",
|
||||
name: "Claude Opus 4.7",
|
||||
supportsVision: true,
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "claude-opus-4.8",
|
||||
name: "Claude Opus 4.8",
|
||||
supportsVision: true,
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "claude-opus-5",
|
||||
name: "Claude Opus 5",
|
||||
supportsVision: true,
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "claude-opus-5-fast",
|
||||
name: "Claude Opus 5 Fast",
|
||||
supportsVision: true,
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "claude-sonnet-4.5",
|
||||
name: "Claude Sonnet 4.5",
|
||||
supportsVision: true,
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "claude-sonnet-4.6",
|
||||
name: "Claude Sonnet 4.6",
|
||||
supportsVision: true,
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "claude-sonnet-5",
|
||||
name: "Claude Sonnet 5",
|
||||
supportsVision: true,
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
},
|
||||
{ id: "deepseek-v4-flash", name: "DeepSeek V4 Flash", supportsReasoning: true, toolCalling: true },
|
||||
{ id: "deepseek-v4-pro", name: "DeepSeek V4 Pro", supportsReasoning: true, toolCalling: true },
|
||||
{
|
||||
id: "gemini-2.5-flash",
|
||||
name: "Gemini 2.5 Flash",
|
||||
supportsVision: true,
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "gemini-3-5-flash",
|
||||
name: "Gemini 3.5 Flash",
|
||||
supportsVision: true,
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "gemini-3-flash-preview",
|
||||
name: "Gemini 3 Flash Preview",
|
||||
supportsVision: true,
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "gemini-3.1-flash-lite",
|
||||
name: "Gemini 3.1 Flash Lite",
|
||||
supportsVision: true,
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "gemini-3.1-pro",
|
||||
name: "Gemini 3.1 Pro",
|
||||
supportsVision: true,
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "gemini-3.1-pro-preview",
|
||||
name: "Gemini 3.1 Pro Preview",
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
},
|
||||
{ id: "glm-4.5", name: "GLM-4.5", supportsReasoning: true, toolCalling: true },
|
||||
{ id: "glm-4.5-air", name: "GLM-4.5 Air", supportsReasoning: true, toolCalling: true },
|
||||
{ id: "glm-4.6", name: "GLM-4.6", supportsReasoning: true, toolCalling: true },
|
||||
{ id: "glm-4.7", name: "GLM-4.7", supportsReasoning: true, toolCalling: true },
|
||||
{ id: "glm-5", name: "GLM-5", supportsReasoning: true, toolCalling: true },
|
||||
{ id: "glm-5.1", name: "GLM-5.1", supportsReasoning: true, toolCalling: true },
|
||||
{ id: "glm-5.2", name: "GLM-5.2", supportsReasoning: true, toolCalling: true },
|
||||
{
|
||||
id: "google/gemini-3.5-flash-lite",
|
||||
name: "Gemini 3.5 Flash Lite",
|
||||
supportsVision: true,
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
},
|
||||
// The GPT-5.x family is tagged for the gateway's native /v1/responses endpoint —
|
||||
// that is the surface OpenAI-family clients (Codex-style) expect for tool loops.
|
||||
{
|
||||
id: "gpt-5.4",
|
||||
name: "GPT-5.4",
|
||||
supportsVision: true,
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
targetFormat: "openai-responses",
|
||||
},
|
||||
{
|
||||
id: "gpt-5.4-mini",
|
||||
name: "GPT-5.4 Mini",
|
||||
supportsVision: true,
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
targetFormat: "openai-responses",
|
||||
},
|
||||
{
|
||||
id: "gpt-5.5",
|
||||
name: "GPT-5.5",
|
||||
supportsVision: true,
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
targetFormat: "openai-responses",
|
||||
},
|
||||
{
|
||||
id: "gpt-5.6-luna",
|
||||
name: "GPT-5.6 Luna",
|
||||
supportsVision: true,
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
targetFormat: "openai-responses",
|
||||
},
|
||||
{
|
||||
id: "gpt-5.6-sol",
|
||||
name: "GPT-5.6 Sol",
|
||||
supportsVision: true,
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
targetFormat: "openai-responses",
|
||||
},
|
||||
{
|
||||
id: "gpt-5.6-terra",
|
||||
name: "GPT-5.6 Terra",
|
||||
supportsVision: true,
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
targetFormat: "openai-responses",
|
||||
},
|
||||
{
|
||||
id: "grok-4.5",
|
||||
name: "Grok 4.5",
|
||||
supportsVision: true,
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "kimi-k3",
|
||||
name: "Kimi K3",
|
||||
supportsVision: true,
|
||||
supportsReasoning: true,
|
||||
toolCalling: true,
|
||||
},
|
||||
{ id: "minimax-m2.7", name: "MiniMax M2.7", supportsReasoning: true, toolCalling: true },
|
||||
];
|
||||
|
||||
export const cheaperinferenceProvider: RegistryEntry = {
|
||||
id: "cheaperinference",
|
||||
alias: "cinf",
|
||||
format: "openai",
|
||||
executor: "cheaperinference",
|
||||
baseUrl: "https://api.cheaperinference.com/v1/chat/completions",
|
||||
// The gateway serves a native, STATELESS /v1/responses endpoint alongside
|
||||
// /v1/chat/completions. Consumed by CheaperInferenceExecutor.buildUrl for the
|
||||
// models tagged targetFormat: "openai-responses" above.
|
||||
responsesBaseUrl: "https://api.cheaperinference.com/v1/responses",
|
||||
authType: "apikey",
|
||||
authHeader: "bearer",
|
||||
models: CHEAPERINFERENCE_MODELS,
|
||||
};
|
||||
@@ -34,7 +34,6 @@ import {
|
||||
resolveAccountKey,
|
||||
isFreeVariantModel,
|
||||
} from "../services/openrouterFreeWindow.ts";
|
||||
import { gateOutboundRequest } from "../services/wafRateLimit.ts";
|
||||
import type { PoolConfig } from "../services/sessionPool/types.ts";
|
||||
import type { Session } from "../services/sessionPool/session.ts";
|
||||
import { SessionPool } from "../services/sessionPool/sessionPool.ts";
|
||||
@@ -46,7 +45,6 @@ import {
|
||||
} from "../services/apiKeyRotator.ts";
|
||||
import type { KeyHealth } from "../services/apiKeyRotator.ts";
|
||||
import { getOpenAICompatibleType, isClaudeCodeCompatible } from "../services/provider.ts";
|
||||
import { usesCcWireImage } from "../services/ccWireImageBuiltins.ts";
|
||||
import {
|
||||
runWithOnPersist,
|
||||
getRefreshLeadMs,
|
||||
@@ -251,10 +249,7 @@ function collectThinkingConfigs(body: unknown): Array<Record<string, unknown>> {
|
||||
if (!body || typeof body !== "object") return [];
|
||||
const root = body as Record<string, unknown>;
|
||||
const configs: Array<Record<string, unknown>> = [];
|
||||
const envelopes: unknown[] = [
|
||||
root.generationConfig,
|
||||
(root.request as Record<string, unknown> | undefined)?.generationConfig,
|
||||
];
|
||||
const envelopes: unknown[] = [root.generationConfig, (root.request as Record<string, unknown> | undefined)?.generationConfig];
|
||||
for (const env of envelopes) {
|
||||
if (!env || typeof env !== "object") continue;
|
||||
const tc = (env as Record<string, unknown>).thinkingConfig;
|
||||
@@ -450,12 +445,6 @@ export class BaseExecutor {
|
||||
);
|
||||
}
|
||||
|
||||
protected usesClaudeCodeProtocol(credentials: ProviderCredentials | null): boolean {
|
||||
if (!isClaudeCodeCompatible(this.provider)) return false;
|
||||
const format = this.resolveAlternate(credentials)?.format;
|
||||
return format !== "openai" && format !== "openai-responses";
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the effective API key via extra-keys round-robin rotation.
|
||||
* Mutates `credentials.providerSpecificData.selectedKeyId` on rotation.
|
||||
@@ -600,15 +589,6 @@ export class BaseExecutor {
|
||||
|
||||
// Intra-URL retry config: retry same URL before falling back to next node
|
||||
static readonly RETRY_CONFIG = { maxAttempts: 2, delayMs: 2000 };
|
||||
// WAF (400 content-blocked) retry config: agentrouter.org's WAF is burst-sensitive
|
||||
// and recovers after a short cooldown. Use exponential backoff with a higher
|
||||
// starting delay than the generic 429 retry (which is 2s) because the WAF
|
||||
// needs more time to clear its per-IP suspicion bucket.
|
||||
static readonly WAF_RETRY_CONFIG = {
|
||||
maxAttempts: 2,
|
||||
delayMs: 1500,
|
||||
backoffMultiplier: 2,
|
||||
};
|
||||
// Timeout for receiving the initial upstream response headers. Once the response
|
||||
// starts streaming, STREAM_IDLE_TIMEOUT_MS / Undici bodyTimeout handle stalls.
|
||||
static FETCH_START_TIMEOUT_MS = FETCH_TIMEOUT_MS;
|
||||
@@ -854,16 +834,15 @@ export class BaseExecutor {
|
||||
);
|
||||
}
|
||||
|
||||
const usesClaudeCodeProtocol = this.usesClaudeCodeProtocol(requestCredentials);
|
||||
const fingerprintProvider =
|
||||
usesCcWireImage(this.provider) && !usesClaudeCodeProtocol ? "codex" : this.provider;
|
||||
const ccRequestDefaults = usesClaudeCodeProtocol
|
||||
const ccRequestDefaults = isClaudeCodeCompatible(this.provider)
|
||||
? getClaudeCodeCompatibleRequestDefaults(requestCredentials?.providerSpecificData)
|
||||
: {};
|
||||
const shouldForwardExtendedContext =
|
||||
extendedContext && modelSupportsContext1mBeta(model) && !usesClaudeCodeProtocol;
|
||||
extendedContext &&
|
||||
modelSupportsContext1mBeta(model) &&
|
||||
!isClaudeCodeCompatible(this.provider);
|
||||
const shouldForwardCcCompatibleContext1m =
|
||||
usesClaudeCodeProtocol &&
|
||||
isClaudeCodeCompatible(this.provider) &&
|
||||
ccRequestDefaults.context1m === true &&
|
||||
!modelHasNativeContext1m(model);
|
||||
if (shouldForwardExtendedContext || shouldForwardCcCompatibleContext1m) {
|
||||
@@ -943,8 +922,8 @@ export class BaseExecutor {
|
||||
!activeCredentials?.apiKey;
|
||||
|
||||
if (
|
||||
((this.provider === "claude" && (isClaudeCodeClient || hasClaudeOAuthToken)) ||
|
||||
usesClaudeCodeProtocol) &&
|
||||
this.provider === "claude" &&
|
||||
(isClaudeCodeClient || hasClaudeOAuthToken) &&
|
||||
typeof transformedBody === "object" &&
|
||||
transformedBody !== null
|
||||
) {
|
||||
@@ -1221,11 +1200,6 @@ export class BaseExecutor {
|
||||
if (ccKeysLower.has(key.toLowerCase())) delete headers[key];
|
||||
}
|
||||
Object.assign(headers, ccHeaders);
|
||||
if (usesCcWireImage(this.provider) && usesClaudeCodeProtocol) {
|
||||
delete headers["Authorization"];
|
||||
headers["x-api-key"] =
|
||||
activeCredentials?.apiKey || activeCredentials?.accessToken || "";
|
||||
}
|
||||
delete headers["X-Stainless-Helper-Method"];
|
||||
|
||||
// OS/arch follow the host running the signed binary. Runtime version
|
||||
@@ -1272,7 +1246,7 @@ export class BaseExecutor {
|
||||
// (tool_result must be in immediately next message).
|
||||
// Only apply for Claude/Claude-compatible — OpenAI allows results
|
||||
// spread across multiple subsequent messages.
|
||||
const isClaude = this.provider === "claude" || usesClaudeCodeProtocol;
|
||||
const isClaude = this.provider === "claude" || isClaudeCodeCompatible(this.provider);
|
||||
// For Claude, fixToolAdjacency may strip tool_use blocks whose
|
||||
// tool_result isn't in the next message; re-run fixToolPairs to
|
||||
// drop any tool_result orphaned by that strip (discussion #2410).
|
||||
@@ -1291,7 +1265,7 @@ export class BaseExecutor {
|
||||
// at this final dispatch point — the single chokepoint every Claude
|
||||
// routing mode (grouped/raw/combo) and the native passthrough share,
|
||||
// before fingerprinting and CCH signing serialize the body.
|
||||
if (this.provider === "claude" || usesClaudeCodeProtocol) {
|
||||
if (this.provider === "claude" || isClaudeCodeCompatible(this.provider)) {
|
||||
enforceThinkingTemperature(transformedBody as Record<string, unknown>);
|
||||
}
|
||||
|
||||
@@ -1308,7 +1282,7 @@ export class BaseExecutor {
|
||||
// `contextEditingDisabled` (set by the 400-fallback) suppresses re-injection
|
||||
// when a fresh `transformedBody` is built for a retry/fallback URL.
|
||||
if (
|
||||
(this.provider === "claude" || usesClaudeCodeProtocol) &&
|
||||
(this.provider === "claude" || isClaudeCodeCompatible(this.provider)) &&
|
||||
contextEditing?.enabled &&
|
||||
!contextEditingDisabled
|
||||
) {
|
||||
@@ -1324,17 +1298,17 @@ export class BaseExecutor {
|
||||
let bodyString = JSON.stringify(transformedBody);
|
||||
|
||||
const shouldFingerprint =
|
||||
isCliCompatEnabled(fingerprintProvider) ||
|
||||
isCliCompatEnabled(this.provider) ||
|
||||
(this.provider === "claude" && (isClaudeCodeClient || hasClaudeOAuthToken));
|
||||
if (shouldFingerprint) {
|
||||
const fingerprinted = applyFingerprint(fingerprintProvider, headers, transformedBody);
|
||||
const fingerprinted = applyFingerprint(this.provider, headers, transformedBody);
|
||||
finalHeaders = fingerprinted.headers;
|
||||
bodyString = fingerprinted.bodyString;
|
||||
}
|
||||
|
||||
// CCH signing — replaces the cch=00000 placeholder in the billing
|
||||
// header with an xxHash64 integrity token over the serialized body.
|
||||
if (usesClaudeCodeProtocol || this.provider === "claude") {
|
||||
if (isClaudeCodeCompatible(this.provider) || this.provider === "claude") {
|
||||
bodyString = await signRequestBody(bodyString);
|
||||
}
|
||||
|
||||
@@ -1399,13 +1373,6 @@ export class BaseExecutor {
|
||||
recordFreeWindowAttempt(openrouterFreeWindowAccountKey);
|
||||
}
|
||||
|
||||
// WAF burst guard: agentrouter.org's content filter becomes more
|
||||
// aggressive after rapid requests. Enforce a small inter-request gap
|
||||
// to avoid tripping it. See open-sse/services/wafRateLimit.ts.
|
||||
if (this.provider === "agentrouter") {
|
||||
await gateOutboundRequest(`agentrouter:${url}`);
|
||||
}
|
||||
|
||||
let response = await fetchWithStartTimeout(url, fetchOptions);
|
||||
|
||||
if (openrouterFreeWindowAccountKey) {
|
||||
@@ -1429,7 +1396,7 @@ export class BaseExecutor {
|
||||
contextEditingDisabled = true;
|
||||
delete (transformedBody as Record<string, unknown>).context_management;
|
||||
let retryBody = JSON.stringify(transformedBody);
|
||||
if (usesClaudeCodeProtocol || this.provider === "claude") {
|
||||
if (isClaudeCodeCompatible(this.provider) || this.provider === "claude") {
|
||||
retryBody = await signRequestBody(retryBody);
|
||||
}
|
||||
log?.debug?.(
|
||||
@@ -1468,7 +1435,7 @@ export class BaseExecutor {
|
||||
thinkingBudgetClampedMax = upstreamMax;
|
||||
if (clampNestedThinkingBudget(transformedBody, upstreamMax)) {
|
||||
let retryBody = JSON.stringify(transformedBody);
|
||||
if (usesClaudeCodeProtocol || this.provider === "claude") {
|
||||
if (isClaudeCodeCompatible(this.provider) || this.provider === "claude") {
|
||||
retryBody = await signRequestBody(retryBody);
|
||||
}
|
||||
log?.info?.(
|
||||
@@ -1499,7 +1466,7 @@ export class BaseExecutor {
|
||||
strippedFields.add(offending);
|
||||
delete (transformedBody as Record<string, unknown>)[offending];
|
||||
let retryBody = JSON.stringify(transformedBody);
|
||||
if (usesClaudeCodeProtocol || this.provider === "claude") {
|
||||
if (isClaudeCodeCompatible(this.provider) || this.provider === "claude") {
|
||||
retryBody = await signRequestBody(retryBody);
|
||||
}
|
||||
log?.debug?.(
|
||||
@@ -1524,7 +1491,7 @@ export class BaseExecutor {
|
||||
addParamToBlocklist(this.provider, autoLearned, model);
|
||||
delete (transformedBody as Record<string, unknown>)[autoLearned];
|
||||
let retryBody = JSON.stringify(transformedBody);
|
||||
if (usesClaudeCodeProtocol || this.provider === "claude") {
|
||||
if (isClaudeCodeCompatible(this.provider) || this.provider === "claude") {
|
||||
retryBody = await signRequestBody(retryBody);
|
||||
}
|
||||
log?.info?.(
|
||||
@@ -1543,34 +1510,6 @@ export class BaseExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
// Intra-URL retry: agentrouter.org WAF returns 400 content-blocked
|
||||
// intermittently (burst-sensitive, recovers after cooldown). Retry the
|
||||
// same URL with exponential backoff before falling through to the
|
||||
// 429/401/fallback chain. See docs/security/AGENTROUTER_WAF.md.
|
||||
if (
|
||||
!skipUpstreamRetry &&
|
||||
response.status === HTTP_STATUS.BAD_REQUEST &&
|
||||
(retryAttemptsByUrl[urlIndex] ?? 0) < BaseExecutor.WAF_RETRY_CONFIG.maxAttempts
|
||||
) {
|
||||
const wafErrText = await response
|
||||
.clone()
|
||||
.text()
|
||||
.catch(() => "");
|
||||
if (/content[_-]blocked/i.test(wafErrText)) {
|
||||
retryAttemptsByUrl[urlIndex] = (retryAttemptsByUrl[urlIndex] ?? 0) + 1;
|
||||
const wafAttempt = retryAttemptsByUrl[urlIndex];
|
||||
const wafBackoff = BaseExecutor.WAF_RETRY_CONFIG.delayMs *
|
||||
Math.pow(BaseExecutor.WAF_RETRY_CONFIG.backoffMultiplier, wafAttempt - 1);
|
||||
log?.debug?.(
|
||||
"WAF_RETRY",
|
||||
`400 content-blocked intra-retry ${wafAttempt}/${BaseExecutor.WAF_RETRY_CONFIG.maxAttempts} on ${url} — waiting ${wafBackoff}ms`
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, wafBackoff));
|
||||
urlIndex--; // re-run this urlIndex on the next loop iteration
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Intra-URL retry: if 429 and we haven't exhausted per-URL retries, wait and retry the same URL
|
||||
if (
|
||||
!skipUpstreamRetry &&
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
import { BaseExecutor, type ProviderCredentials } from "./base.ts";
|
||||
import { PROVIDERS } from "../config/constants.ts";
|
||||
import { getModelTargetFormat, PROVIDER_ID_TO_ALIAS } from "../config/providerModels.ts";
|
||||
|
||||
/**
|
||||
* CheaperInferenceExecutor — api.cheaperinference.com.
|
||||
*
|
||||
* The gateway is OpenAI-compatible on both surfaces, so everything else comes from
|
||||
* BaseExecutor. Two provider-specific facts need handling (both measured against the
|
||||
* live API on 2026-07-31, not inferred from docs):
|
||||
*
|
||||
* 1. `/v1/responses` is STATELESS and REQUIRES `store:false`. Omitting it returns
|
||||
* HTTP 400 ("This Responses-compatible endpoint is stateless. Send store=false…").
|
||||
* chatCore.ts deletes `store` for every provider except "openai" — a strip shared
|
||||
* by ~290 providers that must not be special-cased — so we re-add it here, after
|
||||
* that strip has run. A client-supplied `store:true` is overwritten rather than
|
||||
* forwarded: the endpoint cannot honour it, and forwarding would 400.
|
||||
*
|
||||
* 2. Chat and Responses live at DIFFERENT URLs (unlike providers that switch on a
|
||||
* path suffix). The per-model `targetFormat` registry tag is the single source of
|
||||
* truth for which surface a model uses — the same tag chatCore reads to translate
|
||||
* the body — so resolving the URL from it keeps URL and payload in lockstep.
|
||||
* Same pattern as executors/xai.ts (9router#2439).
|
||||
*/
|
||||
export class CheaperInferenceExecutor extends BaseExecutor {
|
||||
constructor(provider = "cheaperinference") {
|
||||
super(provider, PROVIDERS[provider]);
|
||||
}
|
||||
|
||||
/**
|
||||
* True when this model is served by the native /v1/responses endpoint.
|
||||
*
|
||||
* PROVIDER_MODELS is keyed by provider ALIAS ("cinf"), while PROVIDERS is keyed by
|
||||
* provider ID ("cheaperinference") — so `this.provider` cannot be passed straight
|
||||
* through the way executors/xai.ts does (there the alias equals the id, which hides
|
||||
* the distinction). Resolve the alias first or every lookup silently returns null
|
||||
* and every Responses request 400s upstream.
|
||||
*/
|
||||
private usesResponsesEndpoint(model: string): boolean {
|
||||
const alias = PROVIDER_ID_TO_ALIAS[this.provider] || this.provider;
|
||||
return getModelTargetFormat(alias, model) === "openai-responses";
|
||||
}
|
||||
|
||||
buildUrl(model: string, _stream: boolean, _urlIndex = 0): string {
|
||||
if (this.usesResponsesEndpoint(model)) {
|
||||
return this.config.responsesBaseUrl || this.config.baseUrl;
|
||||
}
|
||||
return this.config.baseUrl;
|
||||
}
|
||||
|
||||
transformRequest(
|
||||
model: string,
|
||||
body: unknown,
|
||||
stream: boolean,
|
||||
credentials: ProviderCredentials
|
||||
): unknown {
|
||||
const cleanedBody = super.transformRequest(model, body, stream, credentials);
|
||||
if (!cleanedBody || typeof cleanedBody !== "object" || Array.isArray(cleanedBody)) {
|
||||
return cleanedBody;
|
||||
}
|
||||
if (!this.usesResponsesEndpoint(model)) {
|
||||
// Chat Completions rejects unknown params — never add `store` on that surface.
|
||||
return cleanedBody;
|
||||
}
|
||||
return { ...(cleanedBody as Record<string, unknown>), store: false };
|
||||
}
|
||||
}
|
||||
|
||||
export default CheaperInferenceExecutor;
|
||||
@@ -55,7 +55,6 @@ import { forwardOpencodeClientHeaders } from "../utils/opencodeHeaders.ts";
|
||||
import { resolveZaiUrl } from "./default/zaiFormatOverride.ts";
|
||||
import { acquireNvidiaConcurrencySlot } from "./default/nvidiaConcurrencyGate.ts";
|
||||
import { resolveAlibabaProviderBaseUrl } from "@/shared/constants/alibabaProviderRegions";
|
||||
import { usesCcWireImage } from "../services/ccWireImageBuiltins.ts";
|
||||
|
||||
import type { PoolConfig } from "../services/sessionPool/types.ts";
|
||||
|
||||
@@ -292,10 +291,6 @@ export class DefaultExecutor extends BaseExecutor {
|
||||
case "minimax":
|
||||
case "minimax-cn":
|
||||
return `${this.config.baseUrl}?beta=true`;
|
||||
case "agentrouter":
|
||||
return this.usesClaudeCodeProtocol(credentials)
|
||||
? `${this.config.baseUrl}?beta=true`
|
||||
: this.config.baseUrl;
|
||||
case "gemini":
|
||||
return `${this.config.baseUrl}/${model}:${stream ? "streamGenerateContent?alt=sse" : "generateContent"}`;
|
||||
default: {
|
||||
@@ -418,7 +413,7 @@ export class DefaultExecutor extends BaseExecutor {
|
||||
applyClineAuthHeaders(headers, credentials, effectiveKey, clientHeaders, false);
|
||||
break;
|
||||
default:
|
||||
if (this.usesClaudeCodeProtocol(credentials)) {
|
||||
if (isClaudeCodeCompatible(this.provider)) {
|
||||
const ccRequestDefaults = getClaudeCodeCompatibleRequestDefaults(
|
||||
credentials?.providerSpecificData
|
||||
);
|
||||
@@ -428,10 +423,6 @@ export class DefaultExecutor extends BaseExecutor {
|
||||
credentials?.providerSpecificData?.ccSessionId,
|
||||
{ redactThinking: ccRequestDefaults.redactThinking === true }
|
||||
);
|
||||
if (usesCcWireImage(this.provider)) {
|
||||
delete ccHeaders["Authorization"];
|
||||
ccHeaders["x-api-key"] = effectiveKey || credentials.accessToken || "";
|
||||
}
|
||||
// CC nodes are also anthropic-compatible-*, so honor operator custom
|
||||
// headers here (the early return skips the shared block below).
|
||||
applyCustomHeaders(ccHeaders, credentials.providerSpecificData?.customHeaders);
|
||||
|
||||
@@ -50,7 +50,6 @@ import { PoeWebExecutor } from "./poe-web.ts";
|
||||
import { VeniceWebExecutor } from "./venice-web.ts";
|
||||
import { NotionWebExecutor } from "./notion-web.ts";
|
||||
import { V0VercelWebExecutor } from "./v0-vercel-web.ts";
|
||||
import { CheaperInferenceExecutor } from "./cheaperinference.ts";
|
||||
import { KimiWebExecutor } from "./kimi-web.ts";
|
||||
import { DoubaoWebExecutor } from "./doubao-web.ts";
|
||||
import { QwenWebExecutor } from "./qwen-web.ts";
|
||||
@@ -166,8 +165,6 @@ const executors = {
|
||||
"kimi-coding": new KimiExecutor(), // Alias
|
||||
moonshot: new MoonshotExecutor(),
|
||||
kimi: new MoonshotExecutor("kimi"), // Hidden legacy Moonshot provider id
|
||||
cheaperinference: new CheaperInferenceExecutor(),
|
||||
cinf: new CheaperInferenceExecutor("cheaperinference"), // Alias
|
||||
"doubao-web": new DoubaoWebExecutor(),
|
||||
db: new DoubaoWebExecutor(), // Alias
|
||||
"qwen-web": new QwenWebExecutor(),
|
||||
@@ -285,5 +282,4 @@ export { ZenmuxFreeExecutor } from "./zenmux-free.ts";
|
||||
export { HyperAgentExecutor } from "./hyperagent.ts";
|
||||
export { XaiExecutor } from "./xai.ts";
|
||||
export { MoonshotExecutor } from "./moonshot.ts";
|
||||
export { CheaperInferenceExecutor } from "./cheaperinference.ts";
|
||||
export { PromptQlExecutor } from "./promptql.ts";
|
||||
|
||||
@@ -71,7 +71,6 @@ import { checkHeapPressureGuard } from "../utils/heapPressure.ts";
|
||||
import { normalizeHeaders } from "../utils/headers.ts";
|
||||
import { resolveChatCoreRequestFormat } from "./chatCore/requestFormat.ts";
|
||||
import { resolveChatCoreTargetFormat } from "./chatCore/targetFormat.ts";
|
||||
import { stripStore, usesClaudeBridge } from "./chatCore/agentRouterProtocol.ts";
|
||||
import { defaultClaudeToolType } from "./chatCore/claudeToolDefaults.ts";
|
||||
import { injectSystemPrompt, injectCustomSystemPrompt } from "../services/systemPrompt.ts";
|
||||
import { translateRequest, needsTranslation } from "../translator/index.ts";
|
||||
@@ -342,6 +341,7 @@ import { OMNIROUTE_RESPONSE_HEADERS } from "@/shared/constants/headers";
|
||||
import { getClaudeCodeCompatibleRequestDefaults } from "@/lib/providers/requestDefaults";
|
||||
import {
|
||||
buildClaudeCodeCompatibleRequest,
|
||||
isClaudeCodeCompatibleProvider,
|
||||
resolveClaudeCodeCompatibleSessionId,
|
||||
} from "../services/claudeCodeCompatible.ts";
|
||||
import { setGeminiThoughtSignatureMode } from "../services/geminiThoughtSignatureStore.ts";
|
||||
@@ -751,7 +751,6 @@ export async function handleChatCore({
|
||||
provider,
|
||||
resolvedModel,
|
||||
apiFormat,
|
||||
sourceFormat,
|
||||
customModelTargetFormat,
|
||||
providerSpecificData: credentials?.providerSpecificData,
|
||||
});
|
||||
@@ -1921,7 +1920,7 @@ export async function handleChatCore({
|
||||
|
||||
let translatedBody = body;
|
||||
const isClaudePassthrough = sourceFormat === FORMATS.CLAUDE && targetFormat === FORMATS.CLAUDE;
|
||||
const isClaudeCodeCompatible = usesClaudeBridge(provider, targetFormat, credentials);
|
||||
const isClaudeCodeCompatible = isClaudeCodeCompatibleProvider(provider);
|
||||
const isClaudeCodeSemanticPassthrough = isClaudeCodeSemanticPassthroughRequest({
|
||||
provider,
|
||||
sourceFormat,
|
||||
@@ -2497,7 +2496,10 @@ export async function handleChatCore({
|
||||
log?.debug?.("PARAMS", `Renamed max_completion_tokens to max_tokens for ${model}`);
|
||||
}
|
||||
|
||||
stripStore(translatedBody, provider, targetFormat);
|
||||
// OpenAI's `store` parameter is not supported by most compatible providers and breaks them
|
||||
if (provider !== "openai" && "store" in translatedBody) {
|
||||
delete translatedBody.store;
|
||||
}
|
||||
|
||||
// Chat clients may send stream_options.include_usage, but OpenAI Responses
|
||||
// upstreams (including Azure AI Foundry /responses) reject stream_options.
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
/**
|
||||
* Per-request AgentRouter protocol decisions kept outside the chatCore orchestration god-file.
|
||||
* AgentRouter exposes Claude Messages, OpenAI Chat, and OpenAI Responses as distinct upstream
|
||||
* protocols, so its dynamic target format must override the connection's default Claude wire image.
|
||||
*/
|
||||
|
||||
import { isClaudeCodeCompatibleProvider } from "../../services/claudeCodeCompatible.ts";
|
||||
import { FORMATS } from "../../translator/formats.ts";
|
||||
|
||||
export function usesClaudeBridge(
|
||||
provider: string,
|
||||
targetFormat: string,
|
||||
credentials: unknown
|
||||
): boolean {
|
||||
const configuredTargetFormat = (
|
||||
credentials as { providerSpecificData?: { targetFormat?: unknown } | null } | null | undefined
|
||||
)?.providerSpecificData?.targetFormat;
|
||||
const effectiveFormat = provider === "agentrouter" ? targetFormat : configuredTargetFormat;
|
||||
return (
|
||||
isClaudeCodeCompatibleProvider(provider) &&
|
||||
effectiveFormat !== FORMATS.OPENAI &&
|
||||
effectiveFormat !== FORMATS.OPENAI_RESPONSES
|
||||
);
|
||||
}
|
||||
|
||||
export function stripStore(
|
||||
body: Record<string, unknown>,
|
||||
provider: string,
|
||||
targetFormat: string
|
||||
): void {
|
||||
const supportsStore =
|
||||
provider === "openai" ||
|
||||
(provider === "agentrouter" && targetFormat === FORMATS.OPENAI_RESPONSES);
|
||||
if (!supportsStore && "store" in body) delete body.store;
|
||||
}
|
||||
@@ -5,8 +5,8 @@
|
||||
* Pure builder extracted from handleChatCore: derives the per-execution credentials object from the
|
||||
* resolved request context. Applies the native-Codex passthrough endpoint override, forces
|
||||
* apiType=responses (and the responses-upstream marker) for Azure AI Foundry / OCI when the model
|
||||
* routes to the OpenAI Responses format, synchronizes AgentRouter's per-request alternate protocol,
|
||||
* and threads the Claude Code session id when present. Side-effect-free.
|
||||
* routes to the OpenAI Responses format, and threads the Claude Code session id when present.
|
||||
* Side-effect-free; behaviour is byte-identical to the previous inline closure.
|
||||
*/
|
||||
|
||||
import { getKimiCodeStaticThinkingPolicy } from "../../config/providers/registry/kimi/coding/runtime.ts";
|
||||
@@ -128,16 +128,6 @@ export function resolveExecutionCredentials(opts: {
|
||||
providerSpecificData.targetFormat = targetFormat;
|
||||
}
|
||||
|
||||
// AgentRouter exposes Claude, OpenAI Chat, and OpenAI Responses on distinct URLs with distinct
|
||||
// auth schemes. Keep the executor's URL/header resolution synchronized with chatCore's resolved
|
||||
// per-request protocol without persisting the inferred selection back to the connection.
|
||||
if (
|
||||
provider === "agentrouter" &&
|
||||
(targetFormat === FORMATS.OPENAI || targetFormat === FORMATS.OPENAI_RESPONSES)
|
||||
) {
|
||||
providerSpecificData.targetFormat = targetFormat;
|
||||
}
|
||||
|
||||
applyKimiExecutionMetadata(providerSpecificData, provider, targetFormat, modelInfo);
|
||||
|
||||
const withApiType = {
|
||||
|
||||
@@ -6,8 +6,8 @@ export function sanitizeChatRequestBody(
|
||||
sourceFormat: string,
|
||||
targetFormat: string
|
||||
): Record<string, unknown> {
|
||||
void sourceFormat;
|
||||
const prefersResponsesTokenField = targetFormat === FORMATS.OPENAI_RESPONSES;
|
||||
const prefersResponsesTokenField =
|
||||
sourceFormat === FORMATS.OPENAI_RESPONSES || targetFormat === FORMATS.OPENAI_RESPONSES;
|
||||
|
||||
if (prefersResponsesTokenField) {
|
||||
if (body.max_output_tokens === undefined) {
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
* chatCore wire target-format resolver (Quality Gate v2 / Fase 9 — chatCore god-file
|
||||
* decomposition, #3501).
|
||||
*
|
||||
* Pure resolution of the provider alias + the upstream target format used to translate the request.
|
||||
* The inbound API shape does not determine the outbound protocol: the model registry target format,
|
||||
* per-model custom override (#2905), and provider configuration take precedence. This lets a
|
||||
* Responses-shaped client request be translated to a Chat Completions-compatible upstream while
|
||||
* preserving native Responses providers configured with an OpenAI Responses target.
|
||||
* Returns both `alias` (reused by the handler when stripping the `alias/` prefix off the upstream
|
||||
* model id) and `targetFormat`.
|
||||
* Pure resolution of the provider alias + the upstream target format used to translate the request:
|
||||
* apiFormat==="responses" forces OpenAI Responses; otherwise the model's registry target format, then
|
||||
* the per-model custom override (#2905), then the provider default. Returns both `alias` (reused by
|
||||
* the handler when stripping the `alias/` prefix off the upstream model id) and `targetFormat`.
|
||||
* Side-effect-free; byte-identical to the previous inline block. Sits alongside the other
|
||||
* request-setup resolvers (resolveChatCoreRequestSetup / resolveChatCoreRequestFormat).
|
||||
*/
|
||||
|
||||
import { PROVIDER_ID_TO_ALIAS, getModelTargetFormat } from "../../config/providerModels.ts";
|
||||
import { getTargetFormat } from "../../services/provider.ts";
|
||||
import { FORMATS } from "../../translator/formats.ts";
|
||||
|
||||
export function resolveChatCoreTargetFormat(opts: {
|
||||
provider: string;
|
||||
@@ -21,12 +21,14 @@ export function resolveChatCoreTargetFormat(opts: {
|
||||
customModelTargetFormat: string | undefined;
|
||||
providerSpecificData: unknown;
|
||||
}) {
|
||||
const { provider, resolvedModel, customModelTargetFormat, providerSpecificData } = opts;
|
||||
const { provider, resolvedModel, apiFormat, customModelTargetFormat, providerSpecificData } = opts;
|
||||
const alias = PROVIDER_ID_TO_ALIAS[provider] || provider;
|
||||
const modelTargetFormat = getModelTargetFormat(alias, resolvedModel);
|
||||
const targetFormat =
|
||||
modelTargetFormat || customModelTargetFormat || getTargetFormat(provider, providerSpecificData);
|
||||
apiFormat === "responses"
|
||||
? FORMATS.OPENAI_RESPONSES
|
||||
: modelTargetFormat || customModelTargetFormat || getTargetFormat(provider, providerSpecificData);
|
||||
return { alias, targetFormat };
|
||||
}
|
||||
|
||||
export type ChatCoreTargetFormat = ReturnType<typeof resolveChatCoreTargetFormat>;
|
||||
export type ChatCoreTargetFormat = ReturnType<typeof resolveChatCoreTargetFormat>;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@omniroute/open-sse",
|
||||
"version": "3.8.50",
|
||||
"version": "3.8.49",
|
||||
"description": "Express SSE sidecar for OmniRoute — handles streaming, protocol translation, and provider orchestration",
|
||||
"type": "module",
|
||||
"main": "index.js",
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
// open-sse/services/combo/strategyDispatch.ts
|
||||
// Runtime source of truth for the known-symbols combo gate (G1).
|
||||
//
|
||||
// HISTÓRICO: `scripts/check/check-known-symbols.ts` (seção 2) costumava descobrir quais
|
||||
// estratégias de roteamento têm branch de despacho lendo a fonte dos arquivos do combo e
|
||||
// extraindo literais `strategy === "..."` por regex. Isso quebra quando o despacho vira um
|
||||
// registry (R0.3): não há mais `strategy === "X"` para casar. Este módulo substitui essa
|
||||
// enumeração por regex-over-source por uma enumeração EXPLÍCITA em runtime, colada ao lado
|
||||
// do código de despacho real.
|
||||
//
|
||||
// Importamos as funções reais de ordenação/despacho (não apenas strings) para amarrar a
|
||||
// enumeração ao código vivo: se a maquinaria de despacho for reestruturada ou um módulo
|
||||
// quebrar, a importação falha no load do gate em vez de casar silenciosamente uma regex
|
||||
// obsoleta. As chaves em HANDLED_COMBO_STRATEGIES DEVEM casar exatamente o conjunto
|
||||
// canônico (ROUTING_STRATEGY_VALUES ∪ INTERNAL_ROUTING_STRATEGY_VALUES).
|
||||
//
|
||||
// Ao adicionar uma estratégia canônica, fie-a no despacho (aqui ou em combo.ts) e inclua-a
|
||||
// nesta lista; ao remover um branch, retire a entrada — o gate acusa qualquer divergência
|
||||
// nas duas direções (canonicalSemDespacho / despachoNaoCanonico).
|
||||
|
||||
import { applyStrategyOrdering } from "./applyStrategyOrdering.ts";
|
||||
import { resolveAutoStrategyOrder } from "./resolveAutoStrategy.ts";
|
||||
import { tryFusionDispatch, tryPipelineDispatch } from "./dispatchPrelude.ts";
|
||||
import { resolveComboTargetPipeline } from "./targetResolution.ts";
|
||||
|
||||
/**
|
||||
* As funções reais que implementam o despacho/ordenação de estratégias. Referenciadas
|
||||
* aqui para (a) provar ao gate que a maquinaria resolve e (b) servir de âncora viva para
|
||||
* a enumeração abaixo — o registry que o R0.3 vai passar a consumir para o branching
|
||||
* `strategy === ...` nasce destas mesmas funções.
|
||||
*/
|
||||
export const COMBO_STRATEGY_DISPATCH_LEAVES = {
|
||||
applyStrategyOrdering,
|
||||
resolveAutoStrategyOrder,
|
||||
tryFusionDispatch,
|
||||
tryPipelineDispatch,
|
||||
resolveComboTargetPipeline,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Conjunto exato de estratégias de roteamento que possuem implementação de despacho real.
|
||||
*
|
||||
* Cobertura esperada (em `main` do gate): este set ∪ IMPLICIT_DEFAULT_STRATEGIES deve
|
||||
* igualar o canônico. Atualmente todas as 20 estratégias canônicas têm branch — então
|
||||
* HANDLED_COMBO_STRATEGIES já contém as 20 e IMPLICIT_DEFAULT_STRATEGIES está vazio.
|
||||
*/
|
||||
export const HANDLED_COMBO_STRATEGIES: readonly string[] = [
|
||||
"priority",
|
||||
"weighted",
|
||||
"round-robin",
|
||||
"context-relay",
|
||||
"fill-first",
|
||||
"p2c",
|
||||
"random",
|
||||
"least-used",
|
||||
"cost-optimized",
|
||||
"reset-aware",
|
||||
"reset-window",
|
||||
"headroom",
|
||||
"strict-random",
|
||||
"auto",
|
||||
"lkgp",
|
||||
"context-optimized",
|
||||
"cache-optimized",
|
||||
"fusion",
|
||||
"pipeline",
|
||||
"quota-share",
|
||||
] as const;
|
||||
@@ -120,44 +120,7 @@ for (const [aliasOrId, models] of Object.entries(PROVIDER_MODELS)) {
|
||||
}
|
||||
}
|
||||
const KNOWN_MODEL_IDS = new Set(MODEL_TO_PROVIDERS.keys());
|
||||
// Bare Codex CLI defaults must always route to the `codex` provider (chatgpt.com
|
||||
// OAuth) even when other providers that also catalog the model id (e.g.
|
||||
// `agentrouter`, `openai`) are active. The Codex cookie quota on the user's
|
||||
// account is the source of truth for capacity, and bare-id requests from
|
||||
// `codex` (CLI)/`Codex` (web) would otherwise silently fan out to whichever
|
||||
// provider won the inference race — leaving the user wondering why the
|
||||
// canonical ChatGPT subscription stopped working. Override per-request by
|
||||
// prefixing the model id (e.g. `agentrouter/gpt-5.6-sol`,
|
||||
// `openai/gpt-5.6-sol`) — the prefix path always wins.
|
||||
export const CODEX_NATIVE_UNPREFIXED_MODELS = new Set([
|
||||
"codex-auto-review",
|
||||
"gpt-5.6-sol",
|
||||
"gpt-5.6-sol-ultra",
|
||||
"gpt-5.6-sol-max",
|
||||
"gpt-5.6-sol-xhigh",
|
||||
"gpt-5.6-sol-high",
|
||||
"gpt-5.6-sol-medium",
|
||||
"gpt-5.6-sol-low",
|
||||
"gpt-5.6-terra",
|
||||
"gpt-5.6-terra-ultra",
|
||||
"gpt-5.6-terra-max",
|
||||
"gpt-5.6-terra-xhigh",
|
||||
"gpt-5.6-terra-high",
|
||||
"gpt-5.6-terra-medium",
|
||||
"gpt-5.6-terra-low",
|
||||
"gpt-5.6-luna",
|
||||
"gpt-5.6-luna-max",
|
||||
"gpt-5.6-luna-xhigh",
|
||||
"gpt-5.6-luna-high",
|
||||
"gpt-5.6-luna-medium",
|
||||
"gpt-5.6-luna-low",
|
||||
"gpt-5.5",
|
||||
"gpt-5.5-xhigh",
|
||||
"gpt-5.5-high",
|
||||
"gpt-5.5-medium",
|
||||
"gpt-5.5-low",
|
||||
"gpt-5.3-codex-spark",
|
||||
]);
|
||||
export const CODEX_NATIVE_UNPREFIXED_MODELS = new Set(["codex-auto-review"]);
|
||||
|
||||
interface ProviderConnectionLike {
|
||||
provider?: unknown;
|
||||
@@ -571,19 +534,7 @@ async function resolveModelByProviderInference(modelId: string, extendedContext:
|
||||
getActiveSyncedProvidersForModel(modelId),
|
||||
getPreferClaudeCodeForUnprefixedClaudeModels(),
|
||||
]);
|
||||
// #FIX: synced catalogs (populated from `/v1/models` per connection) can
|
||||
// claim ownership of models the provider does not actually serve (e.g. a
|
||||
// `kiro` upstream briefly advertising `claude-opus-5` before it was
|
||||
// vendored into the registry). Without this filter the bare-routing path
|
||||
// would forward traffic to providers that 404 on the upstream call.
|
||||
// Auto-discovery still wins when no static registry entry exists for the
|
||||
// model id — only entries that conflict with the static catalog are dropped.
|
||||
const staticCatalogProviders = MODEL_TO_PROVIDERS.get(modelId) || [];
|
||||
const validatedSyncedProviders =
|
||||
staticCatalogProviders.length > 0
|
||||
? activeSyncedProviders.filter((p) => staticCatalogProviders.includes(p))
|
||||
: activeSyncedProviders;
|
||||
const providers = getInferredProvidersForModel(modelId, validatedSyncedProviders);
|
||||
const providers = getInferredProvidersForModel(modelId, activeSyncedProviders);
|
||||
const nonOpenAIProviders = providers.filter((p) => p !== "openai");
|
||||
|
||||
// Bare model IDs from Codex CLI do not preserve OmniRoute's `cx/` prefix.
|
||||
|
||||
@@ -109,50 +109,15 @@ export const REFRESH_LEAD_MS: Record<string, number> = {
|
||||
"gitlab-duo": 5 * 60 * 1000, // GitLab token family revocation on misuse
|
||||
kiro: 5 * 60 * 1000, // AWS SSO OIDC issues one-time-use refresh tokens
|
||||
"kimi-coding": 5 * 60 * 1000, // Moonshot rotates per-refresh
|
||||
// Non-rotating providers — longer lead is safe.
|
||||
iflow: 24 * 60 * 60 * 1000, // 24 hours
|
||||
// Google OAuth refresh_tokens are permanent (non-rotating) — longer lead
|
||||
// is safe and reduces unnecessary upstream chatter.
|
||||
antigravity: 15 * 60 * 1000,
|
||||
agy: 15 * 60 * 1000, // same Google backend as antigravity (non-rotating refresh tokens)
|
||||
"gemini-cli": 15 * 60 * 1000, // legacy stored connections; provider is no longer public
|
||||
};
|
||||
|
||||
/**
|
||||
* Upstream providers that stored connections may still name, but that this build no
|
||||
* longer serves. They are NOT routable — absent from PROVIDERS, from the chat REGISTRY,
|
||||
* and without an executor — so keeping their token fresh maintains a credential that can
|
||||
* never answer a request.
|
||||
*
|
||||
* Deprecation, not deletion: a connection here becomes terminal with a reason that names
|
||||
* where to go instead, rather than silently sitting at `active` doing nothing. The
|
||||
* migration target must be routable — `tests/unit/gemini-cli-deprecation.test.ts` asserts
|
||||
* that, so the notice can never point somewhere useless.
|
||||
*/
|
||||
export const DEPRECATED_PROVIDERS: Readonly<
|
||||
Record<string, { readonly migrateTo: string; readonly reason: string }>
|
||||
> = {
|
||||
"gemini-cli": {
|
||||
migrateTo: "gemini",
|
||||
// The legacy path redeemed the token with PROVIDERS.gemini's client — the very same
|
||||
// public Gemini CLI / Code Assist OAuth client — which is why re-adding the account
|
||||
// under `gemini` is a real migration and not a suggestion to start over.
|
||||
reason:
|
||||
"The gemini-cli provider was discontinued and is not routable. Re-add this account " +
|
||||
"under the `gemini` provider — it uses the same Google OAuth client, so the same " +
|
||||
"login works and the account becomes usable again.",
|
||||
},
|
||||
};
|
||||
|
||||
/** Whether `provider` is a deprecated upstream that must not be refreshed. */
|
||||
export function isDeprecatedProvider(provider: string): boolean {
|
||||
return Boolean(provider) && Object.prototype.hasOwnProperty.call(DEPRECATED_PROVIDERS, provider);
|
||||
}
|
||||
|
||||
/** The migration notice for a deprecated provider, or null when it is not deprecated. */
|
||||
export function getDeprecationNotice(
|
||||
provider: string
|
||||
): { migrateTo: string; reason: string } | null {
|
||||
return isDeprecatedProvider(provider) ? DEPRECATED_PROVIDERS[provider] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the proactive refresh lead time (ms) for a given provider.
|
||||
*
|
||||
@@ -298,27 +263,17 @@ export async function refreshAccessToken(
|
||||
*/
|
||||
async function _getAccessTokenInternal(provider, credentials, log, proxyConfig: unknown = null) {
|
||||
switch (provider) {
|
||||
case "gemini-cli": {
|
||||
// Deprecated (see DEPRECATED_PROVIDERS). This used to refresh successfully against
|
||||
// PROVIDERS.gemini's client, but the provider is not routable, so the fresh token
|
||||
// had nowhere to go — periodic upstream calls maintaining an unusable credential.
|
||||
//
|
||||
// Return the ESTABLISHED unrecoverable contract, so every existing caller
|
||||
// (isUnrecoverableRefreshError, the manual-refresh route) already stops retrying —
|
||||
// but with a code that says WHY and a target to migrate to. A bare `null` here would
|
||||
// read as a transient failure and be retried forever.
|
||||
const notice = DEPRECATED_PROVIDERS[provider];
|
||||
log?.warn?.(
|
||||
"TOKEN_REFRESH",
|
||||
`${provider} is deprecated — not refreshing; migrate this account to ${notice.migrateTo}`
|
||||
case "gemini-cli":
|
||||
// Legacy DB rows can retain this discontinued provider id. Refresh them
|
||||
// with the same public OAuth client used by Gemini CLI without restoring
|
||||
// gemini-cli to the routable provider or OAuth UI registries.
|
||||
return await refreshGoogleToken(
|
||||
credentials.refreshToken,
|
||||
PROVIDERS.gemini.clientId,
|
||||
PROVIDERS.gemini.clientSecret,
|
||||
log,
|
||||
proxyConfig
|
||||
);
|
||||
return {
|
||||
error: "unrecoverable_refresh_error",
|
||||
code: "provider_deprecated",
|
||||
migrateTo: notice.migrateTo,
|
||||
reason: notice.reason,
|
||||
};
|
||||
}
|
||||
|
||||
case "gemini":
|
||||
case "antigravity":
|
||||
@@ -435,6 +390,7 @@ async function _getAccessTokenInternal(provider, credentials, log, proxyConfig:
|
||||
export function supportsTokenRefresh(provider) {
|
||||
const explicitlySupported = new Set([
|
||||
"gemini",
|
||||
"gemini-cli", // legacy refresh compatibility only; not a routable provider
|
||||
"antigravity",
|
||||
"agy",
|
||||
"claude",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user