Commit Graph

8115 Commits

Author SHA1 Message Date
Bob.Hou
6dd82b77de fix(guardrails): pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) (#12169)
* fix(guardrails): pass providerId to getResolvedModelCapabilities in checkComboVision (#12112)

Signed-off-by: Minxi Hou <houminxi@gmail.com>

* chore(quality): register combo-vision providerId test in the stryker tap set

---------

Signed-off-by: Minxi Hou <houminxi@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-09-01 12:08:04 -03:00
Rafa Martins
17792ce0ad Update README.md (#12194)
Atualizaçao link global do Grupo 2

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-09-01 12:07:42 -03:00
Nguyen Thanh Dat
5ff6513ca5 fix(combos): send null to clear an agent feature instead of omitting it (#12177)
* fix(combos): send null to clear an agent feature instead of omitting it

PUT /api/combos/[id] merges its body over the stored record, so an omitted
field means "leave unchanged". The combos editor deleted a cleared agent
feature from the payload, so unchecking context cache protection -- or
emptying the system message or the tool filter -- never persisted: the old
value survived the merge and the editor reopened with the toggle still on.

updateCombo already deletes any key explicitly set to null, which is how
description and context_length are cleared in the same save handler. Use the
same shape for the three agent fields, and make them nullable in
updateComboSchema so the null survives validation.

The clearing logic moves into comboAgentFeatures.ts so it can be tested
directly, matching comboQuotaOnlyFallback.ts next to it.

Fixes #12158

* chore(changelog): point the fragment at the real PR number
2026-09-01 12:07:22 -03:00
Syed Raheemuddin
8fc6834372 fix(system): propagate abort signal to stream reader in HTTP version checks (#12232)
When fetching version metadata from npm registry or GitHub APIs, if the remote connection stalls during stream reading, the 10-second AbortController timer aborts the request signal but the underlying body reader stream was not listening to the abort signal. This caused readBoundedJson reader.read() loop to hang until external socket close.

Now readBoundedJson listens to AbortSignal abort events, triggers reader.cancel(), and releases locks immediately on abort.
2026-09-01 12:07:02 -03:00
Dizzle
ad4b67d631 feat(radar): show the rate limits and training disclosure the feed already sends (#12320)
The Radar feed reports per-model rate limits and whether a provider says it may train on your prompts. Both fields are on RadarMergedEntry and the catalog table rendered neither — grep them in RadarCatalogTable.tsx and the only hits were the type declaration. The training flag is the one that stings: freeModelCatalog.ts documents it as "Surfaced in the UI", a promise the UI did not keep, and thirteen catalog entries carry it today.

Adds a Rate limits column and a badge in the ToS cell when a provider discloses training. No new data, no request, no API change.

Two judgement calls worth keeping: a limit of zero renders as 0/min rather than formatTokens' "rate-only" (right for a monthly budget, nonsense for a ceiling where zero is a real and alarming fact), and the badge condition is === true, since an absent training statement is not a guarantee.

Validation note — read before trusting the green: this PR's own suite (tests/unit/dashboard/radar-catalog-table-limits-training.test.tsx, 14 cases) could NOT be run locally. Vitest fails to resolve react18-json-view, which is declared in package.json and package-lock.json but is not present in this machine's node_modules; two pre-existing .test.tsx files in the same directory fail identically, so the cause is environmental and not this PR. The blocking test-vitest CI job runs npm ci and will execute it.

What was verified locally, in a combined batch worktree with all 11 PRs of this batch: 174/174 node-runner focused tests, typecheck:core clean, complexity 2706/3218, cognitive-complexity 1221/1437, check:cycles and check:docs-counts green, plus both CI i18n gates for the 42-locale pass — check-ui-keys-coverage PASS (all 42 locales at or above 65%) and check-ui-value-drift PASS against the release tip.

Thanks @maxmad64bis.
2026-09-01 11:58:50 -03:00
Dizzle
5253b93b89 feat(rankings): order free providers by measured reliability (#12218)
Free Provider Rankings sorted by the top model's Arena score, so a provider stayed first even if it failed every call — the reliability column from #11546 already showed what each one actually served, but ordering ignored it.

Adds an opt-in ?sortBy=reliability (API) and a "Most reliable first" toggle (page) sharing one comparator in freeProviderRankingsUsage.ts: measured providers first by successRate desc with ELO on ties, then unmeasured in their incoming order. The default is unchanged and locked by tests. successRate is null below MIN_USAGE_REQUESTS = 5 (existing, never zero), and ordering runs before slice(0, limit) so limit counts in the requested order. The toggle composes with the existing sortTypeFirst/groupByType grouping — a stable sort keeps reliability order within each group.

Opt-in is the right default here: the page is for discovery, including providers never called.

13 tests across three files (6 new for the comparator in isolation, plus filter and route coverage including unknown sortBy → 400 and the default path staying off call_logs).

Verified in a combined batch worktree with all 11 PRs of this batch: 174/174 focused tests, typecheck:core clean, complexity 2706/3218, cognitive-complexity 1221/1437, check:cycles and check:docs-counts green. The 42-locale i18n pass was checked with the CI gates: check-ui-keys-coverage PASS (all 42 locales at or above 65%) and check-ui-value-drift PASS against the release tip.

Thanks @maxmad64bis.
2026-09-01 11:58:15 -03:00
Dizzle
51587084ca fix(docs): the free-tier catalog ships no per-row confidence tag (#12318)
docs/reference/FREE_TIERS.md said its numbers were "gathered by web research (confidence tagged per row)". No entry carries one: grep -c confidence on the catalog returns 0, the type does not declare the field, and the API serves nothing of the sort. A reader looking for "how much can I trust this figure" was pointed at a per-row signal that never existed.

Replaced with the two counts the data actually supports — 7 of 446 entries carry hardStopGuaranteed (the field with the strictest sourcing rule in the repo: set only when the provider's own terms document that exceeding the free allowance refuses the request, source in a comment, never defaulted to true) and 13 carry a prompt-training disclosure. check:docs-counts reads both from the catalog at runtime, as required claims, so a reworded or deleted sentence fails rather than passing as "no claim in this file".

The PR deliberately does not add a confidence field — curating one is a product call, and it says so instead of inventing it.

Reconciled on merge: #12316 landed the gate extension underneath, so scripts/check/check-docs-counts-sync.mjs and its test took the tip's side plus this PR's own required-claim additions. Verified afterwards: check:docs-counts green, 48/48 across check-docs-counts-sync and free-catalog-no-confidence-field.

Thanks @maxmad64bis — checking the gate against a number it should reject (7 swapped for 99) is the right way to prove a gate works.
2026-09-01 11:57:54 -03:00
Nads
bdf218387b fix(resilience): per-model 402 on a passthrough gateway no longer terminalizes the whole connection (#12266)
* fix(resilience): per-model 402 on a passthrough gateway no longer terminalizes the whole connection

402 variant of #3027. Passthrough/gateway providers that multiplex many
models behind one credential (kilo-gateway, ollama-cloud, etc.) can 402
on a single PAID model while free models on the same key remain
perfectly usable. Previously any 402 unconditionally set the connection
to a terminal `credits_exhausted` status, which is never auto-recovered
without an operator reset — taking out every remaining model on that
provider, amplified further inside combo routing (measured: one 402
removed 9 of 14 fallback targets in a real combo, dropping success rate
from 98.3% to 74.2% on a fixed load test per the issue report).

Root cause (matches the issue's own analysis):
1. resolveTerminalConnectionStatus() returned "credits_exhausted" for
   ANY status === 402, with no per-model/passthrough check.
2. The generic per-model lockout gate (404/429/>=500) excluded 402.
3. The #3027 403-branch is gated on `!terminalStatus` — since (1) already
   resolves a terminal status for any 402 before that branch runs, simply
   adding 402 to its condition alone would not have fired.

Fix:
- resolveTerminalConnectionStatus() now takes isPerModelQuotaProvider and
  skips the connection-wide terminal path for a bare `status === 402`
  when true, letting it fall through to the per-model lockout branch
  instead. An explicit result.creditsExhausted (a provider's own
  classification, independent of HTTP status) is untouched and remains
  unconditionally terminal.
- Extended the existing #3027 per-model lockout branch to also handle
  402 (reason "credits" vs "forbidden" for 403), reusing the same
  cooldown/lockout machinery and log format.
- Single-credential (non-passthrough) providers are unaffected:
  isPerModelQuotaProvider is false there, so a 402 still terminalizes
  the connection as before — that behavior is deliberate for prepaid
  API keys (#5239 / #10616).

Also checked the issue's 4th root cause (terminal statuses never
auto-recovering) against the current codebase: connectionRecovery.ts
already has a 30-minute credits_exhausted reprobe
(isCreditsExhaustedReprobeCandidate) that the issue's report — filed
against v3.8.49 — didn't account for. The other two files it names
(rateLimit.ts's clearStaleCrashCooldowns, tokenHealthCheck.ts's
OAuth-refresh skip) legitimately exclude credits_exhausted for
unrelated reasons and are not bugs. Moot regardless: this fix prevents
credits_exhausted from being set at all for the passthrough case, so no
recovery wait is needed in the first place.

Tests: tests/unit/auth-passthrough-per-model-402-12242.test.ts, modeled
on the existing #3027 precedent test (real DB-backed integration test
via auth.markAccountUnavailable). Covers: paid-model-only lockout with
free model unaffected, a subsequent free-model request succeeding after
a sibling paid-model 402, single-credential 402 still fully terminal,
and no connection-wide backoff escalation on repeated 402s.

Verified:
- node --import tsx/esm --test tests/unit/auth-passthrough-per-model-402-12242.test.ts: 4/4 pass
- All related pre-existing tests (auth-ollama-cloud-per-model-403-3027,
  auth-terminal-status, openrouter-free-model-credits-exhausted,
  vertex-passthrough-model-lockout, 10347-embed-402-cooldown): 27/27
  pass, no regressions
- npm run typecheck:core: 0 errors
- npm run check:cycles: no cycles
- eslint (auth.ts + new test file, with project suppressions): 0 errors

Fixes #12242

* chore(quality): register 402 per-model test in stryker tap and de-ratchet auth.ts

- stryker.conf.json: add tests/unit/auth-passthrough-per-model-402-12242.test.ts
  to tap.testFiles in its alphabetical slot
- auth.ts: extract the #12242 connection-wide 402 decision into the pure helper
  isConnectionWideCreditsExhausted() so resolveTerminalConnectionStatus stays
  within the cyclomatic ratchet (file back to the base's 11 violations)

---------

Co-authored-by: OmniRoute Dev <dev@local>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-09-01 11:52:21 -03:00
Dizzle
3b82d85081 docs(auto-combo): complete the mode pack table and gate what it claims (#12316)
docs/routing/AUTO-COMBO.md documented four mode packs; six ship. It printed 0.14 where modePacks.ts says 0.1333. And nothing was watching: four documents stated a scoring-factor count and check:docs-counts covered none of them. Wiring them up turned the gate red on seven real drifts — ARCHITECTURE.md and REPOSITORY_MAP.md at "9-factor" (code: 15) and "4 mode packs" (code: 6), RESILIENCE_GUIDE.md and SKILL.md at 13, AUTO-COMBO-GUIDE.md at both 5 and 13. ARCHITECTURE.md did not merely have the wrong number: it named nine factors that are not the engine's, and its four "mode packs" were the auto/* request prefixes.

A product fact fell out of writing the table: no pack sets quality, and applying a pack replaces the weight map wholesale (weights = pack in engine.ts, not a merge), so quality carries 0.03 by default and normalizes to 0 under any pack — pick a mode pack and the observed-quality signal stops voting. Documented, not changed.

The gate reads pack names from the module through the tsx subprocess that already reads every other code-derived count, matching the three spellings the docs actually use; on the reference document a missing claim now fails rather than passing. The dashboard was behind too (four of six packs offered); the count is dropped from the strategy label rather than corrected, since nothing reads selector labels and a right-today number goes stale unnoticed.

Verified in a combined batch worktree: 174/174 focused tests across all 11 PRs of this batch, typecheck:core clean, and check:docs-counts green with the four newly-wired documents.

Thanks @maxmad64bis — finding the quality-under-a-pack behaviour while writing a docs table is the kind of thing a table is for.
2026-09-01 11:50:57 -03:00
Dizzle
d19572fb95 fix(combo): expose every scoring weight the engine actually uses (#12314)
Two of the fifteen factors calculateScore applies could not be set by anyone. scoringWeightsSchema is a plain z.object, so zod strips what it does not name: PUT a combo with connectionDensity and you get a 200 back with nothing saved, and normalizeScoringWeights then reads the gap as a deliberate zero — switching off anti-concentration and the quality signal. DEFAULT_INTELLIGENT_WEIGHTS, the dashboard's own copy, missed the same two and every non-zero value differed from the engine's; summing to 1.05, validateWeights rejected them outright.

This adds the two keys to both lists and takes the dashboard defaults from DEFAULT_WEIGHTS. The scorer is not touched.

One behaviour change, and it is the point: a combo whose stored weights omitted the two keys was running with them at zero and the other thirteen renormalized upward. It now uses the engine's distribution (quota 0.1549 → 0.1429, health 0.1740 → 0.1605) and a test pins those numbers.

Left alone and documented rather than widened: rounded percentages now total 101% (six factors at 4.76% each render as 5%), and five stale .default() values in the schema that only bite when a config omits the key.

Verified in a combined batch worktree: 174/174 focused tests across all 11 PRs of this batch, typecheck:core clean, complexity 2706/3218, cognitive-complexity 1221/1437, check:cycles and check:docs-counts green.

Thanks @maxmad64bis — the red-before-green note (7 of 8 failing, and naming the one that passes on purpose) is exactly the evidence that makes a behaviour change reviewable.
2026-09-01 11:50:33 -03:00
Dizzle
5e6c9a92dc fix(sse): estimate usage in passthrough stream even with include_usage (#12151)
A passthrough stream could end with no usage even though the client asked for it via stream_options: {include_usage: true}, so providers that do not meter always showed 0 tokens. The fix estimates usage at the finish marker when the upstream stays silent (flagged estimated: true) and drops any duplicate trailing usage chunk so the client never sees two.

open-sse/utils/stream.ts:1982,1749 · open-sse/utils/usageTracking.ts:651,664

Six cases: the predicate (finish without usage but with content, trailing valid, empty response, tool-only) plus two SSE harness cases through createSSEStream passthrough.

Note on base: this branch forked 442 commits back and carried a base-red marker for #12109, which is now closed — the release tip has no open base-red issue. It merged cleanly against the current tip regardless.

Verified in a combined batch worktree: 174/174 focused tests across all 11 PRs of this batch (this PR's stream-passthrough-usage-estimation suite included), typecheck:core clean, check:cycles and check:docs-counts green.

Thanks @maxmad64bis.
2026-09-01 11:50:15 -03:00
Dizzle
2f33f2c20d feat(routing): report why the zero-cost guard excluded a candidate (#12319)
With freeAccessPolicy: "strict" the read-only candidate listing silently dropped rows, so an operator could not tell "no free allowance left" from "the quota fetcher is broken" — in a listing whose own module header promises a candidate the routing path would skip "is never dropped". #9133 settled the same question for the resilience filter via a skip opt-out; the zero-cost guard never got one.

It gets it now: the guard is disabled for the inspector build exactly as the resilience filter already is, and every candidate carries freeAccessExclusion — null when satisfied, otherwise one of seven named reasons. The last three (exhausted, state-unknown, no-connection) are the point: they used to look identical because the row just disappeared. STRICT_ZERO_COST.md documents what each asks the operator to do.

Dispatch is untouched and a test pins that. The three existing guard suites were not modified — their 31 cases are the net and still pass. excludeTosAvoid still drops candidates without a reason; documented as a separate question rather than widened into here.

Verified in a combined batch worktree: 174/174 focused tests across all 11 PRs of this batch, typecheck:core clean, complexity 2706/3218, cognitive-complexity 1221/1437, check:cycles and check:docs-counts green.

Thanks @maxmad64bis — the reason table and the honest note about the order change (freshness before status) made this easy to review.
2026-09-01 11:49:56 -03:00
Dizzle
33bdc386bc fix(free-tier): never serve a Radar overlay older than the shipped catalog (#12215)
GET /api/free-tier/summary could answer from a Radar overlay built 2026-08-02 while the release ships a catalog curated 2026-08-30 (FREE_CATALOG_CURATED_AT) — totals computed from older data, still tagged catalogSource: radar-overlay. The route now refuses any overlay built before the shipped catalog and falls back to that catalog through the operator's local state.

Tightens #11550 using the generatedAt persisted by #11435.

Verified in a combined batch worktree: 174/174 focused tests across all 11 PRs of this batch (this PR's free-tier-summary-radar-overlay suite included), typecheck:core clean, check-file-size, check-changelog-integrity, check:cycles and check:docs-counts green.

Thanks @maxmad64bis.
2026-09-01 11:49:49 -03:00
Dizzle
78a0e4b109 fix(usage): declare the Adobe Firefly usage fetcher the dispatcher already calls (#12321)
usage/fetcherProviders.ts exists, in its own words, "so the registration list can't drift from the dispatcher's switch statement". It drifted: #8006 added adobe-firefly and firefly to the dispatcher and to USAGE_SUPPORTED_PROVIDERS but not to this list, so the connection UI advertised usage support while the provider-plugin manifest, genericQuotaFetcher and the free-access quota cache all reported no fetcher — for two ids getUsageForProvider would happily serve.

Declaring them is what makes the balance actually get fetched (registerGenericQuotaFetchers wires a generic fetcher per declared id, and resolveFreeAccessState stops returning early), which the PR states plainly rather than burying as a side effect.

The test turns the docstring's prose invariant into enforcement: it reads the dispatcher's cases from source and compares both directions, and records each accepted difference against USAGE_SUPPORTED_PROVIDERS with a reason plus a staleness check, so the next drift can't hide among them. xiaomi-mimo-token-plan is left flagged as a real gap rather than widening the PR.

Verified in a combined batch worktree: 174/174 focused tests across all 11 PRs of this batch, typecheck:core clean, complexity 2706/3218, cognitive-complexity 1221/1437, check:cycles and check:docs-counts green.

Thanks @maxmad64bis.
2026-09-01 11:49:29 -03:00
Diego Rodrigues de Sa e Souza
accdfa9f33 fix(usage): console-aware Token Plan guidance + subscription hint on bailian 401 (#12288)
* fix(usage): console-aware Token Plan guidance and subscription hint on bailian 401

The personal Token Plan is sold through two consoles with different portals,
gateway hosts and login tickets. Two operator-facing messages ignored the split:

- The quota guidance always said 'get the cookie at home.qwencloud.com', even
  for connections served by the Alibaba Model Studio console — following it
  verbatim produces a cookie the gateway rejects (console mismatch →
  BailianGateway.Login.NotLogined). The guidance now derives the console from
  the provider via resolveConsoleSite, matching what the fetcher will do with
  the pasted cookie.
- Key validation mapped upstream 401 to a bare 'Invalid API key'. An expired
  Token Plan subscription produces the exact same upstream 401 (observed live
  2026-09-01: subscription ended 08-23, the working key started failing), so
  the message now names the subscription as a cause worth checking.

* test(providers): align the remaining bailian 401/403 message pins to prefix match

search-provider-validation.test.ts pinned the exact 'Invalid API key' string for
the bailian validator; the message now also names an expired Token Plan
subscription. Same property asserted (401/403 => invalid), prefix match.
2026-09-01 11:40:40 -03:00
Diego Rodrigues de Sa e Souza
9d04995950 chore(quality): baseline-headroom skips generated and vendored files in the fileSize worst-file signal (#12291)
The nightly headroom monitor flagged fileSize 🟡 permanently because the worst
frozen file was src/app/docs/lib/openapi.generated.ts — frozen at its emitter's
exact output size in #12212, i.e. ~0% headroom BY CONSTRUCTION (growth is
policed by conscious re-freezes, never by editing the module). Same class:
open-sse/vendor/** (upstream code nobody slims by hand).

isMonitorExemptFile() excludes .generated. modules and vendor/ paths from the
monitor's worst/near-cap accounting only — check:file-size itself still
enforces both. The fileSize row now points at the worst HUMAN-EDITABLE frozen
file (currently tests/integration/skills-pipeline.test.ts at 4.5%, a true
early warning: its baseline note already requires a split rationale for any
further growth).

Refs #12149
2026-09-01 11:25:34 -03:00
Diego Rodrigues de Sa e Souza
abbbca216d feat(sse): full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) (#12289)
* feat(sse): add it/ru/zh caveman output instructions

* fix(sse): expose the dormant terse-prose translations through the catalog

* feat(sse): translate less-code to es/de/fr/it/ru/zh

* feat(sse): translate ponytail to es/de/fr/it/ru/zh

* feat(sse): translate i-have-adhd to es/de/fr/it/ru/zh

* test(sse): anchor wave-2 output-style translations to their own language

* feat(dashboard): offer every output-style language in the default-language selector

* feat(sse): let autoDetect pick the output-style instruction language

* docs(compression): consolidate the output-style tables and record full language parity
2026-09-01 11:13:26 -03:00
backryun
fe5f4b0ef9 chore(quality): remove unreachable code and restore test discovery (#11950) 2026-09-01 11:06:04 -03:00
backryun
c818655b5a chore(deps): refresh runtimes and adopt ESLint 10 (#11259)
Co-authored-by: backryun <backryun@daonlab.local>
2026-09-01 11:05:45 -03:00
Bob.Hou
9629d78ece fix(translator): strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) (#12166)
Signed-off-by: Minxi Hou <houminxi@gmail.com>
2026-09-01 11:02:35 -03:00
brick30llc-ctrl
0ff164701d fix(sse): trust finish_reason over reasoning-ratio heuristic in response quality validation (#12262)
* fix(sse): trust finish_reason:length/max_tokens over the reasoning-ratio heuristic in response quality validation

A truncated response with empty content and reasoning_content present was
only rejected by validateResponseQuality() when reasoning consumed >=90%
of completion_tokens. A response truncated at a lower ratio (e.g. 63%)
passed through as "valid" even though the caller received no usable
content and finish_reason was explicitly "length" (or the alternate
"max_tokens" naming some providers use) -- an unambiguous truncation
signal the validator wasn't reading. Reproduced live against
nvidia/nemotron-3-super-120b-a12b: content:null, finish_reason:length,
reasoning_tokens 645/1024 (63%).

Trust finish_reason directly when it's reported, falling back to the
existing token-ratio heuristic only when it isn't. Does not affect the
deliberate-tiny-probe case (e.g. max_tokens:1 connectivity pings) --
those never produce reasoning_content, so the branch this change is in
doesn't run for them.

* docs(changelog): add fragment for #12262

---------

Co-authored-by: brick30llc-ctrl <admin@brick30.com>
2026-09-01 11:02:05 -03:00
backryun
f301d34ce5 fix(dev): qualify Turbopack runtime boundaries (#12258)
Co-authored-by: backryun <backryun@daonlab.local>
2026-09-01 11:01:56 -03:00
Rafa Martins
d920e6495a build: omit the standalone output target for contributor builds (#12204)
Completes the contributor profile: #12198 stopped OmniRoute from assembling the standalone bundle, but Next was still asked to emit one. Making output: "standalone" conditional on OMNIROUTE_BUILD_PROFILE=contributor removes the standalone tracing pass itself, which is where the remaining time went.

Default builds are unaffected — the flag is read from the env at config load and is false everywhere except the contributor profile, so tests/unit/next-config.test.ts still observes output === "standalone" (18/18 green across contributor-build-script, next-config and build-profile-stubs).

Reconciled on merge: CONTRIBUTING.md and scripts/build/backendOnlyPages.mjs already carried this stack's earlier steps on the tip, so both took the tip's side; only the next.config.mjs conditional and its test are this step's delta.

Thanks @rafacpti23 for splitting this into four reviewable steps — it made the whole stack easy to reason about.
2026-09-01 09:38:44 -03:00
Rafa Martins
7f008ed09a build: stub the instrumentation entrypoints in the contributor profile (#12203)
Contributor builds only need compilation to type-check; pulling src/instrumentation.ts drags the whole startup graph (DB boot, model-catalog warm, quota fetchers) into the build. stubContributorInstrumentation() swaps both entrypoints for no-ops before next build and hands them to the existing restoreDashboardPages() path afterwards, reusing the same {file, original} shape and the SIGINT/SIGTERM handlers already registered in that block.

Reconciled on merge: the stub originally wrote 'export async function register() {}' into both files, but src/instrumentation-node.ts exports registerNodejs() (register lives in src/instrumentation.ts). Harmless in practice — instrumentation.ts is its only importer and is stubbed at the same time — but the stub misstated the file's contract, so it now emits the right symbol per file and the test asserts it.

Verified: contributor-build-script 3/3 green.

Thanks @rafacpti23.
2026-09-01 09:37:32 -03:00
Rafa Martins
e3c440e804 build: skip standalone packaging in the contributor profile (#12198)
Makes the contributor profile actually fast: with OMNIROUTE_BUILD_PROFILE=contributor the build stops after next build instead of copying docs/ and running assembleStandalone, which is the expensive half and produces an artifact contributors never ship. Adds isContributorBuild() next to the existing isBackendOnlyBuild() and documents the compile-only contract in CONTRIBUTING.md.

Reconciled on merge: CONTRIBUTING.md's new paragraph was inside the ```bash fence and would have rendered as shell — moved below the closing fence. package.json auto-merged against the tip.

Verified: contributor-build-script + backend-only-smoke-workflows 10/10 green.

Thanks @rafacpti23.
2026-09-01 09:36:08 -03:00
Rafa Martins
d898d1a913 build: force the webpack bundler in the contributor build profile (#12197)
The contributor profile added in #12192 inherited the default Turbopack bundler. Turbopack's native allocator is the documented OOM risk on memory-constrained machines (scripts/build/build-next-isolated.mjs:201-202), and OMNIROUTE_USE_TURBOPACK=0 is the escape hatch the same script already honours at line 139 — the repo's own nightly-compat workflow pins it to "0" for exactly this reason. Setting it on build:contributor makes the fast profile usable on the machines it targets.

Reconciled on merge: the branch was 51 commits behind and #12192 had already landed the script line, so only the env flag is new; the tip's dependency block was kept verbatim rather than taking the stale package.json wholesale.

Verified: tests/unit/build/contributor-build-script.test.mjs 1/1 green.

Thanks @rafacpti23.
2026-09-01 09:35:03 -03:00
Diego Rodrigues de Sa e Souza
ad54249c54 fix(security): strip Qwen/Alibaba console-session cookies from provider API responses (#12287)
The Token Plan console cookie is a browser credential for the operator's
cloud-console account — same class as the ollama/opencode cookies that
sanitizeProviderSpecificDataForResponse already strips — but the four
qwen/alibaba fields (qwenCloudCookie, qwenCloudSecToken, alibabaConsoleCookie,
alibabaConsoleSecToken) were missing from the strip list, so GET /api/providers
returned the operator's console session in the clear to any dashboard session.

The edit modal depended on that leak: it initialized the cookie fields from the
round-tripped response. It now starts them empty, matching the ollama pattern —
the quota-scraping assign skips empty fields and the PUT handler's partial merge
preserves keys the payload does not carry, so 'leave blank to keep the stored
cookie' (already what the field hints promise) holds for real.

Found in the 2026-09-01 audit of the Token Plan quota feature.
2026-09-01 09:22:56 -03:00
Diego Rodrigues de Sa e Souza
eeba382049 fix(combo): bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) (#12285)
getPersistedConnectionCooldownSkipReason() returned a skip for ANY connection
whose testStatus was `unavailable`, with no elapsed-cooldown check:

    if (status === "unavailable") return `Skipping ...`;

That is the raw-label anti-pattern AGENTS.md warns about ("check whether code
is reading raw state instead of using getStatus()/canExecute()") — the
resilience layers are meant to recover lazily. The sibling helper directly
above it, getConnectionStatusQuotaCutoffReason(), does require
hasFutureRateLimitUntil() before treating `unavailable` as blocking.

Its stated justification — "Lazy recovery is unaffected: clearAccountError()
resets the status on first success" — does not hold on this path. This gate
runs BEFORE dispatch, so it prevents the very successful request that would
call clearAccountError(). And a row whose rateLimitedUntil is absent cannot be
rescued by the out-of-band recovery job either, because hasElapsedCooldown()
there requires a timestamp to be present.

Net effect reported in #12168: an entire combo pool answering
ALL_TARGETS_SKIPPED with recordedAttempts === 0 — zero upstream attempts, no
path back to healthy.

The original intent (do not burst into a connection AUTH just retired, before
the timestamp lands) is preserved, but bounded: the bare label is honoured only
while lastErrorAt is inside a grace window, mirroring ERROR_LABEL_GRACE_MS in
src/lib/quota/connectionRecovery.ts so the two never disagree about whether a
label is still meaningful. Past the window the request goes through, and one
real attempt either succeeds (clearing the status) or re-arms the cooldown with
a fresh timestamp.

Regression introduced by #11360, shipped in v3.8.50.

Two assertions in repro-combo-persisted-cooldown-preskip.test.ts encoded the
buggy behavior as intended ("skips an unavailable connection whose cooldown
already expired") and are realigned to the corrected contract, plus a case for
the orphan state (unavailable with no timestamps at all).
2026-09-01 09:22:43 -03:00
Diego Rodrigues de Sa e Souza
412298b624 chore(proxy): purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) (#12290)
The 1proxy marketplace integration was decommissioned in v3.8.4; the code
survived only through the localDb barrel, deleted in #12055. Everything below
had zero consumers (grep-proven across src/, open-sse/, bin/, electron/,
scripts/ and tests/):

- src/lib/oneproxySync.ts and src/lib/oneproxyRotator.ts deleted.
- src/lib/db/oneproxy.ts: upsertOneproxyProxy, getOneproxyProxyById,
  getOneproxyProxyForRotation and markOneproxyProxyFailed removed (their only
  consumers were the two deleted modules); listOneproxyProxies and the record
  interface stay — open-sse/utils/proxyFallback.ts still uses them.
- src/shared/validation/oneproxySchemas.ts and the unmounted
  settings/components/OneproxyTab.tsx deleted (no importer anywhere; the live
  UI is the FreePool* tabs over /api/settings/free-proxies).
- ONEPROXY_ENABLED feature flag removed (readerless since oneproxySync died —
  the toggle no longer controlled anything); flag-count contract test aligned
  54 → 53.
- Docs: PROXY_GUIDE (component rows, env rows, the three omniroute/
  oneproxyRotator snippet sections), CODEBASE_DOCUMENTATION, REPOSITORY_MAP,
  ENVIRONMENT (ONEPROXY_* rows), FEATURE_FLAGS, .env.example — canonical +
  pl/zh-CN/zh-TW mirrors.
- The 308 compat redirects under /api/settings/oneproxy/ stay (deliberate API
  compat), as do the live free-proxy provider and proxy_registry rows.

check:dead-code drops 424 → 417 (baseline kept at the velocity-phase 500 —
banking shrinks is paused until v4.0, headroom grows to 16.6%).
check:docs-all, check:env-doc-sync, typecheck:core and the 8 free-proxy/
proxy-fallback test files are green.

Closes #12091
2026-09-01 09:22:26 -03:00
Benson K B
f5e70950d3 chore(ci): point the circular-deps gate at dpdm's real JS entrypoint (#11615)
`node_modules/.bin/dpdm` is an npm shell wrapper, so `node <that path>` crashed with `SyntaxError: missing ) after argument list` and the advisory circular-deps gate in ci.yml (job quality-extended) reported an error instead of a result on every run. Pointing DPDM_BIN at `node_modules/dpdm/lib/bin/dpdm.js` restores it: the gate now completes and reports circularDeps=154 (exit 0).

Scope reduced during merge — the branch was 522 commits behind and carried three base-drift files that were reconciled back to the release tip: open-sse/services/combo.ts (reverted routing code + a @/lib/localDb barrel import, Hard Rule #2), config/quality/eslint-suppressions.json (dropped ~45% of the frozen suppressions), and tests/unit/cli-env-inline-comment-10100.test.ts (replaced a working module import with new Function() source scraping, Hard Rule #3). Rationale documented in the PR discussion.

Verified: check:circular-deps crashes on the pure tip and completes on the merged branch; tests/unit/cli-env-inline-comment-10100.test.ts 5/5 green against the restored version.

Thanks @benzntech for catching the dpdm breakage.
2026-09-01 07:37:06 -03:00
Diego Rodrigues de Sa e Souza
073b98462d feat(dashboard): orchestration canvas — /dashboard/orchestration with Agents/Routing/Overview tabs (part 2/2) (#12261)
UI completa do Orchestration Canvas sobre o modelo da parte 1: página /dashboard/orchestration com abas em URL (Agents=grafo vivo via FlowCanvas, Routing=ComboLiveStudio intocado, Overview=contadores+kanban com totais reais sob cap), drawer de detalhe com approve/cancel (unwrap por fonte verificado contra as rotas reais, erros client-safe, prUrl https-only), i18n com traduções REAIS em 43 locales, entrada no sidebar. Ciclo SDD: 9 tasks TDD com review por task (Task 15 com fix round: Critical A2A unwrap + rewrite de lint + guard XSS), review final whole-branch (Ready to merge, 0 Critical/Important, refactor de complexity provado behavior-preserving), 3 fixes de CI validados RED→GREEN. CI: tudo verde. Crédito do conceito visual: design da PR #11815.
2026-09-01 05:12:54 -03:00
Diego Rodrigues de Sa e Souza
30a26d9dcb docs(agents): protected-surface merge rule — operator approval for agent-instruction files (#12253)
PR #11770 (2026-09-01) added a CLAUDE.md section instructing every AI agent to
clone and execute a third-party setup script; a merge campaign swept it into
the release branch with no human risk review (reverted in #12249). Review
focus now carries the rule: PRs touching CLAUDE.md / AGENTS.md / GEMINI.md /
llm.txt / skills SKILL.md files are HOLD until explicit per-PR operator
approval — CI validates code, not instruction-surface intent.
2026-09-01 03:40:33 -03:00
Diego Rodrigues de Sa e Souza
d53da4fdc8 chore(quality): dedupe tap.testFiles entries added by racing base-red fixes (#12265)
Dedupe trivial de 1 arquivo; gate mutation --strict verde local no conteúdo pós-merge (363 entradas únicas, 0 duplicatas).
2026-09-01 03:34:13 -03:00
Diego Rodrigues de Sa e Souza
3c8b553811 chore(quality): register native-codex turn-pin tests in stryker tap.testFiles (#12263)
Gate check:mutation-test-coverage --strict red→verde local (registro dos 2 testes turn-pin no tap.testFiles, drift da mesma classe do #12170). O único check vermelho desta PR (Unit shard 4/4) é o base-red dos próprios testes turn-pin desalinhados pelo #12247 — corrigido pela #12259, mergeada na sequência. Reds circulares: cada PR só está vermelha no item que a outra corrige.
2026-09-01 03:29:48 -03:00
Diego Rodrigues de Sa e Souza
aa2aec5e59 docs: Chaos Mode setup guide + weighted strategy semantics (#12250)
Gap surfaced by OmniCopilot#16: the Chaos Mode dashboard page, its per-key
chaosModeEnabled permission and both dispatch endpoints had no setup doc at
all (only the auto/chaos table line existed), and AUTO-COMBO.md never stated
that weighted is a proportional draw where zero-weight steps are never drawn.
New docs/guides/CHAOS-MODE.md (registered in meta.json + docs/README.md) and
a 'weighted semantics' subsection under the strategy table, both written from
the code (chaosConfig.ts, chaosExecutor.ts, both routes, targetSorters.ts,
targetResolution.ts). check:docs-all exits 0.
2026-09-01 03:25:47 -03:00
Diego Rodrigues de Sa e Souza
6c93e74f26 fix(quality): base-red pair — stryker tap registration + turn-pin suites aligned to the window gate (#12255)
* chore(quality): register native-codex-turn-pin tests in stryker tap.testFiles

The mutation-test-coverage gate (--strict) fails on the release tip: the two
native-codex-turn-pin suites (#10379 merge wave) cover open-sse turn-pin code
and src/shared/utils/circuitBreaker.ts but were not listed in
stryker.conf.json tap.testFiles, so their mutant kills would not count. Adds
both files; the gate now passes clean (4728 test files scanned, no drift).

* style: prettier pass on stryker.conf.json

* test(sse): align turn-pin suites to the provider-cooldown window gate

The two native-codex-turn-pin suites landed via the #10379 merge wave after
PR #12247 forked, so #12247's green CI never saw them: they set up 'provider
in global cooldown' with a single recordProviderCooldown call, the pre-#12247
contract. Since the window gate, a provider only counts as cooling after
providerFailureThreshold failures inside the window — the setup now loops to
the profile threshold (same alignment the tracker's own legacy suite got in
Sibling sweep: all 7 suites touching recordProviderCooldown pass (60/60).
2026-09-01 03:18:59 -03:00
Diego Rodrigues de Sa e Souza
2e17161ea2 feat(sse): wire the PROVIDER_PROFILES window gate into the global provider cooldown (#12247)
providerFailureThreshold / providerFailureWindowMs / providerCooldownMs shipped
in PROVIDER_PROFILES with no runtime consumer (2026-08-31 docs audit, P0.1).
Provider-level entries in providerCooldownTracker now honor them: the whole
provider only counts as cooling after providerFailureThreshold failures inside
providerFailureWindowMs, then cools for providerCooldownMs. Connection-level
entries keep the pre-existing exponential backoff, and the layer stays opt-in
(PROVIDER_COOLDOWN_ENABLED, default off) — default behavior is unchanged.

TDD: tests/unit/provider-cooldown-window-gate.test.ts written first (4 red on
the old behavior), then the wiring; legacy tracker suite aligned to the new
contract (23/23 green). Docs: AGENTS.md breaker section + RESILIENCE_GUIDE
opt-in layer subsection; executors soft-drift refresh (104 -> 106).
2026-09-01 01:57:17 -03:00
Diego Rodrigues de Sa e Souza
5eaafe8e17 Revert "docs: recommend gstack for AI-assisted workflows (#11770)" (#12248)
Merging --admin with red discrimination (merge-gates §4). The only failing check is Fast Quality Gates → `mutation-test-coverage`, which cannot be caused by this PR: the diff touches exactly one file, `CLAUDE.md` (13 deleted lines, zero .ts). The same gate is red on #12166, #12167 and #12169 — three unrelated PRs — confirming inherited base drift rather than a PR-introduced defect.
2026-09-01 01:48:51 -03:00
Diego Rodrigues de Sa e Souza
4bcd8cee99 fix(combo): always clear the loop-safety timer, not just on the happy path (#11804) (#12245)
dispatchWithCooldownRetry arms a loop-safety timer (setTimeout, 10 minutes by
default) on every setTry iteration, so a combo that never produces a terminal
response still answers with a 504 instead of hanging. The only clearTimeout in
the whole file sat inside the `if (anySuccess)` branch — the comment said so
verbatim: "clear the safety timer on the happy path".

Every error exit therefore returned the response to the client while leaving a
600s timer pending, its closure retaining orderedTargets and the exhausted
provider/connection sets: all_targets_skipped, all_accounts_inactive, the
aggregated-status return, the final fallback, and the global-timeout branch.
The timer is also re-armed per setTry iteration with no clear in between.

Field evidence from the issue: two requests that failed quality validation
returned 502 to the client immediately, and "Combo loop safety timeout ...
force-terminating" was logged for both exactly 600 seconds later — the leaked
timers firing long after the requests were gone.

Fixed structurally rather than by sprinkling clearTimeout across the five
return sites: the handle is hoisted to function scope and released in a
finally, so a future `return` added to this function cannot silently
reintroduce the leak. The 504 backstop itself is unchanged.

Note the timer already called .unref(), so it never held the event loop open —
this is a memory-retention leak, not a hang.
2026-09-01 01:08:09 -03:00
Diego Rodrigues de Sa e Souza
7f9195cd29 chore(lint): batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved (#12244)
Closes the src/ side of the campaign (no eslint-disable, no new suppressions;
the 37 matching react-hooks/* entries are removed from
config/quality/eslint-suppressions.json — only the 5 CI-divergent entries in
tests/unit/ui remain, frozen by design, see #12144):

- set-state-in-effect (30×): fetch-on-mount and sync-setter effects wrapped in
  the async-continuation pattern (await Promise.resolve() for pure-sync
  bodies), preserving semantics exactly.
- refs/purity (ResilienceConnectionsClient): render now reads stopReason state
  instead of stoppedRef; the receivedAt fallback Date.now() in JSX was dead
  (every setData stamps receivedAt) and became 0.
- exhaustive-deps (ApiTab, SessionInfoCard, useLiveDashboard): clearResults
  wrapped in useCallback; missing t dep added; channels array stabilized via
  channelsKey + useMemo so connect deps are statically checkable.
- global-error: locale/messages load moved into one async continuation (also
  renames the import binding to mod per @next/next/no-assign-module-variable).

Refs #12146
2026-09-01 01:07:46 -03:00
Diego Rodrigues de Sa e Souza
06e7a6d50c Revert "docs: recommend gstack for AI-assisted workflows (#11770)" (#12249)
This reverts commit 8acdd53025.
2026-09-01 01:06:37 -03:00
diegosouzapw
9058e39b61 docs(cli): document CLI_PRIME_AGENT_BIN and correct the CLI Agents count
Two drifts left behind when the prime-agent runtime entry landed:

- CLI_PRIME_AGENT_BIN (src/shared/services/cliRuntime.ts, defaultCommand
  "prime-agent") was in neither ENVIRONMENT.md nor .env.example. The env-doc-sync
  gate does not resolve envBinKey values, so it could not catch this.
- CLI-TOOLS.md's summary table still counted 9 CLI Agents while the catalog holds
  10 — the section-2 heading and the README breakdown were already correct, only
  that cell lagged.

A full sweep of every envBinKey in cliRuntime now shows all of them documented in
both files.
2026-09-01 00:58:43 -03:00
Diego Rodrigues de Sa e Souza
a249a9dc28 docs(api): document every implemented route in openapi.yaml (276 → 692 paths) (#12212)
* docs(api): document every implemented route in openapi.yaml (276 -> 692 paths)

Follow-up nº 3 of the 2026-08-31 docs audit: 416 implemented routes had no
OpenAPI entry (gamification, radar, skills, webhooks, mcp, a2a, tunnels,
version-manager and plugins were absent entirely). Adds a minimal, honest
entry for each — real methods parsed from every route.ts's exports, a group
tag and a neutral path-derived summary; no invented semantics. Rich schemas
remain hand-curated in the existing entries.

Generated by scripts/ad-hoc/gen-openapi-missing-paths.mjs, which enumerates
routes with the same lib check:api-docs-refs uses — the spec now covers
692/692 real routes and the gate verifies every spec path has a real route.

* docs(api): security tiers on generated paths, regenerated API skills, size baseline

The first CI round caught three real contract gaps in the generated coverage:

- Generated operations on LOCAL_ONLY routes now carry x-loopback-only (and
  x-always-protected for ALWAYS_PROTECTED_API_PATHS), resolved through the real
  src/server/authz/routeGuard.ts at generation time. The
  openapi-security-tiers guard now also accepts LOCAL_ONLY_API_PATTERNS —
  param-shaped routes (/api/providers/{id}/login) are classified by regex in
  the runtime and were invisible to the prefix-only check.
- The API agent skills are generated FROM the spec: 18 SKILL.md files
  regenerated via generate-agent-skills --apply so the generator stays 46/46.
- src/app/docs/lib/openapi.generated.ts grew with the spec (171 -> 1347 lines,
  emitted by gen-openapi-module): frozen in file-size-baseline.json with a
  _rebaseline justification — shrink by slimming the spec, never by editing
  the generated module.
2026-09-01 00:53:25 -03:00
Davide Baraldo
e7a65d28db fix(oauth): keep Claude personal and Team organizations apart (#12222)
* fix(oauth): keep Claude personal and Team organizations apart

One Anthropic identity reaches its personal workspace and every Team
organization it belongs to with the same email AND the same accountUUID,
each with its own tokens, plan and rate limits. The OAuth dedup matched on
email alone for every provider except Codex, so authenticating the second
organization overwrote the first connection instead of adding one: only the
most recent organization stayed usable. organizationUUID is the field that
separates them (cliUserID cannot be used, it changes on every login).

Disambiguate on organizationUUID, mirroring how Codex uses
workspaceId/chatgptUserId (#7737):

- findExistingOAuthConnectionMatch routes claude through a new
  isSameClaudeAccount helper, so a login only merges into an existing row
  when the organization agrees;
- isMatchingOauthIdentity gains organizationUUID as a third optional
  disambiguator, compared strictly two-sided;
- createProviderConnection passes the incoming organizationUUID, closing the
  same hole on the create path.

Rows stored before Claude returned organizationUUID keep the bare-email
match, so re-authenticating an existing connection still updates it in place
instead of forking a duplicate. No behaviour change for other providers.

* docs(oauth): changelog fragment for #12222
2026-09-01 00:51:38 -03:00
Bob.Hou
05490304bf fix(oauth): mark empty Antigravity projectId as degraded and clear stale errors (#11284) (#12205)
* fix(oauth): mark empty Antigravity projectId as degraded (#11284)

The #11284 gate only fired when projectDiscoveryOutcome was set. Paste
credentials, persistOAuthConnection, and agy CLI import could persist
projectId="" as testStatus=active, so the dashboard showed Connected
while fetchAvailableModels returned 403.

Degrade on empty projectId itself. Keep the refresh token stored so
request-time bootstrap can still self-heal.

Signed-off-by: Minxi Hou <houminxi@gmail.com>

* fix(oauth): clear stale degrade fields and bind CLI imports to builtin client

persistOAuthConnection left errorCode/lastError on the row when a later
connect discovered a Cloud Code projectId. agy CLI import also kept a
leftover custom: oauthClient marker from dashboard OAuth, so the next
refresh hit the operator web client instead of the public desktop client.

Signed-off-by: Minxi Hou <houminxi@gmail.com>

* fix(oauth): null error fields on healthy create paths too

Update already cleared errorCode/lastError* when a projectId appeared.
Create payloads still omitted the keys; match the update shape so a
fresh row cannot keep a leftover degrade marker.

Signed-off-by: Minxi Hou <houminxi@gmail.com>

* test(oauth): pin healthy create/upsert nulling of degrade fields

Forge flagged create payloads omitting errorCode/lastError* when a
projectId is present. Production already writes explicit nulls; the
reader strips them via cleanNulls, so pin both the payload shape and
the upsert path that must overwrite a leftover degrade marker.

Signed-off-by: Minxi Hou <houminxi@gmail.com>

* fix(oauth): type create payload from AntigravityDegradedProjectState

The persistence helper duplicated a subset of the degrade type and
dropped warning. Align the parameter so the HTTP-only warning field
cannot drift from the exported type.

Signed-off-by: Minxi Hou <houminxi@gmail.com>

* fix(oauth): persist degrade status through a single override helper

OAuth exchange/poll-callback spread the whole degrade object, which
wrote warning into the SQLite row and left healthy updates as {}.
Centralize testStatus/errorCode/lastError* so they always win over a
spread tokenData payload.

Signed-off-by: Minxi Hou <houminxi@gmail.com>

---------

Signed-off-by: Minxi Hou <houminxi@gmail.com>
2026-09-01 00:51:18 -03:00
backryun
dc6daf27b6 fix(dev): silence webpack runtime module warnings (#12228)
Co-authored-by: backryun <backryun@daonlab.local>
2026-09-01 00:50:58 -03:00
Syed Raheemuddin
903d1e0c52 fix(db): use module.require for CommonJS runtime driver loading (#12230) 2026-09-01 00:50:35 -03:00
backryun
8d388912a7 feat(providers): refresh vendored ChatGPT Web connector to v4.0.7 (#12181)
Refresh the existing MIT-licensed miuuyy/codex-chatgpt-web vendor snapshot and its OmniRoute integration as one reviewable change.

Co-authored-by: backryun <backryun@daonlab.local>
2026-09-01 00:50:15 -03:00
killer30001000
debb82bdd7 feat(usage): add Kilo Code balance and Kilo Pass quotas (#12178)
* feat(usage): add Kilo Code balance and Kilo Pass quotas

* feat(usage): add Kilo Pass dashboard meter

* test(usage): cover Kilo Code quota integration

* docs(usage): document Kilo API endpoint override
2026-09-01 00:49:54 -03:00
b3nw
c49ee53bc1 feat(providers): add RPD to rate limit overrides (#12147) 2026-09-01 00:49:33 -03:00