#5249 deliberately changed a MODEL_CAPACITY 400 ('model X not supported with
this account') from STOP to ADVANCE-to-next-combo-target, since a different
model in the combo may be supported. It added a regression test in
combo-strategies.test.ts but did not update the separate
combo-body-specific-400-stop-4279.test.ts, which still asserted the old STOP
behavior for that exact model-not-supported text — leaving the release branch
red on the Unit 2/2 shard (every PR inherited the failure).
Re-point the #4279 test at a genuinely body-specific malformed 400 ('invalid
message format'), which still recurs identically on every target and must STOP.
This preserves the #2101 anti-loop {ok,response} contract coverage while the
advance-on-model-400 path stays covered by combo-strategies.test.ts.
Test-only; no production behavior change.
Follow-up to #5288: propagate the hard-budget unreachable-target warning through the stacked seam (else branch in both sync/async paths) + TDD propagation test. Integrated into release/v3.8.40.
A 400 classified as MODEL_CAPACITY (e.g. 'requested model is not supported') hit the #2101 anti-loop stop-branch and halted the combo instead of advancing. Drop the MODEL_CAPACITY trigger from that stop condition so model-specific 400s fall through to the next combo target; genuinely body-specific 400s (malformed/invalid/bad-request substrings) still stop. Validated: combo-strategies suite 16/16 (incl. the new regression test) green on current release tip, typecheck clean. Stale pre-merge CI was from an older base.
Co-authored-by: Chewji9875 <Chewji9875@users.noreply.github.com>
* test(compression): failing tests for relevance engine scorer + apply
* feat(compression): relevance extractive engine — scores sentences against last user query
* test(compression): regression tests for relevance engine review fixes (threshold/multimodal/force-preserve/whitespace/query)
* fix(compression): relevance engine hardening from core review
- overlapThreshold was dead config (|| kept<budget made it unreachable) → real threshold gate
- multimodal: only compress when exactly one text block (was stamping joined text into every block)
- force-preserve sentences are now 'free' (don't consume budget) so they can't starve top-relevance
- preserve inter-sentence whitespace (\n\n survives) instead of flattening to single spaces
- ROOT-CAUSE: stop gating preserve on ultraHeuristic FORCE_PRESERVE_RE — it matches the period
ending every sentence, so it force-preserved everything (no-op). New SENTENCE_PRESERVE_RE anchors
on real signals (digits/URL/Error:/code/at-frame/path/key=value), mirroring #17's UNIT_PRESERVE_RE
- core-review suggestion to skip the query message: rejected (self-overlap already protects it;
skipping would no-op the single-message RAG case) — documented + test updated
* docs(changelog): restore relevance engine bullet (#7, eaten by rebase)
* test(compression): TDD failing tests for hard-budget post-pass (#17)
node:test suite for applyHardBudget — targetTokens, targetRatio, no-op,
force-preserve, determinism, both-wins, techniquesUsed, integration seam.
All tests fail (module not yet created) — TDD red step.
* feat(compression): hard-budget post-pass (#17) — compress to exactly N tokens
- types.ts: add targetTokens? and targetRatio? to CompressionConfig after contextBudget
- hardBudget.ts: applyHardBudget(body, {targetTokens?,targetRatio?}) → CompressionResult;
splits prose into sentences/lines, ranks by avg scoreToken ascending, drops lowest-
saliency units until ≤ target; UNIT_PRESERVE_RE guards numbers/URLs/errors/code;
targetTokens wins when both set; techniquesUsed:["hard-budget"]
- strategySelector.ts: hard-budget post-pass in runStackedCompression +
runStackedCompressionAsync after engine loop, before finalizeStackedResult;
gated on config.targetTokens || config.targetRatio; mergeStackStep + compressed=true
* fix(compression): preserve digit-less sensitive lines in hard-budget
UNIT_PRESERVE_RE only matched digits/URLs/error-headers/code-fences, so
stack-trace at-frames, key=value credential lines, and digit-less paths
were droppable and could be cut to hit the budget. Add specific anchors
(^\s*at\s, \/[\w.-]+\/, [A-Za-z_]\w*=\S) that never match a bare
end-of-sentence period (which would re-break the feature into a no-op).
Regression tests drive each unit to target=1 (drops every non-preserved
unit) and assert the sensitive line survives; plus a guard that plain
prose ending in a period stays droppable.
* fix(compression): distribute hard-budget across messages + warn when unreachable
Two related correctness fixes in applyHardBudget:
- Aggregate target was passed verbatim to compressText for EACH message,
so an N-message body could come back ~N× over budget. Distribute the
target proportionally per message (floor(target * msgTokens/total)) so
the SUM stays <= target.
- When every unit is preserve-guarded (or a single oversized preserved
unit), the result still exceeds target with no signal. Measure the
result and push a validationWarnings entry when it remains over budget,
so callers are not silently left over the limit.
Regression tests: a 4-message body with target 200 ends with TOTAL <= 200;
an all-numeric (all-preserved) body emits the 'could not reach target'
warning.
* fix(compression): run hard-budget post-pass when targetTokens/targetRatio is 0
The seam gate used `||`, so targetTokens:0 or targetRatio:0 (both falsy)
silently skipped the post-pass in runStackedCompression and
runStackedCompressionAsync. Switch to `!= null` so an explicit 0 still
engages the pass.
Regression test: applyStackedCompression with config.targetTokens:0 must
report 'hard-budget' in techniquesUsed.
* docs(changelog): restore hard-budget bullet (#17, eaten by rebase)
Partition no-auth entries instead of dropping blocked ones; disabled no-auth providers are surfaced in a Disabled group with an in-place Enable button (#5166/#5183). Extracted NoAuthProvidersSection to keep page.tsx under its size freeze.
Admin-merged: the only red checks (Unit/Coverage shard 2/8, Node-compat) are the pre-existing #4076 Dockerfile heap-ordering base-red on main — unrelated to this change (proven: fails locally on a branch that does not touch the Dockerfile or that test) and already de-brittled in the v3.8.40 release line.
roadmap #21: result memoization for deterministic engines (opt-in, default off). Fixed the cache-key under-specification (folded model+supportsVision so lite image-strip can't serve a wrong cached body across vision/non-vision targets) + regression test. Locally validated release-green. Integrated into release/v3.8.40.
roadmap #18: inline transparency annotation. Fixed a live 500 (→/× in the X-OmniRoute-Compression latin-1 header → ByteString throw) → ASCII-only + regression test building real Headers/Response. Locally validated release-green. Integrated into release/v3.8.40.
Call-log correlation ID core (migration 109 + storage + X-Correlation-Id header) — safe subset of #5275. Original work by @hartmark. Integrated into release/v3.8.40.
Strip unsupported sampling params (presencePenalty/frequencyPenalty/logprobs/topLogprobs) before forwarding to Grok Build. Added regression test (Rule #18, TDD-verified). Integrated into release/v3.8.40.
Security: close /v1beta authz bypass — classify /v1beta + /api/v1beta aliases as CLIENT_API so Bearer auth is enforced. Validated authz TDD suite (classify/proxy-contract/pipeline/inventory). Integrated into release/v3.8.40.
* docs: move superpowers/research artifacts to isolated _tasks repo + docs tree cleanup
- Move docs/superpowers/{plans,specs} and docs/research/* into the gitignored,
separately-versioned _tasks/ repo; untrack the two tracked research design docs.
- Add CLAUDE.md "Planning & Research Artifacts" section overriding the superpowers
default save paths (docs/... -> _tasks/...); align REPOSITORY_MAP and
DOCUMENTATION_OVERHAUL_PLAN with the new convention.
- Drop 4 now-obsolete /api/discovery/* entries from check-docs-symbols allowlist
(stale-enforcement) and refresh code/spec path comments to _tasks/...
- Sweeps in concurrent docs-tree restructuring (root-level provider/guide docs,
compression spec cleanup, .mcp.json.example removal).
* docs: reorganize docs/ tree + fix stale facts across ~26 docs
Phase A — reorganization:
- Move 7 orphan root docs into subfolders (providers/ created; TIERS+USAGE_QUOTA→guides/;
plugins+PLUGIN_SDK→frameworks/); delete 8 obsolete/redundant docs (SUBMIT_PR superseded
by CONTRIBUTING; DOCUMENTATION_OVERHAUL_PLAN; INCIDENT_RESPONSE/PERF_BUDGETS/THREAT_MODEL;
3 ops snapshots). Rebuild README index (was missing ~40 files) + per-folder meta.json nav.
- Clean 14 dangling doc-path references in bin/ ops scripts, scripts/, workflow, tests;
fix the dockerignore-docs-coverage required-docs path (PROVIDERS→providers/CLAUDE_WEB).
Phase B — content accuracy (verified against code, not the audit summary):
- Functional: ENVIRONMENT flag defaults (INPUT_SANITIZER/MCP_ENFORCE_SCOPES=true,
COMPRESS_DESCRIPTIONS=false, dynamic heap); MCP-SERVER notion tool names (omniroute_*→
notion_*) + counts 87→94; coverage gate 75/70→60/60/60/60 (RELEASE_CHECKLIST, COVERAGE_PLAN,
ERROR_SANITIZATION, CONTRIBUTING); pre-push hook description; regenerate PROVIDER_REFERENCE (237).
- Count drift: providers 237, executors 70, migrations 106, db modules 94, oauth 19,
strategies 17, MCP 94, flags 38, TS 6.0, open-sse ~900/services 294 across architecture/
frameworks/ops docs; AUTO-COMBO 9→12 factors w/ correct DEFAULT_WEIGHTS; REASONING +2
patterns; STEALTH UA defaults; AGENT_PROTOCOLS +cursor-cloud/list-capabilities;
LANGUAGE_PACKS +id pack.
- Kept Node 20 (runtime guard accepts 20.20.2+; only engines is stricter) and MCP scopes=13
(mcpScopes.ts) — both were correct in the docs; corrected only the attribution.
* docs: finish content refresh — compression engines, CLI_TOKEN merge, metadata sweep
- Compression: document the additional built-in engines (CCR, headroom, ionizer,
session-dedup) in COMPRESSION_ENGINES; clarify LLMLingua-2 is the ultra-mode SLM
backend + cross-ref the extra engines in EXTENDING_COMPRESSION; add the id
(Indonesian) language pack to LANGUAGE_PACKS.
- AUTO-COMBO: replace the orphan 'How tiers fit' weight table (stale weights) with a
pointer to the canonical 12-factor DEFAULT_WEIGHTS table.
- Security: merge CLI_TOKEN_AUTH.md (legacy 32-char SHA-256 format) into CLI_TOKEN.md
as a 'Legacy format — still accepted' section (server accepts both HMAC + legacy),
delete CLI_TOKEN_AUTH.md, drop it from the index + security nav.
- Metadata: bump stale frontmatter (version/lastUpdated) to 3.8.40/2026-06-28 across the
doc set audited this pass, and normalize the in-body 'Last updated' header lines to match.
* fix(runtime): drop Node 20 from supported range + align all docs/diagrams/counts
- Node minimum is now 22 (aligned with package.json engines). SUPPORTED_NODE_RANGE in
src/shared/utils/nodeRuntimeSupport.ts (and the bin/ mirror) drops the 20.x line →
'>=22.22.2 <23 || >=24.0.0 <27'; getNodeRuntimeSupport now rejects Node 20 as
unsupported-major. Test updated (TDD): node-runtime-support.test.ts asserts Node 20
rejected. Docs aligned (TROUBLESHOOTING ×2, TERMUX, RELEASE_CHECKLIST, CODEBASE,
CLI-TOOLS, README, llm.txt + 42 i18n llm.txt mirrors, skills/cli-serve).
- Diagrams regenerated: mcp-tools-87 -> mcp-tools-94 (34 base + pool 6 = 94) and
auto-combo-9factor -> auto-combo-12factor (correct DEFAULT_WEIGHTS); SVGs re-rendered
via mermaid-cli; doc refs + diagrams/README updated; fixed a pre-existing broken
resilience-3layers image path.
- CLAUDE.md + AGENTS.md aligned to real counts (237 providers, 94 MCP tools / 34 base,
106 migrations, 94 db modules, 12-factor auto-combo, 17 strategies); README provider
count 231 -> 237; executor count corrected to 68 (provider executors, excl base/index)
and OAuth to 18 across architecture docs. check:docs-all now passes (0 strict drift,
0 broken links); removed dead .mcp.json.example doc link.
* fix(services): update installer Node hint to >=22.22.2 (aligned with dropped Node 20)
* docs: realign counts to current release tip after rebase
The release tip advanced while this work was in flight (Gemini CLI provider/executor
removed by #5246, plus other PRs). Re-counted against the current code and updated:
providers 237->236, executors 68->67, OAuth modules 18->17, open-sse services 294->298;
regenerated PROVIDER_REFERENCE.md (236). check:docs-all passes (0 strict drift).
* docs(changelog) + i18n: record Node 20 drop + fix nodeIncompatibleHint
- CHANGELOG: add [3.8.40] entries for the Node 20.x removal (runtime) and the docs
reorganization/accuracy audit.
- i18n: nodeIncompatibleHint across all 42 locales no longer lists Node 20.x as
supported (ASCII + CJK full-width variants), aligned with the dropped Node 20.
* fix(docs): repair CI breakages from the doc moves
- test: cli-plugin-system asserted docs/dev/plugins.md exists; the file moved to
docs/frameworks/PLUGINS.md — point the test at the new path (Unit fast-path 2/2 fix).
- frontmatter: PLUGINS.md and PLUGIN_SDK.md moved into the fumadocs-indexed
docs/frameworks/ which requires a 'title' frontmatter; the missing frontmatter
failed the Next.js MDX build (dast-smoke 'invalid frontmatter'). Added frontmatter
to both, plus the providers/ docs (consistency; that folder is not indexed).
A compression_analytics row was written only on a net-positive saving
(chatCore: result.compressed || fallbackApplied || cavemanOutputModeApplied).
A Stacked RTK->Caveman pipeline that ran on already-compact context saved
nothing -> no row -> indistinguishable from 'never dispatched'
(byMode.stacked.count stayed flat while Ultra climbed).
Attempted-but-no-op runs are now recorded via writeCompressionSkip with a
skip_reason. A new skip_reason column (added through the existing
ensureCompressionAnalyticsColumns ALTER path) marks these rows; the analytics
summary excludes them from every saving aggregate (via a single successWhere =
'skip_reason IS NULL' filter, so historical totals/averages are byte-identical)
and surfaces them as per-mode 'skipped' plus totalSkipped / bySkipReason. The
Mode Breakdown shows 'N skipped (no-op)'.
TDD: compressionAnalytics.test.ts gains coverage that skip rows are counted
separately without polluting saving aggregates, and that a skip-only mode still
appears (count 0, skipped > 0). Fail-before proven by reverting the successWhere
exclusion (both new tests fail). typecheck:core + eslint clean; 17/17 suite green.
The wired tray path (serve.mjs -> tray/index.mjs -> traySystray.mjs) loaded
systray2 via an inline loader calling require("module") inside an ESM .mjs file
(package type:module), which throws ReferenceError: require is not defined and
was silently swallowed -> no tray, no diagnostic (regressed in v3.8.34). Even
when fixed, systray2 is not in node_modules; it is lazily installed into
~/.omniroute/runtime by trayRuntime.ts.
initSystrayUnix now delegates loading to trayRuntime.ts::loadSystray (async),
fixes the icon path (icon.png, not icons/icon.png), sets isTemplateIcon:false
(full-color icon rendered as a white square under macOS template mode), and
serve.mjs surfaces tray-start failures to stderr instead of swallowing them.
TDD: tray-systray-loader-4605.test.ts drives the loader seam via an injected
ctor (native binary can't run in unit tests) and asserts the menu/icon/template
invariants; fail-before confirmed by restoring the broken require() loader.
* feat(kilocode): anonymous no-auth access to free models (#4019)
Kilo's gateway serves its free tier without signup: an OpenAI-compatible
request to api.kilo.ai/api/openrouter authenticated with the literal API
key `anonymous` (Authorization: Bearer anonymous) plus an
X-KILOCODE-EDITORNAME header returns free models. Expose it the same way
opencode/mimocode do:
- flag the kilocode dashboard provider `anonymousFallback: true` so a
request with no connected account synthesizes a noauth credential;
- add `anonymousApiKey` to the registry and have DefaultExecutor send it
as the bearer token only when no real credential exists, so the OAuth
paid path is untouched.
Regression test exercises buildHeaders for the anonymous, OAuth-token and
API-key cases plus the anonymousFallback flag (fails before the fix).
Reference implementation pointer courtesy of @Theadd (#4019).
* chore(quality): reconcile inherited file-size base-red for executor-codex.test.ts
The release tip carries tests/unit/executor-codex.test.ts at 1347 lines while
the frozen baseline still reads 1340 — 7 legit lines were added without
ratcheting, so check:file-size fails for every PR that branches off the tip
(proven: the gate already fails on HEAD~1, before this PR's #4019 commit).
Ratchet the testFrozen entry to the current count to unblock; the file itself
is untouched by this PR.
* test: regenerate provider translate-path golden for kilocode editor-name header (#4019)
The #4019 commit adds X-KILOCODE-EDITORNAME to the kilocode registry headers.
The all-providers translate-path golden snapshots per-provider headers, so it
must be regenerated. Diff is exactly the new header on kilocode's apiKey/oauth/
nonStream variants — Authorization (Bearer <TOK>) and every other provider are
unchanged.
sessionStickiness.ts (v3.8.36) keeps a sessionless multi-turn conversation pinned
to the same connection so the upstream prompt-cache stays warm, but the
round-robin handler (handleRoundRobinCombo) returned before reaching the
applySessionStickiness call used by the weighted/priority paths. Clients that send
no session id (Codex CLI, Claude Code, most OpenAI-compatible tools) therefore had
round-robin combos rotate to a different connection every turn -> prompt-cache
miss -> cold high-reasoning starts, intermittent 504s and throughput collapse
under concurrency (#3825).
Reuse the existing mechanism: when a sticky connection is bound to the
conversation and present in the current targets, start the round-robin rotation at
it (failover to the other targets is preserved), and (re)record the binding on
success. Different conversations still spread across connections on their first
turn -- only intra-conversation rotation is removed. A diagnostic load harness
confirmed request-dispatch CPU is not the bottleneck; this is a routing fix.
Adds an integration regression test driving the real handleComboChat: a sessionless
round-robin conversation re-pins across turns (fails before the fix), and distinct
conversations still spread (round-robin distribution preserved).
Refresh impersonation UAs + TLS profiles to current client versions. Review fixes (perplexity UA kept on Firefox 148 to match firefox_148 TLS profile; golden snapshot regenerated; release file-size/complexity baselines reconciled) co-authored. Thanks @backryun.
Integrated into release/v3.8.40 — corrective max_tokens part already cherry-picked (e8d13ec17); this brings the registry maxOutputTokens caps. Thanks @adivekar-utexas.
Cherry-picked the corrective part of #5221 only: the executor stops fabricating
`max_tokens` (= per-model registry cap) when the client omits it, which caused
`400 "expected <=200000"` on /alpha/generate for high-cap models. An explicit
oversized client value is clamped to the 200k endpoint ceiling. The PR's registry
maxOutputTokens recaps (open-sse/config/providers/registry/command-code/index.ts)
are intentionally NOT included pending reconciliation; #5221 stays open for that.
Antigravity: retry excluded accounts via fallback LRU + family-inferred 429 cooldown. Test strengthened into a real LRU regression guard; cooldown constant extracted. Integrated into release/v3.8.40.
serve.mjs imports scripts/build/runtime-env.mjs (added with the #5213 heap
auto-calibration fix) but it was missing from package.json files whitelist,
breaking every global npm install at startup. Add it to files and guard with
a regression test that asserts all bin/ runtime imports of scripts/ are
packaged.
Scope textual thinking-tag extraction to tag-native model families; preserve GEMINI_CLI registration. Resubmit of #5216 without the regression. Integrated into release/v3.8.40.