Skip to content

Release v3.8.30 - #4267

Merged
diegosouzapw merged 74 commits into
mainfrom
release/v3.8.30
Jun 20, 2026
Merged

diegosouzapw merged 74 commits into
mainfrom
release/v3.8.30

Conversation

@diegosouzapw

@diegosouzapw diegosouzapw commented Jun 19, 2026 •

Copy link
Copy Markdown
Owner

[3.8.30] — 2026-06-20

✨ New Features

  • feat(dashboard): category (media serviceKind) filter on the providers page — /dashboard/providers gains a media-category filter row (Image / Video / Music / Text→Speech / Speech→Text / Embedding) that composes with the existing search, free-only and "show configured only" filters. Membership is derived from the backend media registries (a provider that serves a kind is surfaced even if it never declared serviceKinds), keeping the UI in lockstep with the backend. (#4240)
  • feat(combo): per-step account allowlist — scope a round-robin/weighted step to a subset of a provider's connections — a combo model step can now carry a first-class account allowlist so a round-robin (or weighted) strategy is scoped to a chosen subset of a provider's connections (e.g. only foo1+foo2 out of foo1..foo4) without hand-pinning one step per account. Empty = the whole active pool (unchanged). When a step both has an allowlist and is tag-routed, the two intersect (most-restrictive wins); a single pinned account still takes precedence. The combo builder's Precision step editor gains an optional "Restrict to accounts" picker. (#3266)
  • feat(providers): add OpenAdapter, dit.ai and TokenRouter as OpenAI-compatible providers — three community-requested OpenAI-compatible aggregators now register as standard named OpenAI-style providers with live /v1/models discovery (the zenmux pattern), falling back to a seeded catalog when the upstream list is unavailable: OpenAdapter (https://api.openadapter.in/v1, free tier, 70+ open-source models — #4239), dit.ai (https://api.dit.ai/v1, dynamic-pricing router/gateway — #4155), and TokenRouter (https://api.tokenrouter.com/v1, free MiniMax model — #3841, thanks @FerLuisxd). No custom executor/translator — default OpenAI passthrough.
  • feat(api): x-omniroute-no-memory request header — per-request opt-out of memory/skills injection — clients that manage their own context (e.g. their own RAG/memory) can send x-omniroute-no-memory: true (mirrors the existing x-omniroute-no-cache convention) to skip the gateway injecting up to memorySettings.maxTokens (~2k) tokens of memory and skills context into that chat request — avoiding the token/cost inflation it otherwise adds on every call. Absent the header, behavior is unchanged. (PRD-2026-06-19-no-memory-header)
  • feat(dashboard): MITM tool card lists the exact hosts-file entries to add manually — the CLI-tools MITM card's "How it works" section now lists the full set of 127.0.0.1 <host> lines for the selected tool (sourced from the canonical MITM target registry) instead of a single example domain. Users on locked-down machines — where the automatic, sudo-gated hosts-file edit isn't available — can now copy every required entry by hand. (thanks @mrcyclo)
  • feat(cli): omniroute launch-codex + setup-codex — run/configure the Codex CLI against OmniRoute — a launcher and setup command that point the Codex CLI at an OmniRoute endpoint (remote-mode aware). (#4270)
  • feat(cli): Claude Code launcher + setup — remote mode + profiles — omniroute launch/setup for Claude Code with remote-mode support and named connection profiles. (#4274)
  • feat(cli): OpenCode setup — OpenAI-compatible provider + remote-aware plugin — setup-opencode registers OmniRoute as an OpenAI-compatible provider for OpenCode and installs a remote-aware plugin. (#4277)
  • feat(cli): one-command setup for popular AI coding tools — new setup-* commands that configure each tool to talk to OmniRoute: Cline (#4280), Kilo Code (#4284), Continue (#4289), Cursor (#4291), Roo Code (#4292), Crush (#4298), Goose (#4300), Qwen Code (#4301), Aider (#4302) and the Gemini CLI (native /v1beta) (#4303).
  • feat(providers): provider model sweep — live discovery, refreshed catalogs, dead-provider cleanup — a broad sweep that enables live /v1/models discovery for more OpenAI-style providers (the zenmux pattern), refreshes the seeded catalogs with current models, and marks dead providers deprecated. (#4324)
  • feat(mitm): translate Antigravity cloudcode end-to-end (Gap B) — the MITM decrypt path now translates Antigravity cloudcode traffic end-to-end. (#4299)
  • feat(keys): per-key USD usage quota controls — an API key can now carry a USD spend quota that caps its usage once the threshold is reached. (#4327 — thanks @Witroch4)

🔧 Changed

  • change(memory): memory is now OFF by default — DEFAULT_MEMORY_SETTINGS.enabled now defaults to false. Enabling memory injects up to ~2,000 tokens of retrieved context into every chat request (and that context is billed), which was a surprising default for new installs and for clients with their own context. Memory is now an explicit opt-in: installs that already enabled it keep it on; installs that never configured it default to off. The Settings → Memory panel now shows a token-cost warning when memory is enabled. (PRD-2026-06-19-no-memory-header)

🐛 Fixed

  • fix(compliance): startup cleanup honors the dashboard data-retention setting instead of always trimming to 7 days — on every restart, cleanupExpiredLogs() (run at startup) read retention only from the CALL_LOG_RETENTION_DAYS / APP_LOG_RETENTION_DAYS env vars, which default to 7 days when unset, and trimmed usage_history (the Usage Analysis data) before the dashboard-based runAutoCleanup() — which respects the configured retention — ever ran. So a dashboard "Data Retention" of 90 days was silently overridden and the Usage Analysis page only ever showed the last 7 days after a restart. Retention now follows the precedence explicit env var → dashboard DB setting → 7-day default, per table (usage_history→usageHistory, call_logs/proxy_logs/request_detail_logs→callLogs, mcp_tool_audit→mcpAudit); an operator who sets the env var still wins, and non-DB deployments still fall back to it. (#4354 — thanks @akbardwi)
  • fix(providers): bailian-coding-plan static fallback catalog matches the registry (10 models) — the provider-model sweep (feat(providers): provider model sweep — live discovery, refreshed catalogs, dead-provider cleanup #4324) added four current Model Studio coding-plan models (qwen3.7-plus, qwen3-coder-plus, qwen3-coder-next, glm-4.7) to the bailian-coding-plan registry entry but missed the static fallback mirror in staticModels.ts, which still listed only the older six. The static catalog (served when live discovery is unavailable) therefore diverged from the registry, and the existing static↔registry parity test went red on the release branch (only surfacing when test-impact analysis happened to select it). The static mirror now carries all ten models in registry order, restoring parity. (#4324)
  • fix(executors): ArenaLLM accepts LMArena's split Supabase SSR auth cookie — LMArena migrated to @supabase/ssr chunked auth cookies: the single arena-auth-prod-v1 cookie is now empty and the real session is split across arena-auth-prod-v1.0, arena-auth-prod-v1.1, … (ascending). A user who pasted the (now-empty) single cookie therefore sent an empty session and upstream rejected it as "invalid cookie". The LMArena executor now reconstructs the single cookie from its chunks — reading .0, .1, … in ascending numeric order until one is missing and concatenating their raw values (@supabase/ssr's combineChunks rule: plain join(""), no base64-decode, no JSON-parse, the base64- prefix kept verbatim) — while preserving the rest of the pasted jar. A non-empty single cookie is still forwarded unchanged (back-compat). The credential UX now instructs pasting the full Cookie header and tracks the .0/.1 storage keys. (#4271 — thanks @caussao)
  • fix(compression): preserve the cacheable prefix for automatic-cache providers — OpenAI / Codex (and Azure-OpenAI) use automatic prefix caching: the upstream caches the longest matching prefix of a request (system prompt + earliest messages) without any explicit cache_control markers in the body. The cache-aware compression guard only protected that prefix when the request carried explicit cache_control, so for automatic-cache providers the guard was skipped — and with compression enabled and preserveSystemPrompt: false (or a prefix-compressing mode like aggressive/ultra) it rewrote the system prompt / earliest messages, guaranteeing a cache miss and higher token spend through OmniRoute than going direct. The guard now treats a caching provider as sufficient on its own (isCachingProvider alone, independent of cache_control) to skip the system prompt and downgrade prefix-compressing modes, and OpenAI/Codex/Azure are now recognized as caching providers. Compression is still off by default — this only affects operators who enabled it with prefix preservation turned off. (#3955)
  • fix(executors): DuckDuckGo AI Chat uses duckduckgo.com (fixes 400) — the DuckDuckGo AI Chat executor fetched status/chat and set Origin/Referer against https://duck.ai while still sending Sec-Fetch-Site: same-origin, so the request's same-origin triplet (host + Origin + Referer) was inconsistent and the backend rejected it with HTTP 400. All current DDG reverse-engineering references — and the provider registry's own baseUrl — use https://duckduckgo.com; the executor now uses it consistently for the status URL, chat URL, Origin, and Referer (the same-origin header is now coherent). The x-fe-version scrape regex also required a 40-hex tail but the real served token has a 20-hex tail (e.g. serp_20250401_100419_ET-19d438eb199b2bf7c300), so it silently fell back to a hardcoded default; the pattern is relaxed to a bounded {20,40} tail (still ReDoS-safe). This addresses the DuckDuckGo half of the report; the separate Chipotle/chipotle upstream breakage is tracked independently. (#4037 — thanks @daniij)
  • fix(security): bound the prompt-injection scan to the first 16 KB (hot-path perf) — the prompt-injection guard joined every message/system string into one buffer and ran several regexes over the whole thing on every chat request, with no size cap — so a 300 KB body (pasted code, RAG context) meant O(body) CPU scanning on the hot path, a self-inflicted latency/GC source under concurrency. Both detection call sites (detectInjection in inputSanitizer.ts and the custom-pattern scan in promptInjection.ts) now slice the joined text to the first 16 KB (MAX_INJECTION_SCAN_BYTES) before the regex loop. Injection directives sit near the top of a prompt, so the generous cap preserves real detection while scanning only a bounded prefix; the existing 10 MB body-size cap (which protects ingestion) is unchanged. (#3932 — thanks @KooshaPari)
  • fix(sse): retry direct-connection socket failures on a fresh socket (fewer 502 bursts) — the default direct-connection undici dispatcher pools keep-alive sockets for up to 4 s, but some edges (e.g. nvidia, opencode-zen) silently close idle keep-alive sockets within that window, so the next request reusing a pooled socket fails with UND_ERR_SOCKET ("other side closed") — in bursts. proxyFetch already retried once on such transient errors, but the retry reused the same pooled dispatcher and could grab another stale socket, then fell through to native fetch (which also pools) → the job sat in the rate-limit queue until the 30 s timeout → 502 + circuit-breaker open. The retry now uses a dedicated no-keep-alive / no-pipelining dispatcher so it opens a brand-new socket that can't be a dead pooled one; the first attempt still uses the pooled dispatcher (healthy keep-alive reuse is preserved). Complements the v3.8.29 diagnostics (describeFetchCause, fix(sse): surface undici err.cause on dispatcher failure (#4252) #4281). (#4252 — thanks @klimadev)
  • fix(sse): combo now stops at the first body-specific 400 instead of trying every target — the #2101 guard that detects a body-specific 400 (context overflow / malformed / model-access-denied, e.g. "model is not supported when using Codex with a ChatGPT account") logged "stopping combo" but executed a bare break, which only exited the inner retry loop; executeTarget then returned null and the outer target loop treated that as "this target produced nothing" and advanced to the next model. A combo of N targets that all reject the same request body therefore marched through all N (the report shows a 143-model Codex combo iterating every target), wasting upstream calls and per-attempt work. The guard now surfaces the 400 via the { ok, response } contract (mirroring the 499 client-disconnect path) so the combo resolves and stops immediately. (#4279)
  • fix(sse): non-streaming combo over a Responses-API target no longer returns empty content — a Responses-API target (codex/cx) streams from upstream even on stream:false, and its terminal response.completed snapshot can carry a non-empty output that lacks the assistant message item (e.g. only a reasoning item) while the streamed output_text deltas had reconstructed the full message. The SSE→JSON aggregator preferred the terminal output wholesale, dropping the reconstructed text → HTTP 200 with empty content (hit notably via n8n, which defaults to stream:false). The aggregator now falls back to the reconstructed delta output when the terminal output has no message item but the reconstruction does; the terminal snapshot still wins whenever it already carries the message. (#3948)
  • fix(executors): preserve tool-name casing on native Claude OAuth (read no longer leaks back as Read) — native Claude OAuth traffic runs through an anti-fingerprint tool-name cloak that renames a tool literally named read to Read on the wire and records the reverse alias on a non-enumerable _toolNameMap, which the response side uses to restore the client's original casing. Since v3.8.27 the executor returned a JSON-round-tripped copy of the body as transformedBody, and that round-trip dropped the non-enumerable map — so the restore saw an empty map and the cloaked Read streamed verbatim to the client, corrupting the tool name. The executor now re-attaches the cloak map onto the serialized body (mirroring the Antigravity executor), so tool-name casing round-trips correctly. (#4307 — thanks @dev-cj)
  • fix(api): cache-HIT X-OmniRoute-Response-Cost now reports the incremental cost (≈0), not the original — on a semantic-cache HIT the gateway serves the stored response without an upstream call, but X-OmniRoute-Response-Cost was reporting the original call's full cost (recomputed from the cached usage). A consumer summing response-cost for billing was therefore charging for responses that cost ≈$0 to serve (and stale entries could inflate it). Cache hits now bill X-OmniRoute-Response-Cost: 0.0000000000 (the real incremental cost), and the avoided cost is surfaced in a new X-OmniRoute-Cost-Saved header for cache analytics — mirroring the existing tokens_saved concept. The MISS path is unchanged. (PRD-2026-06-19-cache-hit-cost-reporting)
  • fix(models): imported vision-capable models keep their vision capability — after importing a provider key, vision-capable models (e.g. OpenRouter models whose architecture declares image input, and other synced providers) were listed as text-only in /v1/models and the dashboard — even though image requests actually worked. Synced model records never captured the vision flag, and the catalog's OpenRouter live-enrichment (which derives vision from architecture.input_modalities) is skipped once a provider has synced models. Discovery now captures supportsVision at sync time (from architecture.input_modalities, the string architecture.modality, or a top-level input_modalities), mirroring the existing supportsThinking capture, and the catalog surfaces capabilities.vision for synced models. (#4264 — thanks @FerLuisxd)
  • fix(providers): Cloudflare Workers AI model discovery shows model names, not UUIDs — importing a Cloudflare Workers AI key listed models with internal UUID identifiers (e.g. 429b9e8b-d99e-…) instead of their usable slugs (@cf/meta/llama-3.1-8b-instruct). Cloudflare's /ai/models/search returns { id: "<uuid>", name: "@cf/…" }, and discovery was passing the raw objects through — so the UUID id became the callable model id. The cloudflare-ai discovery now maps each result's name → id, surfacing the real @cf/… model ids. (#4259 — thanks @FerLuisxd)
  • fix(translator): clamp Responses API call_id to 64 characters — the OpenAI Responses API rejects call_id values longer than 64 characters with a 400. Long upstream tool-call ids (some clients emit ids well over the limit) are now clamped deterministically on both the function_call item and its matching function_call_output, so the pair stays matched through the orphaned-output filter and the request is accepted. (thanks @anuragg-saxenaa, @ngapngap)
  • fix(oauth): GitHub Copilot token refresh now sends the public client_id — the github provider config never carried a clientId, so GitHub OAuth refresh_token exchanges either omitted client_id or sent the literal string undefined (and a bogus client_secret=undefined), which GitHub rejects — leaving a Copilot connection stuck once its short-lived token expired and the long-lived refresh path was needed. The provider now resolves its public device-flow client_id from the embedded public credential and omits client_secret entirely (GitHub's Copilot app is a public client with no secret). (thanks @baslr)
  • fix(translator): a tool property named pattern survives Gemini/Antigravity schema sanitization — the Gemini schema sanitizer strips JSON-Schema constraint keywords Gemini rejects (pattern, minLength, …) at every nesting level, but it also deleted any tool property literally named one of those keywords. glob/grep tools declare a property called pattern, so on ag/* (Antigravity) backends that argument (and its required entry) was silently dropped, breaking the tools. Keyword stripping is now position-aware: it only removes constraint keywords at the schema-node level and never against the user-defined names inside a properties map. A genuine string-level pattern constraint is still stripped. (thanks @youthanh)
  • fix(translator): MCP namespace tools flatten to individual functions on the Responses→Chat path — when a Codex CLI client routes a Responses-API request to a non-Codex backend (e.g. kr/claude-opus-4.7), each MCP server is declared as a namespace tool ({ type:"namespace", name, tools:[…] }). The Responses→Chat translator had no namespace branch, so the whole group collapsed into a single empty-schema function named mcp__<server>__ and every MCP call returned unsupported call: mcp__<server>__, breaking all MCP-based workflows (context7, codegraph, custom MCPs) for that combination. The translator now expands a namespace into one Chat function per sub-tool (preserving each sub-tool's name and parameters); an empty namespace yields no tools instead of a broken placeholder. The native Codex passthrough path was already correct. (thanks @V13t4nh)
  • fix(cli): the active remote-context credential wins over an ambient OMNIROUTE_API_KEY — when a remote context is selected, its scoped access token now takes precedence over an OMNIROUTE_API_KEY present in the environment, so the connected remote is targeted as expected. (#4364)
  • fix(cli): wire the contexts command into the CLI program — the omniroute contexts command (list/switch saved remote contexts) was implemented but never registered, so it was unreachable; it is now wired into the CLI program. (#4369)
  • fix(mitm): mask bare Bearer <token> header values in the Traffic Inspector — the inspector now redacts bare Authorization: Bearer … values so tokens don't leak into captured traffic. (#4358)
  • fix(pricing): price the gpt-5.x-pro OpenAI models + align the opencode-go discovery test — adds pricing for the gpt-5.x-pro models so cost telemetry reports a real cost instead of zero. (#4355)
  • fix(sse): release the reader and cancel the stream on abort/error (no more Undici pool socket leak) — on abort or a mid-stream error the response reader is released and the stream cancelled, preventing leaked pooled sockets that degraded later requests. (#4309 — thanks @Ardem2025)
  • fix(kiro): emit an early role-only start chunk to release the stream-readiness gate — Kiro streams now send an initial role-only chunk so the stream-readiness gate releases promptly instead of stalling. (#4311 — thanks @artickc)
  • fix(dashboard): the proxy modal stops pre-filling new scopes with an unrelated proxy — adding a new scope assignment no longer inherits a previously-selected proxy's configuration. (#4312)
  • fix(open-sse): inner-ai stops silently rerouting unmatched models to models[0] — an unmatched model id is no longer silently served by the first available model; the lookup now returns null and the request is handled explicitly. (#4310)
  • fix(pollinations): handle auth-required premium models (claude, gemini, midjourney) — premium Pollinations models that require authentication are now handled correctly instead of failing. (#4266 — thanks @oyi77)
  • fix(codex): isolate the Spark quota scope — Codex Spark usage is tracked under its own quota scope so it no longer bleeds into other Codex quotas. (#4293 — thanks @xz-dev)
  • fix(dashboard): improve the API "try it" functionality — fixes the request path used by the dashboard's API "try it" panel. (#4296 — thanks @edrickrenan)
  • fix: polyfill crypto.randomUUID for non-secure contexts — restores UUID generation when the dashboard is served over a non-secure (plain-HTTP) origin where crypto.randomUUID is unavailable. (#4287 — thanks @pizzav-xyz)
  • fix(proxy): allow concurrent proxy dispatcher streams — the proxy dispatcher no longer serializes streams, so concurrent requests through a proxied connection run in parallel. (#4288 — thanks @wilsonicdev)
  • fix(build): co-locate llmlingua SLM optionals into dist/node_modules (postinstall) — the optional llmlingua SLM packages are co-located into the standalone build so the compression worker can actually spawn in production. (#4286)
  • fix(mitm): surface AgentBridge traffic in the Traffic Inspector (D4 ingest) — AgentBridge requests now appear in the Traffic Inspector. (#4285)
  • fix(sse): surface undici err.cause on dispatcher failure — dispatcher failures now flatten the cause chain (and AggregateErrors) into the error detail for diagnosability. (#4281)
  • fix(cli): harden launch/launch-codex with free-claude-code patterns — the launchers adopt the hardened launch patterns ported from free-claude-code. (#4278)
  • fix(compression): end-to-end audit — fixes across the whole compression flow — a sweep of the compression pipeline fixing ultra/aggressive/lossless edge cases, accessibility-anchor handling, language detection, and mode decoupling. (#4323)

🧪 Tests

📝 Maintenance

  • refactor(combo): ComboContext + extract phaseComboSetup (god-file split, phase 1) — begins decomposing the combo god-file by extracting combo setup into a context object, without touching dispatch/semaphore logic. (#4326)
  • feat(quality): cap test-file size — anti-reinflation Layer 1 — freezes the existing god-tests and caps new test files at 800 lines to stop re-inflation. (#4273)
  • feat(quality): seed per-module mutationScore floors + a blocking aggregation ratchet (T3) — adds per-module mutation-score floors with a blocking aggregate gate. (#4305)
  • feat(quality): make the a11y gate real (@axe-core/playwright in nightly) — wires the previously-phantom accessibility gate into the nightly run with real baselines. (#4321)
  • feat(quality): unblock R1 — test-redundancy measurement via disableBail — enables the test-redundancy measurement that was previously blocked by fail-fast. (#4322)
  • fix(quality): the complexity gate now covers bin/ + electron/, and tracked-artifacts runs in pre-commit — extends the complexity gate's scope and moves the tracked-artifacts check into the pre-commit hook. (#4318)
  • fix(quality): restore release/v3.8.30 green — 3 latent reds from concurrent merges — fixes three latent test reds surfaced by concurrent merges into the release branch. (#4335)
  • fix(combo): keep phaseComboSetup under the complexity ceiling — extracts a helper so the new combo setup phase stays under the complexity gate. (#4338)
  • ci(mutation): split over-budget batches by range/pair so every batch fits the job cap — re-splits the mutation batches so each fits the CI job budget. (#4272)
  • chore(ci): align the electron audit gate to the root advisory policy — the electron-workspace audit gate now follows the same advisory policy as the root. (#4275)
  • chore(quality): reconcile the complexity/quality baselines across concurrent-merge drift — rolls up the cycle's baseline reconciliations driven by concurrent merges into the release branch. (#4330, #4336, #4370)
  • docs: ban AI-generation footers in commits/PRs/CHANGELOG (Hard Rule fix(ci): fix npm publish auth — support vars.NPM_TOKEN #16) — codifies the prohibition on AI-generation footers and bot co-author trailers. (#4328)
  • docs(design): add the OmniRoute design system and visual identity specification — adds the design-system / visual-identity specification document. (thanks @diegosouzapw)

🔒 Security

  • fix(sse): harden the DuckDuckGo lite scraper sanitization (CodeQL) — closes four HIGH CodeQL alerts in the no-key web-search scraper: decodeEntities now resolves &amp; last so an already-escaped entity (e.g. &amp;lt;) survives as literal text instead of being double-unescaped (js/double-escaping); stripTags decodes entities first, then strips tags in a loop to a fixpoint and drops any trailing unclosed <…, so entity-encoded markup like &lt;script&gt; can never reach the LLM/client as a live tag (js/incomplete-multi-character-sanitization); and the host checks in the search tests use new URL().hostname equality instead of substring .includes (js/incomplete-url-substring-sanitization). (#4356)

🔧 Dependencies

  • fix(deps): bump undici to 7.28.0 and dompurify to 3.4.11 (security) — addresses the undici SOCKS5-TLS / cache advisories and the dompurify advisory. (#4306)
  • chore(deps): bump actions/checkout from 4 to 7 — CI checkout-action update. (#4297)


🙌 Contributors

Thanks to everyone whose work landed in v3.8.30 (external contributors first, maintainer last):

Contributor PRs / Issues
@oyi77 #4266
@pizzav-xyz #4287
@wilsonicdev #4288
@xz-dev #4293
@edrickrenan #4296
@Ardem2025 #4309
@artickc #4311
@Witroch4 #4327
@FerLuisxd #4264, #4259, #3841
@caussao #4271
@daniij #4037
@KooshaPari #3932
@dev-cj #4307
@klimadev #4252
@akbardwi #4354
@mrcyclo #4325
@anuragg-saxenaa #4317
@ngapngap #4317
@baslr #4320
@youthanh #4339
@V13t4nh #4340
@diegosouzapw maintainer

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request bumps the version of OmniRoute and its associated packages (including electron, open-sse, and the OpenAPI specification) from 3.8.29 to 3.8.30. It also adds placeholder entries for version 3.8.30 in the internationalized changelog files. No review comments were provided, so there is no additional feedback to address.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@github-actions

github-actions Bot commented Jun 19, 2026 •

Copy link
Copy Markdown
Contributor

CI Coverage Report

  • Coverage job: failure
  • PR test policy: success

Coverage artifact was not available for this run.

diegosouzapw and others added 26 commits June 19, 2026 09:36
* feat(cli): Codex CLI launcher + setup commands

Two new CLI subcommands mirroring the `launch`/`configure` pattern, for driving the
OpenAI Codex CLI against OmniRoute:

- `omniroute launch-codex` — boots OmniRoute (if needed) and launches Codex CLI
  pointed at it (local or remote VPS), with no manual env/config editing.
- `omniroute setup-codex` — generates ~/.codex profile files from OmniRoute's live
  model catalog.

Registered in the command registry; en.json + pt-BR.json locale sections added
(keeps the cli-i18n-catalog top-level parity gate green). CODEX-CLI-CONFIGURATION.md
refreshed. Consolidated from work-in-progress that was uncommitted on the shared
checkout; reconstructed on release/v3.8.30.

* chore(vscode): reduce git repo-detection overhead for nested worktrees

Disable VS Code's git auto-repository-detection / submodule scan / autofetch so the
Source Control view stops indexing the ~44 nested repos (worktrees + _references/*
+ _mono_repo/*), which caused constant "validating" churn. Only the root repo is
tracked. Editor-only settings; no runtime impact.
Align electron audit gate to root policy (critical blocks, high warns). Un-blocks the Lint job on release/v3.8.30.
…cation

Introduce a design system and visual identity plan to unify the OmniRoute
dashboard and marketing site. The document covers color alignment,
missing design tokens, and the implementation strategy for a consistent
brand experience.
…les) (#4274)

Brings Claude Code to parity with the Codex CLI integration.

- `omniroute launch` is now remote-aware: --remote <url>, --profile <name>,
  --api-key. Resolves base URL + auth from the active context (so
  `omniroute connect <vps>` then `omniroute launch` just works), accepts a bare
  port OR a full base URL, health-checks the (possibly remote) server, and sets
  CLAUDE_CONFIG_DIR for the chosen profile.
- `omniroute setup-claude` (new): fetches the live /v1/models catalog and writes
  ~/.claude/profiles/<name>/settings.json per model. Claude Code has no native
  profile files, so CLAUDE_CONFIG_DIR is the idiomatic mechanism. Reuses the SAME
  profile names as setup-codex (glm52, kimi-k27, …) via the shared categoriseModel
  (now exported). The auth token is NEVER written to disk — launch injects it.
- Docs: docs/guides/CLAUDE-CODE-CONFIGURATION.md + README index. i18n (en + pt-BR).
- Tests: setup-claude profile generation (incl. "no token on disk"), buildClaudeEnv
  (port + URL + CLAUDE_CONFIG_DIR), resolveLaunchTarget.

check:cli-i18n + check-docs-sync green; 13 unit tests pass.
Reconcile baseline + test drift on release/v3.8.30 (complexity, opaque surface, search count, tproxy addon). Round-robin left as a canary for the undici-dispatcher issue.
…fits the job cap (#4272)

Split over-budget mutation batches by range/pair + union same-file mutants across sibling batches. CI-only + TDD.
…te-aware plugin) (#4277)

setup-opencode: remote-aware openai-compatible provider generator; API key referenced by env var (never on disk). 6 tests.
… god-tests, cap new at 800 (#4273)

Layer 1 anti-reinflation: cap test-file size (freeze god-tests, cap new at 800). Gate validated green against the full combined tree.
…4278)

Applies proven patterns from the free-claude-code reference adapters:

launch (Claude Code):
- always set ANTHROPIC_AUTH_TOKEN — a no-auth sentinel when none is resolved —
  so newer Claude Code doesn't stop at its local login gate before contacting
  OmniRoute (an open backend ignores the value; ANTHROPIC_API_KEY stays stripped).

launch-codex:
- remote-aware: resolves the root base URL + auth from --remote/--api-key, the
  active context, then localhost (was localhost/--remote only).
- inject the `omniroute` provider via `-c` flags (model_provider + base_url +
  env_key + wire_api=responses + requires_openai_auth=false) so it works WITHOUT
  a pre-existing ~/.codex/config.toml.
- strip OPENAI_*/CODEX_* from the child env (defense-in-depth) and set
  OMNIROUTE_API_KEY to the token or a sentinel. (Honest note: this does NOT
  silence codex's refresh_token log noise — that comes from ~/.codex/auth.json,
  is cosmetic, and does not block requests.)
- replace a hard-coded Tailscale IP in --remote help with a placeholder.

Tests: buildCodexEnv (strip + sentinel + no-mutate), buildCodexProviderArgs
(inline provider def), resolveCodexTarget; updated buildClaudeEnv sentinel test.
Validated remotely vs VPS v3.8.30: `launch-codex --remote ... exec` → "OK".
19 unit tests pass; check:cli-i18n green.
Surface err.cause + propagate diagnosable error fast on undici dispatcher failure. TDD. #4252.
… UUIDs (#4259) (#4282)

Cloudflare's /ai/models/search returns { id: "<uuid>", name: "@cf/..." } where
name is the callable slug and id is an internal UUID. The cloudflare-ai discovery
config passed the raw objects through (parseResponse: data.result), so buildResponse
used id (the UUID) as the model id — the dashboard/import listed UUIDs instead of
@cf/... model names. Map each result's name -> id (mirrors the gemini/huggingface/
clarifai parseResponse normalizers in the same map); falls through to the local
catalog on error so import never breaks.

TDD: tests/unit/cloudflare-models-uuid-4259.test.ts (RED on UUID ids -> GREEN on slugs).

Closes #4259
…S Code hints) (#4280)

CLI #4 of the series. Cline's VS Code extension keeps config in opaque VS Code
globalStorage (not file-writable); its CLI/standalone mode reads ~/.cline/data/.

`omniroute setup-cline`:
- writes ~/.cline/data/globalState.json (act/planModeApiProvider=openai,
  openAiBaseUrl = ROOT url WITHOUT /v1 — Cline appends /v1/chat/completions —
  openAiModelId + planModeOpenAiModelId) and ~/.cline/data/secrets.json
  (openAiApiKey), both merged to preserve existing state. Matches the dashboard
  cli-tools/cline-settings schema.
- remote-aware (--remote/--api-key → active context → localhost).
- model resolved via --model or an interactive pick from /v1/models (Cline has
  no model auto-discovery).
- prints the exact VS Code extension settings (Base URL/key/model) to paste,
  since the extension's storage can't be written directly.

Researched against the current Cline docs (saoudrizwan.claude-dev): confirmed
the openai-compatible keys, the Plan/Act split, and that openAiBaseUrl must be
the ROOT (no /v1). Cline's wire (/v1/chat/completions) already validated → "OK".

Tests: buildClineGlobalState (provider+root+model, merge-preserve),
buildClineSecrets (key + placeholder), resolveClineTarget (/v1 strip, key win).
6 unit tests; check:cli-i18n green.
…) (#4283)

After importing a provider key, vision-capable models (OpenRouter models whose
architecture declares image input, and other synced providers) were shown as
text-only in /v1/models and the dashboard, even though image requests worked.

Root cause: SyncedAvailableModel never captured a vision flag, and the catalog's
OpenRouter live-enrichment block (which derives vision from architecture.input_modalities)
is skipped once a provider has synced models. So the synced path emitted no vision.

Fix (mirrors the existing supportsThinking capture):
- modelDiscovery.normalizeDiscoveredModels derives supportsVision via the new
  detectVisionInput() from architecture.input_modalities, the string
  architecture.modality ("text+image->text"), or a top-level input_modalities.
- SyncedAvailableModel gains supportsVision; the read-normalize path preserves it.
- catalog.ts emits capabilities.vision for synced models and merges (not clobbers)
  capabilities when the model already exists.

TDD: tests/unit/openrouter-vision-sync-4264.test.ts — capture unit test + an
end-to-end /v1/models assertion (RED before, GREEN after).

Closes #4264
… VS Code settings) (#4284)

CLI #5 of the series. `omniroute setup-kilo` configures Kilo Code
(kilocode.kilo-code, a Cline/Roo descendant) to use OmniRoute.

Two surfaces (both written, matching the dashboard cli-tools/kilo-settings):
- ~/.local/share/kilo/auth.json — CLI mode: auth["openai-compatible"] =
  { apiKey, baseUrl (WITH /v1 — Kilo appends /chat/completions), model }.
- VS Code settings.json — extension: kilocode.customProvider (name/baseURL/apiKey)
  + kilocode.defaultModel. Only touched when the file already exists.

Remote-aware (--remote/--api-key → active context → localhost). Model via --model
or an interactive pick from /v1/models (Kilo's extension has no auto-discovery).
Prints the exact UI settings to paste. Merges both files (preserves existing).

Researched against current Kilo docs: confirmed openAiBaseUrl needs /v1 (unlike
Cline's root url), the openai-compatible keys, and the export/import + CLI surfaces.
Kilo's wire (/v1/chat/completions) already validated → "OK".

Tests: buildKiloAuth (provider + /v1 + merge + key fallback), buildKiloVscodeSettings
(kilocode.* keys + preserve), resolveKiloTarget (/v1 ensure, key win). 6 unit tests;
check:cli-i18n green.
…Route (#4289)

CLI #6 of the series. `omniroute setup-continue` writes Continue's file-based,
mergeable ~/.continue/config.yaml (shared by the VS Code/JetBrains extensions AND
the `cn` CLI) from the live model catalog.

- Each curated model → a Continue model entry: provider: openai, model: <id>,
  apiBase WITH /v1 (Continue appends /chat/completions), apiKey:
  ${{ secrets.OMNIROUTE_API_KEY }} (secret referenced, never written), roles
  [chat, edit, apply] (+ autocomplete for the fast tier).
- Merges into existing config.yaml (js-yaml load/dump): drops prior models on the
  same apiBase, preserves the user's other models + top-level keys.
- Remote-aware (--remote/--api-key → active context → localhost); --only filter.
- Prints how to provide the key (shell env for cn; ~/.continue/.env for IDE).

Researched against current Continue docs: provider: openai + custom apiBase (with
/v1), the ${{ secrets.X }} syntax, roles, and that the `cn` CLI shares the same
config. Continue's wire (/v1/chat/completions) already validated → "OK".

Tests: buildContinueModels (provider/apiBase/secret/roles, fast→autocomplete,
skip uncategorised), mergeContinueConfig (replace-ours/keep-others/defaults),
resolveContinueTarget (/v1). 6 unit tests; check:cli-i18n green.
CLI #7 of the series. Cursor stores its OpenAI key + "Override OpenAI Base URL"
in an opaque SQLite DB (state.vscdb) with no stable schema — not safe to
file-write. So `omniroute setup-cursor` prints the exact in-app steps and lists
real model names from /v1/models.

- Resolves apiBase WITH /v1 (Cursor appends /chat/completions) + key from
  --remote/--api-key → active context → localhost.
- Prints Settings → Models → Override OpenAI Base URL + key + model-name steps,
  with a clear caveat that the custom base URL powers Cursor's CHAT panel only
  (Composer / inline-edit / autocomplete stay on Cursor's backend).

Researched against current Cursor behavior. Tests: resolveCursorTarget (/v1, key),
buildCursorInstructions (base URL + /v1 note + model samples + caveat). 4 unit
tests; check:cli-i18n green.
…+ autoImport + UI) (#4292)

CLI #8 of the series. Roo Code (RooVeterinaryInc.roo-cline, a Cline fork) keeps
live settings in opaque VS Code globalStorage, but supports Settings Import and
an `roo-cline.autoImportSettingsPath` (VS Code settings.json) that loads a JSON
at startup.

`omniroute setup-roo`:
- writes ~/.omniroute/roo-settings.json — a Roo provider profile
  (providerProfiles.apiConfigs.OmniRoute: apiProvider=openai, openAiBaseUrl WITH
  /v1 — Roo appends /chat/completions — openAiApiKey, openAiModelId).
- sets roo-cline.autoImportSettingsPath in VS Code settings.json when present
  (preserves other settings).
- prints the guaranteed UI path (Settings → Providers → OpenAI Compatible) +
  the "Import Settings" fallback.
- remote-aware; model via --model or interactive pick.

Researched against current Roo docs: OpenAI-compatible needs baseUrl WITH /v1 and
native tool-calling (OmniRoute supports it). Roo's wire (/v1/chat/completions)
already validated → "OK".

Tests: resolveRooTarget (/v1, key), buildRooImport (provider profile + /v1 + key
fallback), buildRooVscodeAutoImport (pointer + preserve). 5 unit tests; cli-i18n green.
… token-cost alert (PRD-2026-06-19) (#4290)

* feat(memory): x-omniroute-no-memory opt-out + memory off-by-default + token-cost UI alert

PRD-2026-06-19-no-memory-header. The gateway injects up to memorySettings.maxTokens
(~2k) of memory (and skills) context into every chat call for memory-enabled keys,
inflating tokens+cost ~137x for clients that manage their own context (e.g. Omniflow).

Three changes:
- A) x-omniroute-no-memory request header (mirrors x-omniroute-no-cache): when truthy
  (true/1/yes), skip memory+skills injection for that request. New pure helper
  isNoMemoryRequested() in chatCore/headers.ts; chatCore passes memoryOwnerId=null on
  opt-out (a null owner disables both injection branches).
- B) Memory OFF by default: DEFAULT_MEMORY_SETTINGS.enabled true->false. Enabling injects
  billed context per request, so it's now an explicit opt-in. Installs that already
  enabled it keep it; unset installs default off (no migration seeds memoryEnabled).
- C) Settings -> Memory shows a token-cost warning callout when memory is enabled
  (new settings.memoryTokenCostWarning i18n key, interpolating the configured maxTokens).

Tests: no-memory-header.test.ts (5, helper truthiness/case/Headers); memory-settings-default
and chatcore-memory-skills-injection aligned to the new off-by-default. 65/65 memory+chatcore
tests green; typecheck/lint/file-size/i18n(@65) clean.

* test(memory): enable memory in memory-tools test (memory now off by default)

The full CI unit suite flagged memory-tools.test.ts 'memory search ...' failing
after DEFAULT_MEMORY_SETTINGS.enabled flipped to false: omniroute_memory_search
routes through retrieveMemories, which returns [] while memory is disabled
(enabled:false → maxTokens 0). The memory MCP tools operate within the memory
subsystem, so the test now enables memory explicitly (updateSettings + cache
invalidation) — the realistic precondition for a client using the tools.
Aligns the test to the intentional off-by-default change; assertions unchanged.
…on (#4298)

CLI #9 of the series. `omniroute setup-crush` writes Crush's file-based
~/.config/crush/crush.json with an `openai-compat` provider for OmniRoute:
base_url WITH /v1, api_key referenced as $OMNIROUTE_API_KEY (secret off disk),
curated catalog models with context_window. Merges (preserves existing config).
Remote-aware (--remote/--api-key → active context → localhost); --only filter.

Researched against charmbracelet/crush: openai-compat provider type, base_url
needs /v1, $VAR api_key references. Crush's wire (/v1/chat/completions) already
validated → "OK".

Tests: resolveCrushTarget (/v1, key), buildCrushProvider (openai-compat + env-ref
+ curated models + skip-unknown), mergeCrushConfig (preserve). 4 unit tests; cli-i18n green.
… env recipe) (#4300)

CLI #10 of the series. `omniroute setup-goose` writes Goose's file-based
~/.config/goose/config.yaml (GOOSE_PROVIDER=openai, GOOSE_MODEL=<model>,
OPENAI_HOST=<root, NO /v1 — Goose appends the path itself>), merges to preserve
existing keys, and prints the guaranteed env-var recipe (the key lives in the env
/ OS keyring, never the config). Remote-aware (--remote/--api-key → context →
localhost); model via --model or interactive pick.

Researched against block/goose: provider openai + OPENAI_HOST base (no /v1).
Goose's wire (/v1/chat/completions) already validated → "OK".

Tests: resolveGooseTarget (/v1 strip, key), buildGooseConfig (provider/model/host
+ preserve), buildGooseEnvRecipe (env-ref key). 4 unit tests; cli-i18n green.
…son modelProvider) (#4301)

CLI #11 of the series. `omniroute setup-qwen` writes Qwen Code's file-based
~/.qwen/settings.json: an openai `modelProvider` (id omniroute, authType openai,
baseUrl WITH /v1, envKey OMNIROUTE_API_KEY — secret stays in the env), selects it,
sets the model. Merges (de-dupes the omniroute provider, preserves the rest).
Remote-aware; model via --model or interactive pick; headless test `qwen -p`.

Researched against QwenLM/qwen-code: modelProviders authType openai, baseUrl /v1,
envKey reference. Qwen's wire (/v1/chat/completions) already validated → "OK".

Tests: resolveQwenTarget (/v1, key), buildQwenSettings (openai provider + /v1 +
envKey + model, de-dupe + preserve). 4 unit tests; cli-i18n green.
…ngest) (#4285)

The standalone server.cjs proxy intercepts AgentBridge requests inline (no
MitmHandlerBase / agentBridgeHook), so intercepted traffic never reached the
TS hook that pushes into globalTrafficBuffer — the Traffic Inspector stayed
empty for AgentBridge even on successful intercepts. Three gaps closed:

- _internal/ingest.cjs (new): pure payload builder + fire-and-forget poster
  (never throws — capture must not break proxy traffic).
- server.cjs: intercept() accumulates response (bounded) + status/headers and
  posts the captured entry to the local-only /internal/ingest endpoint in a
  finally block; also captures error/4xx intercepts.
- manager.ts: resolves the ingest token via getIngestTokenForBootstrap() and
  passes it to the spawned proxy so the endpoint accepts the post.
- authz management policy: exempt the loopback /internal/ingest endpoint from
  management auth — it has its own shared-secret token gate, and server.cjs
  has no dashboard cookie. Stays strictly loopback (LOCAL_ONLY gate unchanged).
- ingest route: masks secrets / strips hop-by-hop headers before buffering
  (server.cjs sends raw over the token-gated loopback) — Hard Rule #12.

Tests: ingest shim (build/post/no-token/error) + route sanitization + 403 +
management-policy carve-out (loopback allow / remote LOCAL_ONLY).
…(postinstall) (#4286)

The compression "ultra" SLM tier (#4257) runs @atjsh/llmlingua-2 + transformers + tfjs
+ js-tiktoken in a worker thread shipped under dist/. These are optionalDependencies
installed into the ROOT node_modules on --include=optional, but the Next.js standalone
trace bundles ONLY @huggingface/transformers (3.5.2, pinned) into dist/node_modules —
not the dynamically-imported optionals.

Result: the worker resolves transformers from dist/node_modules (3.5.2) for its env
config but resolves @atjsh/llmlingua-2 from the ROOT, whose own transformers import
hits a DIFFERENT instance. The cacheDir config never reaches the instance llmlingua-2
uses, so the local model never loads and the SLM tier silently fails-open (and on a
root transformers 4.x, llmlingua-2 throws on the tokenizer API change).

Fix: postinstall co-locates the SLM optional closure from the root node_modules into
dist/node_modules (no-clobber, so the pinned dist transformers/onnxruntime stay), so
the worker resolves a single 3.5.2 instance and the local model loads.

VPS-validated (Rule #18): the co-located layout produced real 54.8% compression
(11520->5203 chars) via real ONNX inference on the production host, both the default
and the #4257 modelPath code paths.

- scripts/build/colocateOptionals.mjs: closure walk (deps+optionalDeps, skips the
  transformers peer) + no-clobber co-location; idempotent + fail-soft
- wired into scripts/build/postinstall.mjs next to ensureSwcHelpers
- registered in package.json files + pack-artifact allow/required lists
- tests/unit/colocate-optionals.test.ts: closure, no-clobber, idempotence, gates
- docs/ops/RELEASE_CHECKLIST.md: note the auto co-location
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](actions/checkout@v4...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* fix: polyfill crypto.randomUUID for non-secure contexts

crypto.randomUUID() requires a secure context (HTTPS or localhost).
When accessing the dashboard over HTTP on a LAN IP, the function is
undefined, causing 'Failed to add account' errors on providers that
generate account IDs client-side (e.g. mimocode).

Adds a lightweight polyfill that falls back to a Math.random()-based
UUID v4 generator when the native API is unavailable.

* fix: address review comments on crypto.randomUUID polyfill

- Use crypto.getRandomValues() for cryptographic security instead of Math.random()
- Add typeof window !== 'undefined' guard to avoid ReferenceError in non-browser envs
- Use window.crypto for safe access instead of bare crypto reference
- Replace var with const and == with === for modern JS syntax
- Add fallback to Math.random() when getRandomValues is unavailable
- Add unit tests verifying valid UUID v4 format, version/variant nibbles,
  uniqueness, and preference for getRandomValues over Math.random

* test(dashboard): regression guard for crypto.randomUUID polyfill (#4287)

Reads src/app/layout.tsx and asserts the blocking inline script installs a
guarded window.crypto.randomUUID polyfill (RFC4122 v4 shape, getRandomValues
preferred with a Math.random fallback). Fails on the pre-fix tree (no polyfill),
passes with the fix — Rule #18 regression guard for the non-secure-context
(HTTP/LAN-IP) dashboard breakage.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

---------

Co-authored-by: pizzav-xyz <pizzav-xyz@users.noreply.github.com>
Co-authored-by: ci <ci@local>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
, #4240) (#4313)

* feat(providers): add OpenAdapter, dit.ai and TokenRouter OpenAI-compatible providers (#4239, #4155, #3841)

Three community-requested OpenAI-compatible aggregators register as standard
named OpenAI-style providers (the zenmux pattern): live /v1/models discovery via
NAMED_OPENAI_STYLE_PROVIDERS, falling back to a seeded catalog on upstream error.
No custom executor/translator — default OpenAI passthrough.

- OpenAdapter  https://api.openadapter.in/v1  (free tier)            #4239
- dit.ai       https://api.dit.ai/v1          (dynamic-pricing)      #4155
- TokenRouter  https://api.tokenrouter.com/v1 (free MiniMax model)   #3841

Base paths confirmed live (each returns a 401 OpenAI-style error body). Seed
catalogs are intentionally minimal (author/doc-cited ids only; TokenRouter
deepseek ids come from production via #3946); full upstream model lists arrive
through live discovery once a key is configured.

* feat(combo): per-step account allowlist for round-robin over a connection subset (#3266)

A combo model step can now carry a first-class `allowedConnectionIds` so a
round-robin / weighted strategy is scoped to a subset of a provider's
connections (e.g. {foo1, foo2}) without hand-pinning one step per account.

- steps.ts: parse `allowedConnectionIds` on the model step (trim + drop empty)
- comboStructure.ts: second writer — propagate the step allowlist onto the
  resolved target (tag routing is the first writer)
- autoStrategy.ts: when a step allowlist AND tag routing both apply, intersect
  them (most-restrictive wins); empty intersection drops the target
- builderDraft.ts + combos UI: optional 'Restrict to accounts' picker in the
  Precision step editor (a pinned single account still takes precedence)

The downstream credential-selection filter (auth.ts) already honours
allowedConnectionIds, so a round-robin scoped to {foo1, foo2} provably never
selects foo3/foo4 (regression test included). Ships the enhancement only; the
#2829 bug-triage half stays open pending the reporter.

* feat(dashboard): category (media serviceKind) filter on the providers page (#4240)

Add a media-category filter row (Image / Video / Music / Text→Speech /
Speech→Text / Embedding) to /dashboard/providers that composes with the existing
search, free-only and 'show configured only' filters.

- serviceKindIndex.ts: client-side resolver unioning a provider's declared
  serviceKinds with the registry-derived media kinds (memoised)
- providerPageUtils: filterConfiguredProviderEntries gains a serviceKindFilter
  argument; threaded through every provider section on the page
- ProviderSummaryCard: a second chip row drives the serviceKind filter

Membership is derived from the backend media registries, so a provider that
serves a kind is surfaced even when it never declared serviceKinds — keeping the
UI in lockstep with the backend (mirrors the media-providers pages).

* chore(quality): rebaseline file-size for the v3.8.30 harvested features

Four frozen files grew from their own additive feature wiring (#4239/#4155/#3841
providers, #3266 combo allowlist UI, #4240 serviceKind filter):
- src/shared/constants/providers.ts 3169->3213 (3 provider entries)
- src/app/api/providers/[id]/models/route.ts 2554->2560 (3 NAMED set entries)
- src/app/(dashboard)/dashboard/combos/page.tsx 4350->4385 (allowlist picker)
- src/app/(dashboard)/dashboard/providers/page.tsx 1925->1927 (serviceKind state)

All cohesive additive wiring at existing chokepoints; rationale recorded in the
_rebaseline_2026_06_19_v3830_harvest_features key.
…on flow (#4323)

* fix(compression): SLM worker resolves deps+worker file without import.meta.url (B-SLM)

The Next.js standalone bundle (webpack) replaces createRequire(import.meta.url)
with a stub that always throws MODULE_NOT_FOUND, and freezes import.meta.url to the
build-machine path. So depsAvailable() was always false (the worker never spawned)
and resolveWorkerFile() anchored on a path absent at runtime — the SLM silently
fell back to the aggressive summarizer in production. Confirmed by inspecting
dist/.build/next/server/chunks/26410.js (stub module 215743 + frozen file:// path).

Replace both with filesystem probing from runtime anchors (process.cwd(),
process.argv[1]) that survive the bundle. Necessary complement to #4286 (deps
co-location) for the SLM to actually engage in prod; still fail-open without it.
VPS live validation deferred (Rule #18); local resolver regression tests added.

* fix(compression): ultra heuristic preserves code blocks / inline code / URLs (B-ULTRA-CODE)

ultra.ts called pruneByScore on raw text with no tombstoning, so the token pruner
dropped low-score code tokens (`b)`, `{`, `+`) inside fenced blocks while leaving the
fence markers intact — output that looked like valid code but was syntactically
destroyed. caveman + llmlingua both extract/restore preserved blocks first; ultra was
the only pruning engine that didn't.

Add pruneProseOnly(): extractPreservedBlocks tombstones fenced code, inline code,
URLs, CONST_CASE, versions; only the prose between placeholders is pruned; preserved
blocks are re-stitched verbatim.

* fix(compression): GCF round-trips values containing the inline-array pattern [..]: (B-GCF-QUOTE)

A value like `ERR[404]: Not Found` / `[Speaker 1]: Hello` nested one level deep was
emitted bare and re-parsed by the decoder as an inline-array header → it threw
`count_mismatch` (or silently decoded wrong), losing the whole block. headroomEngine
.apply() ships such blobs in prod, so this was a reachable lossless violation.

Two complementary fixes, both per SPEC §2.4:
- encode: needsQuote() now quotes strings matching `[`…`]``:` (spec compliance / other
  decoders).
- decode: the inline-array branch only fires when the bracket is in the KEY position
  (no `=` before it), so a quoted `note="ERR[404]: …"` value falls through to key=value.

* fix(compression): aggressive fidelity — keep text blocks, compress Anthropic tool_result, don't corrupt JSON (B-AGG-*)

Three fidelity fixes in the aggressive path (each TDD, aggressive-fidelity.test.ts):
- B-AGG-TEXTDROP: replaceTextContent dropped 2nd+ text blocks unconditionally; now a
  trailing block is dropped only when its text is already subsumed by newText, else kept.
- B-AGG-ANTHROPIC-TR: tool-result compression only fired for OpenAI role:tool messages;
  now Anthropic-shape tool_result content blocks (inside user messages) are compressed
  too, preserving tool_use_id + block structure.
- B-AGG-JSONTAG: the [COMPRESSED:aging:*] prefix corrupted JSON/code payloads; pure JSON
  is now kept verbatim+untagged (stays parseable), fenced blocks get the tag on a
  preceding line.

* fix(compression): accessibility collapse preserves [ref] anchors + fires on interleaved trees (B-MCPA11Y-*)

- B-MCPA11Y-ANCHORS: collapseRepeated silently dropped the omitted middle siblings'
  [ref=eNN] anchors (the agent could no longer click them); now every omitted ref is
  kept alongside the collapse notice. Wires the previously-dead preserveRefPattern.
  Invariant: extractRefs(input) ⊆ extractRefs(output).
- B-MCPA11Y-COLLAPSE: noise removal blanked lines (replace→""), and a blank line broke
  the sibling run so collapse never fired on realistic interleaved trees; noise lines
  are now deleted, and the sibling walk skips stray blanks.

* fix(compression): rtk intensity scales the line budget (B-RTK-INTENSITY)

The intensity knob only set smartTruncate's preserveHead/Tail (16↔24), which rarely
fired because the matched filter capped lines first — so minimal/standard/aggressive
produced byte-identical output on filter-matched tool output. effectiveMaxLines() now
scales the effective line budget (minimal 1.5x, standard 1x, aggressive 0.5x) at both
the per-filter and engine-level truncation sites. Both go through smartTruncate with
priorityPatterns, so error/failure lines survive at every intensity (tested).

* fix(compression): robust language detection + auto-detect honors the detected pack (B-LANG-*)

- B-LANG-DETECTOR: detector was first-match-wins on a single keyword, and some hints are
  English-ambiguous ("configuration" in fr, "error" in es) → English text misclassified.
  Now score-based (count native-keyword hits, highest wins), and the two English-ambiguous
  words are removed from the hint lists, so a lone shared word never misclassifies while
  sparse-keyword languages (id) still detect on a single native word.
- B-LANG-DORMANT: with autoDetectLanguage on but enabledPacks ["en"], detected non-English
  text fell back to the English pack, whose `articles` rule deletes foreign articles
  (pt-BR "a"/"o"). Auto-detect now uses the detected pack directly (it always has rules);
  enabledPacks still gates manual selection.

* fix(compression): mode selection enables its engine + align stacked allowlist (B-MODE-ENGINE-DECOUPLE, B-PIPELINE-DIVERGENCE)

- B-MODE-ENGINE-DECOUPLE: picking the standard/rtk MODE now runs caveman/rtk regardless of
  the per-engine enabled flag — the mode selection is the enable signal (the per-engine flag
  still gates stacked pipeline steps). Previously an operator who picked a mode but left the
  engine toggle off got silent 0% compression.
- B-PIPELINE-DIVERGENCE: the global stackedPipeline normalizer stripped
  session-dedup/ccr/headroom/llmlingua (engines the combo path accepts via KNOWN_ENGINE_IDS).
  The allowlist now matches, so the global setting can use all registered engines.

* docs(compression): correct SLM "stable" claim + document partial packs / stacked telemetry limits

- The llmlingua `stable:true` comment claimed the bundle walk-up + deps-gate were
  "confirmed against the live install" — that was wrong (webpack froze import.meta.url and
  stubbed createRequire, so the worker never spawned in prod). Corrected to reflect B-SLM.
- COMPRESSION_ENGINES.md: add a Known limitations section (SLM dep co-location requirement,
  partial de/fr/ja packs, no-op engines absent from engineBreakdown).

* fix(compression): cast normalized engine id to CompressionPipelineStep['engine'] (typecheck)
… drift) (#4330)

#4313 (harvested features) and #4323 (compression e2e audit) added new conditional
branches that landed after #4318 measured 1888; the release fast-path doesn't run
check:complexity, so reconcile on the merged release tip (Rule #9, release-volatile).
Extend Hard Rule #16 beyond Co-Authored-By trailers to also forbid AI-generation
footers/descriptions (e.g. "Generated with Claude Code") anywhere in a commit
message, PR title/body, or CHANGELOG — they are equivalent to crediting an AI as
co-author. Explicitly overrides any harness/template default that auto-appends
such a footer.
});

test("getMitmToolHosts returns the hosts for a known tool and [] for unknown ids", () => {
assert.ok(getMitmToolHosts("antigravity").includes("cloudcode-pa.googleapis.com"));
diegosouzapw and others added 19 commits June 19, 2026 22:18
#4332)

The prompt-injection guard joined every message/system string into one
buffer and ran several regexes over the whole thing on every chat
request, with no size cap. At high concurrency with large bodies (300 KB
of pasted code / RAG context) that is O(body) CPU scanning on the hot
path — a self-inflicted latency/GC source under load.

Bound both detection call sites — detectInjection() in inputSanitizer.ts
and the custom-pattern scan in promptInjection.ts — to the first 16 KB
via a named MAX_INJECTION_SCAN_BYTES constant, slicing the joined text
before the regex loop. Injection directives sit near the top of a
prompt, so the generous cap preserves real detection while scanning only
a bounded prefix. No call site removed and opt-out behavior unchanged;
this only bounds the scan length. The existing 10 MB body-size cap that
protects ingestion is separate and untouched.

TDD: tests/unit/injection-guard-scan-bound-3932.test.ts proves a
directive at the top of a >16 KB body is still detected (case 1) while
the same marker placed beyond the 16 KB cap is no longer scanned
(case 2, RED before the fix), at both call sites.

Refs #3932
…4037) (#4333)

The DuckDuckGo AI Chat executor fetched status/chat and set Origin/Referer
against https://duck.ai while sending Sec-Fetch-Site: same-origin, making the
same-origin triplet (host + Origin + Referer) inconsistent so the backend
rejected the request with HTTP 400. Repoint the executor's status URL, chat
URL, Origin, Referer, and warm fetch to https://duckduckgo.com (matching the
provider registry baseUrl and current DDG reverse-engineering references); the
same-origin header is now coherent.

Also relax FE_VERSION_PATTERN from a 40-hex tail to a bounded {20,40} tail so it
matches the real served x-fe-version token (20-hex, e.g.
serp_20250401_100419_ET-19d438eb199b2bf7c300) instead of silently falling back
to the hardcoded default. The bound keeps the pattern ReDoS-safe.

This is the DuckDuckGo half of the report; the separate Chipotle upstream
breakage is tracked independently.

TDD: tests/unit/duckduckgo-domain-4037.test.ts (8 assertions, RED before the
fix, GREEN after). Baseline bump 917->925 for the added comments.

Refs #4037
…ders (#3955) (#4334)

OpenAI / Codex / Azure-OpenAI use automatic prefix caching: the upstream
caches the longest matching prefix of a request (system prompt + earliest
messages) WITHOUT any explicit cache_control markers. The cache-aware
compression guard only protected that prefix when the body carried explicit
cache_control, so for automatic-cache providers the guard was skipped — and
with compression active + preserveSystemPrompt:false (or a prefix-compressing
mode) it rewrote the prefix, guaranteeing a cache miss and higher token spend
through OmniRoute than going direct.

getCacheAwareStrategy now treats isCachingProvider alone as sufficient to skip
the system prompt and downgrade aggressive/ultra (the explicit cache_control
path is a subset). openai/codex/azure are added to CACHING_PROVIDERS so they
are recognized as automatic-cache providers (this also activates the intended
prompt_cache_key cache-routing hint for OpenAI in chatCore).

Compression remains off by default — this only affects operators who enabled
it with prefix preservation turned off.

TDD: tests/unit/compression-cache-guard-3955.test.ts (RED 5/7 fail → GREEN
7/7). Aligned the existing cachingAware / strategySelector-cache-aware /
cache-control-policy / cache-control-claude-providers tests that encoded the
old (buggy) "openai is non-caching" behavior.

Refs #3955
LMArena migrated to @supabase/ssr chunked auth cookies: the single
arena-auth-prod-v1 cookie is now empty and the session is split across
arena-auth-prod-v1.0, .1, … (ascending). Pasting the now-empty single
cookie sent an empty session, which upstream rejected as "invalid cookie".

reconstructLMArenaCookie() rebuilds the single cookie from its chunks
(ascending join, no decode/parse — combineChunks semantics), preserving
the rest of the pasted jar; a non-empty single cookie is forwarded
unchanged (back-compat). The credential UX now instructs pasting the full
Cookie header and tracks the .0/.1 storage keys.

Closes #4271
…it fase 1) (#4326)

Primeiro incremento da decomposição do god-file combo.ts (2604 LOC). Extrai o
bloco de setup do handleComboChat (strategy/relay/resilience/universal-handoff/
context-cache pinning/agent middleware/config cascade/timeout) para
phaseComboSetup(ctx), com ComboContext carregando o body mutável compartilhado.

- combo/context.ts: ComboContext (carrier do body mutável) + createComboContext.
- combo/comboSetup.ts: phaseComboSetup(ctx): ComboSetup (byte-equivalente ao bloco
  inline; reescreve ctx.body no pin + middleware, retorna os locals derivados).
- comboConfig.ts: resolveComboSetupConfig (DRY — encapsula o ternário do config,
  tipo único p/ ComboContext.config sem cast).
- combo.ts: -47 linhas; rebinda os locals do ctx, resto de handleComboChat intacto;
  remove 6 imports que ficaram órfãos.

Comportamento preservado: 357 testes de caracterização combo seguem verdes (+4 novos),
integração sse-correctness 5/5, typecheck 0, file-size encolhe (sem _rebaseline).
Fases 2-N = sub-planos follow-up. Ver _tasks/quality/2026-06-19-DESIGN-godfiles-decomposition.md.
* feat(keys): add per-key USD usage quotas

Adds daily and weekly API key USD caps with reset-aware weekly windows, exposes quota controls in API key permissions and costs views, and returns Claude Code-safe 400 responses when caps are exceeded.

Validations:

- node --import tsx/esm --test tests/unit/api-key-usage-limits.test.ts tests/unit/internal-usage-command.test.ts

- npm run typecheck:core

- npm run check:file-size

- npm run check:migration-numbering

- npm run lint

- Docker image build/deploy smoke test on 100.64.0.1:20128

* fix(db): renumber api_key_usage_limits migration 100->101 (avoid cli_access_tokens collision)

Migration version 100 is taken by 100_cli_access_tokens.sql on release; the
migrationRunner version-collision guard would otherwise skip one. Renumber to 101.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* chore(quality): bump apiKeys.ts file-size baseline 1661->1662 (USD quota fields)

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

---------

Co-authored-by: Wital <wital@example.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
… lote drift) (#4336)

#4327 (per-key USD usage quotas), #4334 (cache-aware compression guard) and #4326
(phaseComboSetup) added new conditional branches landing after #4330 measured 1890;
the release fast-path doesn't run check:complexity. Measured 1896 on the merged tip.
…urrent merges (#4335)

* fix(providers): bailian-coding-plan static catalog matches registry (10 models)

The provider-model sweep (#4324) added qwen3.7-plus, qwen3-coder-plus,
qwen3-coder-next and glm-4.7 to the bailian-coding-plan registry entry but
left the static fallback mirror in staticModels.ts at the older six, so the
static↔registry parity test (bailian-coding-plan-provider.test.ts) went red on
release/v3.8.30 whenever TIA selected it. Restore the mirror to all ten models
in registry order and align the two legacy count/ID assertions.

* chore(test): collect tests/unit/combo/ in the unit runner glob

PR #4326 (ComboContext god-file split) added tests/unit/combo/combo-context.test.ts
but the unit-runner brace glob had no 'combo' entry, so its 4 tests were orphaned —
check:test-discovery flagged a NEW orphan, a second latent red on release/v3.8.30.
Add 'combo' to the glob across all lock-step collectors: the 7 package.json test
scripts, build-test-impact-map.mjs, check-test-discovery.mjs and the 4 ci.yml run
lines. Folded here (rather than a separate PR) because the two release reds are
interdependent for Fast-QG: a package.json change triggers the full suite, so a
combo-only PR would still trip the bailian red and vice-versa — fixing both in one
PR is the only way to land a genuinely green Fast-QG.

* chore(db): register apiKeyColumnFallbacks + apiKeyUsageLimitFields as db-internal

The api-key usage-limits feature (migration 101) split two helper modules out of
src/lib/db/apiKeys.ts — apiKeyColumnFallbacks.ts and apiKeyUsageLimitFields.ts — but
did not register them with check:db-rules, so both were flagged as new db/ modules
not re-exported by localDb.ts (Hard Rule #2), a third latent red on release/v3.8.30.
Both are imported only by db/apiKeys.ts (within src/lib/db/), so they are db-internal:
add them to INTENTIONALLY_INTERNAL with that classification (mirrors healthCheck /
stateReset) rather than re-exporting internal helpers onto the public localDb surface.
…s->Chat path (#4340)

When a Codex CLI client routes a Responses-API request to a non-Codex
backend (e.g. kr/claude-opus-4.7), each MCP server is declared as a
`namespace` tool: { type:"namespace", name, tools:[{name, description,
parameters}] }. The Responses->Chat translator had no namespace branch, so
the whole group collapsed into one empty-schema function named
`mcp__<server>__` and every MCP call failed with
`unsupported call: mcp__<server>__`, breaking all MCP workflows for that
combination. The translator now expands a namespace into one Chat function
per sub-tool (name + parameters preserved); an empty namespace yields no
tools. The native Codex passthrough path was already correct.

Reported-by: V13t4nh (decolua/9router#1534)

Co-authored-by: V13t4nh <201110185+V13t4nh@users.noreply.github.com>
…schema sanitization (#4339)

The Gemini/Antigravity schema sanitizer strips JSON-Schema constraint
keywords Gemini rejects (pattern, minLength, ...) at every nesting level,
but it also deleted any tool property literally NAMED one of those keywords.
glob/grep tools declare a property called `pattern`, so on ag/* backends
that argument (and its `required` entry) was silently dropped, breaking the
tools. Keyword stripping is now position-aware: constraint keywords are only
removed at the schema-node level, never against the user-defined names inside
a `properties` map. A genuine string-level `pattern` constraint is still
stripped.

Reported-by: youthanh (decolua/9router#1368)

Co-authored-by: youthanh <74104625+youthanh@users.noreply.github.com>
…baseline (#4338)

#4326 (ComboContext) introduziu uma violação de complexity: phaseComboSetup media
17 (>15) — a extração moveu os condicionais de pinning/ternários para uma função
própria que estourava o teto (irônico para uma decomposição). Extrai o pinning para
resolveContextCachePin; phaseComboSetup volta a <15.

- comboSetup.ts: helper resolveContextCachePin (server-side context-cache pinning),
  phaseComboSetup chama-o; remove 'log' órfão do destructure.
- complexity-baseline 1890->1895: -1 do fix + drift de features mergeadas após a
  reconciliação #4330 (#4327 per-key USD quota + outros). Medido no tip 70d89d2.

Comportamento preservado: 369/369 testes combo (incl. combo-context), typecheck 0.
…4271 arena metadata) (#4346)

* test(db-rules): align audited-module count to 28 (apiKey db helpers)

#4335 added apiKeyColumnFallbacks + apiKeyUsageLimitFields to INTENTIONALLY_INTERNAL
(both db-internal, consumed only by db/apiKeys.ts) but did not update the parity test
in check-db-rules-classification.test.ts, which still expected exactly 26 audited
modules — leaving it red on release/v3.8.30 (28 != 26). Add the two modules to the
expected list and bump the count to 28. Test-only alignment; no production change.

* test(web-session): align lmarena metadata to the split-cookie source (#4271)

#4271/#4331 updated webSessionCredentials.ts for lmarena's split auth cookie —
new placeholder text and storageKeys arena-auth-prod-v1.0/.1 — but did not update
web-session-credentials.test.ts, which still asserted the old placeholder + 3-key
storageKeys. Align the expected metadata to the merged source; test-only, no prod change.
…covery test (#4355)

* fix(pricing+test): price gpt-5.x-pro openai models + align opencode-go discovery test

Two pre-existing reds on the release's full unit suite (only surface under __RUN_ALL__):

1. catalog-updates-v3x: the provider sweep added gpt-5.5-pro / gpt-5.4-pro to the
   openai registry but no pricing rows, so they resolved to $0 and tripped the
   'Every OpenAI registry model resolves a non-zero pricing row' gate. Add both
   under the openai pricing block (mirroring their base family tier until OpenAI
   publishes a distinct pro rate).

2. provider-models-route: opencode-go discovery now (a) stamps owned_by on each
   discovered model (fallback = provider id) and (b) probes ${base}/v1/models then
   ${base}/models (T39 multi-endpoint). The test fixtures predated both — add
   owned_by to the expected model and bump the fail-path fetchCalls 1 -> 2. Test-only
   alignment to the intentional route behavior.

* chore(quality): rebaseline file-size for #4355 (pricing +11, route test +2)
…4358)

sanitizeHeaders() masks header *values* — it calls maskSecret("Bearer
<token>") with the "authorization:" key already stripped. The BEARER regex
was anchored to a literal "authorization:" prefix, so it never fired on those
values; tokens shorter than the sk-(16+)/opaque-(40+) thresholds then leaked
verbatim into the Traffic Inspector buffer (Hard Rule #12).

Found by the AgentBridge live capture: a 'Bearer sk-secret-TESTE' request
header showed up unmasked in /api/tools/traffic-inspector/requests. Real Google
OAuth tokens are long enough to be caught by LONG_TOKEN, but the Bearer pattern
must mask regardless of length.

Re-anchor BEARER to a standalone \bBearer\s+<token> (still ReDoS-safe:
bounded char class, no nested quantifiers). Masks both bare 'Bearer <token>'
header values and 'authorization: Bearer <token>' raw lines; existing cases
(sk-/ak-/pk- keys, short keys, no-secret strings) unchanged.

Tests: bare Bearer value, short opaque Bearer, realistic Google OAuth Bearer,
plus an authorization:-prefixed regression.
… just env 7d (#4354) (#4363)

cleanupExpiredLogs() ran on every startup and read retention only from the
CALL_LOG_RETENTION_DAYS / APP_LOG_RETENTION_DAYS env vars (default 7d when unset),
trimming usage_history before the dashboard-based runAutoCleanup() — which respects
the configured retention — ever ran. A dashboard 'Data Retention' of 90d was silently
overridden, so the Usage Analysis page only showed 7 days after a restart.

Retention precedence is now: explicit env var > dashboard DB setting > 7-day default,
applied per table (usage_history->usageHistory, call/proxy/detail->callLogs,
mcp_tool_audit->mcpAudit). An explicit env var still wins (operator override) and
non-DB deployments still fall back to it. Adds getCallLogRetentionDaysOverride /
getAppLogRetentionDaysOverride (null when env unset).

TDD: log-retention.test.ts gains a case where the env is unset and the dashboard
configures 90d — a 30-day usage_history row must survive (was deleted at the 7d
default). RED before, GREEN after; the existing env-explicit cases are unchanged.

Co-authored-by: akbardwi <akbardwi@users.noreply.github.com>
…TE_API_KEY (#4364)

Found by end-to-end testing of remote mode against a live VPS: after
`omniroute connect <remote>` saved the scoped admin token as the active context,
every remote *management* command (`tokens list/create/revoke`, etc.) failed with
"Invalid management token".

Root cause: the global `--api-key` option is bound to the env var
(.env("OMNIROUTE_API_KEY")), and users keep OMNIROUTE_API_KEY (their inference
key) in the shell. Commands that spread `optsWithGlobals()` into apiFetch
therefore carry `opts.apiKey` === the env value, which buildHeaders treated as an
explicit override that outranked the active context — so the local inference key
was sent to the remote instead of the scoped token, defeating remote mode.

Fix (single chokepoint in buildHeaders): an `opts.apiKey` that merely mirrors the
ambient OMNIROUTE_API_KEY is treated as ambient (a fallback), not as an explicit
override; only a DISTINCT key — a real `--api-key <x>` flag or a command-supplied
token like `connect --key` — counts as explicit and wins. Precedence becomes:
explicit distinct key -> active-context credential -> ambient env key. This keeps
`connect --key`, local/default usage, and explicit overrides working while making
`connect` actually route management commands to the remote.

Regression tests added to cli-remote-mode.test.ts (RED before, GREEN after):
context token wins over an opts.apiKey echoing the env; a distinct explicit key
still wins; ambient env remains the no-context fallback.
…post-lote drift) (#4370)

Concurrent-session PRs (#4355/#4364/#4363/#4358/#4332) added a new conditional after
#4338 ratcheted to 1895; release fast-path doesn't run check:complexity. Measured 1896.
Found by end-to-end testing of remote mode: `omniroute contexts list/current/use`
fell through to `serve` ("too many arguments for 'serve'"). `connect` even tells
users "Switch back to local with: omniroute contexts use default" — a dead command.

Root cause: `bin/cli/commands/contexts.mjs` implements `registerContexts` (with
list/add/use/current/show/remove/rename/export/import), and it had an isolated unit
test using a FAKE program — but `registry.mjs` never imported or called it, so the
command was never wired into the real CLI. Add the import + registration alongside
the other remote-mode commands (connect/tokens/configure).

Regression test: build the REAL program via createProgram() and assert the
top-level contexts/connect/tokens/configure commands exist and that `contexts`
exposes its list/use/current subcommands (RED before, GREEN after). The previous
isolated fake-program test could not catch the missing wiring.
…bSearch hardening + docs reconcile

- CHANGELOG: complete the [3.8.30] section to 1:1 coverage of all 70 commits
  since v3.8.29 (Features/Changed/Fixed/Tests/Maintenance/Security/Dependencies).
- security: cherry-pick the DDG-lite scraper sanitization hardening from #4356
  (closes 4 HIGH CodeQL alerts: js/double-escaping, js/incomplete-multi-character-
  sanitization, js/incomplete-url-substring-sanitization x2).
- docs: ANTHROPIC_AUTH_TOKEN (not the bare AUTH_TOKEN shorthand) in the Claude Code
  guide; allowlist GEMINI_API_KEY/GOOGLE_GEMINI_BASE_URL (Gemini CLI vars) in the
  fabricated-docs checker — fixes the docs-sync-strict gate.
@diegosouzapw
diegosouzapw merged commit db362b0 into main Jun 20, 2026
9 of 10 checks passed
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
Release v3.8.30 — see CHANGELOG.md [3.8.30] for the full release notes.
Poid-ZA pushed a commit to Poid-ZA/OmniRoute that referenced this pull request Aug 5, 2026
Release v3.8.30 — see CHANGELOG.md [3.8.30] for the full release notes.
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
Release v3.8.30 — see CHANGELOG.md [3.8.30] for the full release notes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants