Skip to content

Commit ee24eb5

Browse files
authored
Release v3.8.33 (#4515)
Release v3.8.33 — full CHANGELOG in the PR body. Blocking gates green (Build, Lint, Unit Tests 8/8, Package Artifact, Quality Gates, Quality Ratchet, Docs Sync, PR Test Policy, test-vitest). Admin-merged over a Node 26 future-compat timer flake (1/4) + an E2E UI flake (3/9) — both verified non-deterministic; full test:unit validated locally (16936 pass).
1 parent bfaf459 commit ee24eb5

241 files changed

Lines changed: 12035 additions & 1450 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,7 @@ CODEX_USER_AGENT="codex-cli/0.132.0 (Windows 10.0.26200; x64)"
789789
GITHUB_USER_AGENT="GitHubCopilotChat/0.45.1"
790790
ANTIGRAVITY_USER_AGENT="antigravity/2.0.1 linux/arm64 google-api-nodejs-client/10.3.0"
791791
KIRO_USER_AGENT="AWS-SDK-JS/3.0.0 kiro-ide/1.0.0"
792+
# KIRO_VERIFY_FULL_CRC=false # opt-in: full per-frame message CRC validation on the Kiro event stream (debug corrupted streams; prelude CRC + TLS already protect framing)
792793
# Optional override for the Kiro social device-code OAuth clientId. Kiro's
793794
# device endpoint accepts any non-empty string and behaves like a User-Agent
794795
# rather than a secret. Only override if AWS ever starts enforcing this field.
@@ -1611,6 +1612,7 @@ QUOTA_STORE_DRIVER=sqlite # sqlite | redis
16111612
# QUOTA_SATURATION_THRESHOLD=0.5 # 0..1; >= threshold ativa modo strict (sem empréstimo)
16121613
# QUOTA_SOFT_DEPRIORITIZE_FACTOR=0.7 # 0..1; multiplicador do score quando soft policy ativa
16131614
# QUOTA_CONSUMPTION_RETENTION_DAYS=14 # GC de buckets quota_consumption.updated_at antigos
1615+
# QUOTA_PREFLIGHT_CUTOFF_ENABLED=false # opt-in (default OFF): hard quota cutoff drops low-quota candidates before auto-routing scoring
16141616

16151617
# ─── OpenCode config regeneration (scripts/ad-hoc/regen-opencode-config.ts) ───
16161618
# Base URL of the OmniRoute instance to query for /v1/models when regenerating
@@ -1701,3 +1703,21 @@ QUOTA_STORE_DRIVER=sqlite # sqlite | redis
17011703
# BIFROST_API_KEY=
17021704
# BIFROST_STREAMING_ENABLED=true
17031705
# BIFROST_TIMEOUT_MS=30000
1706+
1707+
# ── Scheduled VACUUM (Issue #4437) ────────────────────────────────────────
1708+
# Master switch for the scheduled SQLite VACUUM job.
1709+
# - OMNIROUTE_VACUUM_ENABLED=1 (default) → start a setInterval timer at boot
1710+
# that runs VACUUM once every OMNIROUTE_VACUUM_INTERVAL_HOURS.
1711+
# - 0 → disable the scheduler entirely (manual "Vacuum Now" still works).
1712+
# Source: src/lib/db/vacuumScheduler.ts
1713+
# OMNIROUTE_VACUUM_ENABLED=1
1714+
1715+
# How often to run VACUUM. Default: 24 hours. Minimum: 1. Must be an integer.
1716+
# The actual interval is computed lazily from OMNIROUTE_VACUUM_INTERVAL_HOURS
1717+
# at boot time, so changes here require a restart (or `omniroute vacuum restart`).
1718+
# OMNIROUTE_VACUUM_INTERVAL_HOURS=24
1719+
1720+
# Window in which the first VACUUM is allowed to run (cron-like start window).
1721+
# Default: 02:00-04:00 (local server time). Outside the window the scheduler
1722+
# waits until the window opens. Format: HH:MM-HH:MM (24-hour).
1723+
# OMNIROUTE_VACUUM_WINDOW=02:00-04:00

CHANGELOG.md

Lines changed: 88 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -287,12 +287,12 @@ Result: 4 layers of fallback = zero downtime
287287

288288
</div>
289289

290-
> Recent highlights from **v3.8.20 → v3.8.32**. Full history in [`CHANGELOG.md`](CHANGELOG.md).
290+
> Recent highlights from **v3.8.20 → v3.8.33**. Full history in [`CHANGELOG.md`](CHANGELOG.md).
291291
292292
- **🤖 One-command CLI/agent setup** — a dedicated `setup-*` command configures each coding tool to route through OmniRoute (Claude Code, Codex, Cline, Continue, Cursor, Roo Code, Kilo Code, Crush, Goose, Qwen Code, Aider, OpenCode, Gemini CLI); `omniroute launch` / `omniroute launch-codex` are zero-config launchers. → [CLI Integrations](docs/guides/CLI-INTEGRATIONS.md)
293293
- **🛰️ Remote mode** — drive a remote OmniRoute from any machine with scoped access tokens (`omniroute connect` / `omniroute contexts` / `omniroute tokens`). → [Remote Mode](docs/guides/REMOTE-MODE.md)
294-
- **🧭 Smarter auto-routing** — OpenRouter-style `auto/<category>:<tier>` combos (e.g. `auto/coding:fast`, `auto/reasoning:pro`), live Arena-ELO + models.dev model intelligence, and per-step account allowlists. → [Auto-Combo](docs/routing/AUTO-COMBO.md)
295-
- **🗜️ Pluggable compression** — an async compression pipeline with Compression Studios, a stable LLMLingua-2 ONNX engine, RTK, delegated Anthropic Context Editing, and a unified settings panel that is the single source for per-engine on/off + level. → [Compression](docs/compression/COMPRESSION_ENGINES.md)
294+
- **🧭 Smarter auto-routing** — OpenRouter-style `auto/<category>:<tier>` combos (e.g. `auto/coding:fast`, `auto/reasoning:pro`), live Arena-ELO + models.dev model intelligence, per-step account allowlists, provider-wildcard combo steps, nested combo-ref execution, sticky weighted selection, and `web_search`-aware routing to a configured model. → [Auto-Combo](docs/routing/AUTO-COMBO.md)
295+
- **🗜️ Pluggable compression** — an async compression pipeline with Compression Studios, a stable LLMLingua-2 ONNX engine, RTK, delegated Anthropic Context Editing, and a unified settings panel with named profiles + an active-profile selector as the single source for per-engine on/off + level. → [Compression](docs/compression/COMPRESSION_ENGINES.md)
296296
- **🕵️ Transparent MITM decrypt (TPROXY)** — capture & translate traffic from CLIs that ignore proxy env vars, with a per-SNI certificate authority and a trust-store installer. → [MITM/TPROXY](docs/security/MITM-TPROXY-DECRYPT.md)
297297
- **💸 Cost telemetry everywhere**`X-OmniRoute-*` cost/usage headers on every endpoint (including media), a non-token cost engine, a cache-HIT `X-OmniRoute-Cost-Saved` header, and per-key USD spend quotas. → [API Reference](docs/reference/API_REFERENCE.md)
298298
- **🧠 Memory you control** — opt-in int8 vector quantization (Qdrant + sqlite-vec), memory off by default, and a per-request `x-omniroute-no-memory` header. → [Memory](docs/frameworks/MEMORY.md)

bin/cli/runtime/processSupervisor.mjs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
import { spawn } from "node:child_process";
22
import { dirname } from "node:path";
33
import { writePidFile, cleanupPidFile, killAllSubprocesses } from "../utils/pid.mjs";
4+
import {
5+
RESTART_RESET_MS,
6+
DEFAULT_MAX_RESTARTS,
7+
shouldExitInsteadOfRestart,
8+
computeRestartDelayMs,
9+
waitUntilPortFree,
10+
} from "./supervisorPolicy.mjs";
411

512
const CRASH_LOG_LINES = 50;
6-
const RESTART_RESET_MS = 30_000;
713

814
export class ServerSupervisor {
9-
constructor({ serverPath, env, maxRestarts = 2, memoryLimit = 512, onCrashCallback }) {
15+
constructor({ serverPath, env, maxRestarts = DEFAULT_MAX_RESTARTS, memoryLimit = 512, onCrashCallback }) {
1016
this.serverPath = serverPath;
1117
this.env = env;
1218
this.maxRestarts = maxRestarts;
@@ -54,7 +60,10 @@ export class ServerSupervisor {
5460
const exitCode = typeof code === "number" ? code : null;
5561
cleanupPidFile("server");
5662

57-
if (this.isShuttingDown || exitCode === 0) {
63+
// #4425: only exit on an intentional shutdown. A spontaneous code-0 exit (e.g. a
64+
// systemd MemoryMax cgroup kill, which reports the process exited cleanly) is anomalous
65+
// and must be restarted, not treated as a graceful stop that leaves the gateway dead.
66+
if (shouldExitInsteadOfRestart(this.isShuttingDown)) {
5867
process.exit(exitCode ?? 0);
5968
return;
6069
}
@@ -79,12 +88,18 @@ export class ServerSupervisor {
7988
}
8089

8190
this.restartCount++;
82-
const delay = Math.min(1000 * 2 ** (this.restartCount - 1), 10_000);
91+
const delay = computeRestartDelayMs(this.restartCount);
8392
console.error(
8493
`\n⚠ Server exited (code=${code ?? "?"}). Restarting in ${delay / 1000}s... (${this.restartCount}/${this.maxRestarts})`
8594
);
8695
if (this.crashLog.length) this.dumpCrashLog();
87-
setTimeout(() => this.start(), delay);
96+
// #4425: after a crash the OS may not have released the listen socket yet — restarting
97+
// immediately produced the EADDRINUSE cascade that exhausted the restart budget. Wait
98+
// (bounded) for the port to free up before respawning.
99+
setTimeout(async () => {
100+
await waitUntilPortFree(process.env.PORT || 20128);
101+
this.start();
102+
}, delay);
88103
}
89104

90105
dumpCrashLog() {
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import net from "node:net";
2+
3+
// #4425: bumped from 30s — the old window reset the crash counter too quickly, so during
4+
// an EADDRINUSE cascade the supervisor kept "recovering" then crashing within the window
5+
// and exhausted its restart budget. A longer window keeps the counter meaningful.
6+
export const RESTART_RESET_MS = 60_000;
7+
8+
// #4425: bumped from 2 — more recovery headroom before the supervisor gives up.
9+
export const DEFAULT_MAX_RESTARTS = 3;
10+
11+
/**
12+
* #4425: a clean child exit (code 0) is only intentional when the supervisor itself is
13+
* shutting down. A spontaneous code-0 exit is anomalous — e.g. a systemd `MemoryMax`
14+
* cgroup kill reports the process exited with code 0 — and MUST be restarted, not treated
15+
* as a graceful stop (which left the gateway dead with `Restart=on-failure`).
16+
*/
17+
export function shouldExitInsteadOfRestart(isShuttingDown) {
18+
return isShuttingDown === true;
19+
}
20+
21+
/** Exponential backoff (1s, 2s, 4s, …) capped at 10s, matching the prior inline formula. */
22+
export function computeRestartDelayMs(restartCount) {
23+
return Math.min(1000 * 2 ** (Math.max(1, restartCount) - 1), 10_000);
24+
}
25+
26+
/** Resolve true when nothing is listening on `port` (so a restart won't hit EADDRINUSE). */
27+
export function isPortFree(port, host = "127.0.0.1") {
28+
return new Promise((resolve) => {
29+
const tester = net.createServer();
30+
tester.once("error", (err) => {
31+
// EADDRINUSE = something is bound → not free. Any other error → treat as free.
32+
resolve(!(err && err.code === "EADDRINUSE"));
33+
});
34+
tester.once("listening", () => {
35+
tester.close(() => resolve(true));
36+
});
37+
tester.listen(port, host);
38+
});
39+
}
40+
41+
/**
42+
* #4425: wait until `port` is free before respawning. After a crash the OS may not have
43+
* released the listen socket yet; restarting immediately produced the EADDRINUSE cascade
44+
* that exhausted the restart budget. Polls up to `timeoutMs`, then proceeds anyway so a
45+
* stuck port never blocks recovery forever.
46+
*/
47+
export async function waitUntilPortFree(port, timeoutMs = 10_000, intervalMs = 250) {
48+
const p = Number(port);
49+
if (!Number.isFinite(p) || p <= 0) return true;
50+
const deadline = Date.now() + timeoutMs;
51+
for (;;) {
52+
if (await isPortFree(p)) return true;
53+
if (Date.now() >= deadline) return false;
54+
await new Promise((r) => setTimeout(r, intervalMs));
55+
}
56+
}

config/quality/complexity-baseline.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
{
22
"_comment": "Catraca de complexidade (check-complexity.mjs, ESLint core rules complexity>=15 e max-lines-per-function>80 sobre src+open-sse+electron+bin via eslint.complexity.config.mjs). Conta total de violacoes; so pode cair. --update ratcheta.",
3-
"count": 1905,
3+
"count": 1915,
4+
"_rebaseline_2026_06_21_v3833_4537_nested_combo": "Reconciliacao 1913->1915 (+2) do PR #4537 (nestedComboMode execute — black-box combo-ref execution). O +2 vem do branch novo de dispatch nested em handleComboChat (combo.ts: normalizeNestedComboMode + executeModeUnits/hasExecutableComboRef + o ramo simpleExecuteStrategies que roda resolveComboRuntimeUnits com recursion caps depth/cycle/budget) — ramos condicionais no chokepoint de dispatch do combo. Medido com `node scripts/check/check-complexity.mjs` no estado merged. Crescimento de feature legitimo recem-TDD'd, nao regressao; reducao estrutural fica como debt (#3501).",
5+
"_rebaseline_2026_06_21_v3833_reviewprs_r4_owner": "Reconciliacao release-volatil 1911->1913 (+2) apos o lote de PRs do owner desta rodada (#4560 RTK cache_control, #4552 Cursor auto-import macOS, #4551 Codex /responses probe, #4554 Cursor Composer </think> decode + o stack #3501 #4538/#4544/#4548). O fast-path do release nao roda check:complexity (so release->main). As 3 extracoes chatCore #3501 (#4538 recupera 4 leaves, #4544 failureUsage, #4548 claudeSystemRole+upstreamExecuteHeaders) sao PURAS/complexity-neutras (movem codigo p/ leaves sob o teto, chatCore ENCOLHE); o +2 vem dos condicionais NOVOS das features .32-portadas (#4554 decode de bloco </think> visivel do Composer + #4551 probe do endpoint real Codex /responses). Medido com `node scripts/check/check-complexity.mjs` no tip 4b34a75fe. Crescimento de feature legitimo recem-TDD'd, nao regressao; reducao estrutural fica como debt (#3501).",
6+
"_rebaseline_2026_06_21_v3833_reviewprs_r4": "Reconciliacao release-volatil 1906->1911 (+5) apos o lote /review-prs r4 (5 PRs de contribuidores mergeados em release/v3.8.33: #4557 health-polls, #4556 mobile-table, #4545 provider-wildcard, #4558 isHidden, #4489 sticky-weighted) + 1 merge concorrente de sessao paralela (#4565 quota perf). O fast-path do release nao roda check:complexity (so release->main), entao o ramo acumula sem rebaselinar. Condicionais NOVOS legitimos: #4545 (expandProviderWildcardsInCombo/Collection + wildcardMatch glob/registry branching em providerWildcard.ts/wildcardRouter.ts), #4489 (eligibility pass sticky-weighted: resolveWeightedStepGroups + isTargetSelectableForWeighted + renormalizacao em combo.ts), #4558 (filtro isHidden em buildAutoCandidates/virtualFactory) e #4565 (guardas de skip de quota_snapshots idle). Medido com `node scripts/check/check-complexity.mjs` no tip 39b2bbfea. Mesma familia dos rebaselines anteriores — crescimento de feature legitimo recem-TDD'd, nao regressao; reducao estrutural fica como debt (#3501).",
7+
"_rebaseline_2026_06_21_v3833_cycle_open_stranded": "1905 -> 1906 (+1). Abertura do ciclo v3.8.33: o +1 vem dos 4 commits stranded na release/v3.8.32 (pós-merge da v3.8.32) trazidos via cherry-pick para release/v3.8.33 — isolado em #4483 (autoStrategy.ts evaluateQuotaCutoff/quotaPreflight.ts, guardas de quota-cutoff inerentemente ramificadas, opt-in default-OFF). main mede 1905; o .33 branch (main + cherry-picks) mede 1906. Crescimento de feature legitimo recém-mergeado, não regressão; redução fica como debt (#3501).",
48
"_rebaseline_2026_06_20_reviewprs_mine_r2": "Reconciliacao release-volatil 1900->1905 (+5) apos o lote /review-prs 'apenas minhas' r2 (17 PRs meus mergeados em release/v3.8.32). Breakdown medido com `node scripts/check/check-complexity.mjs` + diff por-arquivo eslint complexity JSON entre o tip pre-lote (9052c5a78 = 1902) e o tip pos-lote (1905): (a) +2 JA latentes no tip pre-lote (1900->1902), drift de merges concorrentes de outras sessoes ANTERIORES a este lote (nao introduzidos por mim) que o fast-path do release nao rebaselina (check:complexity so roda release->main); (b) +3 deste lote, isolados em DOIS arquivos: open-sse/translator/helpers/geminiHelper.ts +1 (convertOpenAIContentToParts passou de 80->93 linhas pela branch de audio do #4426 — max-lines-per-function, funcao de dispatch coesa por tipo de content part) e open-sse/translator/response/openai-to-gemini-sse.ts +2 (translator SSE NOVO do #4453, openAIChunkToGeminiChunk + convertOpenAIResponseToGemini em complexity 19 cada — conversores de chunk SSE inerentemente ramificados, levemente acima de 15). Crescimento de feature legitimo recem-TDD'd, nao regressao; refatorar feature recem-mergeada so para raspar +3 seria over-engineering arriscado. Reducao fica como debt.",
59
"_rebaseline_2026_06_20_reviewprs_v3831_batch": "Reconciliacao release-volatil 1896->1900 (+4): drift do lote /review-prs v3.8.31 (25 PRs A+B+C + merges concorrentes). Condicionais NOVOS legitimos — sobretudo #4381 (rotas /api/local/redis/{start,stop,status} detectRuntime + guardas + bifrost relay) e #4366 (classificacao de exhaustion de erro entre os 2 dispatchers de combo). Medido 1900 ESTAVEL em fd1391c0b E f46c69f2a com `node scripts/check/check-complexity.mjs` (commit concorrente intermediario foi complexity-neutro). O fast-path do release nao roda check:complexity (so release->main), entao o ramo acumula sem rebaselinar. Mesma familia dos rebaselines anteriores — crescimento de feature legitimo, nao regressao; valor final reconciliado no release->main.",
610
"_rebaseline_2026_06_20_postlote_concurrent_drift": "Reconciliacao release-volatil: 1895->1896 (+1). Drift de condicional NOVO de PRs mergeados pela sessao concorrente APOS o #4338 ratchetar para 1895 (#4355 pricing gpt-5.x-pro / #4364 cli active-context cred / #4363 compliance cleanup / #4358 mitm mask / #4332 injection-guard-16KB). O fast-path do release nao roda check:complexity (so release->main), entao o ramo acumula sem rebaselinar. Medido com `node scripts/check/check-complexity.mjs` no tip cdfd71c17. Mesma familia — crescimento de feature legitimo, nao regressao.",

0 commit comments

Comments
 (0)