Skip to content

fix(sse): harden DuckDuckGo lite scraper sanitization (CodeQL) [cherry-pick → v3.8.30] - #4357

Closed
diegosouzapw wants to merge 68 commits into
release/v3.8.31from
fix/codeql-freewebsearch-v3830
Closed

diegosouzapw wants to merge 68 commits into
release/v3.8.31from
fix/codeql-freewebsearch-v3830

Conversation

@diegosouzapw

Copy link
Copy Markdown
Collaborator

Resumo

Cherry-pick de aef66dd07 (PR #4356 → main) para release/v3.8.30. Aplicou-se limpo (arquivos idênticos entre os branches).

Corrige os 4 alertas CodeQL HIGH no scraper DuckDuckGo lite:

Validação

  • TDD: 3 testes novos (RED→GREEN) em free-web-search.test.ts.
  • 13/13 testes unitários passam; typecheck:core limpo.

Equivalente em main: #4356.

diegosouzapw and others added 30 commits June 19, 2026 07:14
* 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>
* fix(dashboard): improve api try it functionality and allow manual key entry

* test(api): cover generateExampleFromSchema for the Try It panel (#4296)

Export generateExampleFromSchema from the /api/openapi/spec route and add a
unit test covering type handling, property-name heuristics, $ref/oneOf/anyOf/
allOf resolution, the 'required + first 3 optional' object policy, and the
depth-3 recursion guard — the example bodies the dashboard Try It panel
pre-fills. Rule #18 regression guard for the new helper.

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

---------

Co-authored-by: ci <ci@local>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
…ml + env recipe) (#4302)

CLI #12 of the series. `omniroute setup-aider` writes Aider's ~/.aider.conf.yml
(openai-api-base = ROOT url, NO /v1 — LiteLLM appends /v1/chat/completions —
model: openai/<id>), merges to preserve existing config, and prints the env
recipe (OPENAI_API_BASE + OPENAI_API_KEY in the env, never the file) plus the
headless command (aider --message ... --yes). Remote-aware; model via --model or
interactive pick.

Researched against aider.chat: OpenAI-compatible via OPENAI_API_BASE (base, no
/v1) + --model openai/<id>. Aider's wire (/v1/chat/completions) already validated → "OK".

Tests: resolveAiderTarget (/v1 strip, key), buildAiderConfig (openai-api-base +
openai/<model> + preserve), buildAiderRecipe (env-ref key + headless). 4 unit tests; cli-i18n green.
* fix(codex): isolate Spark quota scope

* fix(codex): address Spark quota review feedback

* fix(ci): update Electron undici override

* fix(ci): update root undici overrides

* test(integration): sync stale expectations

* test(tproxy): tolerate available native addon

* test(tproxy): avoid environment-specific skips

* test(tproxy): keep assertion count stable

* fix(ci): stabilize quality and tproxy checks

* chore(ci): rebaseline auth file size

* fix(ci): extend node compatibility budget

* chore(quality): reconcile complexity + file-size baselines after release/v3.8.30 merge (#4293)

Measured on the actual merged tree (not the PR's main-based estimate):
complexity 1885->1887 (+2); file-size auth.ts 2219->2279, chatCore.ts 5116->5125,
accountFallback.ts 1727->1731, + the 4 Codex test files. Drift test-file conflicts
(search-providers-catalog, tproxy-transparent-socket, integration-wiring) resolved
to the already-merged release versions (#4276).

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

---------

Co-authored-by: ci <ci@local>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
diegosouzapw and others added 24 commits June 19, 2026 21:34
…s in pre-commit (#4318)

* fix(quality): complexity gate varre bin/ + electron (6A.11, fake-green fix)

ESLINT_ARGS passava só 'src open-sse', mas o config eslint.complexity.config.mjs
e o complexity-baseline.json já documentavam o escopo src+open-sse+electron+bin.
A edição do scan nunca tinha sido aplicada: o gate alegava cobrir bin/electron e
nunca os varria (fake-green — uma god-function nova em bin/ passava verde).

- exporta ESLINT_ARGS + adiciona 'electron' e 'bin' (casa o config)
- teste de build trava o escopo do scan
- baseline 1887->1888: electron+bin medem 0 (widening 0-custo); o +1 é drift
  pré-existente de src/open-sse da base a23d0d6 (#4308 et al.), não do widening

* fix(quality): check:tracked-artifacts no pre-commit (6A.12)

O gate estava no CI + pre-push, mas faltava no pre-commit — e o incidente do
symlink (artefato de build trackeado) acontece no 'git add'. Fecha o buraco no
ponto mais cedo.
…ss gate (#4311)

* fix(deps): bump undici to 7.28.0 and dompurify to 3.4.11 (security) (#4304)

* fix(deps): bump undici to 7.28.0 and dompurify to 3.4.11 (security)

Resolves Dependabot alerts on package-lock.json and electron/package-lock.json:

- undici 7.x -> 7.28.0: TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent (GHSA-vmh5-mc38-953g, HIGH) + cross-user information disclosure via shared-cache whitespace bypass (GHSA-pr7r-676h-xcf6, MEDIUM). Fixed in the root (jsdom transitive) and electron lockfiles.

- dompurify -> 3.4.11: permanent ALLOWED_ATTR pollution via setConfig() bypassing the hook clone-guard (GHSA-cmwh-pvxp-8882, MEDIUM). Bumped the overrides floor from ^3.4.9 to ^3.4.11.

Also bumps node-gyp's transitive undici 6.26.0 -> 6.27.0, clearing the <6.27.0 advisories (WebSocket DoS, Set-Cookie handling) surfaced by npm audit. Lockfile/override-only change; no production source touched.

* ci(quality): exclude dependency manifests/lockfiles from PR test-policy

The PR test-policy gate classifies any changed file under src/, open-sse/, electron/, or bin/ as production code requiring tests. This false-flags lockfile/manifest-only changes (e.g. this Dependabot security bump touching electron/package-lock.json), since a lockfile cannot have a meaningful unit test.

Adds package.json / package-lock.json to EXCLUDED_PATTERNS, consistent with the existing .md/.yaml/.yml exclusions. Real production-code changes remain flagged.

* fix(kiro): emit early role-only start chunk to release stream-readiness gate

CodeWhisperer sends framing/metadata frames before the first content token; on large/agentic contexts that gap can be many seconds. ensureStreamReadiness holds the whole response from the client until it sees a useful SSE frame, so without an early frame the client sees a frozen connection up to STREAM_READINESS_TIMEOUT_MS (180s in VibeProxy) then a burst. Emit a role-only chat.completion.chunk on the first parsed AWS EventStream frame (a non-ping structured payload that satisfies hasStreamReadinessSignal) to hand off immediately, mirroring Claude message_start / OpenAI response.created.

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>
…Undici pool socket leak (#4309)

* fix(sse): release reader and cancel stream on abort/error to prevent Undici pool socket leak

* test(sse): regression guard for Undici socket release on abort (#4309)

Proves collectStreamToResponse releases the reader and cancels the response
body on the abort/error path (fails without the fix, passes with it) — Rule #18.

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

---------

Co-authored-by: Dmitry Kuznetsov <dmitry@kuznetsov.me>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
The CLI-tools MITM card's "How it works" section showed a single hardcoded example
domain (only antigravity/kiro). It now lists every 127.0.0.1 <host> entry for the
selected tool, so users on locked-down machines — where the automatic, sudo-gated
hosts-file edit isn't available — can add them manually. Hosts come from a client-safe
projection of the canonical src/mitm/targets registry (MITM_TOOL_HOSTS), kept in
lock-step with the registry by a sync test (no duplicated source of truth).

Co-authored-by: mrcyclo <13806369+mrcyclo@users.noreply.github.com>
…il (#4322)

* feat(quality): config stryker disableBail para medir redundância (R1)

Estende stryker.conf.json com disableBail:true (killedBy lista TODOS os killers,
não só o primeiro) + incremental:false (medição limpa). One-off, não toca o
nightly — só roda via mutation-redundancy.yml.

* feat(quality): workflow on-demand de mutação disableBail (R1)

mutation-redundancy.yml (workflow_dispatch): roda os 6 leaf-batches combo+chatCore
do nightly com o override disableBail e sobe os reports. Batches granulares (d/e/f/
g/h/i) em vez de 2 mega-batches: disableBail é mais caro e Stryker só grava o report
se COMPLETAR, então cada batch cabe nos 300min.
zizmor 139->145: +3 drift de base + 3 do workflow (@vn, convenção do repo). Ver nota.

* feat(quality): mutation-radiography --candidates (lista de prune R1)

Reusa aggregateRadiography (DRY) em vez de um script novo: redundancyCandidates()
retorna 🔴 empty ∪ 🟠 redundant (zero kills únicos) = candidatos a prune. Sob
disableBail o killedBy é completo, então 🟠 redundant fica ACURADO. CLI --candidates
emite a lista com o aviso do gate humano (excluir segurança/contrato/repro).
…ly) (#4321)

* feat(quality): instala @axe-core/playwright + allowlist (T13)

Pré-requisito do gate de a11y real. O spec tests/e2e/a11y.spec.ts já tem o
mecanismo REQUIRE_AXE=1 que falha se o pacote estiver ausente quando exigido —
faltava só o pacote + o job nightly + o baseline real.

- @axe-core/playwright@^4.11.3 em devDependencies (via --package-lock-only,
  não toca o node_modules compartilhado das worktrees)
- adicionado à dependency-allowlist (check:deps OK, 127 deps)
- vuln-ratchet OK (1 moderate pré-existente, baseline 10 — axe não regride)

* feat(quality): job a11y nightly (REQUIRE_AXE=1) + gate per-PR (T13)

- novo job 'a11y' em nightly-resilience.yml: o webServer do Playwright builda o
  Next (build-next-isolated.mjs) e sobe o standalone sozinho (sem artefato pré-
  buildado como o test-e2e do ci.yml), REQUIRE_AXE=1 roda a análise axe real.
- gate de nightly no spec: a11y.spec.ts é casado pelo glob 'tests/e2e/*.spec.ts'
  do job per-PR (9 shards); sem gate, instalar @axe-core/playwright ligaria axe
  em TODO PR (e falharia em baseline 0). Os 4 testes de página agora skipam a
  menos que REQUIRE_AXE=1 — per-PR fica rápido/verde, nightly roda real.
- zizmor 139->145: +3 drift pré-existente da base a23d0d6 (release fast-path
  não ratcheta workflows) + 3 do job novo (checkout/setup-node/cache @vn, mesma
  convenção deliberada e INTOCADA de todos os workflows). Ver nota no baseline.

* feat(quality): congela baseline real de violações a11y (T13)

Medido no primeiro run nightly (27852779527, REQUIRE_AXE=1): /login=1,
/dashboard=4, /dashboard/providers=3, /dashboard/settings=5. O webServer
buildou+subiu o app e o axe rodou nas 4 páginas; a falha do run foi só o
assert vs baseline 0 (esperado), não infra.
…alogs, dead-provider cleanup (#4324)

* feat(providers): refresh core official model catalogs (sweep lote 1)

Adiciona modelos GA atuais (verificados online) aos provedores oficiais core:
- openai: gpt-5.5-pro, gpt-5.4-pro
- anthropic: claude-opus-4.8 + claude-fable-5 (sampling fixo 4.7+, espelha 4.7), claude-opus-4.5
- groq: qwen/qwen3.6-27b, openai/gpt-oss-safeguard-20b
- xai: grok-build-0.1

Fase 4 do provider-model-sweep. provider-consistency/file-size/typecheck:core verdes.

* feat(providers): wire live /models discovery for 7 openai-style providers (sweep lote 2)

venice, deepinfra, wandb, pollinations, nscale, inference-net and moonshot each
expose a real live `<baseUrl>/models` catalog (the sweep probed each upstream),
but were classified fixed-official, so import served their small hardcoded seed
and re-staled the catalog. Add them to NAMED_OPENAI_STYLE_PROVIDERS so import does
a live `<baseUrl>/models` fetch, keeping the registry seed only as the offline
fallback — same fix shape as #4249 (vercel-ai-gateway) / #4202 (zenmux) / #3976
(llm7/byteplus). siliconflow was already classified.

TDD regression in tests/unit/provider-sweep-live-discovery.test.ts pins each
derived /models URL + the local-seed fallback path. file-size baseline bumped
2538->2548 (+10 = 7 Set entries + 3-line comment; not extractable).

* feat(providers): wire live /models discovery for 12 aggregator marketplaces (sweep lote 3)

crof, featherless-ai, ovhcloud, sambanova, orcarouter, uncloseai, opencode-go,
baseten, hyperbolic, nebius, scaleway and together are GPU-cloud / aggregator
marketplaces hosting large, volatile OSS catalogs. The sweep probed each and
confirmed a live `<baseUrl>/v1/models` endpoint (200 public or 401/403 = exists +
keyed), yet they were classified fixed-official and served a small hardcoded seed.
Add them to NAMED_OPENAI_STYLE_PROVIDERS so import does a live `<baseUrl>/models`
fetch (graceful fallback to the registry seed on any upstream error), keeping the
catalog fresh instead of re-staling a hardcoded list.

Extends tests/unit/provider-sweep-live-discovery.test.ts to 20 cases pinning each
derived /models URL. file-size baseline bumped 2548->2564 (+16; not extractable).

* feat(providers): add verified new models to nvidia, meta-llama, morph (sweep lote 4)

Curated first-party / specialist menus (kept hardcoded — their per-model flags
like toolCalling/supportsReasoning can't be inferred from a live catalog):

- nvidia: + stepfun-ai/step-3.7-flash, deepseek-ai/deepseek-v4-flash
  (supportsReasoning), moonshotai/kimi-k2.6 — all confirmed present in the live
  NIM /v1/models catalog. minimaxai/minimax-m3 deliberately left out per #3329
  (now listed, but its inference still needs confirmation before re-adding).
- meta-llama: + Llama-3.3-8B-Instruct.
- morph: + morph-qwen35-397b, morph-minimax27-230b, morph-qwen36-27b,
  morph-dsv4flash (Morph-hosted fast models, with context lengths).

Skipped this batch after review: upstage solar-pro2 (older than the solar-pro3
already in the registry); longcat LongCat-2.0-Preview (deliberately commented out).

* feat(providers): refresh Chinese first-party model catalogs, online-verified (sweep lote 5)

Each registry held a single stale id; refreshed against official docs after
per-id online verification (subagent research, cross-checked against first-party
sources). Rejected/omitted entries are documented inline.

- baidu: + 15 ERNIE ids (5.0/5.1 are the current flagships, confirmed live on Qianfan).
- doubao: + 8 Seed-2.0/1.x dated Ark ids (Seed 2.0 GA 2026-02-14, confirmed real).
- sensenova: + 8 SenseChat/SenseNova ids (V6.5-Pro flagship; 6.7-flash-lite lowercase).
- tencent: + hunyuan-turbos-latest/t1-latest/vision/functioncall/lite. Dropped legacy
  standard/-256K/code/role + pinned turbos-20250226. NOTE: legacy Hunyuan platform
  EOLs turbos/t1 on 2026-06-22 (migrating to TokenHub/hy3-preview) — revisit.
- baichuan: + Baichuan4-Turbo/Air, Baichuan3-Turbo/-128k (official pricing page).
- stepfun: + step-3.7-flash (flagship), step-3.5-flash(-2603), step-1o-turbo-vision.
- iflytek: + 4.0Ultra, max-32k, generalv3, pro-128k, lite (exact HTTP domains).
- sparkdesk: + 4.0Ultra, generalv3, pro-128k. Rejected spark-x (separate /v2|/x2 endpoint).
- volcengine: + doubao-seed-2-0-pro-260215, kimi-k2-5-260127 (Ark-hosted).

* feat(providers): add verified models to kie, nlpcloud, publicai (sweep lote 6)

- kie: + claude-opus-4-8, gemini-3-5-flash (current flagships the proxy surfaces;
  gemini-3-pro skipped — registry already carries the newer gemini-3-1-pro).
- nlpcloud: + chatdolphin, dolphin (branded models), finetuned-llama-3-70b,
  llama-3-1-405b. Host confirmed reachable.
- publicai: + Apertus-8B, Gemma-SEA-LION-v4-27B, Olmo-3-7B, EuroLLM-22B (open models).

Skipped after review: minimax M2/M2.1 (older than the M2.5 floor the registry
curates); yi (api.lingyiwanwu.com degraded + 01.AI exited foundation models);
llamagate (host llamagate.ai unreachable, code 000) — both flagged for Track C.

* feat(providers): finish Track B tail — cloudflare-ai, bailian, suno, +5 (sweep lote 7)

- cloudflare-ai: + 7 Workers AI catalog ids (llama-3.3-70b-fp8-fast, qwen2.5-coder-32b,
  qwq-32b, llama-3.2-3b, glm-4.7-flash, kimi-k2.6, gemma-4-26b).
- bailian-coding-plan: + qwen3.7-plus, qwen3-coder-plus, qwen3-coder-next, glm-4.7.
- suno: + chirp-fenix (V5.5), chirp-crow (V5).
- monsterapi: + Meta-Llama-3.1-8B, Llama-3.3-70B.
- huggingchat: + Qwen3-235B-A22B, Mistral-Small-3.1-24B.
- vertex-partner: + claude-opus-4-8, claude-opus-4-6.
- puter: + google/gemini-3.5-flash.
- codestral: + codestral-2508.

Skipped after verification: windsurf + devin-cli — docs.devin.ai exposes DASHED ids
(claude-opus-4-8-low, MODEL_PRIVATE_4 for "Grok Code Fast 1", minimax-m2-5) while the
registry uses DOTTED (claude-opus-4.7-max); id-form ambiguity needs owner confirmation
before adding 13+ entries. leonardo/ideogram (image UUID-vs-friendly convention),
glmt (shared GLM_SHARED_MODELS, redundant with the live `glm` provider).

* fix(providers): drop retired models, add codestral-2405 forward (sweep lote 8, Track C C1)

Confirmed removals that interacted with the sweep's adds:
- codestral: drop codestral-2405 (retired 2025-06-16, Mistral official docs) from the
  menu + add a codestral-2405 -> codestral-2508 deprecation alias so old configs forward.
- monsterapi: drop llama-3-8b-fuse (no longer evidenced in the catalog).
- volcengine: drop kimi-k2-thinking-251104 (retired on Ark; superseded by kimi-k2-5-260127).

* chore(providers): mark 6 dead providers deprecated (sweep lote 9, Track C C2)

The sweep verified these providers are no longer reachable/operational, so flag
them with the existing deprecation mechanism (deprecated:true + a deprecation risk
notice) instead of silently offering non-working options. Conservative — plumbing
(executors/icons/free-catalogs) is left intact; only the UI-facing metadata changes.

- kluster, glhf, predibase, inclusionai, galadriel: api host DNS no longer resolves.
- phind: API shut down 2026-01 (www.phind.com/api/chat no longer serves).

Not touched: gemini-cli (Google OAuth infra still live), qwen (already deprecated),
chipotle (easter-egg, out of scope). file-size baseline bumped 3169->3198.

* fix(providers): replace retired LongCat-Flash line with LongCat-2.0-Preview (sweep lote 10)

The LongCat-Flash-* models (Lite/Chat/Thinking/Omni-2603) were officially retired
2026-05-29; the current longcat.chat/platform docs expose only LongCat-2.0-Preview
(confirmed via WebFetch of the live API docs). Swap the stale 4-model seed for the
single current model so the provider stops offering dead ids.

* chore(quality): reconcile antigravity.ts file-size baseline 1664->1680

#4309 (Undici socket-leak fix) grew antigravity.ts by +26 lines but its file-size
baseline was not bumped at merge time; reconcile it here on the combined tree so the
release file-size gate stays green (Rule #9, release-volatile reconciliation).
, #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.
#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.
Fixes 4 open CodeQL code-scanning alerts (all HIGH) in the free DuckDuckGo lite search path:

- js/double-escaping (#657, freeWebSearch.ts): decodeEntities decoded &amp; BEFORE the other entities, so an already-escaped value like '&amp;lt;' was double-unescaped into a real '<' instead of staying the literal '&lt;'. Decode &amp; last.

- js/incomplete-multi-character-sanitization (#656, freeWebSearch.ts): stripTags removed tags BEFORE decoding entities, so '&lt;script&gt;' decoded into a live '<script>' that reached the caller. Now decode first, strip tags to a fixpoint, and drop any leftover unclosed '<...'.

- js/incomplete-url-substring-sanitization (#658/#659, search-handler-duckduckgo.test.ts): asserted/routed on capturedUrl.includes('lite.duckduckgo.com'); switched to exact parsed-hostname comparison (new URL(...).hostname === ...).

TDD: added 3 failing-then-passing tests in free-web-search.test.ts (double-unescape, live <script>, unclosed tag). 13/13 unit tests pass; typecheck:core clean.
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@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.

@diegosouzapw
diegosouzapw changed the base branch from release/v3.8.30 to release/v3.8.31 June 20, 2026 10:40
@diegosouzapw

Copy link
Copy Markdown
Collaborator Author

Shipped in v3.8.30 — the DDG-lite scraper sanitization hardening (commit 71c21e5d8 here) was cherry-picked into the release via #4356/#4267 (tag v3.8.30) and closes all four HIGH CodeQL alerts. This branch is also base-stale (forked from an earlier release/v3.8.30 tip), so its diff would now revert ~100 cycle files. Closing as redundant — the fix is live on main.

@diegosouzapw
diegosouzapw deleted the fix/codeql-freewebsearch-v3830 branch June 21, 2026 13:20
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.

9 participants