Commit Graph

78 Commits

Author SHA1 Message Date
Diego Rodrigues de Sa e Souza
20ea78c943 feat(sse): restate agentrouter quota 403/400 as retryable 429 with provider-scoped error rules (#10335)
agentrouter.org signals temporary quota exhaustion with HTTP 403/400 and a Chinese body (用户额度不足) instead of 429, so clients like Claude Code treat it as permanent and abort, and the fallback engine classified it as a generic apikey AUTH_ERROR.

New registry open-sse/config/upstreamStatusRestatement.ts restates those statuses to 429 with a synthetic Retry-After at a single hook in chatCore's providerFailure block (after parseUpstreamError), so classification, combo aggregation and the client response all see a retryable error. 无权访问模型 (permanently no model access) is veto-listed and never restated.

agentrouter classification rules are registered in providerErrorRules.ts and reach the real checkFallbackError path through resolveRuleMatchBody() with an exclusive FULL_TEXT_RULE_PROVIDERS allowlist — every other provider keeps its previous behavior byte-for-byte.

Known limitations tracked in #10334: the rules' scope field is informational (persistence applies per-model lockout for agentrouter), the 403-only model-access rule has no production path yet, and errors embedded in 200 SSE streams are not restated.

Refs #10334
2026-08-14 12:42:58 -03:00
Diego Rodrigues de Sa e Souza
90458a613c fix(sse): stop the executor-contract guard from hot-looping the router (#10373)
The `instanceof Response` guard from #10256 broke two ways:

1. `instanceof` is nominal against `globalThis.Response`, but proxyFetch dispatches
   through the npm undici package's fetch, whose Response is a different class — so
   valid upstream responses were rejected as contract violations. Replaced with
   `isResponseLike()` (instanceof fast path + structural brand/member probe); genuinely
   malformed shapes still throw.
2. The thrown error had no `.status`, so it fell through to chatCore's BAD_GATEWAY
   default — an internal defect was treated as a flaky provider, cooling the connection
   down and retrying forever. It now carries status 500 + `executor_contract_violation`,
   registered as request-scoped and terminal (no cooldown, no breaker, no retry).

batch_api.test.ts went from exit 124 (infinite hang, pinning Unit shard 4/4 in every
open PR) to exit 0, 22/22 passing.

Closes #10360
2026-08-14 10:03:10 -03:00
Diego Rodrigues de Sa e Souza
97aac6ac6c fix(ci): clear base-reds on release/v3.8.50 (round 4) (#10260)
* fix(ci): clear base-reds on release/v3.8.50 (round 4)

Drains the HARD failures reported by Release-Green run 31693210948 on issue #9985
(ESLint errors: 2) plus the merge-integrity red every open PR is inheriting.

- ESLint error 1: @omniroute/opencode-plugin/src/index.ts had a stray extra
  '});' (introduced by #9316) that broke parsing with 'unexpected file in NFT list'
  on the build path.
- ESLint error 2: cli-env-inline-comment-10100.test.ts used new Function to extract
  parseEnvValue from the bin entrypoint (no-new-func, Hard Rule #3). Extracted the
  helper to bin/cli/utils/parseEnvValue.mjs and import it from both the entrypoint
  and the test (same behavior, no eval).
- open-sse-typecheck (Fast Quality Gates): open-sse/utils/stream.ts imported
  sseCommentsEnabled twice (#9378) causing TS2300 Duplicate identifier; removed the
  duplicate import.
- Merge integrity (changelog + generated skills): skills/omni-settings/SKILL.md was
  edited manually by #10169 without updating the generator source, so
  check:agent-skills-sync failed on every PR (Generated: 1). Moved the curated
  thinking-budget content into a <!-- skill:custom-start --> block (the documented
  preservation mechanism), which the generator now keeps in sync.

Refs #9985

* fix(tests): align wave1-a poolside test with #10216 probed catalog

#10216 published Poolside's two authenticated-probe models
(poolside/laguna-xs-2.1, poolside/laguna-s-2.1) as static seeds, but the
wave1-a free-tier test still asserted 'no invented static model ids'
(entry.models === []), failing every open PR. Separate poolside from the
empty-models assertion and pin its probed catalog explicitly so a future
catalog change is a deliberate update, not a silent drift.

* fix(pack): register parseEnvValue.mjs in PACK_ARTIFACT_REQUIRED_PATHS

The extract of parseEnvValue to bin/cli/utils/parseEnvValue.mjs added a new
direct import to bin/omniroute.mjs, which pack-artifact-entrypoint-closures
enforces against PACK_ARTIFACT_REQUIRED_PATHS. Register the module so a future
tarball omission fails loudly.

* fix(combo): restore default same-model retry semantics after #10217

#10217 wired config.failoverBeforeRetry into the same-model retry guard in
both the priority/auto and round-robin loops, but DEFAULT_COMBO_CONFIG
defaulted the flag to true — flipping same-model retry off for every combo
that never touched the setting, not just the opt-in case. Round-4 bisect
(06f41cda63 vs d2fd88dfbc) reproduced this against
tests/unit/combo-499-abort.test.ts, tests/unit/combo-quota-exhaustion-only-fallback.test.ts
and tests/unit/combo-stream-readiness-fallback.test.ts. Flip the default to
false so the historical retry-before-failover behavior returns for combos
that never set the flag, while explicit opt-in (the two new tests #10217
added to combo-routing-engine.test.ts) still works.

* fix(quality): register visionBridge-responses-9597 in stryker tap.testFiles

check-mutation-test-coverage.mjs flagged tests/unit/guardrails/visionBridge-responses-9597.test.ts
as covering open-sse/services/combo/comboStructure.ts without being listed
in stryker.conf.json's tap.testFiles array. Add it so mutation coverage
attribution stays accurate.

* test(pack): expect parseEnvValue.mjs in the missing-artifact-paths fixture

The prior commit on this branch registered bin/cli/utils/parseEnvValue.mjs
in PACK_ARTIFACT_REQUIRED_PATHS but the "findMissingArtifactPaths flags
missing root runtime files in the tarball" test still hardcoded the old
expected list, so it never accounted for the new required path being
absent from the simulated tarball. Add it in its alphabetical slot.

* chore(lint): prune stale no-explicit-any suppression for call-log-file-rotation

--prune-suppressions found tests/unit/call-log-file-rotation.test.ts no
longer produces the 5 suppressed @typescript-eslint/no-explicit-any
warnings recorded in config/quality/eslint-suppressions.json. Remove the
dead entry so a regression would be caught again. Full-tree run with
--max-warnings 0 is clean: 0 errors, 0 warnings.

* fix(combo): decouple failoverBeforeRetry same-model guard from the skipUpstreamRetry default

Audit found that DEFAULT_COMBO_CONFIG.failoverBeforeRetry has defaulted to
true since before #10217 (predates #2417), and that value also feeds the
independent skipUpstreamRetry mechanism (src/sse/handlers/chat.ts:859,1126).
The previous commit on this branch flipped that default to false to fix the
#10217 same-model retry guard, which silently disabled skipUpstreamRetry's
own default-on behavior for every combo without an opt-in — a regression in
the opposite direction (executor-level retries before the loop's own
failover, changing latency/failure behavior).

Revert the default back to true and decouple the two mechanisms instead:
resolveComboConfig/resolveComboSetupConfig now also compute
failoverBeforeRetryExplicit, true only when a cascade layer (combo/provider/
global) literally sets failoverBeforeRetry to true — not merely inherited
from the default. The #10217 same-model retry guards in combo.ts (priority/
auto and round-robin loops) now read failoverBeforeRetryExplicit instead of
config.failoverBeforeRetry, restoring opt-in-only behavior for that guard
while the skipUpstreamRetry pass-through (config.failoverBeforeRetry at
combo.ts:1297,2865) is untouched and keeps its historical default-on.

* fix(combo,i18n): align getDefaultComboConfig with 10217 explicit flag; pt denoRelay entities

Two round-4 follow-ups exposed by the combinated base-red PR run:

1. comboConfig.ts: #10217 round-4 fix (104afeda4e) added
   failoverBeforeRetryExplicit to resolveComboConfig/resolveComboSetupConfig
   but getDefaultComboConfig() returned only DEFAULT_COMBO_CONFIG, so the
   combo-config.test.ts deepEqual (resolveComboConfig(null) ===
   getDefaultComboConfig()) failed on the extra field. Mirror the opt-in flag
   as false in the default.

2. pt.json: denoRelayOrgDomainHint still carried raw <app-name>/<org-slug>
   (the UNCLOSED_TAG RSC regression) — encode as &lt;...&gt; like the other
   42 locales, greening i18n-deno-relay-unclosed-tag.test.ts.

* chore(lint): disable @next/next/no-location-assign-relative-destination pending per-case review (#10292)

The eslint-config-next bump in #10043 shipped this new rule, flagging 6
pre-existing window.location.href navigations — several are deliberate
full-page reloads (login/logout state reset). Off with tracking issue
rather than a blanket router.push rewrite.

* fix(i18n): fill 439 missing UI keys (thinkingMode ×39 locales + pt catch-up) to restore 100% coverage

The #10169 Thinking Budget keys existed only in en/pt-BR/vi and the pt (PT-PT)
catalog from #10250 lagged 88 recent keys, dropping i18nUiCoverage to 99.3%
vs the frozen 100% ratchet baseline. Translated via the i18n:sync-ui marker
pipeline; glossary + ICU placeholder post-pass clean.

* fix(i18n): zh-TW glossary — replace retired 默認 with canonical 預設 in new thinkingMode keys

* chore(quality): rebase dead-code baseline 248 -> 409 for knip 6.32 bump (#10043)

dependabot #10043 upgraded knip 6.27 -> 6.32, which detects 162 MORE
genuinely-unused exports (331 vs 169) that 6.27 missed; DEAD_FILES
unchanged (78). Reproduced identically on the clean release/v3.8.50 tip
266e39d3 with a fresh 6.32 node_modules, so every PR is born red until
the tool change is absorbed. Owner authorized rebaseline (2026-08-13 via
PR #10260). Structural cleanup of the newly-surfaced dead exports remains
separate debt.

---------

Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
Co-authored-by: backryun <bakryun0718@proton.me>
2026-08-13 23:02:48 -03:00
Diego Rodrigues de Sa e Souza
d259d9fcba fix(ci): clear base-reds on release/v3.8.50 (round 3) (#10213)
* fix(ci): clear base-reds on release/v3.8.50 (round 3)

- CHANGELOG.md: restore the top [Unreleased] section dropped by the #10189
  reconcile (docs-sync gate: first section must be Unreleased)
- env-doc-sync: document CONDUCTOR_ORCHESTRATOR_TOKEN + CONDUCTOR_SPOKESPERSON_URL
  in .env.example/ENVIRONMENT.md; allowlist the CI-only GITHUB_STEP_SUMMARY and
  TS7_BASE_REF (ts7 ratchet signals); drop a stray merge artifact line
- providers: restore the audited chatanywhere metadata entry that base-reds
  round 2 dropped together with its duplicate — the provider was half-wired
  (registry+endpoint without APIKEY metadata), which is what the wave3 test
  catches; re-pin providers-constants-split at the measured 228
- docs counts: 338 -> 339 (today's +2 void-ai/helixmind, -1 Puter) via
  gen:provider-reference + README/AGENTS/llm.txt/package.json/diagrams/i18n mirrors
- file-size ratchet: annotated rebaseline for the two pre-existing drifts
  (ModelSelectModal 1138, gateways 1250) following the 2026-08-11 precedent

Refs #9985

* fix(ci): base-reds round 3b — stale sibling tests + mode-pack weight contract

- check-docs-counts-sync.test.ts: drop the imports/subtests of the four helpers
  #10196 removed from the gate script (readMcpFactsFromSource, listLocalizedDocs,
  makeRequiredCountsValidator, checkFreeTierInventory) — the new-API tests that
  #10196 added stay; the file now loads again under the node runner
- quota-connection-recovery.test.ts: convert from vitest APIs to node:test —
  the file lives in tests/unit/*.test.ts (node-runner glob) and the vitest
  runtime crashes when imported outside vitest, killing the whole shard entry
- modePacks.ts: re-normalize all six mode packs to sum 1.0 — #8940 added
  sessionAvailability: 0.05 to every pack without rebalancing (1.05 total);
  ratios preserved exactly (÷1.05), so post-normalizeScoringWeights behavior
  is unchanged; restores the declared sum-to-1.0 contract the 4235 test pins

Refs #9985

* fix(ci): base-reds round 3c — vitest siblings, weights default, secrets FP, mutation tap

- DistributeProxiesButton.test.tsx: wrap renders in NextIntlClientProvider —
  #9245 localized the component (useTranslations) and left the test without
  the intl context, failing all 14 cases
- scoring.ts: re-normalize DEFAULT_WEIGHTS to sum 1.0 (same #8940 class as the
  mode packs — sessionAvailability added without rebalancing; ratios preserved)
- .gitleaks.toml: generalize the kimi sponsor-banner localStorage-key allowlist
  to -v\d+ — #10200 bumped v1→v2 and the stale regex regressed the secrets
  ratchet with a false positive
- stryker.conf.json: register 6 covering unit tests in tap.testFiles (4 modules)
  so their mutant kills count — unblocks check:mutation-test-coverage --strict

Refs #9985

* fix(ci): base-reds round 3d — inspector factor gap, stale registry/gap tests, i18n key sync

- comboScoringInspector: add cacheAffinity/sessionAvailability/connectionDensity
  to FACTOR_KEYS + the factor-key type — calculateScore() weighs them but the
  breakdown omitted them, so the explained contributions never summed to the
  reported score (inspector bug, red on the pure tip)
- combo-scoring-inspector.test: make the explicit-weights override sum-neutral
  (±0.05 shift) so it stays valid for any DEFAULT_WEIGHTS values — the hardcoded
  override only summed to 1.0 against the pre-#8940 defaults, which is also why
  explicit weights silently fell back to 'default' on the tip
- unorouter-registry.test: align to the canonical .com host (api.unorouter.ai
  301-redirects there, verified live) and to wave4's live model discovery
  (passthrough, no static seed) — the .ai/auto-model expectations were stale
- check-migration-numbering.test: 147 left KNOWN_GAPS when
  147_api_keys_model_access_mode.sql landed — assert absent (same as 143)
- i18n: sync-ui pass — 35,914 missing UI keys stamped as __MISSING__ placeholders
  across 42 locales (mechanical; greens the pt-BR key-presence integrity test;
  coverage pct unchanged by design — translation is a separate workstream)

Refs #9985

* fix(ci): base-reds round 3e — 2 real defects + 14 stale sibling tests (waves A-E)

Real defects fixed:
- src/lib/db/apiKeys.ts: #9313's empty-allowlist early return bypassed the group
  permission check, silently disabling group deny rules (#8817) for every key
  without a per-key allowlist; fall-through restored, restricted+[] deny-all kept
- open-sse/utils/proxyFetch.ts: #10032 re-appended the raw transport error to the
  propagated message, reintroducing the proxy user:password leak #9837 closed;
  new redactProxyDetailsInMessage() keeps the reason, redacts URL/credentials
- .github/workflows/quality.yml: #10134 added the TS7 ratchet as a separate
  blocking step AFTER the aggregated gates — the exact #8542 masking mechanism;
  folded into the non-fail-fast loop (still blocking, still PR-only) ⚠️ CI edit,
  gate-strengthening — explicit owner sign-off requested on the PR
- src/i18n/messages/ko.json: 3 machine-mistranslation regressions caught by the
  #8244 glossary checker (장애인→비활성화됨, 양말5://→socks5://, 비클로드→Claude가 아닌)

Stale sibling tests aligned to deliberately-moved contracts (each cites its mover):
request-log-detail-layout + -stream (#9245 intl provider), repro-8542 pin update,
quality-rail-gate-membership (#10134 shape), agentSkills-routes 45→46 (#9058),
cloudflare-ai-catalog-8717 (#8804 supersedes #8808), executor-xai (#9994),
vision-bridge-claude-wire (#9463 minimax→openai), sse-auth forced-pin (#8893),
tls-proxy-context (strengthened leak guards), rate-limit-local-error-classification
(#9164/#9342), minimax-thinking-signature (#9463), codebuddy-cn (#9723 +1 test),
github-copilot-custom-model (#9050), providers-g4f-batch3 (#9584),
synced-capability-warmup (#9199, stricter), sidebar-tools-group (#8221),
oauth-modal-grok-cli-paste (#9245); agentSkills/catalog.ts comment 45→46;
file-size rebaseline for proxyFetch (+19, annotated)

Refs #9985

* fix(ci): base-reds round 3f — waves F-J: 9 more real defects + stale sibling sweep

Real production defects fixed (all red on the pure tip, each with its origin):
- routeGuard.ts: #8949 accidentally DELETED the /api/providers/[id]/login
  local-only pattern — the route spawns a browser, so the loopback gate for a
  process-spawning route was gone (Hard Rules #15/#17); restored (314 guard
  tests green)
- agentSkills generator: #9058's category dispatch gave the config category an
  empty body, wiping skills/config-codex-cli/SKILL.md at the #10131 sync;
  fixed + SKILL.md regenerated via the official generator
- imageRegistry: #9982 broke same-provider bare aliasing (antigravity preview
  id sent upstream unresolved); new resolveSameProviderBareAlias() keeps the
  fal cross-provider fix intact
- imageRegistry: #9982's prefix strip handed the bare nano-banana ids to fal-ai,
  violating the pinned 2026-07-31 operator decision (adobe-firefly owns them);
  fal entries made prefix-only (dispatch already re-prefixes)
- mediaGeneration/fal.ts: the missing-credential 401 guard was lost when #10198
  deleted the superseded falHandler — tests were hitting the live network
- bottleneckPatch/rateLimitManager: #9041's merge clobbered #9604, resurrecting
  the Bottleneck v2.19.5 heartbeat bug (reservoir never refills); patched the
  library defect at the root and re-aligned chat-rate-limit-body-lock to the
  working reservoir contract
- processSupervisor.mjs: #9761 regressed the Node spawn to bare "node" (the
  #9156 launchd bug) and dropped #9209's ipv4first args; both restored
- openai-responses/pureHelpers: #9423's Agent null-sentinel was unreachable on
  the schemaless JSON-string path; gate extended
- i18n en.json: #8222's regen reverted the #9976 unclosed-tag fix and #8559's
  combo-cooldown copy; #9038 shipped 40 t() calls with no messages (runtime
  MISSING_MESSAGE); all restored/added + official sync-ui stamps, and vi's
  zero-marker policy re-established via the sanctioned translation backend

Stale sibling tests aligned (movers cited inline): chat-helpers (#9447),
executor-antigravity (#9351), video-fal-grok (#9982), visionBridge (#9759),
web-session-credentials (#8974), production-build-module-integrity (positive
anchor added), agentSkills-generator/skillManifestsLint/skills-injection/
agentSkillTools-mcp/listCapabilities-a2a (#9058), memory-settings (#10010),
model-catalog-policy-invalidation (#8906), model-alias-seed (#9485),
reactive-context-compaction (#8949), combo-provider-wildcard (broken upsert
helper), oauth-google-loopback (43-locale resurrected-key removal)

Validation: 501/501 across the 47 touched test files; typecheck:core, lint,
file-size, docs-sync all green.

Refs #9985

* fix(ci): base-reds round 3g — wave K/L: 4 more real defects + stale alignments

Real defects:
- base/reasoningEffort.ts: the stale duplicate cherry-pick #9612 re-added the
  codex minimal→low rewrite that #9883 had deliberately removed (OMP minimal
  passthrough); block removed again
- cursorImages.ts: #9840 wired prepareCursorImageForWire (sharp re-encode,
  fail-closed) into the SHARED resolveCursorImages, breaking zai-web and
  conol-web image uploads (HTTP 400 'undecodable'); new prepareForWire opt-out,
  Cursor default path unchanged (8 cursor suites green)
- modelCapabilities/snapshot: catalog prepare still issued 323 per-model reads
  of model_context_overrides + max_input_tokens overrides, violating #9199's
  bulk-load contract; both now resolve from the snapshot single pass
- v1-models-discovery-conformance: re-pinned to the bounded 30s SWR window
  (#9199/#10198) — the old 'stale-first regardless of age' contract is gone

Stale tests aligned (movers cited inline): codex-tools-strict-default (#9828
redundant-oneOf strip), devin-providers (#9245 i18n), db-migrationrunner-
constants-split (147→151 renumber #8228), gitlab-duo-oauth-setup (#9245),
chatcore-extracted-modules (#9161 outbound-protocol keying)

compression-api CI failures were cascade artifacts of codex-tools-strict-default
failing in the same force-exit shard process — no own defect (171/171 local).

Refs #9985

* fix(test): compression-api — register both describes before the runner starts

The DATA_DIR setup + route/db top-level awaits sat BETWEEN the two describes;
under --test-force-exit (the CI unit-runner flag) the process exits once the
already-registered tests finish, so on slow CI machines the whole second
describe died as 'Promise resolution is still pending' — the recurring
CI-only shard-2 failure that never reproduced locally without the flag.
Moved to the top of the file; 10/10 under --test-force-exit locally.

Refs #9985

* fix(quality): freeze modelCapabilities.ts at 1006 (annotated) — snapshot routing growth

Refs #9985

* fix(quality): move the modelCapabilities freeze into the frozen map (nested schema)

Refs #9985

* fix(i18n): translate all 39,718 pending UI keys across 42 locales (owner-approved)

Mass-translated every __MISSING__ placeholder via the official i18n:sync-ui
--translate-markers pipeline (operator backend), restoring i18nUiCoverage to the
100 baseline (was 89.9 after the merge-storm UI landings + the 42 keys #9038
never shipped).

Post-pass repairs, all caught by the existing gates:
- glossary: retired renderings the machine reintroduced normalized again
  (提供商→提供者 zh-CN/zh-TW, 鏈接→連結, 文檔→文件, 調用→呼叫, 供應商→提供者,
  響應→回應, 不活躍→未啟用 zh-TW; 클로드→Claude, 옴니루트→OmniRoute ko);
  DATA_DIR forbidden rendering avoided via 数据文件夹 rephrase
- ICU integrity: 120 values with renamed/dropped {params} repaired (39
  positional renames, 81 reset to the en source — functional over fluent)

Validation: glossary/pt-BR/vi/deno-relay/settings-keys/value-drift/google-
loopback suites 76/76; placeholder diff en×42 locales = 0; worst-locale
coverage = 100.0%.

Refs #9985

---------

Co-authored-by: backryun <bakryun0718@proton.me>
2026-08-13 00:02:25 -03:00
Will Gordon
4795825513 fix(sse): make Claude effort/no-think catalog variants dispatchable on every provider (#9006)
* fix(executors): route Claude-via-Vertex through native rawPredict with real streaming

Claude models on Vertex AI were being sent through the generic OpenAI-
compatible partner endpoint, which 404s/errors for Claude on at least
some projects. Route them through Vertex's native Anthropic Messages
API (publishers/anthropic/.../rawPredict) instead, stripping the
body-level model field rawPredict rejects and injecting the required
anthropic_version field.

rawPredict only ever returns a complete JSON body, never real SSE
framing, so streaming requests now get a genuine Anthropic-format SSE
stream synthesized from that JSON (message_start/content_block_*/
message_delta/message_stop), which the existing claude-to-openai
response translator already knows how to parse.

Also fixes two response-format resolution bugs that silently dropped
a custom model's DB-stored targetFormat override whenever the model
id also existed in the static provider registry (as claude-sonnet-4-6
and claude-opus-4-7 do under vertex): resolveModelOrError had its own
ad-hoc resolution that never consulted the override, and even once
fixed, executeChatWithBreaker discarded the correctly-resolved format
before handleChatCore's own resolution ran a second time.

* docs: add changelog fragment for #8909

* refactor(sse): extract shared Claude effort-model predicate

* fix(sse): strip Claude effort-suffix ids for any provider serving a real Claude model

* fix(sse): keep no-think and CC-discovery catalog variant roots unprefixed

* fix(dashboard): re-qualify no-think playground model ids correctly

* fix(sse): scope Vertex 404s to a per-model lockout via passthroughModels

* docs: add changelog fragment for the Claude catalog/dispatch fix

* fix(sse): align regex naming and changelog formatting

* fix(sse): clarify effort-variant strip comment and add cross-module drift guard

* fix(sse): disambiguate Vertex connection-wide vs per-model 403s

* docs: document Vertex 403 disambiguation in changelog fragment

* fix(sse): correlate reason and resource within the same ErrorInfo detail

* fix(sse): extract Vertex error classifier and rebaseline frozen file sizes

* test: register vertex-passthrough-model-lockout in stryker tap.testFiles

* fix(sse): reconciles rebase-onto-tip drift for 9006

Two categories of inherited base-branch breakage surfaced when
rebasing onto release/v3.8.50's latest tip, both confirmed unrelated
to this PR's own diff:

- check:file-size: base.ts and chat.ts drifted further past their
  frozen caps via already-merged commits (7163081f5 and others) that
  didn't rebaseline after growing them. Documented and bumped in
  file-size-baseline.json.
- chat-helpers.test.ts: two gpt-5.5 routing assertions predate #9275
  (fix(routing): bare model ids route to codex first), which
  deliberately made gpt-5.5 route to codex unconditionally, regardless
  of which other providers are active. Confirmed via #9275's own
  commit message and code comments this is intentional, not a
  regression; verified reproducible on the raw base tip alone, with
  no changes from this PR involved. Updated both assertions and their
  names to match the new, intentional default.

* ci: re-trigger checks after GitHub Actions incident (2026-08-07, resolved)

* ci: re-trigger checks (previous push event was dropped)

* fix(quality): rebaseline combo-routing-engine.test.ts own-comment growth

The ALL_ACCOUNTS_INACTIVE->ALL_TARGETS_SKIPPED fix (a32aed738) added explanatory comments (+7 lines), pushing the file past its frozen 3457 cap. CI's PR-mode check:file-size caught it; local check-file-size.mjs was not re-run after that specific commit.
2026-08-11 10:02:48 -03:00
Xiangzhe
36959c37d3 [v3.8.50] fix(models): keep model catalogs responsive (#9199)
* fix(models): preserve catalog on affinity bookkeeping

Related to #8697.

Focused follow-up to #8728; this does not replace or supersede that contribution.

* docs(changelog): record model catalog affinity fix

* fix(models): keep cold catalog builds responsive

* docs(changelog): record catalog responsiveness fix

* fix(models): snapshot auto candidate capabilities

* fix(models): invalidate capability catalog snapshots

* test(models): register catalog invalidation coverage

* fix(models): bulk-load catalog capability snapshots

Resolve synced capabilities and persisted overrides from one build-local view instead of repeating per-target SQLite reads. Keep ordinary runtime lookups on demand and preserve catalog generation invalidation.

Refs: #9199

* fix(models): snapshot catalog pricing once per build

Production profiling showed per-model models.dev pricing reads and JSON parsing dominated cold catalog builds. Reuse one build-local pricing snapshot during enrichment and yield before publication so queued health checks can run, while preserving fresh reads for ordinary callers.

* docs(changelog): record catalog pricing snapshot
2026-08-11 09:59:45 -03:00
Dizzle
0cbdc95023 feat(sse): server-side template expansion for combo system prompts (#5501) (#9414)
* feat(sse): server-side template expansion for combo system prompts (#5501)

* fix(quality-gates): register combo-system-prompt-templates-5501 test in stryker tap.testFiles

check:mutation-test-coverage --strict flagged tests/unit/combo-system-prompt-templates-5501.test.ts
as covering src/shared/utils/circuitBreaker.ts without being listed in stryker.conf.json
tap.testFiles, so its mutant kills wouldn't count.

Co-authored-by: maxmad64bis <maxmad64bis@users.noreply.github.com>

---------

Co-authored-by: Max <maxmad64@gmail.com>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: maxmad64bis <maxmad64bis@users.noreply.github.com>
2026-08-11 04:35:20 -03:00
Will Gordon
57744aeb14 feat(cursor): proactively renews Cursor sessions and fixes manual refresh (#9173)
* refactor(cursor): extracts token extraction into shared lib

Moves tryIdeAuth/tryAgentAuth and supporting helpers out of the
auto-import route into src/lib/cursor/tokenExtractor.ts, and adds
an agent-cli-state.json fallback candidate path to tryAgentAuth
(alongside the existing auth.json candidate) so the extraction
logic can be reused by the upcoming renewal orchestrator.

* feat(cursor): adds cursor-agent-backed token renewal orchestrator

Builds the renewal orchestrator in src/lib/cursor/renewal.ts: a
bounded, unattended-safe --list-models nudge, a side-effect-free
status availability check, an in-flight spawn lock keyed by
command, and renewCursorConnection() which nudges cursor-agent
then independently re-scrapes the IDE and cursor-agent credential
sources to detect whichever refreshed. Extends cursorAgent.ts's
binary resolution and spawn helper with fixed-paths-only mode and
a SIGKILL follow-up for background use. Adds a generic keyed-mutex
utility (src/shared/utils/keyedMutex.ts) for serializing a
connection's renew-then-persist cycle, and forwards a busy-timeout
through driverFactory's node:sqlite fallback path.

* feat(cursor): proactively renews Cursor sessions in the sweep

Adds src/lib/tokenHealthCheckCursor.ts, sweep-side glue that calls
the renewal orchestrator and persists the result, wired into
tokenHealthCheck.ts's checkConnection() via a new Cursor-specific
branch placed ahead of the generic no-refresh-token fallthrough.
Carves out a non-terminal exception for a Cursor connection that
already landed at testStatus "expired" via the request-time 401
path, excluding permanently-dead account_deactivated connections.
Extends buildRefreshFailureUpdate() with an overrides param so
Cursor's failure path can use a distinct, non-terminal errorCode
instead of the generic refresh_failed/expired taxonomy.

* feat(cursor): adds local-only manual refresh route

Adds POST /api/providers/[id]/refresh-cursor, a dedicated
loopback-only route that calls the renewal orchestrator on demand
for a single Cursor connection, bounded by a 30s per-connection
cooldown. Classifies the new route in LOCAL_ONLY_API_PATTERNS and
closes the manage-scope-bypass gap for dynamic-segment spawn-capable
routes under /api/providers/ via a new SPAWN_CAPABLE_PATTERNS /
SPAWN_CAPABLE_PATTERN_ANCESTORS mechanism, which also retroactively
covers the pre-existing /login route. The existing shared
/api/providers/[id]/refresh route is untouched and stays
remote-reachable for every other provider.

* feat(cursor): surfaces a dismissible cursor-agent nudge

Adds GET /api/providers/cursor/agent-availability, a credential-free
LOCAL_ONLY route returning only { cursorAgentAvailable: boolean },
backed by a 5-minute cached wrapper around the renewal orchestrator's
existing availability check. Surfaces a dismissible dashboard banner
on the Cursor provider page suggesting cursor-agent installation
when it isn't detected, following the existing dismissible-banner
convention. Also fixes a pre-existing bracket character in a
routeGuard.ts comment that was silently truncating
check-openapi-security-tiers.mjs's view of LOCAL_ONLY_API_PREFIXES.

* fix(cursor): wires manual refresh button to the new route

Branches handleRefreshToken to call the dedicated Cursor refresh
route instead of the generic /refresh route, which silently 502s
for Cursor connections today since they carry no refresh token.
Every other provider's refresh behavior is unaffected. Adds the
cursorSessionUnchanged i18n key and syncs it (plus a pre-existing,
unrelated 28-key backlog) across all 42 locale files.

* fix(cursor): addresses Phase 4/4.5 review findings

Restores the legacy stdout/stderr auth-pattern fallback in
checkCursorAgentAvailability() that the plan's Task 2 Step 4
required but the implementation had dropped. Threads an optional
deps parameter through checkCursorConnectionIfNeeded() so its
error branch is reachable in tests, and switches both it and the
manual-refresh route to exhaustive switch statements over the
renewal result. Adds a short-lived host-keyed dedup cache around
tryIdeAuth() so multiple due Cursor connections sharing a host
don't each open the same state.vscdb file in one sweep tick.
Adds opportunistic eviction to the manual-refresh cooldown map,
an outer try/catch to the availability route for defense-in-depth
consistency with the plan's other routes, and corrects a stale
JSDoc claim about the /login route's auth check. Documents the
now-empirically-confirmed agent-cli-state.json schema mismatch
found while validating against a real cursor-agent install.

* docs(cursor): adds changelog fragments for the renewal plan

Adds one fragment per user-facing outcome per changelog.d/README.md's
convention for a PR that both fixes and adds. PR number placeholder
to be filled in once the PR is opened.

* fix(i18n): translates the new Cursor keys into Vietnamese

The i18n:sync-ui run in an earlier commit left __MISSING__
sentinels for the 4 new Cursor keys in every locale, but
Vietnamese has a dedicated completeness test requiring zero
internal missing markers. Provides real translations for
cursorSessionUnchanged, cursorAgentNudgeTitle,
cursorAgentNudgeBody, and cursorAgentNudgeDismiss.

* fix(cursor): addresses quality-gate Layer 1.5 findings

Restores a comment that misrepresented execFile's actual argv shape
after an earlier bracket-removal fix, this time avoiding literal
closing-bracket characters entirely so the openapi checker's naive
array parser can't be broken by either version. Bounds the sweep-
and manual-route-triggered tryIdeAuth() busy-timeout to 250ms
(down from the interactive auto-import path's 2000ms), since both
share the main event loop with all other in-flight requests and
should fail fast on a WAL-lock collision rather than block the
whole instance for up to ~4s. Has the manual refresh route bypass
the sweep's IDE-auth dedup cache so a click always sees a fresh
read, consistent with this plan's existing "manual actions never
see stale cached data" convention. Documents the previously-missing
agent-availability route in ROUTE_GUARD_TIERS.md's spawn-capable
table.

* fix(cursor): adds SIGKILL follow-up to the status-check spawn

Matches the nudge spawn's existing SIGTERM+SIGKILL pattern so an
unresponsive cursor-agent status check can't leak a lingering
process if it ignores SIGTERM.

* docs(cursor): fills in the PR number for changelog fragments

Renames the 3 changelog.d fragments to their PR-numbered filenames and replaces the (#PR) placeholder with #9173, now that the PR exists.

* fix(cursor): corrects changelog fragments to reference PR #9173

The prior commit only staged the git mv rename — a git add invocation with a stale (pre-rename) pathspec aborted before the actual (#PR) -> (#9173) content edit was staged, so the rename landed without the fix it was meant to carry. This captures the actual content change.

* docs(cursor): regenerates the agent-skills catalog for the new route

check:agent-skills-sync (CI's Merge integrity gate) requires SKILL.md files to stay in sync with the live route catalog. Adding /api/providers/cursor/agent-availability in an earlier commit needed a regen this branch never ran.

* chore(quality): rebaselines file-size caps grown by agentrouter merges

Two already-merged agentrouter commits (564c204ef, ec150a006) on release/v3.8.50 grew open-sse/executors/base.ts, open-sse/handlers/chatCore.ts, and tests/unit/chatcore-translation-paths.test.ts past their frozen caps before this PR branched — unrelated to the Cursor renewal changes here. No PR branch is left to fix the growth in-place, so the caps are bumped to the current real sizes, following the existing release-green rebaseline precedent in this file.

* fix(sse): imports getModel helpers from db/models, not localDb

A recently-merged agentrouter commit added a @/lib/localDb import in chatCore.ts, violating the no-restricted-imports rule (Hard Rule #2 — never barrel-import from localDb.ts). Points the import at the owning module, src/lib/db/models.ts, where both functions are actually defined, and prunes the now-stale suppression entry.

* fix(sse): scopes CC-relay anthropic-beta to its own requestDefaults

Two already-merged agentrouter commits widened usesClaudeCodeProtocol()'s native-Claude system-transform block (billing header + selectBetaFlags-derived anthropic-beta) to also run for generic CC-compatible relay connections, not just real claude traffic and agentrouter's own wire-image mimicry. selectBetaFlags() has no visibility into a relay's own providerSpecificData.requestDefaults, so its header replacement silently wiped out an earlier context-1m append and force-included redact-thinking regardless of the relay's own opt-in. Restores both for plain CC-compatible relays only; real claude/agentrouter traffic is unaffected.

Also bumps four stale hardcoded Codex/Claude Code CLI version-string test assertions (0.144.1->0.146.0, 2.1.219->2.1.220) that drifted when the same two commits bumped the version constants without updating their tests, and rebaselines base.ts's frozen file-size cap for this fix's own +35 lines.

* fix(sse): preserves bare CC-relay native treatment and context-1m

The previous commit's fix was too broad in one direction: excluding ALL CC-compatible relays from the native-Claude header block broke two pre-existing tests (cc-compatible-provider.test.ts, v3.6.6) that rely on that treatment for a 'vanilla' relay with no providerSpecificData.requestDefaults configured.

Refines the gate to this whole native-Claude header-replacement block: replace headers for real claude traffic, agentrouter's wire-image mimicry, OR a CC-relay with no requestDefaults at all — only a relay with EXPLICIT requestDefaults (context1m/redactThinking/summarizeThinking) gets to keep buildHeaders()'s own correctly-computed header set. A redact-thinking-beta strip (unconditional, a no-op when native treatment didn't apply) covers the one remaining gap: selectBetaFlags() force-includes it for a bare relay's opaque client, which a bare relay never explicitly opted into.

Verified against all three previously-conflicting pre-existing tests simultaneously: executor-default-base.test.ts's '1M beta' test, both cc-compatible-provider.test.ts SSE-forcing tests, and provider-request-failure-pipeline.test.ts's 'keeps request beta headers' test (the last of which was already broken by the raw agentrouter merge, confirmed via direct comparison against that exact commit).

* fix(sse): fills in remaining stale CLI version literals

The same two agentrouter commits bumped Codex/Claude Code CLI version constants (0.144.1->0.146.0, 2.1.219->2.1.220) without updating every hardcoded test assertion. This round covers the ones the previous version-string commit missed: the anthropic-cache-fingerprint billing-version constant, a cc-bridge-transforms body assertion, the UI-mirror parity test's own snapshot plus its RoutingTab.tsx source of truth, an integration test's User-Agent assertion (inconsistent with its own dynamic Version assertion two lines up), and the translate-path golden snapshot. Also updates a stale doc comment referencing the old literal by value instead of by constant name.

* fix(cursor): imports from db/ modules, not the localDb barrel

Both files violated Hard Rule #2 (never barrel-import from localDb.ts) — a genuine lint error that had gone uncaught locally. refresh-cursor/route.ts imported getCachedProviderConnectionById from @/lib/localDb instead of its owning module, @/lib/db/readCache. tokenHealthCheckCursor.ts copied the same pattern from its sibling tokenHealthCheckCopilot.ts (an existing, already-suppressed violation) for updateProviderConnection; imports it from @/lib/db/providers instead, with no circular-import fallout (verified via the existing token-health-check-cursor and refresh-cursor-route test suites).

* fix(db): removes stale raw-SQL allowlist entry for cursor route

The cursor auto-import route no longer contains raw SQL — that query
now lives in src/lib/cursor/tokenExtractor.ts, outside the
route/handler scope check-db-rules scans. The allowlist entry was
stale, tripping the stale-enforcement gate.

* fix(test): registers cursor test files in stryker tap.testFiles

Three unit test files covering mutation-tested modules
(route-guard-cursor-agent-availability, route-guard-cursor-refresh,
cursor-renewal) were missing from stryker.conf.json's tap.testFiles,
tripping the mutation-test-coverage gate's drift detection.

* chore(ci): retriggers checks (stuck GH Actions runner on shard 2/4)

* fix(sse): restores CC-relay context1m/redact-thinking test coverage

Rebasing onto release/v3.8.50's new tip (35405be60, an unrelated
agentrouter protocol-inference commit) silently flipped two assertions
this branch's own earlier fix (687fbda62) depends on, in the same test
files that commit touched for other reasons:

- executor-default-base.test.ts: calls[0] (a bare CC-relay with no
  requestDefaults) expected redact-thinking-beta absent; flipped to
  present. calls[1] (context1m+redactThinking requestDefaults) expected
  the context-1m beta preserved; flipped to absent.
- provider-request-failure-pipeline.test.ts: expected Accept:
  text/event-stream and the context-1m beta present for a relay with
  explicit requestDefaults; flipped to application/json and absent.

35405be60 did not touch open-sse/executors/base.ts at all, so these
were test-only edits made without visibility into the still-unmerged
CC-relay header-preservation fix on this branch — they quietly matched
the assertions back to the pre-fix (buggy) behavior instead. Restores
the original, validated expectations; all three interdependent test
files (executor-default-base, cc-compatible-provider,
provider-request-failure-pipeline) verified passing together again.

* ci: re-trigger checks after GitHub Actions incident (2026-08-07, resolved)

* ci: re-trigger checks (previous push event was dropped)

* fix(quality): restore dropped vi.json cursor-renewal keys + rebaseline test growth

vi.json was missing 4 keys (cursorSessionUnchanged, cursorAgentNudgeTitle/Body/Dismiss) that this PR's own pre-merge branch had translated -- the original merge's 'git checkout --theirs' resolution for the 7 conflicted locale files discarded them since upstream's vi.json has no cursor-token-renewal feature. Restored from pre-merge tip a38003e30. Also rebaselines combo-routing-engine.test.ts (3457->3464) for the comment growth from the ALL_ACCOUNTS_INACTIVE fix, caught by CI's PR-mode check:file-size.

* chore(tests): drop explanatory comments on ALL_TARGETS_SKIPPED assertions

Kept the assertion value fix (ALL_ACCOUNTS_INACTIVE -> ALL_TARGETS_SKIPPED); the comments were unnecessary. Reverts the file-size baseline bump these comments caused (combo-routing-engine.test.ts back to its original 3457).
2026-08-11 04:31:24 -03:00
Arthur Bodera
8bdd29f835 fix(resilience): recover idle-capacity limiter wedges early (#9041)
* fix(resilience): recover idle-capacity limiter wedges early

* docs(changelog): note limiter wedge recovery

* fix(resilience): harden limiter wedge recovery

* fix(resilience): close limiter recovery review gaps

* test(resilience): preserve scoped exhaustion guards

* docs(changelog): remove self-credit suffix

* test: include limiter regressions in mutation coverage

* chore(quality): reconcile v3.8.50 file-size baselines

* fix(docs): add WAF MDX title frontmatter

* fix(docs): complete WAF frontmatter metadata
2026-08-11 04:30:29 -03:00
diegosouzapw
493cae2ed9 Merge remote-tracking branch 'origin/release/v3.8.50' into codex/wave3b-9424
# Conflicts:
#	config/quality/file-size-baseline.json
#	src/i18n/messages/en.json
#	src/i18n/messages/pt-BR.json
#	src/shared/constants/featureFlagDefinitions.ts
#	stryker.conf.json
2026-08-09 18:25:44 -03:00
Diego Rodrigues de Sa e Souza
c6195cec62 fix(security): harden Adobe credential parsing gates 2026-08-09 16:31:24 -03:00
Diego Rodrigues de Sa e Souza
2d49f1c743 maint: follow-up cherry-pick fix-in-place #9833 (conflict-resolved fallback) (#9899)
* fix(nvidia): keep 410 failures model-scoped

* test: register NVIDIA 410 regression for mutation coverage

* chore: preserve Stryker config formatting

* fix(auth): reduce file size

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: Zartharas <1402357+Zartharas@users.noreply.github.com>
2026-08-09 09:47:17 -03:00
diegosouzapw
8b29e73b27 Merge remote-tracking branch 'origin/release/v3.8.50' into fix/release-v3.8.50-base-reds-9737-final
# Conflicts:
#	tests/unit/adobe-firefly.test.ts
#	tests/unit/claude-code-rendering-fixes.test.ts
#	tests/unit/telemetry-auto-cleanup-6848.test.ts
#	tests/unit/triage-bugs-2026-08-02.test.ts
2026-08-08 21:45:42 -03:00
diegosouzapw
b7864cdb6c fix(ci): clear coverage pack and unit gate regressions 2026-08-08 21:33:11 -03:00
Diego Rodrigues de Sa e Souza
77cce62357 fix(ci): restore current release test integrity (#9819)
* fix(ci): restore base test integrity

* fix(quality): clear inherited fast gate drift

* fix(ci): clear inherited unit test reds

* fix(i18n): preserve Vietnamese catalog parity

* fix(i18n): preserve Portuguese catalog parity

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-08 19:32:39 -03:00
Diego Rodrigues de Sa e Souza
36abd86929 fix(ci): clear the 08-08 base-red layers — dead-code, prod crash in chat.ts, Responses payload regression, born-red stdio test, gate drifts (#9757)
* fix(ci): drop unused RadarReferrals type export — dead-code ratchet back to 227 baseline

The radar referral-links feature (#9697) exported the inferred type
RadarReferrals from feedSchema.ts but nothing imports it (the singular
RadarReferral is the consumed type). knip counts it as a new dead export,
pushing the dead-code ratchet to 228 > 227 and failing Fast Quality Gates
on every PR born after the merge. RadarReferralsSchema itself stays — it
is used by RadarFeedSchema.

Refs #9737

* fix(ci): clear the 08-08 base-red layer — prod crash in chat.ts, Responses API payload regression, born-red stdio test, gate drifts

Six independent base-reds from the 08-07 evening merge batch, each verified
against the pure release/v3.8.50 tip:

- src/sse/handlers/chat.ts: #9467's squash carried a refactor hunk that
  renamed the all-rate-limited breaker guard to an UNDEFINED variable
  (isAllRateLimited) — a production ReferenceError on the all-accounts-429
  path (chat.ts is outside typecheck:core scope, so only tests caught it).
  Restore credentials?.allRateLimited. Guard: chat-rate-limit-body-lock (2/2),
  also un-breaks batch_api and chat-combo-live-test.
- open-sse/utils/stream.ts: #9315 switched providerPayload summaries to the
  accumulated responseBody, but in passthrough paths that body is synthesized
  in chat-completion shape — Responses API lost its `response` object in the
  dashboard payload. Keep the events-derived summary for OPENAI_RESPONSES
  only. Guard: stream-utils + stream-collector-9315 suites (51/51).
- tests/unit/mcp-stdio-json-purity.test.ts: born red — the full CLI chain
  takes ~10s (2x tsx import + DB init) and the test slept a fixed 4s. Poll
  for the first stdout line with a 60s deadline instead.
- tests/unit/plugins-route-error-sanitization.test.ts: register #9445's new
  marketplace/install route in PLUGIN_ROUTES (route already sanitizes) (33/33).
- tests/unit/provider-models-route-codex.test.ts: realign pinned GPT-5.6
  input limit to #9432's deliberate 272000→922000 bump (7/7).
- lint: fix 11 no-explicit-any errors in repro-9630 + specialty-9293 tests,
  prune 1 orphaned suppression, allowlist the opencode-ai devDependency
  (#8869, publisher-verified), and reword a doc line the fabricated-docs
  gate misread as an env var.

Gates re-verified locally: lint:json --max-warnings 0 exit 0, dead-code 227,
typecheck:core clean, check:deps OK, check:fabricated-docs OK.

Refs #9737

* fix(ci): clear the third 08-08 base-red layer — invalid ru rule pack, stale event pin, orphaned UI repro test, pack/mutation/file-size drifts

Follow-up to the previous layer: the serial fast-gates chain unmasked one
more stratum after file-size/dead-code went green, all verified against the
merged release/v3.8.50 tip:

- compression rules ru/ultra.json (#9581): two rules shipped
  minIntensity "notes", which is not a valid CavemanIntensity
  (lite|full|ultra) — loading ANY language pack list threw and killed the
  rtk-loader suite. Mapped both to "ultra" (they are the most aggressive
  punctuation/case rules, matching the en pack tiers). 2/2.
- plugins-welcome-banner-e2e: #9668 added the onStreamComplete builtin
  event (real emission path via runOnStreamCompleteHooks) and missed this
  pinned-list sibling. 35/35.
- tests/unit/free-pool-frontend-repro (#9046): landed as .tsx with
  node:test semantics — no runner collects tests/unit/*.tsx, so it NEVER
  ran (test-discovery NEW-orphan). It contains zero JSX; renamed to .test.ts
  so the unit runner's existing glob collects it. 5/5 (first real run).
- pack-policy: allow + require bin/mcpStdioConsoleGuard.mjs (#9281) — it is
  preloaded via node --import by bin/mcp-server.mjs, so a published artifact
  without it crashes 'omniroute --mcp' at startup.
- stryker.conf.json: add 5 covering unit tests from the batch (#8779/#9204/
  #9330/#9630/openrouter-passthrough) to tap.testFiles (--strict drift).
- file-size-baseline: consolidate the base-drift rebaseline for the 12
  files grown by the 08-06..08-08 batches (#9616's entries never reached the
  base; measured on this branch's tree — this PR's own source edits add zero
  lines to any frozen file).

Local battery: file-size/deps/test-discovery/mutation/pack-policy/dead-code/
duplication/docs-all/secrets/vuln/workflows ratchets all exit 0; full lint
gate --max-warnings 0 exit 0.

Refs #9737

* fix(types): clear the 3 uncovered open-sse-typecheck regressions + realign combo skip-code siblings

Fourth base-red layer unmasked by the serial gates. The other 4 typecheck
regressions (codex.ts, kiro.ts, tierResolver.test.ts, translator/index.ts)
already have dedicated open [TS7] PRs (#9748/#9753/#9742/#9747) — not
duplicated here. This commit covers only what no open PR owns:

- devin-agentic/serializer.ts TS2367: drop the dead 'role === "system"'
  branch — the guard above already narrows role to user|assistant (system
  throws unsupported_role). Devin suites 104/104.
- raycast.ts TS2416: the buildHeaders 'override' never matched the base
  signature (2nd param is the signed payload string, not the stream
  boolean) — renamed to a private buildRaycastRequestHeaders helper so a
  polymorphic buildHeaders(credentials, true) call can never bind here.
- modelMetadataRegistry.ts TS2352: PricingByProvider → nested-record cast
  now goes through unknown (shape is runtime-guarded by findInsensitive).
- combo-routing-engine.test.ts: realign 2 pre-dispatch-skip expectations to
  #9630's deliberate ALL_TARGETS_SKIPPED contract (87/87).

Refs #9737

* fix(ci): clear the fifth 08-08 base-red layer — reasoning-placeholder contract sweep, GPT-5.6 limits sweep, vi key parity

The 08-08 merges (#9610 reasoning replay, #9432 GPT-5.6 limits, #9630 combo
skip codes, #9336 provider key links) each changed a contract and left
sibling tests pinning the old one. Full grep sweep per contract, not just
the shard that happened to go red:

- reasoning placeholder (#9573/#9610): the fix DELIBERATELY removed
  NON_ANTHROPIC_THINKING_PLACEHOLDER injection on cache miss — the model
  echoed the placeholder as its own reasoning (empty stop) and re-poisoned
  cache + client history; DeepSeek's 400 is specific to an EMPTY STRING, not
  an absent field. Realigned reasoning-cache (2 cases, renamed to describe
  omission) + tool-request-sanitization (1 case + dead import). 60/60.
- GPT-5.6 Codex limits (#9432, 272000 -> 1050000 ctx / 922000 input):
  realigned vscode-token-routes-gpt56 (2) + vscode-token-routes (3). 43/43
  together with t23-t24.
- combo skip codes (#9630): t23-t24-fallback-resilience T24 now expects
  ALL_TARGETS_SKIPPED like the combo-routing-engine siblings.
- vi.json key parity: #9336 added providers.getApiKey/getApiKeyDescription
  to en.json without syncing vi (the only locale with a parity gate).
  Translated both; providers block reordered to match en key order. 5/5.
- pack-artifact-policy.test.ts: sibling of this PR's own required-paths
  change (bin/mcpStdioConsoleGuard.mjs). 10/10.
- combo-routing-engine.test.ts: dropped the 6 comment lines added in the
  previous commit so the frozen test file-size stays at its baseline (the
  rationale lives in that commit message, not the test body).

Gates: file-size, test-discovery, mutation-test-coverage, pack-policy,
open-sse-typecheck, dead-code all exit 0.

Refs #9737

* fix(translator): keep the reasoning_content placeholder for Xiaomi MiMo — #9610 traded one live 400 for another

The xiaomi-mimo replay test (9router#1321) went red on the base after #9610
removed the NON_ANTHROPIC_THINKING_PLACEHOLDER injection globally. That test
is NOT stale — it guards a documented upstream 400 ('Param Incorrect: The
reasoning_content in the thinking mode must be passed back to the API'), so
realigning it would have masked a reintroduced production bug.

Two real bugs conflict here:
- #9573: forwarding the placeholder makes the model continue its chain of
  thought FROM that text (echo -> empty stop) and re-poisons cache/history.
- 9router#1321/#1337: omitting reasoning_content on a plain replay turn makes
  Xiaomi MiMo reject the request outright.

#9610's evidence for omitting is provider-specific — it verified that
deepseek-v4-flash accepts an ABSENT field. It does not extend to MiMo. So the
omission stays for every provider #9610 covered, and the placeholder survives
the cache miss only for xiaomi-mimo (new requiresReasoningContentPresence
predicate next to isReasoningOnlyReplayTarget). The echo that comes back is
still stripped on the way in by isInternalReasoningPlaceholder(), so #9573's
cache/history poisoning stays fixed for MiMo too.

Both contracts now hold simultaneously: xiaomi-mimo replay + reasoning-cache +
tool-request-sanitization 61/61; placeholder-strip/responses/translator/combo
regression sweep 168/168. Gates: file-size, open-sse-typecheck, dead-code,
mutation-test-coverage exit 0; typecheck:core clean.

A live check on the VPS (Hard Rule #18 path 2) is the only way to confirm the
DeepSeek half of #9610's empirical claim; flagging it in the PR rather than
widening this fix on speculation.

Refs #9737

* test(translator): pin the reasoning-placeholder provider scope so neither half of the conflict can silently re-break

#9610 removed the placeholder globally on the strength of ONE provider's
observed behavior (deepseek-v4-flash accepting an absent reasoning_content),
which re-opened the MiMo 400 (9router#1321). The previous commit scoped the
placeholder to xiaomi-mimo; this pins BOTH directions in one test so the next
global edit fails loudly instead of trading the bugs again:

- xiaomi-mimo plain replay turn, cache miss -> reasoning_content present
  (narrowing the scope away from MiMo re-opens 9router#1321)
- deepseek plain replay turn, cache miss -> reasoning_content absent
  (widening it back to DeepSeek re-opens the #9573 echo bug)

Guard verified by mutation: forcing requiresReasoningContentPresence() to
return true makes the DeepSeek half fail (1 pass / 1 fail), and the file was
restored from the pre-probe copy before committing.

Also checked kimi-coding/kimi-coding-apikey, the other strict-contract entries
in REASONING_REPLAY_PROVIDERS: their originating PR (#7673) fixes capture and
replay of REAL reasoning and documents no 400 on an absent field, so they stay
out of the placeholder scope — evidence-scoped, not speculatively widened.

Reasoning suites together: 87/87. Gates: file-size, test-discovery,
mutation-test-coverage, dead-code exit 0; eslint clean.

Refs #9737

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-08 09:08:45 -03:00
Diego Rodrigues de Sa e Souza
348102a114 feat(api): add response content encoding verification (#6736) (#9444)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:53:15 -03:00
Diego Rodrigues de Sa e Souza
c40d4b17ee fix(ci): clear the NEW base-reds from the 08-06 merge batch (migration collision #2 + broken import) (#9688)
* test(base): realign six suites with contracts that #9100/#8990/#9009 deliberately changed

Continuing the base-red drain — every one of these reproduces on the pure tip.

- tests/snapshots/provider/translate-path.json: regenerated via UPDATE_GOLDEN=1.
  The diff is ADDITION-ONLY — the unorouter block from #9009; no existing
  provider entry changed. 3/3.
- tests/unit/provider-models-route.test.ts: ff012ff420 added onboardUser as a
  bootstrap fallback next to loadCodeAssist; the mock now excludes it from the
  discovery-URL ledger like it already excluded loadCodeAssist, otherwise it
  consumed the injected 503 and the retry assertion misfired. 59/59.
- tests/unit/responses-commentary-passthrough-6199.test.ts: #8990 (c996dc93c2)
  deliberately preserves `tools` on the TERMINAL response.completed snapshot
  (Codex CLI rebuilds its tool list from it); the assertion now pins the echoed
  tools instead of their absence. Still stripped on created/in_progress. 7/7.
- tests/unit/vision-compression-authoritative-capability-7237.test.ts:
  68cb678780 added the 'gpt-5' fragment, so the heuristic-vs-spec DRIFT this
  suite documented no longer exists; the cases now guard the agreement, keep a
  conservative-for-unknown-ids probe, and reproduce the strip-bug shape with an
  explicit false instead of deriving it. 4/4.
- tests/unit/provider-limits-proxy-fail-closed.test.ts +
  tests/unit/image-generation-route.test.ts: #9100 made the proxy reachability
  probe NON-BLOCKING (optimistic dispatch; the probe aborts only in-flight
  requests — its own t14 sibling was updated to this exact pattern). Instant
  mocks therefore won the race and the PROXY_UNREACHABLE 503 became unobservable
  (a success or a generic 502). The mocks now stay in flight (never-resolving,
  so the aborted continuation cannot reach the restored real fetch), and the
  fail-closed proof is the settled rejection itself plus zero egress AFTER the
  fast-fail. Production fail-closed semantics are unchanged — the proxy dispatch
  path still throws; only the mock timing was stale. 3/3 and 20/20.

Refs #9298

* fix(guardrails): forward the router deps seam through callVisionModel

tests/unit/guardrails/vision-bridge-sse-and-reasoning.test.ts was 7/7 red on any
clean box (CI shard 3/4): callVisionModel() called getBestVisionModel()/
getFallbackModels() WITHOUT the routers' existing VisionBridgeRouterDeps seam,
so the credential check always hit the live connections DB — no vision-capable
connection meant 'No vision-capable provider connected' before the mocked fetch
was ever reached, and on a dev box auto-selection could swap the fixed model
under the assertions.

The routers already accepted deps; only the forwarding was missing. Added the
optional 5th param (backward compatible — the sole production caller,
visionBridge.ts, injects its own callVisionModel and is unaffected) and the
suite now pins selection with hasUsableCredentials: async () => null
(indeterminate → the fixed model is honored, DB untouched). 7/7.

Sibling suites re-run green: vision-bridge-callmodel 2/2, visionBridge 25/25,
visionBridgeHelpers.callVisionModel 8/8, visionBridgeRouter 10/10,
vision-bridge-cc-no-reroute 8/8.

Refs #9298

* fix(db,combo): clear the NEW base-reds the 08-06 merge batch introduced

The tip moved while the first sweep PR (#9600) was in review, and three fresh
base-reds landed with it — same classes as before, all reproduced on the pure
tip 9995bc4893:

1. ANOTHER migration collision: #9061 shipped 134_ccr_blocks.sql onto the slot
   134_proxy_logs_egress_ip.sql (#9291) has held since 08-04. getMigrationFiles()
   throws on collision, so every DB-touching test died at bootstrap again.
   Renumbered to 139 (next free slot). No retroactive guard needed this time:
   both statements are IF NOT EXISTS, and no DB can have applied it as 134 —
   the runner refused to run at all while the collision existed.
2. BROKEN IMPORT killing the combo module graph: #8894 imported
   preferAntigravityConnectionsWithStoredProject from
   ../antigravityProjectPersistence.ts — a module that exists NOWHERE in the
   repo (it came from an unmerged sibling branch). Anything importing
   quotaStrategies.ts died with ERR_MODULE_NOT_FOUND. Implemented the helper in
   the real persistence module (antigravityProjectPersist.ts, #8491) with the
   semantics the call site needs — prefer connections that already carry a
   stored projectId, never emptying the pool — and pointed the import there.
   New regression suite tests/unit/antigravity-prefer-stored-project.test.ts
   (5/5), including an import-graph probe that reproduces the break shape.
3. Sibling-test drift from #9106 (gemini-3.1-pro-high now user-callable): its
   own suites were updated but provider-models-route.test.ts was not. Expected
   discovery list realigned; testFrozen 1784->1787 justified in the baseline
   (irreducible +2 after comment compression; gate counts split-newlines).

Also regenerated tests/snapshots/provider/translate-path.json — addition-only:
devin-cli-agentic, raycast, regolo (today's provider merges), zero removals.

image-generation-route 20/20 (was import-dead), provider-models-route 59/59,
antigravity-prefer-stored-project 5/5, provider-translate-path-golden 3/3.

Refs #9298

* fix(changelog): convert the #9415 fragment to the required bullet shape

Another base-red from the 08-06 batch: bd4407cb64 landed
changelog.d/features/9415-newapi-sub2api-aggregator-balance.md as YAML
frontmatter + a prose paragraph. Every other fragment in changelog.d/ is a
single markdown bullet, and both consumers enforce that —
scripts/check/check-changelog-integrity.mjs:97 and the release aggregator
(scripts/release/aggregate-changelog.mjs:57) reject anything that does not
start with '- ', so 'Merge integrity (changelog + generated skills)' was red
for every PR targeting the release branch.

Rewritten as a bullet with the standard issue link, preserving the feature
description (aggregator gateway toggle, /api/user/self balance read, dashboard
badge, quota-preflight skip, NEWAPI_AGGREGATOR_BALANCE flag default off,
quotaPerUnit override). Swept the rest of changelog.d/ — this was the only
malformed fragment.

check:changelog-integrity OK.

Refs #9298

* fix(types,docs): clear the 5 typecheck errors and the fabricated env vars on the base

Third pass over the base-reds, from the 2026-08-06T22:51Z verdict on #9298 —
it reported "Typecheck (core)" with only the FIRST error; there are five, all on
the pure tip 9995bc4893. Two are real production defects.

**Real bugs**

- open-sse/services/compression/engines/ccr/index.ts:295 called
  enforceGlobalBudget(entry.bytes) against an (owner, bytes) signature. The
  `bytes` argument arrived undefined, so `ccrTotalBytes + undefined` is NaN,
  `NaN > MAX` is false (the eviction loop exits immediately) and `NaN <= MAX` is
  false (the re-admit is refused). The #9061 durable tier therefore NEVER
  repopulated its in-memory map: every retrieve after a restart or an eviction
  re-read from SQLite forever, and evictions could not prefer the owning
  principal. Fixed and pinned by a new case in
  tests/unit/ccr-durable-store-9061.test.ts (11/11) — verified failing against
  the buggy call and passing against the fix.
- open-sse/services/combo/fusionPanel.ts:54 read `step.model` after #8894
  widened ComboStep with ComboProviderWildcardStep (which carries modelPattern,
  not model), so a wildcard step in a fusion panel pushed `undefined` onto the
  panel. Now resolved through getComboModelString(), which already handles every
  step shape and returns null for the ones without a concrete model id.

**Type-only**

- accountSemaphore.ts:203 — isBypassed() returns a plain boolean and cannot
  narrow `number | null` (an `x is null | undefined` predicate would be unsound:
  0 bypasses too). Added resolveActiveCap(), the narrowing companion isBypassed
  is now defined in terms of; the acquire path uses the narrowed value.
- comboStructure.ts:140 — same #8894 widening: `prompt` only exists on a model
  step, so it is now read under a kind check.
- firecrawlQuotaFetcher.ts:136 — the function returns full FirecrawlQuota
  objects but was annotated Promise<QuotaInfo | null>, which made the
  custom-base literal an excess-property error. Widened to the accurate type
  (FirecrawlQuota extends QuotaInfo, so callers are unaffected).

**Fabricated docs (the "Docs sync + fabricated-docs (strict)" HARD failure)**

docs/ops/VM_DEPLOYMENT_GUIDE.md recommended OMNIROUTE_MAX_POOL_SIZE and
OMNIROUTE_DB_POOL_SIZE (#9471). Neither is read anywhere in the codebase.
Replaced with the two knobs that do exist and are already documented in
ENVIRONMENT.md: OMNIROUTE_MEMORY_MB and OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT.

typecheck:core 5 errors -> 0. check:fabricated-docs + check:env-doc-sync OK.
accountSemaphore 6/6, ccr-durable-store 11/11, ccr-protocol 9/9,
combo-fusion-strategy 10/10, combo-fusion-comboref 5/5, combo-fusion-warn 4/4,
firecrawl-executor 7/7, executor-firecrawl-fetch 4/4.

Refs #9298

* fix(tests): type the #3440 vertex helpers instead of `any` (the 3 base ESLint errors)

The "ESLint errors: 3 error(s)" HARD failure in the #9298 verdict is
tests/unit/vertex-functioncall-id-3440.test.ts lines 32/41/50: the three
find*(result: any) walkers. `@typescript-eslint/no-explicit-any` is an ERROR in
tests/ (and open-sse/) since #6218, and this file landed on 2026-08-04 without a
suppressions entry, so every run of `lint:json --max-warnings 0` failed. That
step prints nothing on failure, which is why the gate looked like a silent
crash across the open PRs.

Replaced with a GeminiRequestLike interface describing exactly what the three
walkers traverse (contents[].parts[]), so the assertions keep their meaning and
nothing is cast away.

eslint on the file: clean. Suite: 6/6.

Refs #9298

* docs(proxy): use an RFC 5737 documentation IP in the proxy examples

The #9298 verdict headlines its docs failure with
`L810 [stale-version] 1.2.3: const removed = await failOneproxyProxy("1.2.3.4", 8080)`.
That is a false positive: check-deprecated-versions.mjs matches
`/\bv?[12]\.\d+\.\d+\b/`, and the example IP literal 1.2.3.4 contains "1.2.3".

Swapped both occurrences in PROXY_GUIDE.md (and its pl mirror) for 203.0.113.7,
from the RFC 5737 documentation range that exists precisely for examples — it
cannot collide with a version pattern and is the correct thing to print in docs
regardless. Drift count 64 -> 62; no gate threshold was touched.

The gate that actually FAILED under "Docs sync + fabricated-docs (strict)" was
check:fabricated-docs (the invented pool env vars), fixed in the previous
commit; this one removes the misleading line the verdict quotes.

* test(base): allowlist probeUtils and realign the #7849 suite to the replacement bound

Two more base-reds, both visible only after the migration collision stopped
killing the shards.

**check-db-rules — src/lib/db/probeUtils.ts not classified**

#9541 added probeUtils.ts (transient-error retry for the SQLite corruption
probe). It is imported ONLY by src/lib/db/core.ts, exactly like its siblings
schemaColumns / optimizationSettings / providerNodeSelect, so re-exporting it
through localDb.ts would push callers toward the barrel-import anti-pattern the
gate exists to prevent. Added to INTENTIONALLY_INTERNAL with that rationale.
check-db-rules 22/22, check:db-rules exit 0.

**session-dedup-memory-7849 — pinned a mechanism that was replaced**

7f36b192f0 (#7855 follow-up) swapped the shared "suffix work budget" for the
MAX_SUFFIX_STARTS / MAX_TOTAL_BLOCK_BYTES guards and deleted both the budget and
its SUFFIX_WORK_BUDGET_WARNING string. It updated session-dedup.test.ts but not
this sibling, so 3 of its 4 cases asserted a warning that can no longer be
emitted.

Realigned to the contract that actually survives — which is the invariant #7849
was opened for, not the mechanism:
- the pathological pair must stay BOUNDED (completes in <4s, body intact) —
  measured at ~280ms on the current guards;
- it must FAIL OPEN — original body returned by identity, compressed false,
  stats null (the explanatory zero-savings stats belonged to the removed
  budget path, which skipped before producing any);
- the 512 MiB child fixture must still exit 0 with the full engine chain
  (session-dedup, lite, rtk, headroom, caveman) — that IS the OOM guard — and
  session-dedup must still report its skip, now pinned by prefix since the
  reason string moved with the mechanism.

No threshold was loosened and no case was deleted: 4/4 here, 8/8 on the sibling
session-dedup.test.ts.

Refs #9298

* docs(mcp): bump the tool count to 105 and realign two vitest count pins

Three more base-reds from the same 08-06 batch, all count/contract drift that
the merged PRs left in sibling files.

**Docs Gates (fast-path) — 3 STRICT drifts**

check:docs-counts measures the MCP tool set from live code: it is 105 now
(#8925 added omniroute_create_combo), while README.md, AGENTS.md and
docs/frameworks/MCP-SERVER.md still claimed 104. Updated all five occurrences
(two of them inside SVG alt text). check:docs-all exits 0.

**Vitest (fast-path) — 2 failures**

- open-sse/mcp-server/__tests__/essentialTools.test.ts pinned 11 phase-1 tools;
  #8925 shipped omniroute_create_combo as phase 1, making it 12. Verified by
  enumerating MCP_ESSENTIAL_TOOLS directly.
- tests/unit/autoCombo/provider-family-combos.test.ts pinned the auto/glm
  provider set to [auggie, glm, zai]. #8914 (Devin ACP bridge) added
  devin-cli-agentic, whose catalog (registry/devin/catalog.ts:90-93) advertises
  the glm-5-2* line — so it belongs in the family pool for exactly the reason
  the test's own comment gives for auggie: a no-auth backend that genuinely
  serves a family model is a legitimate member. Expected set updated, invariant
  unchanged.

npm run test:vitest 36/36 files, 340/340 tests.

Refs #9298

* fix(combo,usage,oauth): drain the base-reds the shard fix exposed

With the migration collision and the broken import out of the way the four unit
shards actually run, and a further layer of base-reds became visible on the pure
tip 9995bc4893. Three are production defects.

**Production defects**

- open-sse/services/combo/runtimeUnitCapacity.ts:58 called resolveComboTargets()
  WITHOUT the hidden-model snapshot, so it fell back to the default
  getHiddenModelsByProvider() — a fresh full key_value read PER nested combo-ref
  unit, on every request. #8878 threaded the snapshot through the other call
  sites and missed this one. Threaded it from executeRuntimeUnitCombo (and from
  the dispatchPrelude call site), restoring the one-snapshot-per-request
  invariant combo-hidden-leaf-routing.test.ts pins. 9/9.
- open-sse/services/usage/firecrawl.ts silently ignored its own `apiKey`
  parameter: 91bb6aa619 moved the fetch to
  fetchFirecrawlQuota(connectionId, connection), which reads the key off the
  connection record, so any caller passing the key directly got "Firecrawl API
  key not available". The explicit key is now merged into the connection passed
  down. firecrawl-usage 8/8.
- src/lib/oauth/constants/oauth.ts was missing a RAYCAST entry in PROVIDERS
  while src/lib/oauth/providers/index.ts registers `raycast` (#8895), so every
  consumer reading PROVIDERS did not know Raycast Pro exists. Also added its
  OAUTH_TEST_CONFIG entry (checkExpiry only — it is an `import_token` provider
  with refreshToken always null), which #8408's guard explicitly requires rather
  than grandfathering. oauth-providers-config 25/25, oauth-test-config-8408 2/2.

**Count / contract drift from the same batch**

- feature flags 45 -> 46, APIKEY_PROVIDERS 197 -> 198 (Raycast Pro #8895),
  unique MCP tools 107 -> 108. Each re-derived from the source of truth.
- vi + pt-BR locales: translated the 8 keys #9415 added
  (providers.newApiAggregator* and providers.modelTestQuotaTooltip) instead of
  relaxing the parity guard. i18n-vi 5/5, i18n-pt-br 3/3.
- login-bootstrap-route: #9491 added `authenticated` to the require-login
  payload so /login can redirect an active session; the three deepEqual bodies
  now carry it. 10/10.

**Flaky-by-construction, made deterministic**

tests/unit/chat-combo-live-test.test.ts asserted the early-keepalive frame with
a 100ms mocked upstream while resolveKeepaliveThreshold() is 2000ms for
openai/*. It only ever passed while unrelated handler latency happened to push
the total past the threshold — incidental, not deterministic, and it stopped
holding once the handler got faster. The mock now sleeps 2400ms so the slow path
is guaranteed and the assertion means what it says. 5/5.

typecheck:core exit 0. check:file-size (base-relative) OK.

Refs #9298

* test(base): run the orphaned #8890 suite and realign three mechanism pins

**check:test-discovery — a suite that had NEVER executed**

#8890 landed open-sse/services/__tests__/fail-fast-concurrency-gate.test.ts into
a directory no runner collects (only one explicit file from that folder is in
vitest.mcp.config.ts), so it ran zero times since it merged. Wired it into the
runner AND into check-test-discovery.mjs's mirrored collector list, which the
gate keeps in sync deliberately. It passes 4/4 now that it actually runs —
test:vitest goes 36 -> 37 files, 340 -> 344 tests.

**check-db-rules-classification** — 37 -> 38 audited modules, adding probeUtils
alongside the INTENTIONALLY_INTERNAL entry from the previous commit.

**ratelimit-reservoir-refresh** — #9604 (rolling RPM leases) DELETED Bottleneck's
fixed-window reservoir, so currentReservoir() is null and the poll for
`reservoir === 2` could never settle. It updated several sibling suites but not
this one. The pin on the removed mechanism is gone; what remains is the
invariant the original Bottleneck heartbeat bug actually broke and that #9529
opened this test for — after a header-learned updateSettings() the limiter must
keep admitting work, proven by racing a post-exhaustion request against a 5s
timer. 1/1.

**translator-openai-to-gemini** — #9568 (c9a3361e5a) made
buildChangedToolNameMap emit IDENTITY entries too, because Gemini lowercases
tool names in functionCall responses and the response translator needs a key to
map them back. Any request carrying tools therefore carries `_toolNameMap` in
the Antigravity envelope now. Expected key list updated and the map's contents
asserted explicitly rather than left implicit. 45/45.

Refs #9298

* fix(db): restore node-backed synced catalogs and realign the #8944 context hints

**Production regression from #9294 (d69f521491)**

lookupModelMeta moved from getSyncedAvailableModels(providerId) to
getActiveSyncedCatalog(providerId). The new reader unions models only from rows
in `provider_connections` with isActive = 1 — but a provider NODE lives in
`provider_nodes` and NEVER has a connections row, so filtering by active
connection ids silently dropped every node's synced catalog.

The consequence was not just a missing list: lookupModelMeta reads that catalog
for RUNTIME METADATA, so for openai-compatible nodes it took out
- `supportedThinkingEfforts`, which is what splitSyncedEffortSuffix needs — so
  `<prefix>/<model>-high` stopped resolving to the base id and the effort was
  never derived (#7694), and
- `contextWindow` / `maxInputTokens`, used by the combo context-window filter.

getActiveSyncedCatalog now falls back to the provider-wide key_value set — the
exact pre-#9294 source — when no active connection carries a catalog, and marks
that fallback explicitly NON-authoritative. #9294's live-catalog gating is about
what an active connection actually serves, so a node-backed catalog informs
metadata while never being able to reject a model as unavailable. `available`
therefore stays fail-open for nodes, as it was before.

sync-reasoning-supported-efforts-7694 23/23 (was 21/2).
live-model-catalog-reconciliation-8926 11/11 and combo-provider-wildcard 23/23
confirm #9294's own coverage is untouched.

**#8944 sibling-test drift**

714a315a1a ("Treat context metadata as a routing hint") deliberately turned the
context-window check from a HARD filter into an ordering hint: a catalog-too-small
target is demoted, not removed, because a stale catalog entry must never delete
the only target that could accept the request at runtime. The PR updated one case
in this suite and left three asserting the old drop behaviour. Realigned to the
new contract — the too-small target must lose the ordering to the fitting one
while remaining present — and renamed them from "still rejects"/"still dropped"
to "is demoted"/"ordered last" so the names stop describing the removed
behaviour. 14/14.

**file-size**

tests/unit/translator-openai-to-gemini.test.ts testFrozen 1616 -> 1619: the
frozen value sat exactly at the base size, so the 3 lines the previous commit's
_toolNameMap alignment needs could not fit. Justified in the baseline.

typecheck:core exit 0.

Refs #9298

* chore(stryker): register the two covering suites missing from tap.testFiles

check:mutation-test-coverage flags any unit test that covers a mutated module but
is absent from stryker.conf.json tap.testFiles — without the entry its mutant
kills do not count toward the module's score.

- tests/unit/antigravity-prefer-stored-project.test.ts covers
  open-sse/services/combo/quotaStrategies.ts (added earlier in this PR).
- tests/unit/executor-devin-cli-agentic-acp.test.ts covers
  src/sse/services/auth.ts — pre-existing drift, same gate, same fix.

Inserted in alphabetical position only; the rest of the file is byte-identical
(it is not prettier-formatted upstream and reformatting it is out of scope here).

Refs #9298

* fix(db): drop the never-wired getSessionModelUsageCounts (knip regression)

The dead-code ratchet only ran once the earlier Fast Quality Gates steps stopped
failing, and it lands at 228 vs baseline 227.

The extra symbol is src/lib/db/contextHandoffs.ts::getSessionModelUsageCounts,
added by #8894 "for least-used strategy" and never wired: the least-used branch
in applyStrategyOrdering.ts uses the pre-existing sortTargetsByUsage(), and the
helper has no caller in src/, open-sse/ or tests/. It is the same incomplete-PR
shape as that PR's import of a module which does not exist in the repo (fixed
earlier in this branch).

Removed rather than baselined — bumping the ratchet would loosen the gate, and
removal is exactly the remedy the gate prescribes. Same treatment the Dario
installer's never-wired uninstall() got in #9600. The implementation is
recoverable from a598fbb090 whenever someone actually wires a session-aware
least-used strategy.

check:dead-code 228 -> 227 (baseline untouched). check:db-rules exit 0.
context-handoff 13/13, db-context-handoffs 7/7, service-context-handoff 11/11.

Refs #9298

* fix(security): embed the Raycast signature secret via resolvePublicCred (HR#11)

The secret-scan ratchet only ran once the earlier Fast Quality Gates steps
stopped failing, and it lands at 1 finding vs baseline 0.

The finding is open-sse/services/raycast.ts:19 —
RAYCAST_DEFAULT_SIG_SECRET, a 64-hex request-signature secret that #8895
committed as a bare string literal. It is genuinely public (community-extracted
from the Raycast macOS client; the SAME value ships to every install, it is not
a per-user credential), which is exactly the category Hard Rule #11 governs:
public upstream credentials MUST go through resolvePublicCred()
(open-sse/utils/publicCreds.ts), never a literal — see
docs/security/PUBLIC_CREDS.md.

So the fix is the mandated pattern, not a .gitleaks.toml allowlist entry: added
`raycast_sig_secret` to EMBEDDED_DEFAULTS as the XOR-masked byte sequence and
resolved it with the existing RAYCAST_SIG_SECRET env override. The
providerSpecificData.sigSecret override is untouched. Verified the decoded value
is byte-identical to the literal it replaces.

check:secrets secretFindings 1 -> 0. check:public-creds exit 0.
publicCreds 12/12, raycast-auth 6/6, raycast-local-extract 1/1,
trae-publiccred 3/3. typecheck:core exit 0.

Refs #9298

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-07 11:13:20 -03:00
Diego Rodrigues de Sa e Souza
5ea43c7a9d feat: make forwarded upstream response-header budget configurable (#9243) (#9492)
Validated in local merge-train (diegosouzapw batch)
2026-08-06 10:39:15 -03:00
Diego Rodrigues de Sa e Souza
04683029a6 fix(build): exec native esbuild binary directly in prepublish (dast-smoke base-red) (#9558)
* fix(build): exec native tool binaries directly in runBuildTool

#8858 routed every resolved local bin through process.execPath to avoid
Windows .cmd shims — but esbuild >=0.25 ships bin/esbuild as the NATIVE
platform executable (ELF on Linux), so Node parsed machine code as JS and
build:cli died with 'SyntaxError: Invalid or unexpected token', turning
dast-smoke red for every PR.

runBuildTool now sniffs the entry's magic bytes (ELF / Mach-O / PE) and
execs native binaries directly; JS entries keep going through this Node
binary (the .cmd-shim avoidance #8858 wanted).

Validation (RED->GREEN on this box):
- RED: node node_modules/esbuild/bin/esbuild --version -> SyntaxError (ELF)
- GREEN: the exact failing CI step reproduced via the new logic bundles
  open-sse/mcp-server/server.ts successfully (4.2MB output, 1.3s).

* fix(docs): add MDX frontmatter to the 20 remaining docs without it

Same failure class as AGENTROUTER_WAF (#9503) and DOCKER_RELEASE_CHANNELS
(this run's dast-smoke red): any doc without frontmatter breaks the
fumadocs MDX loader during next build, killing build:cli/dast-smoke for
every PR. Swept ALL of docs/ (i18n mirrors excluded) in one pass so this
class cannot recur one file at a time.

* docs(env): document OMNIROUTE_INTERNAL_SERVICE_TOKEN(+_FILE), OPENROUTER_PROVIDER_STATS_* and embedded-Redis binding vars

Pre-existing env/docs contract drift from recently merged features made
check:env-doc-sync red for any docs-touching PR. Values and defaults read
from the defining modules (internalServiceAuth.ts, openrouterProviderStats.ts).

* fix(build): resolve bundled npm-cli.js in the standard Unix layout + safe npm fallback off-Windows

The opencode-plugin step hard-failed on GitHub runners because
resolveBundledNpmEntry only looked next to the node binary (Windows zip
layout); hostedtoolcache Node keeps npm at <prefix>/lib/node_modules/npm.
Added that candidate, and when neither exists on non-Windows the step now
falls back to plain 'npm' — the .cmd-shim hazard #8858 avoids is
Windows-only.

* test(mutation): register xai-agent-tools-passthrough.test.ts in stryker tap.testFiles

The test landed on release/v3.8.50 covering
open-sse/handlers/chatCore/passthroughHelpers.ts without the stryker
registration, so Fast Quality Gates' drift detection reds any PR that
carries it. Mechanical registration so its mutant kills count.

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-06 02:19:57 -03:00
diegosouzapw
4dbbaeb746 test(mutation): register capability-filter.test.ts in stryker tap.testFiles
The mutation test-coverage drift gate (check:mutation-test-coverage --strict)
failed because tests/unit/capability-filter.test.ts covers
open-sse/utils/error.ts (a mutated module) but was missing from
stryker.conf.json tap.testFiles.
2026-08-05 19:55:14 -03:00
Xiangzhe
2c966c28af test(mutation): include adaptive admission coverage 2026-08-05 08:45:00 -03:00
Nick Sullivan
7b8055c7f8 fix(resilience): count STREAM_EARLY_EOF as a provider failure in combo routing (#9251)
* fix(resilience): count STREAM_EARLY_EOF as a provider failure in combo routing

A STREAM_EARLY_EOF is an upstream that accepted the request (HTTP 200), opened
the SSE stream, then closed it without emitting a single non-ping event. The
combo path classified it together with STREAM_READINESS_TIMEOUT through
isStreamReadinessFailureErrorBody(), and the readiness exemption in
shouldRecordProviderBreakerFailure meant the whole-provider circuit breaker
never saw it.

During a provider-wide outage that makes the breaker blind. Over a 7-day window
on our router we recorded 311 of these events, 302 of them on one model, 265
inside the upstream's published incident window — and the provider breaker sat
at CLOSED / failure_count=0 the entire time. Every request kept being dispatched
to the failing provider instead of shedding to the next combo target.

The two codes are different signals. The readiness probe is a pre-flight
liveness check on a connection we have not committed to, so failing it means
"this connection looks stale". An early EOF means the provider took the request
and then failed to serve it. The single-model path already treats it that way:
shouldTripProviderBreakerForResult has no readiness exemption, so a 502 early
EOF trips the breaker there. This makes the combo path consistent.

isStreamReadinessFailureErrorBody keeps matching both codes, because the
transient-retry and round-robin semaphore-cooldown paths in combo.ts do want
identical treatment for both. Only the breaker needs to tell them apart, so the
distinction is added as a narrow predicate and an optional argument rather than
by changing the shared classifier. Omitting the new argument reproduces the
previous behaviour exactly.

Follows the additive-override pattern established by the isProxyUnreachable
work, and leaves the existing exclusions for client aborts and plain 429s
untouched.

* test: register stream-early-eof-breaker in stryker tap.testFiles

The mutation test-coverage gate (check:mutation-test-coverage --strict)
detects unit tests that cover a mutated module but are missing from
stryker.conf.json tap.testFiles, so their mutant kills would not count.

comboPredicates.ts is one of the mutated modules, and the new
stream-early-eof-breaker.test.ts covers it, so the gate correctly flagged
the omission. 8376-econnrefused-breaker.test.ts -- the test this one is
modeled on -- is already registered; this just brings the new file in line.

No production code change.

---------

Co-authored-by: Nick Sullivan <nick@technick.ai>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-04 18:07:09 -03:00
Shixi Li
a8216c92fe fix(sse): preserve error-only stream diagnostics (#9022)
* fix(sse): preserve error-only stream diagnostics

* test(ci): register stream readiness mutation coverage

* chore(changelog): finalize PR 9022 fragment
2026-08-04 10:06:25 -03:00
diegosouzapw
d6c06932ec chore(quality): re-pin ceilings after merge-train 3 + register CF-1010 test
file-size: apiKeys.ts 1518 -> 1529 (#8805, treating cx/* and codex/* as
equivalent provider prefixes in API-key model permissions) and chatCore.ts
5006 -> 5020 (#8806, passing the real response payload into plugin onResponse
hooks instead of a hardcoded {status:200}). Both extend existing call sites
rather than adding a branch.

stryker: account-fallback-cf1010-no-retry-8775.test.ts covers
accountFallback.ts but was missing from tap.testFiles, which would redden
Fast Quality Gates on every subsequent PR.
2026-07-27 22:39:06 -03:00
Wilson
f8487648c8 fix(resilience): keep resource 404s from cooling models (#8756) 2026-07-27 21:12:42 -03:00
diegosouzapw
cd7a492984 chore(quality): re-pin file-size ceilings after merge-train 1H + fix stryker drift
file-size: nine frozen entries could not absorb the combined result of the
31-PR train. Two distinct causes, kept apart in the baseline note on purpose:

  (1) GENUINE irreducible growth at existing chokepoints —
      providerLimits/auth (#8632), rateLimitManager (#8616),
      models-catalog-route.test (#8610).
  (2) COLLISION with #8585, which banked shrinks measured on the pre-train
      release tip while 30 sibling PRs in the SAME train grew those files
      again — chat/accountFallback (#8628), chatCore (#8613),
      videoGeneration (#8581), imageGeneration.

Ceilings re-pinned to the post-merge tip. #8612 (also in this train) automates
shrink-banking so this self-inflicted drift stops recurring.

stryker: three covering unit tests were missing from tap.testFiles —
isLocalStreamLifecycleError-abort-shape (circuitBreaker.ts, a shared base-red
that was reddening Fast Quality Gates on every open PR),
noauth-autocombo-lockout-7623 (accountFallback.ts) and
kimi-quota-reset-recovery (auth.ts), the latter two landed with this train.
2026-07-27 19:13:07 -03:00
MumuTW
1d44ee00f8 refactor(sse): extract combo dispatch prelude into combo/dispatchPrelude.ts (#8582)
* refactor(sse): extract combo dispatch prelude into combo/dispatchPrelude.ts

Pure move, no behaviour change. First of ~7 PRs decomposing the combo.ts
god-file (#3501).

handleComboChat evaluates a series of dispatch branches before it ever
reaches target resolution or the sequential attempt loop. None of them
iterate targets in priority order or need the failover/retry/credential
gate machinery that follows, so they move to a leaf:

  - context-cache pin routing (Fix #679), including the
    pinIsDurablyUnhealthy / isPinnedModelDurablyUnhealthy health gate
  - fusion panel dispatch + the #6455 misconfiguration warn
  - pipeline chaining
  - nested combo-ref execute-mode runtime-unit dispatch

Only the chaos and round-robin hand-offs stay inline (11 and 13 lines);
extracting those would be pure indirection.

open-sse/services/combo.ts 3642 -> 3341 (-301)
open-sse/services/combo/dispatchPrelude.ts: 619 (under the 800 cap)

Each helper keeps the fall-through protocol the inline blocks had: return
a Response to OWN the request, return null to fall through. A flipped
null/Response would silently bypass the whole combo strategy, so the new
tests pin both directions for every branch.

combo.ts re-exports pinIsDurablyUnhealthy so combo-pin-health-gate.test.ts
keeps resolving. The leaf takes handleComboChat as a `runCombo` parameter
instead of importing it, so combo/ keeps zero back-edges into combo.ts.

Complexity-neutral: the first cut added +3 violations (two
max-lines-per-function, one complexity) inside the new leaf, so
evaluatePinnedResponse, orderRuntimeUnits, recordRuntimeUnitStickySuccess
and buildBaseOptions were split out. check:complexity now measures 2169
and check:cognitive-complexity 956 — identical to the pristine base.

* test(sse): close the dispatch-prelude coverage holes found by mutation testing

An adversarial mutation audit of the suite added in the previous commit
found it guarded the fall-through protocol well but asserted almost
nothing about what the helpers do once they OWN the request. 5 of 12
seeded mutations survived. Worst case: deleting the pinned-model
dispatch call outright left all 12 tests green.

Three holes, now closed (8 tests -> 20):

Hole A — the honored-pin path had zero coverage. Both existing pin tests
DROP the pin, so the dispatch, the 200-but-empty quality gate, the
[408, 429, 500, 502, 503, 504] failover list and the catch(pinErr)
branch were unguarded — exactly the logic the 2026-06-21 / 2026-06-22
incident comments call load-bearing. Adds five tests over a seeded
healthy provider connection so the pin is actually honored.

Hole B — orderRuntimeUnits was only ever driven with `priority`, which
is a no-op through it. Four of five strategy branches could be deleted
with nothing failing. Adds round-robin rotation and weighted sticky
ordering tests.

Hole C — recordRuntimeUnitStickySuccess never did anything under test:
both its guards need weighted/round-robin, so an early return changed
nothing. Covered by the new sticky-batch test.

Verified by re-running the mutations rather than assuming: all 7 that
previously survived (delete-pin-dispatch, serve-despite-failed-quality,
never-fail-over-on-transient, rr-counter-not-advanced, rotation-removed,
weighted-sticky-skipped, sticky-recording-no-op) are now killed.

The first sticky-batch test I wrote was itself vacuous — asserting "same
unit twice" holds equally when the recording helper is stubbed out, since
nothing advances the counter either. It now asserts the batch runs out
and rotation resumes on the third dispatch, which is what actually
distinguishes the two.

Also restores API_KEY_SECRET in test.after; it was set at module load and
never put back, inconsistent with the DATA_DIR handling beside it.

* fix(ci): teach known-symbols gate the relocated fusion/pipeline dispatch

The combo sub-check of check:known-symbols asserts every canonical routing
strategy has a real dispatch branch. It scanned a hardcoded file list and
matched only `strategy === "..."`, so the prelude extraction tripped it twice:

  [combo] 2 estratégia(s) canônica(s) sem branch de despacho em combo.ts:
      ✗ fusion
      ✗ pipeline

Both branches are still wired — they just moved to combo/dispatchPrelude.ts and
took the early-return guard form `if (strategy !== "fusion") return null;` that
extracting a branch into a `tryXDispatch()` leaf naturally produces.

Two changes, both extending existing precedent (the list already carries the
Block J leaves for the same reason):

- register combo/dispatchPrelude.ts in comboDispatchFiles
- widen the extractor to `strategy [!=]== "..."` so the inverted guard counts

Loose `==`/`!=` stay rejected, and no `handledNotCanonical` fallout: the gate
now reports 20 canonical strategies, all 20 via despacho.

* chore(ci): register combo-dispatch-prelude test in stryker tap.testFiles

check:mutation-test-coverage --strict failed once the known-symbols fix let
Fast Quality Gates advance to it:

  ✗ 2 covering unit test(s) across 2 module(s) are missing from
    stryker.conf.json tap.testFiles
      open-sse/services/combo/comboStructure.ts
      open-sse/services/combo/rrState.ts

The new tests/unit/combo-dispatch-prelude.test.ts exercises both modules, and
both are already in stryker's mutate list, so without the registration its
mutant kills would not have counted toward the nightly mutation gate.

Note (unchanged, still out of scope): combo/dispatchPrelude.ts itself is not in
stryker's `mutate` list. Adding it would widen the nightly mutation surface,
which is a separate call from fixing this drift.

* docs(changelog): add fragment for #8582 combo dispatch prelude
2026-07-27 19:06:56 -03:00
Diego Rodrigues de Sa e Souza
5be61dbcd6 feat(sse): relay upstream 4xx error bodies verbatim on the Anthropic request path (#8622)
* feat(sse): selective upstream 4xx error passthrough util

* feat(sse): relay upstream 4xx error bodies verbatim on the Anthropic request path

createErrorResult() gains an opt-in 7th param opts.passthrough; when set and
the upstream body is eligible (per shouldPassthroughUpstreamError), the
returned response body is the upstream 4xx body verbatim instead of the
sanitized wrapper. Internal classification fields (error/rawMessage/
errorType/errorCode) are never affected.

Wired into chatCore.ts's 7 upstream-error createErrorResult call sites
(model_unavailable / context_overflow / generic upstream-error branches),
gated on sourceFormat === FORMATS.CLAUDE so only /v1/messages requests get
the verbatim body — OpenAI-format paths are unchanged.

* test(quality): register upstream-error-passthrough in stryker tap.testFiles

`tests/unit/upstream-error-passthrough.test.ts` covers `open-sse/utils/error.ts`,
an instrumented module, so `check:mutation-test-coverage --strict` requires it in
`tap.testFiles` — the gate flagged the drift on this PR.
2026-07-26 16:30:52 -03:00
Diego Rodrigues de Sa e Souza
a096116f08 fix(quality): register the #8494 covering test in stryker tap.testFiles (#8692)
`tests/unit/8488-capability-filter-fail-closed.test.ts` landed with #8494 and
covers `open-sse/services/combo/comboStructure.ts`, an instrumented module, but
was never added to `tap.testFiles`. The `check:mutation-test-coverage --strict`
gate therefore fails on `release/v3.8.49` itself, turning the Fast Quality Gates
job red on every PR cut from it.

Registering the file restores the gate (verified: "No drift") and makes that
test's mutant kills count toward the module's mutation score.
2026-07-26 15:15:33 -03:00
MumuTW
c06cd83022 fix(sse): shallow per-target copy for the combo attempt body (#7847) (#8553)
* fix(sse): shallow per-target copy for the combo attempt body (#7847)

combo.ts deep-cloned the request body for every target. On a 3.05 MiB agent request that
is 9.53 MiB at 3 targets, and it scales linearly:

  3 targets    9.53 MiB (3.12x wire)  ->  ~0.001 MiB
  5 targets   15.89 MiB (5.19x wire)  ->  ~0.000 MiB
 10 targets   31.78 MiB (10.39x wire) ->  ~0.001 MiB

The isolation it bought only ever needed to contain TOP-LEVEL SCALAR writes. The full
mutation surface on this path is two assignments:

  combo.ts     bodyRecord.max_tokens = ...   (reasoning buffer)
  chatCore.ts  body.model = model            (Background Task Redirection T41)

Nothing mutates the nested payload; applyCompression and injectUniversalHandoffBody both
return new objects (verified empirically -- neither touches its input, and both tolerate a
frozen one). So a fresh top-level object per target gives identical isolation while sharing
the expensive messages/tools arrays.

Also fixes a REAL cross-target leak in handleRoundRobinCombo. It already used a shallow
copy, but took it only when the reasoning buffer actually changed max_tokens -- every other
attempt shared the caller's object outright. The new test reproduces it on the unmodified
code: target 2 received model "mutated-by-openai/gpt-4o-mini". In production that is a
Background Task Redirection on one round-robin target rewriting body.model for the next.
The copy is now unconditional.

The invariant is pinned by tests rather than by a comment listing mutation sites, so the
clone strategy can change again without anyone re-deriving them by hand:
 - a target's in-place write must not leak into the next (priority / fill-first /
   round-robin; the stub reproduces chatCore's body.model write)
 - the caller's body is never mutated
 - the per-target copy stays shallow (targets share one messages array)
 - freeze probe: combo's own body handling performs no in-place writes

* test(sse): register the combo attempt-body isolation test in stryker tap.testFiles

The new test resets the circuit breaker in beforeEach, so it counts as a covering test
for src/shared/utils/circuitBreaker.ts. Without registering it,
check:mutation-test-coverage --strict reported a 4th drift entry that was not there on the
pristine tip -- new drift introduced by this PR. Registered in sorted position; the gate is
back to the 3 pre-existing entries (accountFallback.ts, error.ts, comboPredicates.ts) that
#8538 addresses.
2026-07-26 03:52:35 -03:00
MumuTW
cc63ac9f53 test(sse): register #8396 and #8376 unit tests in stryker tap.testFiles (#8538)
* test(sse): register #8396 and #8376 unit tests in stryker tap.testFiles

check:mutation-test-coverage fails on release/v3.8.49 at its own HEAD:
two unit tests cover mutated modules but are absent from tap.testFiles,
so their mutant kills do not count and the drift gate blocks every
PR->release run.

- tests/unit/8396-cooldown-429-cap.test.ts covers
  open-sse/services/accountFallback.ts (imports checkFallbackError)
- tests/unit/8376-econnrefused-breaker.test.ts covers
  open-sse/services/combo/comboPredicates.ts (imports
  shouldRecordProviderBreakerFailure)

Both inserted in the array's existing sorted position; no other key
touched.

* test(sse): register repro-7503-no-choices in stryker tap.testFiles
2026-07-25 11:10:52 -03:00
backryun
7a8f9156da test(sse): repair two base-red gates on release/v3.8.49 (#8490)
* test(sse): repair two base-red gates on release/v3.8.49

`release/v3.8.49` is red at its own HEAD (36f8fd10) — `Quality Gates` and
`Release-Green (continuous)` both fail — so every PR opened against it inherits
four failing checks regardless of content. Two of those causes had no owner:
#8480/#8481/#8482 cover the compression ladder, the antigravity catalog, and the
resilience/translator regressions respectively, none of them these.

1. `chatcore-client-usage-buffer.test.ts` — 5/5 failing.

   #8331/#8356 (e8719783e) inserted an `options` parameter between
   `clientResponseFormat` and `deps` on `applyClientUsageBuffer()`. The five call
   sites here still passed `deps` in the fourth position, so the injected spies
   landed in the `options` slot and the real implementations ran — every
   `calls.*.length` assertion saw 0. The `Parameters<typeof
   applyClientUsageBuffer>[3]` cast in `makeDeps()` masked the type error, which is
   why it reached the branch. Call sites updated to `(resp, body, format, {}, deps)`
   and the cast repointed to `[4]`.

   Two cases added for the parameter that caused this, since nothing at this layer
   exercised it: `preserveContextBudgetInVisibleUsage` re-folds `context_budget_*`
   into the visible fields for the Claude-Code path, and the default path keeps the
   real unbuffered #8331 numbers.

2. `claude-to-openai-think-close-5123.test.ts` — 4 unsuppressed
   `@typescript-eslint/no-explicit-any` errors, failing `npm run lint`.

   Its suppression entry allows `count: 2` but the file had grown to four `(chunk:
   any)` callbacks. Rather than raise the frozen count, the cause is fixed:
   `collectChunks()` returns `StreamChunk[]` instead of `unknown[]`, narrowing once
   at the boundary so all four callbacks need no annotation. The suppression entry
   is then stale and removed, which ratchets the file to zero.

Test-only plus one allowlist deletion; no production code. Verified on a clean
worktree of the base commit: `npm run lint` clean (was 4 errors),
`chatcore-client-usage-buffer` 7/7 (was 0/5), `claude-to-openai-think-close-5123`
3/3.

* chore(ci): rebaseline five inherited file-size overages on release/v3.8.49

`check:file-size` fails on release/v3.8.49 at its own HEAD (36f8fd10), which is
what turns `Fast Quality Gates` red for every PR against this base:

  tokenHealthCheck.ts   832 -> 841
  chat.ts              1865 -> 1866
  auth.ts              2475 -> 2486
  accountFallback.ts   1941 -> 1960
  combo.ts             3630 -> 3642

None of these files is touched by this PR. The growth was inherited from already
merged PRs that did not bump their entries, so there is no offending branch left
to fix — the same situation the baseline already records under
`_rebaseline_2026_07_02_5798_release_green`, and the procedure it documents is to
raise the frozen values with a justification note.

Raised to the current base values only. The files stay frozen and cannot grow
further; an in-flight PR that adds lines to them bumps its own entry as usual
(#8482 touches accountFallback.ts and combo.ts and will need that).

* chore(ci): register 11 covering unit tests in stryker tap.testFiles

`check:mutation-test-coverage --strict` fails on release/v3.8.49 at its own HEAD:
11 unit tests that cover mutated modules are absent from `stryker.conf.json`
`tap.testFiles`, so their mutant kills do not count. The gate does not self-heal —
it names the exact files to add.

  open-sse/services/accountFallback.ts  + 8247-accountfallback-model-unhealthy,
                                          8248-accountfallback-nvidia-degraded,
                                          model-lockout-exact-cooldown-cap,
                                          repro-antigravity-404-family-cooldown-hijack
  src/sse/services/auth.ts              + 7993-noauth-proxy-routing,
                                          8200-perplexity-web-401-cooldown,
                                          sse-auth-antigravity-credits
  src/server/authz/routeGuard.ts        + authz/route-guard-vnc-session-local-only
  open-sse/utils/error.ts               + error-sensitive-redaction
  open-sse/utils/publicCreds.ts         + adobe-firefly
  src/shared/utils/circuitBreaker.ts    + 8332-combo-vision-fallback

None is a file this PR touches, and the gate reports the identical 11 on a worktree
carrying none of these base-red fixes. Additions only (11 insertions, 0 deletions);
the array stays sorted. This widens what the mutation run accounts for rather than
relaxing anything.

* chore(ci): re-measure accountFallback.ts file-size cap against the current base tip

The entry frozen in this PR (1960) was the value at 36f8fd10; the base has
since advanced to 1cafd328c and the file is 1966 there, so check:file-size
would still have been red on the merge commit. Re-measured to 1966.

Same inherited drift the note already documents: check:file-size does not run
on the PR->release fast path, so growth accrues unmeasured between release
rebaselines. The other four entries still match the current tip
(tokenHealthCheck 841, chat 1866, auth 2486, combo frozen 3642 >= 3640).

---------

Co-authored-by: backryun <busan011@ormbiz.co.kr>
2026-07-25 02:53:25 -03:00
Diego Rodrigues de Sa e Souza
6602af7478 feat: narrow mcp:connect scope + per-key HTTP tool-scope binding (#7895) (#7967)
* feat: narrow mcp:connect scope + per-key HTTP tool-scope binding (#7895)

Adds MCP_CONNECT_SCOPE ("mcp:connect"), a narrow additive API-key scope
(kept out of MANAGEMENT_API_KEY_SCOPES, same precedent as SELF_USAGE_SCOPE)
that authorizes ONLY the /api/mcp/ LOCAL_ONLY route-guard carve-out --
remote MCP-only callers no longer need broad manage/admin scope just to
reach the transport routes. Scoped strictly to /api/mcp/; every other
LOCAL_ONLY bypass prefix still requires hasManageScope() unchanged.

Also resolves the caller's real api_keys.scopes over HTTP/SSE
(httpAuthContext.ts::resolveMcpCallerAuthInfo) and passes it to the MCP
SDK's transport.handleRequest(req, { authInfo }), so extra.authInfo.scopes
reaching tool calls reflects the Bearer key's own scopes instead of the
OMNIROUTE_MCP_SCOPES env fallback -- scopeEnforcement.ts already prioritized
authInfo, it was simply unfed over HTTP. Does not flip the
OMNIROUTE_MCP_ENFORCE_SCOPES default; stdio is unaffected (no per-caller
identity, stays on the meta/env fallback chain).

Closes #7895

* test(mcp): register mcp-connect-scope test in stryker tap.testFiles (#7895)
2026-07-22 02:34:54 -03:00
Innokentiy Solntsev
f909b1d45e fix(resilience): don't cool down accounts or trip the breaker on client aborts (#7908)
* fix(resilience): don't cool down accounts or trip the breaker on client aborts

When the caller drops the connection mid-stream, the in-flight request surfaces
request_signal_aborted, "Client disconnected", or a DOM AbortError with no
upstream status code. These shapes were counted as provider failures: the
serving connection went into cooldown, the provider circuit breaker accrued
failures, and healthy accounts ended up marked unavailable from client-side
cancellations alone.

Treat client aborts as local stream lifecycle events (#4602 policy): extend
isLocalStreamLifecycleError() to recognize abort shapes and skip connection
disable and breaker accounting for them. Genuine upstream failures (5xx/429/401)
are still counted.

Fixes #7907.

* fix(resilience): guard the two remaining breaker-trip call sites against client aborts (#7907)

PR #7908 correctly wired isLocalStreamLifecycleError() into
shouldSkipConnDisable() and chatHelpers.ts's onStreamFailure, but two
separate breaker._onFailure()-triggering call sites were purely
status-code gated and never checked it, so a client-side abort (no
upstream status, defaults to 502, error='request_signal_aborted')
still tripped the whole-provider circuit breaker — the highest
blast-radius of the 3 resilience mechanisms:

- src/sse/handlers/chat.ts: the single-model, non-combo terminal-failure
  path called breaker._onFailure() directly, bypassing the isFailure
  option (which only applies inside breaker.execute()). Extracted the
  predicate into shouldTripProviderBreakerForResult() and added the
  missing isLocalStreamLifecycleError guard.
- open-sse/services/combo/comboPredicates.ts::shouldRecordProviderBreakerFailure(),
  used by handleComboChat's executeTarget (open-sse/services/combo.ts),
  gained the same guard via a new optional `error` field.

Added tests/unit/circuit-breaker-abort-provider-trip-7907.test.ts
exercising both real predicates directly (not just the isolated
isLocalStreamLifecycleError() helper) — confirmed red on the unfixed
code (missing export) and green after the fix, alongside the existing
#4602/#7908/combo-breaker-429 suites (24/24 pass, no regressions).

file-size-baseline.json: +1 combo.ts (irreducible call-site wiring for
the new `error` field) and +1 chatHelpers.ts (own growth from the PR's
already-verified onStreamFailure guard, surfaced only now since
fast-gates PR->release skip check:file-size).

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouzapw@users.noreply.github.com>
Co-authored-by: insoln <insoln@ya.ru>

* test(quality): register circuit-breaker abort tests in stryker tap.testFiles

The two new tests (circuit-breaker-abort-provider-trip-7907, circuit-breaker-client-abort)
import mutated modules (circuitBreaker.ts, comboPredicates.ts) but were not listed in
stryker.conf.json tap.testFiles, tripping check:mutation-test-coverage --strict.

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

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouzapw@users.noreply.github.com>
2026-07-21 12:01:47 -03:00
Diego Rodrigues de Sa e Souza
39ccfcf28c fix: parse Gemini 429 RetryInfo.retryDelay for model lockout (#7940) (#7961)
* fix(sse): parse Gemini 429 RetryInfo.retryDelay for model lockout (#7940)

Gemini free-tier 429 bodies carry a short explicit retry hint --
error.details[].{"@type": google.rpc.RetryInfo, retryDelay: "26s"} plus a
"Please retry in Ns." message -- but parseRetryFromErrorText only matched
'reset after'/'will reset after' text, so quotaResetHintMs came back null and
recordModelLockoutFailure fell back to getMsUntilTomorrow() for
quota_exhausted, locking the model out for ~19h instead of ~26s.

parseRetryHintFromJsonBody (retryAfterJson.ts) now walks error.details[] for
a google.rpc.RetryInfo entry and parses its retryDelay via a shared
parseDelayString helper (moved out of accountFallback.ts so
parseRetryAfterFromBody and the model-lockout path use the same grammar).
parseRetryFromErrorText also gained a 'please retry in Ns' text fallback for
bodies without a parseable details[] array. Both new paths are capped by a
dedicated MAX_SHORT_RETRY_HINT_MS (24h), independent of the existing 30-day
MAX_PROVIDER_COOLDOWN_MS, since RetryInfo/please-retry-in are short
throttling hints, not long-lived quota resets like Antigravity's 160h.

Regression test: tests/unit/bug-7940-gemini-retrydelay.test.ts (RED before
the fix: parseRetryFromErrorText returned null and the resulting lockout was
~19h; GREEN after: ~26s).

* chore(quality): register bug-7940-gemini-retrydelay test in stryker tap.testFiles
2026-07-21 09:15:27 -03:00
Diego Rodrigues de Sa e Souza
a47ce51d4c fix(api): classify /api/acp/agents as loopback-only (#7948) (#7966)
* fix(api): classify /api/acp/agents as loopback-only (#7948)

/api/acp/agents is spawn-capable (POST registers a client-chosen `binary`
via src/lib/acp/registry.ts; GET / POST {action:"refresh"} runs
detectInstalledAgents() -> execFileSync(probe.command, probe.args, { shell })
transitively) but was never added to LOCAL_ONLY_API_PREFIXES in
src/server/authz/routeGuard.ts, unlike every sibling spawn-capable route
(Hard Rules #15/#17). A leaked JWT via tunnel could reach the version-probe
execFileSync path.

Adds the prefix to the loopback gate plus a direct regression test
(tests/unit/route-guard-acp-agents-local-only.test.ts) and an assertion in
tests/unit/check-route-guard-membership.test.ts documenting why the existing
source-scan subcheck cannot catch this class of gap (the spawn call is
transitive via registry.ts, not in the route file itself).

* chore(quality): register route-guard-acp-agents-local-only test in stryker tap.testFiles
2026-07-21 09:03:31 -03:00
Innokentiy Solntsev
d3f8bbe555 fix(sse): recover invalid Anthropic thinking signatures once (#7906) 2026-07-20 22:22:30 -03:00
backryun
65e0aeda79 [Part 1/3]refactor(qwen): replace legacy Qwen Code and remove OAuth provider (#7866)
* refactor(cli): remove legacy Qwen Code integration

* refactor(qwen): remove deprecated Qwen OAuth provider

* feat(cli): rebuild Qwen Code integration for upstream V4

* fix(qwen): clear stale CLI auth on reset

* test(qwen): align retired provider coverage

* fix(db): renumber qwen-cleanup migration 129 -> 130

release/v3.8.49 tip took slot 129 via #7843 (usage_history_codex_strong_identity,
itself renumbered from 128 during the #7838/#7840 base-red cleanup) after this
branch forked; renumber remove_unregistered_qwen_data to 130.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>
2026-07-20 17:53:04 -03:00
Diego Rodrigues de Sa e Souza
b2efa35982 fix(sse): CC bridge loses OpenAI-format image input (OpenCode/Kilo/Cline → AgentRouter) (#7888)
* fix(sse): convert OpenAI media parts to Claude blocks in the CC bridge

OpenAI-format clients (OpenCode/Kilo/Cline) reach the Claude-Code-compatible
bridge untranslated: chatCore skips the OpenAI->Claude translator when
sourceFormat is OPENAI, so image_url / AI-SDK image / file parts either went
upstream in OpenAI shape (silently ignored) or were dropped by the text-only
extraction, and media-only user turns were removed by hasValidContent().

- claudeCodeCompatible: convertOpenAiMediaBlock() converts image_url
  (base64 + remote), AI-SDK string image and file parts (pdf->document,
  image mime->image) to Claude blocks in both bridge paths; Claude-native
  blocks pass through unchanged and the text-only wire image is preserved.
- claudeHelper: hasValidContent() now counts image/document blocks so
  media-only user turns are not silently deleted.

Reported-by: beingshafin
Refs #7777

* refactor(sse): extract CC media-block conversion to ccOpenAiMediaBlocks.ts

claudeCodeCompatible.ts is frozen at 1202 lines by check:file-size; the #7777
helpers pushed it to 1291. Move them to a dedicated module, no behavior change.

* test(quality): register cc-bridge-openai-image-7777 test in stryker tap.testFiles
2026-07-20 15:56:40 -03:00
Diego Rodrigues de Sa e Souza
3ba3cd145e chore(quality): fix release-tip base-reds — providerTierField case collision + stryker 7806 registration
(1) #7838 added providerTierField.ts next to ProviderTierField.tsx in the same
directory — a case-only collision that breaks webpack on case-insensitive
filesystems; the #6584 guard fails Unit shard 4/4 on every fresh PR run.
Rename the helper to providerTierFieldApi.ts (import + test path adjusted).
(2) check:mutation-test-coverage --strict fails on the tip because merged
#7806's combo-skip-conn-disable-plugin-block test was never registered in
stryker tap.testFiles. Register it.
2026-07-20 13:45:29 -03:00
Diego Rodrigues de Sa e Souza
0fb5fa66bc fix(quality): register nvidia-quota-phase1 and service-provider-plugin-registry in stryker tap.testFiles (#7796) 2026-07-19 16:38:02 -03:00
Diego Rodrigues de Sa e Souza
07b2cf9b7e test(ci): register 5 covering unit tests in stryker tap.testFiles (base-red unblock)
Clears the release base-red where account-fallback-lockout-eviction,
cliproxyapi-dedicated-credential-7645, combo-least-used-account,
combo/recovery-hint and route-guard-forge-jcode-settings-local-only
were covering mutated modules but missing from tap.testFiles.
2026-07-19 10:25:01 -03:00
Jan Leon
ea650253af Stream model health probes for slow providers (#7377)
* fix(model-test): stream slow chat probes

* fix(model-test): handle JSON responses for streaming probes

* fix(model-test): preserve transient errors from streaming probes

* fix(model-test): keep transient probe failures visible

* chore(ci): rerun pull request checks

* docs(model-test): clarify transient failure handling

* chore(ci): rerun pull request checks

* test(model-test): cover slow timeout response path

* chore(quality): register base-branch mutation tests

* fix(sse): stop real-network leak and DOMException crash in model-test-runner timeout path

Two new tests added by this PR fail against the current release tip:

- tests/unit/model-test-runner.test.ts's slow-timeout regression test races
  the cold-start cost of the chat-completions pipeline (SSE translators,
  compression settings, etc. all lazily init on the first real request in a
  process). With a 1s AbortController timeout, the abort can fire before
  chatCore ever reaches the executor's fetch() call; the mocked fetch is then
  invoked after the test's own `finally` block has already restored the real
  fetch, so the assertion on the mock never fires and the request leaks onto
  the real network. Warm up the pipeline with one fast, resolving mock call
  before timing the 1s scenario.

- Once the warm-up unblocks that race, a second, real bug surfaces:
  withRateLimit's abort handling (open-sse/services/rateLimitManager.ts)
  mutates `reason.name = "AbortError"` in place. When `AbortController.abort()`
  is called with no explicit reason (as modelTestRunner's timeout path does),
  the default reason is a native DOMException, whose `name` is a read-only
  getter — the mutation throws `TypeError: Cannot set property name of
  [object DOMException] which has only a getter` instead of rejecting
  cleanly. Build a fresh Error instead of mutating the caller-supplied
  reason, preserving the original as `.cause`.

Adds a focused regression test in tests/unit/rate-limit-manager.test.ts that
reproduces the DOMException crash directly against withRateLimit.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 21:18:42 -03:00
Rafael Dias Zendron
a5e5e88092 fix: DDG circuit breaker (#6999) + null content validation (#7000) (#7001)
* feat(6922): register effort-tier aliases for glm-5.2 & mimo-v2.5 on opencode-go

Previously only deepseek-v4-pro had effort-tier aliases on the opencode-go
provider. GLM-5.2 and MiMo-V2.5 only had base model ids, making it impossible
to pin reasoning effort per combo target.

Changes:
- Generalize parseDeepSeekEffortLevel → parseEffortLevel with EFFORT_TIERS table
- deepseek-v4-pro: low/medium/high/max (unchanged)
- glm-5.2: high/max only (OpenAI transport; low/medium not supported)
- mimo-v2.5: high/max only (same reasoning)
- Register alias model ids in opencode-go registry
- Mark base models supportsReasoning: true
- 9 unit tests covering registry + executor + backward compat

Closes #6922

* ci: retrigger CI for Electron Package Smoke flaky test

* ci: retrigger flaky Electron Package Smoke

* test(#6922): rewrite tests to call real parseEffortLevel function

- Export parseEffortLevel from opencode.ts so tests can import it
- Replace grep-on-source-file assertions with real function calls
- 13 tests: 4 deepseek tiers + 2 glm-5.2 tiers + 2 mimo-v2.5 tiers
  + 5 negative cases (unknown model, unsupported tiers, empty, base-only)
- Remove dependency on readFileSync / string matching

* fix: DDG circuit breaker (#6999) + null content validation (#7000)

#6999: Add lightweight circuit breaker to DuckDuckGo executor.
After 5 consecutive failures (429, 5xx, network errors), the breaker
opens for 30s — during that window every request fast-fails with 503
so the combo engine can immediately fail over to the next provider
instead of waiting for timeouts. Half-open probing happens naturally
once the cooldown expires. A single success resets the counter.

#7000: Fix false positive in validateResponseQuality where multimodal
content arrays (empty []) and whitespace-only strings passed as valid.
Now properly validates: arrays must have >=1 non-empty part; strings
must have non-zero trimmed length.

* test: add regression tests for DDG circuit breaker (#6999) and null content validation (#7000)

- Circuit breaker: verifies 400 for empty messages is unaffected by CB state,
  and that CB starts closed (no 503 on first request)
- Null content (#7000): verifies validateResponseQuality correctly flags
  null content, empty array content [] as invalid, and array with text as valid

* fix(ci): add ddg-circuit-breaker test to stryker tap.testFiles for mutation coverage gate

* test(#6999): exercise the DDG circuit breaker state machine directly

The existing "circuit breaker fast-fails with 503 after consecutive
failures" test never actually drives 5 consecutive failures — it makes a
single real network call and only asserts the response isn't 503, which
passes whether or not the breaker logic works at all (confirmed by
disabling the open-threshold check entirely: that test stayed green).

Exports cbIsOpen/cbRecordFailure/cbRecordSuccess/CB_THRESHOLD/
CB_COOLDOWN_MS (previously module-private) plus two test-only helpers
(__setDdgCircuitBreakerStateForTests/__getDdgCircuitBreakerStateForTests,
following the __xxxForTests convention already used in
src/shared/utils/circuitBreaker.ts) so tests can drive the module-level
singleton directly instead of needing a full network mock through
warmSession/seedChallengeChain/acquireAuthHeaders, and without waiting
CB_COOLDOWN_MS=30s in real time for the half-open case.

New tests cover: starts closed; opens on the CB_THRESHOLD-th consecutive
failure (not before); execute() fast-fails with 503 while open without
reaching the network (verified: disabling the cbIsOpen() gate makes the
same test fall through to a real network call, ~1s slower and red);
still open just before cooldown elapses; self-closes once cooldown has
elapsed (half-open); cbRecordSuccess resets the counter.

Red-first proof (both independently green->red->restored-green):
1. `if (false && failures >= CB_THRESHOLD ...)` — neuters the open
   transition. Result: the new "opens after CB_THRESHOLD..." test fails;
   the pre-existing weak test stays green regardless.
2. `if (false && cbIsOpen())` — neuters the execute() gate. Result: the
   new "execute() fast-fails with 503 while open" test fails (and takes
   ~1s longer, falling through to a real network attempt instead of
   short-circuiting).

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 15:17:57 -03:00
Diego Rodrigues de Sa e Souza
b6de89fae4 chore(quality): register #6672 test in stryker tap.testFiles (base-red unblock) (#7652)
Validated in local merge-train @ 8f27177d1 (full parity suite green: typecheck+file-size+complexity+cognitive+changelog+unit shards 1&2+vitest)
2026-07-17 22:09:02 -03:00
Diego Rodrigues de Sa e Souza
6695cbbf7a feat: add EdgeTTS audio-tts provider (#6668) (#7605)
* feat(sse): add EdgeTTS audio-tts provider (#6668)

Registers Microsoft Edge "Read Aloud" as a new no-API-key AUDIO_SPEECH_PROVIDERS
entry — the first WebSocket-transport TTS provider in the registry. Reverse-
engineered/unofficial endpoint, same class of integration already accepted for
other "-web" style providers (chatgpt-web.ts, copilot-web.ts).

- open-sse/executors/edgeTts.ts: pure Sec-MS-GEC token construction (SHA-256
  over a public trusted-client-token + rounded Windows file-time ticks, ported
  from rany2/edge-tts drm.py), WS message framing (speech.config/ssml),
  binary-chunk demuxing, SSML building/escaping, and the WS synth call itself
  (injectable WebSocket ctor for tests, lazy `import("ws")` in production so
  it never enters esbuild's top-level CJS bundle graph). Per-client-IP
  sliding-window throttle (SlidingWindowLimiter) since there's no per-user key
  — one abusive deployment could otherwise get the shared trusted token
  rate-limited for everyone.
- open-sse/utils/publicCreds.ts: embeds the trusted-client-token via
  resolvePublicCred() (Hard Rule #11) — it's a constant hardcoded in every
  Edge build and every open-source edge-tts port, not a per-user secret.
- Extracted open-sse/utils/audioResponse.ts (shared response helpers) and
  open-sse/executors/awsPollyTts.ts (AWS Polly handler) out of
  open-sse/handlers/audioSpeech.ts to stay under its frozen file-size ratchet
  baseline while making room for the new branch — no behavior change to
  either extracted piece.
- src/app/api/v1/audio/speech/route.ts: thread the caller's IP through to the
  handler for the new throttle.

Tests: tests/unit/edgetts-provider.test.ts (23 cases) — Sec-MS-GEC determinism
and cross-check against a hand-derived reference vector, message framing,
binary demux, SSML escaping/injection-safety, registry lookup, publicCreds
shape, and the error path via an injected fake WebSocket (upstream failure ->
sanitized 502, no stack/path leak; Hard Rule #12), plus the per-IP rate limit.
No live upstream is required or used — the reverse-engineered protocol can't
be validated against real credentials, but every pure/testable seam is
covered per the TDD path in the bug/feature validation gate.

* test(mutation): register edgetts-provider.test.ts in stryker tap.testFiles (#6668)

The new provider's unit test covers a mutated module, so the strict
mutation-test-coverage gate requires it in stryker.conf.json's
tap.testFiles. Single-line addition (kept the file's existing formatting).
2026-07-17 11:32:25 -03:00
Diego Rodrigues de Sa e Souza
624aba2498 feat(cli): add Grok Build CLI tool setup (~/.grok/config.toml) (#7241)
* feat(cli): add Grok Build CLI tool setup (~/.grok/config.toml)

Registers xAI's Grok Build TUI coding agent as a configurable CLI tool in
/dashboard/cli-code, so OmniRoute can write itself in as a custom model
provider in ~/.grok/config.toml.

Mechanism: Grok Build reads a TOML config that can hold several user-defined
[model.*] sections plus a [models].default pointer. Unlike the sibling Forge
handler (which owns its whole config file and can full-replace it), this one
surgically upserts ONLY the [model.omniroute] section and rewrites
[models].default, leaving every other section byte-intact. Apply records the
previous default in an `# omniroute-prev-default` marker comment so Reset can
restore the user's original default instead of guessing.

Built on OmniRoute's existing CLI-tools infrastructure rather than replaying
the upstream shape: getCliRuntimeStatus() for detection (no ad-hoc
`which grok` exec), Zod validation via cliModelConfigSchema, the write guard,
createBackup(), the cliToolState DB module, and sanitizeErrorMessage() for
every error path (Hard Rule #12).

Security: GET reaches getCliRuntimeStatus(), which spawns a child process to
locate and healthcheck the `grok` binary. That is the same transitive-spawn
surface that classified /api/skills/collect/, so the route is registered in
LOCAL_ONLY_API_PREFIXES and loopback-enforced before any auth check
(Hard Rules #15 + #17). Writing a local CLI's config file is inherently a
local-machine operation, so this costs no real capability.

Co-authored-by: rixzkiye <rizkiyemubarok05@gmail.com>
Inspired-by: https://github.com/decolua/9router/pull/2571

* chore(changelog): fragment for #7241

* fix(cli): shrink cliTools.ts/cliRuntime.ts under the file-size ratchet + fix stale catalog counts

The grok-build registry/runtime entries pushed cliTools.ts (916->932) and
cliRuntime.ts (1128->1137) past their frozen file-size caps. Extract the
grok-build entries into cliToolsGrokBuild.ts (registry, typed) and
cliRuntimeGrokBuild.ts (runtime metadata, deliberately untyped/no
cliCatalog import so it doesn't drag that schema file into the
typecheck:core curated allowlist's transitive graph). The amp runtime
entry rides along in the same runtime file for the extra headroom needed
to clear cliRuntime.ts's cap with zero slack.

Also update the two catalog-cardinality canaries (cli-tools-schema.test.ts,
cli-catalog-counts.test.ts) and EXPECTED_CODE_COUNT to include grok-build:
20->21 visible code entries, 24->25 total code entries, 32->33 grand total.

Fixes CI reds on #7241 surviving a release/v3.8.49 merge: Fast Quality
Gates (check:file-size) and Unit Tests fast-path (1/4, 2/4).

* test(stryker): register grok-build route-guard test in tap.testFiles

check:mutation-test-coverage --strict flagged
tests/unit/route-guard-grok-build-settings-local-only.test.ts as a covering
unit test for src/server/authz/routeGuard.ts missing from
stryker.conf.json's tap.testFiles allowlist (only became reachable once the
Fast Quality Gates job got past the file-size fix earlier in this branch).

---------

Co-authored-by: rixzkiye <rizkiyemubarok05@gmail.com>
2026-07-17 10:40:41 -03:00
Diego Rodrigues de Sa e Souza
fce2bb67ad fix: recognize Ollama Cloud session usage-limit 429 as quota-exhausted (#7071) (#7181)
* fix: recognize Ollama Cloud session usage-limit 429 as quota-exhausted (#7071)

Ollama Cloud's 5-hour "session" usage-limit 429 body ("you (NAME) have
reached your session usage limit...") was never recognized as
quota-exhausted -- only the sibling "weekly usage limit" wording was
fixed (#6638/#3709). Neither the generic QUOTA_PATTERNS list nor the
dedicated weekly-quota classifier matched the session wording, so
checkFallbackError() fell through to the generic ~3s rate-limit
backoff instead of a long QUOTA_EXHAUSTED cooldown -- combo/LKGP
routing cycled back to the "exhausted" account almost immediately
instead of advancing to the next one.

Adds isSessionUsageLimitText()/buildSessionQuotaFallback() to
quotaTextCooldowns.ts, mirroring the weekly-quota pair, with a 5h
cooldown matching Ollama Cloud's documented session window. Wired
unconditionally into checkFallbackError() next to the weekly check so
apikey-category providers like ollama-cloud are covered.

* chore(test): register issue-7071-ollama-session-quota.test.ts in stryker tap.testFiles (#7071)
2026-07-14 21:45:20 -03:00
Diego Rodrigues de Sa e Souza
2a5f9a5ed7 fix(sse): flatten structured (array) content in Qwen Web executor (#6927)
* fix(sse): flatten structured (array) content in Qwen Web executor

foldMessages did String(m.content), turning OpenAI-style content-part arrays
into the literal "[object Object]" prompt. Add contentToText() to extract the
text parts. Reported on the support mesh.

TDD: red->green regression test tests/unit/qwen-web-content-array-serialization.test.ts

* docs(changelog): add fragment for #6927

* fix(stryker): register qwen-web content-array test in tap.testFiles

Fast Quality Gates flagged the new coverage for open-sse/executors/qwen-web.ts
as missing from stryker.conf.json's tap.testFiles list.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-12 10:26:58 -03:00