Commit Graph

968 Commits

Author SHA1 Message Date
Nguyễn Viết Tuấn
55691e0416 fix(usage): allow quota refresh for FREE lease-reserved connections (#11758)
Boarded in a combined worktree with 6 other PRs: typecheck:core, check:dashboard-typecheck, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles, check-deps all green. Verified the root-cause diagnosis directly against the code: isConnectionUnavailableToAuxiliaryActivity() does return true for any connection reachable by an active exclusive lease regardless of whether the lease is actively serving a request, confirming the fix's scoping is correct. The change is surgically limited to providerLimits.ts's live-usage-fetch path — the shared isolation function and its other call sites (warmupScheduler, quotaAutoPing, modelTestRunner, etc.) are untouched. Well tested (214 lines across 3 test files). Thanks for tracking this down.
2026-08-30 04:07:06 -03:00
Webman
38a29661d3 URGENT fix(build): route ChatGPT Web MCP bundle through runBuildTool (Windows/Node 24 build crash) v.50/.51 (#11706)
Confirmed the bug is real and unfixed on the current tip before merging: `scripts/build/prepublish.ts` line 332 was still calling `execFileSync(NPX_BIN, ...)` directly (raw win32 npx.cmd spawn), the exact CVE-2024-27980 shim pattern the file's own header warns about. Root cause, fix, and evidence match — routing through the existing `runBuildTool()` helper. Thanks for catching the one call site the earlier refactor missed.
2026-08-30 02:27:54 -03:00
Bob.Hou
38e2baa879 fix(resilience): clear persisted LKGP pin on target exhaustion and skip (#11911) (#12013)
When an auto/*/lkgp combo target failed into exhaustion (e.g. an unauthenticated free-tier 401) or was skipped pre-dispatch (cooldown, model lockout, unavailability), the Last Known Good Provider pin was never cleared — so subsequent requests kept re-selecting the same dead provider, causing repeated failures and mass-skipping instead of falling through to a healthy target. Centralizes invalidation into clearStaleLKGP(), invoked from both handleComboChat and handleRoundRobinCombo on exhaustion, pre-dispatch skip, and body-specific 400 termination.
2026-08-29 19:52:06 -03:00
Bob.Hou
5698769aba fix(cli): support --base-url alongside --endpoint in nodes subcommands (#11999) (#12033)
Commander's top-level global --base-url option was shadowing the flag on `omniroute nodes add/update/validate`, rejecting the command with "required option '--endpoint <url>' not specified" even when --base-url was correctly supplied. Now both flags are accepted on all three subcommands, falling back to whichever the user passes.
2026-08-29 19:51:59 -03:00
Diego Rodrigues de Sa e Souza
065d998407 fix(cli): update flow now says whether the running process needs a restart (#11885) (#12005)
Fixes three defects in the "update doesn't restart the running process" bug class: CLI update guidance now detects a live server and tells the operator to restart instead of implying the update is already live; the dashboard's Update button tries OmniRoute's own PID-file supervisor before falling back to pm2 instead of hardcoding pm2 and silently skipping; getLatestVersionFromNpmCli now uses --prefer-online (same fix pattern as #4376). TDD throughout, 63/63 targeted regression tests pass.
2026-08-29 15:27:53 -03:00
Diego Rodrigues de Sa e Souza
d32c76f85a fix(config): correct Hermes-4-405B display label from 7B to 405B (#11861) (#11993)
Fixes a copy-paste label typo (Hermes-4-405B mislabeled "7B") in both the registry and the free-model catalog data, spotted in the #11861 comment thread. TDD: 3/3 tests, generic parameter-size consistency check + exact regression guard.
2026-08-29 15:27:49 -03:00
Diego Rodrigues de Sa e Souza
c9b1c12cfd fix(db): include local no-API-key providers in Qdrant embedding-model list (#11949) (#11995)
Local no-API-key providers (ollama-local, lm-studio, vllm, etc.) were invisible in the Qdrant embedding-model dropdown because configuredProviders required a real apiKey or OAuth. Extended the filter to also include providerAllowsOptionalApiKey(connection.provider) — the same canonical helper already used for the identical check elsewhere. TDD: 21/21 integration tests pass (was 20/21 before the fix).
2026-08-29 15:27:44 -03:00
Diego Rodrigues de Sa e Souza
c8dc982eaa fix(ci): drop the stale ESLint cache restore-keys fallback from ci.yml (#11600) (#11996)
Fixes the blocking Lint job's own ci.yml cache: PR #11963 removed the stale restore-keys fallback from quality.yml but left ci.yml's two "Restore ESLint file cache" steps carrying the same prefix-match fallback that lets a cache from a different lint config report stale per-file verdicts. Byte-level parity with #11963's already-merged fix.

Deliberately half of #11600 — the other half (run-eslint-json.mjs) is covered by PR #11983 from a parallel session, so the two don't collide on the same file.
2026-08-29 15:27:39 -03:00
Diego Rodrigues de Sa e Souza
c4bd8b8ec4 fix(release): electron lockfile resync, build_ref, curated notes and SBOM on dispatch (twin of #11982 + #12020) (#12022)
* fix(release): resync the electron lockfile, build a dispatch from a repaired ref, keep curated notes, attach the SBOM on dispatch (release/v3.8.51 twin of #11982 + #12020)

Same four changes as #11982 and #12020 on main, applied to this branch's own copies:

- electron/package-lock.json regenerated (271 -> 284 entries): the optional
  electron-builder-squirrel-windows subtree was missing and `npm ci` refused the lock
  (EUSAGE) on the Linux and macOS legs; a clean `npm ci --ignore-scripts` on the
  result exits 0.
- electron-release.yml: `build_ref` dispatch input (default: the version tag) and
  `generate_release_notes` only on the tag push (a re-attach dispatch appended
  GitHub's auto notes to the curated body on v3.8.50).
- npm-publish.yml: the SBOM attaches to the GitHub Release on workflow_dispatch
  publishes too, whenever a release for the tag exists.

actionlint and prettier clean; electron-release-desktop-channel-8949,
electron-release-efficiency, electron-release-latest-yml.repro, check-workflows
and npm-publish-artifact-provenance suites pass.

* fix(release): validate build_ref in the validate job before any checkout uses it

CodeQL (actions/cache-poisoning/poisonable-step, high) on release/v3.8.51 — the
default branch: a raw dispatch input checked out next to setup-node's npm cache is a
cache-poisoning vector. The input now goes through the validate job's regex
allowlist (main or release/vX.Y.Z, empty = the version tag) and every build job
checks out needs.validate.outputs.build_ref, never the input itself.

* fix(release): drop the build_ref input — a dispatch builds the ref it is dispatched on

CodeQL (actions/cache-poisoning/poisonable-step) tracks the input through the
validate job's output regardless of the regex allowlist: an input-controlled
checkout next to setup-node's npm cache on the default branch is a cache-poisoning
vector. The ref is not an input any more; the checkouts use github.ref, so
`gh workflow run electron-release.yml --ref v3.8.50 -f version=v3.8.50` rebuilds
the tag and `--ref main` builds the repaired line. The tag-push path is unchanged.
2026-08-29 09:28:03 -03:00
Diego Rodrigues de Sa e Souza
e6de61f0c2 fix(sse): stop the auto-combo candidates inspector from dropping blocked rows (#9133) (#11994)
* fix(sse): stop the auto-combo candidates inspector from dropping blocked rows (#9133)

prepareVirtualAutoComboInputs applied filterResilienceBlockedCandidates
before the #7819 read-only candidate inspector ever saw the pool, so a
model-locked or cooled-down candidate silently disappeared from
/auto-combo/*/candidates instead of showing up as reachable:false with a
reason (modelLocked/connectionCooldown/breakerState were dead fields by
construction). Add an opt-in `skip` parameter so the inspector builds its
own unfiltered pool; routing (createVirtualAutoCombo/createBuiltinAutoCombo
called without a prepared override) is unchanged. Also aligns
isModelLocked's model argument to the bare model id, matching every lock
writer and the routing-side filter, instead of the "provider/model" string.

Regression test: tests/unit/auto-combo-candidates-locked-model-visible.test.ts
(red before the fix — locked account's row silently missing; green after).

* chore(quality): register the #9133 regression test in stryker tap.testFiles

tests/unit/auto-combo-candidates-locked-model-visible.test.ts covers
open-sse/services/accountFallback.ts (via isModelLocked) but wasn't listed,
so its mutant kills wouldn't count toward mutation coverage.

---------

Co-authored-by: Markus Hartung <mail@hartmark.se>
2026-08-29 08:10:40 -03:00
Diego Rodrigues de Sa e Souza
02ba573730 fix(providers): scope Antigravity mitmAlias tier ids to the safe static alias (#11824) (#11988)
Co-authored-by: Markus Hartung <mail@hartmark.se>
2026-08-29 08:10:37 -03:00
Diego Rodrigues de Sa e Souza
bd04bb9cc6 fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches (#11810) (#11986)
Co-authored-by: Markus Hartung <mail@hartmark.se>
2026-08-29 08:10:33 -03:00
Diego Rodrigues de Sa e Souza
322b218f06 fix(cli): drop the never-produced dist/index.cjs requirement from prepublish's opencode-plugin skip check (#11787) (#11990)
* fix(cli): drop the never-produced dist/index.cjs requirement from prepublish's opencode-plugin skip check (#11787)

* test(build): resolve tsup/npm portably in the #11787 regression test instead of a hardcoded .bin path

The old test assumed @omniroute/opencode-plugin/node_modules/.bin/tsup
already existed. A fresh checkout (CI's npm ci never installs this
standalone package's own deps) has no such node_modules at all, so the
test failed with MODULE_NOT_FOUND in CI while passing locally on a devbox
that had installed it before. Mirror scripts/build/prepublish.ts's own
install-then-resolveLocalBinEntry approach.

---------

Co-authored-by: Markus Hartung <mail@hartmark.se>
2026-08-29 08:10:29 -03:00
Diego Rodrigues de Sa e Souza
71093eda77 fix(codex): keep parallel_tool_calls:false on translated Responses Lite path (#11707) (#11984)
enforceCodexResponsesLiteParallelToolCalls() forces parallel_tool_calls:false
at the top of CodexExecutor.execute(), but transformRequest() early-returns
the body before its RESPONSES_API_ALLOWLIST field filter only when
_nativeCodexPassthrough is set. Any request that reaches the codex
executor via the translated (non-native-passthrough) path never gets that
flag, so the allowlist filter silently deleted parallel_tool_calls right
before the fetch body was sent, reproducing the reported upstream
rejection ('X-OpenAI-Internal-Codex-Responses-Lite requires
parallel_tool_calls to be false') for every model.

Add parallel_tool_calls to RESPONSES_API_ALLOWLIST so the value survives
the translated path too. Update the sibling #2608 allowlist test that
previously asserted parallel_tool_calls gets stripped like other Chat
Completions-only fields -- it is a legitimate Responses API field that
must now survive.

Co-authored-by: Markus Hartung <mail@hartmark.se>
2026-08-29 08:10:26 -03:00
Diego Rodrigues de Sa e Souza
fb9cbe9566 fix(ci): pass --pass-on-unpruned-suppressions in run-eslint-json.mjs (#11600) (#11983)
Co-authored-by: Markus Hartung <mail@hartmark.se>
2026-08-29 08:10:21 -03:00
Diego Rodrigues de Sa e Souza
674cc5feb1 fix(db): rate-limit Arena ELO fetch-failure warnings on repeated timeouts (#11500) (#11989)
* fix(db): rate-limit Arena ELO fetch-failure warnings on repeated timeouts (#11500)

* test(quality): split the #11500 fetch-failure-dedup tests into their own file

tests/unit/arena-elo-sync.test.ts crossed the 1000-line new-test-file cap
(file-size gate, PR mode). The two new tests don't need the file's DB
fixture (fetchArenaLeaderboards() never touches the DB), so they move to a
self-contained sibling file instead of growing the frozen suite.

---------

Co-authored-by: Markus Hartung <mail@hartmark.se>
2026-08-29 08:10:17 -03:00
Diego Rodrigues de Sa e Souza
d2ad71cf56 fix(kie): route flux/kontext to its dedicated endpoint, not the Market createTask flow (#11296) (#11985)
flux/kontext is catalogued with isMarket: true, so handleKieImageGeneration
routed it through KIE's unified Market createTask endpoint with
model: "flux/kontext". KIE does not expose Flux Kontext through the Market
catalog at all -- it lives under a dedicated API tree
(POST /api/v1/flux/kontext/generate, poll GET /api/v1/flux/kontext/record-info,
models flux-kontext-pro/flux-kontext-max) -- so the Market endpoint rejected it
with "model name not supported", matching the reporter's exact error text.

Special-case flux/kontext ahead of the isMarket branch so it hits the
dedicated endpoint/payload shape instead of being treated as a Market entry.
z-image/4.0-*/4.5-* remains intentionally untouched (still blocked on
reporter/live confirmation per the existing in-code comment).

Co-authored-by: Markus Hartung <mail@hartmark.se>
2026-08-29 08:10:13 -03:00
Diego Rodrigues de Sa e Souza
757cc3bb9d fix(release): let the Electron workflow start again — grant actions:read to the npm leg (release/v3.8.51 twin of #11973) (#11974)
Same three changes as #11973 on main, applied to this branch's newer copy of the
workflow so the v3.8.51 tag does not repeat v3.8.50's zero-asset release:
publish-npm grants actions:read (the called publish job requests it — a caller that
grants less is refused at startup and the release job dies with it), a publish_npm
dispatch input gates the npm leg, and web-build/build/release check out the tag
named by the dispatch. actionlint clean; the five workflow-pinning suites pass.
2026-08-29 02:42:13 -03:00
Diego Rodrigues de Sa e Souza
a94fe23e89 fix(release): drain the twelve reds every PR against release/v3.8.51 was born with (#11940)
* fix(release): drain the twelve reds every PR against release/v3.8.51 was born with

Measured on the cycle tip: fifteen unit files were red on every PR. Two came
from the v3.8.50 sync-back (fixed in #11929); the other thirteen predate it and
are the branch's own drift. This sweep clears all of them but the ESLint debt
(#11924), each with the smallest change that keeps the guard honest:

- .env.example + ENVIRONMENT.md: NEXT_PUBLIC_SW_BUILD_ID / OMNIROUTE_SW_BUILD_ID /
  SOURCE_VERSION (#11779 service-worker cache busting) documented — the env/docs
  contract gate was failing on every PR.
- stryker.conf.json: the six tests the mutation gate found covering mutated modules
  (four retirement runtime-block suites, combo connection-aware expansion, tunnel
  error sanitization) registered in tap.testFiles.
- dependency-allowlist: eslint-plugin-react-hooks 7.0.1 approved; its findings are
  tracked in #11924.
- i18n: the six combo.sort.* strings (d5dfcfff58) translated for vi (strict parity)
  and pt-BR.
- docs/providers/CHATGPT_WEB.md: the retirement test is migration-168, not 163.
- g4f gateways: authHint now says member key, which the discontinued-providers
  guard asserts.
- tests realigned to the catalog the branch actually ships: qwen-web (#11713) and
  chatgpt-web (#11720) are retired, so web-session-contract and
  token-health-check-webcookie use perplexity-web, grok-web and chatgpt-web-codex.
- db-core-init: the two minimal legacy fixtures gained the columns migrations 164-168
  UPDATE (error_code, last_error*, test_status) — they exist on every real legacy DB
  (base CREATE TABLE); the fixtures simply never declared them.
- no-js-extension guard: a .js specifier whose target is a genuine JavaScript file
  (open-sse/lib/deepseek-pow-hash.js, shared with a worker) is not the #10674
  defect; the test now skips targets that exist as .js.

All twelve files pass locally; docs-sync, docs-counts, env-doc-sync, the tap
drift gate and the fabricated-docs gates are green on the tree.

* test(release): move the deferred-finish translator test into a collected path

tests/unit/translator/ is not one of the unit collectors (package.json test:unit,
merge-train.sh, build-test-impact-map, check-test-discovery), so the suite that
dd35750e5f added there never ran — check:test-discovery flagged it as a new orphan
on every PR. Relocated next to its sibling openai-to-claude-trailing-usage-11817
under tests/unit/, where the root glob collects it (5/5 pass).

* fix(dashboard): type the four sort-method sites #11812 left red on the dashboard typecheck ratchet

d5dfcfff58 added the combo model sort and raised combos/page.tsx from 23 to 27
scoped TypeScript errors (TS2339 +1, TS2345 +2, TS2322 +1), which fails
check:dashboard-typecheck on every PR against release/v3.8.51:

- initialSortMethod: sanitizeComboRuntimeConfig() is untyped, so config.modelSort is
  unknown; narrow it before reading .method (normalizeSortMethod takes unknown anyway).
- handleAddModels: the batch path passes ComboBuilderDraftModelStep[] to the ComboStep[]
  sort helpers without the cast handleSortChange already uses; mirror it.
- ComboSortSelect expects a translate-with-fallback (k, f) => string, but received
  next-intl's Translator whose second argument is a values object. Pass the page's
  getI18nOrFallback adapter instead of the raw translator — that is also what makes
  the `has()` check and the fallback text actually work at runtime.

Baseline untouched (no widening). Scoped tsc: 0 new/regressed errors.
2026-08-28 18:18:33 -03:00
diegosouzapw
9968e1ce6e Merge remote-tracking branch 'origin/release/v3.8.51' into chore/sync-main-into-3851-20260828b 2026-08-28 17:27:31 -03:00
Diego Rodrigues de Sa e Souza
33763f06cc chore(changelog): add missing fragments for #11919/#11918/#11916 (#11938)
Adds the 3 missing changelog fragments.
2026-08-28 16:26:32 -03:00
Diego Rodrigues de Sa e Souza
cea1baa797 fix(ui): guard remaining ProviderIcon lookups against prototype collisions (#11920 port) (#11935)
Ports the 3 still-needed guards from #11920 that #11880 didn't cover. 90/90 + 4/4 focused tests passing.
2026-08-28 16:19:07 -03:00
Diego Rodrigues de Sa e Souza
dd35750e5f fix(sse): defer OpenAI-to-Claude finish emission until real usage arrives (#11915 follow-up on #11883) (#11933)
Merges #11883's already-merged usage-harvesting extraction with #11915's finish-deferral mechanism, verified to fix a real remaining bug: the client-visible message_delta carried stale/zero usage when finish_reason arrived before the trailing usage chunk. 86/86 tests passing across 16 translator regression files.
2026-08-28 16:11:38 -03:00
Diego Rodrigues de Sa e Souza
c661e1c811 port(playground): specific step warnings from #11882, keep #11862's string-step handling (#11930)
Ports the specific-warning improvement from #11882 (combo-ref/provider-wildcard steps get their own message instead of a generic count) onto #11862's already-merged crash fix. 4/4 focused tests passing.
2026-08-28 15:51:37 -03:00
diegosouzapw
529e4415c5 chore(release): sync main into release/v3.8.51 — the five post-release pipeline fixes
Brings e4683cd22d (#11867 Alibaba allowlist time bomb), 09de69edc7 (#11891
config expiry detector), e71be03398 (#11893 runner janitor), 9dc8eab70e
(#11895 provenance × self-hosted lint) and f564b64f7d (#11901 heavy-build
lanes). main is already an ancestor of this branch (v3.8.50 sync-back), so the
merge is exactly these five commits.

# Conflicts:
#	tests/unit/alibaba-free-tier-allowlist.test.ts
2026-08-28 15:49:53 -03:00
NoxzRCW
f08f35d6f0 fix(providers): pass xAI reasoning_effort xhigh through to grok-4.6+ (#11879)
normalizeXaiReasoningEffort() folded xhigh onto high before the request reached xAI, so anyone picking xhigh on grok-4.6 silently got high instead. xhigh is a real xAI tier (grok-4.6+); xAI already degrades it itself on unsupported models, so forwarding verbatim is safe everywhere. Closes #11816. Measured against live grok-4.6: reasoning_tokens 830 (high) vs 1052 (xhigh) — previously indistinguishable. Thanks!
2026-08-28 15:49:42 -03:00
NoxzRCW
d846692c30 fix(dashboard): guard provider icon lookups against prototype collisions (#11880)
getLobeProviderIcon() indexed two plain-object maps with no own-property check — a provider id that lowercases to an Object.prototype member (e.g. constructor) resolved through the prototype chain and threw on the follow-up .color/.mono lookup, surfacing as the misleading 'Failed to load providers, check your connection' error boundary card with a healthy server and clean logs. Thanks for the precise root-cause trace!
2026-08-28 15:49:32 -03:00
NoxzRCW
c5ebbb733c fix(skills): expand shorthand property types in injected tool schemas (#11881)
Every request through a strictly-validating provider (reproduced on opencode-go/glm-5.3-flash) failed with a 400: normalizeInputSchema() wrapped a skill's shorthand property map without expanding string values, so every injected omr_skill_* tool carried an invalid JSON Schema. Closes #11856. Thanks for the root-cause!
2026-08-28 15:49:22 -03:00
NoxzRCW
b8c7ee599d fix(translator): keep upstream usage from trailing empty-choices chunks (#11883)
openaiToClaudeResponse() returned early on !chunk.choices?.[0], dropping the trailing usage-only chunk many OpenAI-compatible upstreams send when stream_options.include_usage is set (confirmed on Fireworks kimi-k3) — state.usage stayed undefined and billing fell back to an uncached token estimate. 154/154 focused assertions across the fix + regression suite. Thanks for tracking down the billing impact!
2026-08-28 15:49:14 -03:00
Diego Rodrigues de Sa e Souza
e4683cd22d fix(test): stop the Alibaba allowlist test from expiring with the catalog (#11867)
`Unit Tests (1/8)` went red on 2026-08-28 across every PR and on main, with
nothing changed — the clock had moved past the shipped catalog's expiry:

  config/alibaba-free-tier-allowlist.json → "validUntil": "2026-08-27"

isAlibabaFreeTierAllowlistPackValid() compares that against Date.now(), so from
28/08 loadAlibabaFreeTierAllowlistPack() returns null and the old
assert.ok(pack) could never pass again. Refreshing the date would only reschedule
the same break.

Production was never affected: resolveActiveAllowlistPack() falls back to the
embedded list when a pack expires, which is the intended design. The defect was
the test asserting the shipped catalog is currently fresh — a data property, not
a behavioral contract.

The test now writes its own packs to a temp dir with dates it controls, and
pins both halves of the contract:

  - inside the validity window, the pack REPLACES the embedded list (anchored on
    a model that exists nowhere else, so loading alone cannot satisfy it);
  - once expired, the pack is ignored and the embedded list serves.

That second path is what production has been running since 27/08 and had no
coverage at all, which is why the expiry surfaced as a red test rather than as
understood behavior. A third case pins the comparison against an injected
instant, including the no-expiry pack that never goes stale.

Whether the curated free-tier catalog still matches reality — and so deserves a
freshly dated pack — is a data question left to the operator in #11866.

Closes #11866
2026-08-28 15:43:53 -03:00
diegosouzapw
21c488f210 chore(release): bring main's post-tag fixes into release/v3.8.51 — sync-back step 2/2
The eighteen commits main carries beyond the cycle branch, and what each one
became here:

  already in release/v3.8.51 by its own PR (no-op, verified by content):
    b090b601a5 / 026e1cadaa  deps: nanoid 3.3.18 equal, dompurify 3.4.14 newer
    918fba5e39  .gitignore: /_tasks already anchored
    5f0a394091  #10026 hide health-check-excluded models — same helper, 5 call sites
    c68cda7dfb  #11075 shared passthrough providers — superseded by #11071/#11078
  superseded, one piece kept:
    ca23eed77c  #10055 memoize models.dev pricing — the cycle memoizes on the
                catalog cache version already; only the resetDbInstance() hook is
                ported, wired to that memo
  applied as-is:
    8778ea7d18  stamp dist/BUILD_SHA before the npm provenance gate (#11721)
    aa52351113  decouple the Bun image from the release manifest (#11724)
    925feb27b8  let the bun digest artifact be absent (#11740)
    b65ef333da  size the install-upgrade gate to a measured run
    0ce21232db  #11845 converge install/upgrade schemas (migration renumbered in
                the next commit: 163 collides with 163_radar_feed_cache_generated_at)
    b7c07edad8  #11855 install-upgrade gate on disk, not tmpfs
    8e2fb04329  #11864 drop *.nft.json from the npm tarball (413)
    dea6bb8b6b  #11877 publish npm from a hosted runner (provenance 422)
  handled by the sync script that follows (CHANGELOG protocol):
    b4ec7807ab  Release v3.8.50 — squash of content this branch already carries
    5458026c21 / c44c0a29e8  CHANGELOG aggregation, stats and top-25
  applied separately (its own commit, ten files):
    65e81158ab  #11088 Ollama capability routing — a 5,094-file squash from a
                stale base; only the Ollama files are the change

Every cherry-pick that touched a file this branch had also changed was
resolved by hand and re-run through the tests both sides own for it.
2026-08-28 14:21:44 -03:00
diegosouzapw
b68af3f090 chore(release): merge release/v3.8.50 tip into release/v3.8.51 — sync-back step 1/2
The v3.8.50 close left 134 post-freeze commits on release/v3.8.50 that never
reached the cycle branch (the freeze cut release/v3.8.51 at 3192eb88d5). A
plain merge of main reproduces all of them through the `Release v3.8.50`
squash against a July merge-base and conflicted on 551 files; merging the
release tip first, against the recent common ancestor, narrows the real
conflicts to 102 (51 generated, 51 judged file by file with a proof each —
see _tasks/postmortems/2026-08-25-release-v3.8.50-pipeline-eficiencia.md,
Parte IV). Step 2 brings main's own post-tag fixes and the finalized
CHANGELOG through scripts/release/sync-next-cycle.mjs.

Resolution rules applied, in order of evidence:
- generated files regenerated with the repo's own generators
  (sync-llm-mirrors, gen-budget-card-svg, gen-provider-reference);
- where release/v3.8.51 already carried the same fix in a newer shape
  (#11524 search sweep, #11551 catalog scheduler, Google BYOP retry, KIE
  Market id map, Docker worker budget measured in #7518) its version stays;
- where release/v3.8.50 carried the newer shape (Volcengine cookie-domain
  CodeQL fix + shared Zod schemas, #11355/#10534 cooldown release helper,
  positive-anchor tests for security-hardening and cli-oneproxy) it wins;
- GPL-retired Raycast/Hailuo (#11691) stay retired: nothing of theirs comes
  back and the public-route test keeps the retired route out;
- the ten changelog.d fragments of v3.8.50 are dropped — they are already
  aggregated in main's CHANGELOG and would double-aggregate at v3.8.51.

Three things git's auto-merge silently produced were caught by a per-line
detector and fixed: providerLimits.ts lost T's imports and the
windowStillExhaustedAfterRealReset helper; catalogCache.ts and
providerLimits.ts kept both sides' identical copies of three declarations;
contextHandoff.ts's new provider-allowlist skip returned undefined against
the #11552 outcome type. Every decision was re-run through the tests both
sides own for it.
2026-08-28 14:07:39 -03:00
Dizzle
b7102140d5 fix(socks): forward Agent.connectTimeout to SocksClient and TLS, unify family null (#11842)
Fixes a SOCKS proxy timeout bypass: Agent.connectTimeout now reaches both the SocksClient.createConnection handshake and the TLS buildConnector phases (previously a stalled/blackholed SOCKS connection could hang past the configured budget), and the fetch-socks family===null path is unified onto createSocksDispatcherWithFamily. Verified against a faux RFC 1928 SOCKS server exercising both pre-grant and post-grant stalls. 6/6 focused tests passing. Thanks!
2026-08-28 12:37:32 -03:00
Paco Cartones
13afbfafb2 fix(vision-bridge): honor authoritative live catalogs (#11813)
Reconciles Vision Bridge auto-selection with each provider's authoritative live model
catalog, revalidating cached selections and preserving routable aliases / live-catalog
IDs / registered effort variants, with fail-open behavior kept when the catalog is
unavailable or non-authoritative. Closes #11767.

One test-side fix applied before merge: "accepts a registry model whose liveCatalogIds
match upstream" used `cgpt-web` (ChatGPT Web) as its fixture provider — retired by #11754
after this PR was authored, which removed every live registry entry populating
`liveCatalogIds` and made the test's expected model unreachable (null, not the retired
id). Swapped the fixture to a synthetic PROVIDER_MODELS entry (the registry Proxy is
writable and reverted in `finally`) so the same production predicate is exercised without
depending on since-deleted registry data. 16/16 focused tests passing on the current tip.
Thanks for the fix!
2026-08-28 11:19:18 -03:00
Paco Cartones
e3563d2512 fix(search): allow explicit AnySearch web fetch (#11716)
Allows /v1/web/fetch callers to explicitly select the already-advertised anysearch-search provider — the REST schema previously rejected it with a Zod error while MCP's web-fetch tool already accepted it. TDD RED demonstrated, 11/11 + 14/14 focused tests passing. Thanks!
2026-08-28 11:14:43 -03:00
Paco Cartones
db87fb6207 fix(providers): honor start day for overnight peak windows (#11718)
Fixes overnight peak-hour windows so 'days' is interpreted as the UTC day the window STARTS, keeping the post-midnight segment protected until its exclusive end boundary — a Monday-only 22:00-02:00 window incorrectly returned inactive on Tuesday at 01:00Z. TDD RED demonstrated, 5/5 focused tests passing. Follow-up to #11622. Thanks!
2026-08-28 11:14:33 -03:00
Paco Cartones
808992a717 fix(antigravity): cap unverified body retry hints (#11823)
Preserves whether a 429 retry hint came from transport headers, structured google.rpc.RetryInfo, or unverified response-body text, and caps body-derived cooldowns at the operator's configured maxCooldownMs so an unverified upstream hint can no longer force an arbitrarily long model/semaphore lockout — authoritative header/structured resets stay intact across combo, chat, and Responses paths. Closes #11695. 29/29 focused tests passing. Thanks!
2026-08-28 11:14:24 -03:00
Paco Cartones
bd4fd893e6 fix(profile): expose accessible status and progress (#11838)
Exposes Profile loading/terminal-error states and exact clamped XP progressbar semantics to assistive technologies, plus a responsive page heading that doesn't duplicate the desktop Dashboard heading. 6/6 focused a11y tests passing. Thanks!
2026-08-28 11:14:12 -03:00
Paco Cartones
07541c4d1f fix(cli): separate node endpoint from server base URL (#11860)
Renames the provider-node URL flag to --endpoint for nodes add/update/validate so it no longer collides in meaning with the global --base-url (the OmniRoute server target), while keeping the API payload field as baseUrl. Closes #11818. Thanks!
2026-08-28 11:14:03 -03:00
Paco Cartones
49a6336b68 fix(playground): simulate persisted combo models (#11862)
Fixes /api/playground/simulate-route to map persisted combo model steps to ordered playground simulation targets and resolve configured providers through their canonical identity, warning explicitly when structural combo steps are omitted instead of silently reporting a complete simulation. Closes #11822. Thanks!
2026-08-28 11:13:54 -03:00
Paco Cartones
5c0b68eeee fix(ui): expose DataTable loading semantics and hide the decorative spinner (#11869)
DataTable's loading state now mirrors PageLoading's a11y convention (role=status, aria-live=polite, aria-busy=true on the container, aria-hidden on the decorative glyph) instead of announcing a bare emoji as content to assistive tech. Honest scope note in the PR body about when aria-live actually fires today. Thanks!
2026-08-28 11:13:47 -03:00
Paco Cartones
0c3181ce16 fix(api): bound provider login timeout to the shared browser-login window (#11871)
Bounds an unclamped provider-login timeout on the generic web-cookie path — a body of {"timeout": 9007199254740991} produced a 9-trillion-iteration poll budget on the shared headful-browser login slot. Extracts the same clamp contract the two provider-specific login services already enforce (300000 default, 15000 min, 600000 max) into a single reusable src/lib/api/loginTimeout.ts. 6/6 focused tests passing. Thanks!
2026-08-28 11:13:33 -03:00
Paco Cartones
dff3f8b424 fix(api): return public-safe errors from the tunnel and MITM routes (#11872)
Closes a real Hard Rule #12 violation — 14 catch blocks across the tunnel/MITM routes echoed a raw err.message, which for Tailscale could leak a live tskey-* credential and always disclosed host layout / OS account name. Routes all 14 sites through a new toPublicSafeTunnelError() classifier, verified by a dedicated regression suite (14/14 passing) asserting no route echoes a raw error.message. Thanks for the security fix!
2026-08-28 11:13:19 -03:00
Paco Cartones
3f35f3afad fix(open-sse): sample stream TTFT/ITL from a monotonic clock (#11873)
Switches stream TTFT/ITL sampling from Date.now() (wall clock) to performance.now() (monotonic) — an NTP correction or manual clock adjustment mid-stream was poisoning routing metrics (inflated TTFT on forward steps, silently-dropped negative TTFT on backward steps). Matches the existing earlyStreamKeepalive.ts precedent on the same streaming path. Thanks!
2026-08-28 11:13:08 -03:00
Diego Rodrigues de Sa e Souza
dea6bb8b6b fix(ci): publish npm from a hosted runner so provenance is accepted (#11877)
* fix(ci): publish npm from a hosted runner so provenance is accepted

The v3.8.50 staged publish failed at the upload:

  npm error code E422
  npm error 422 Unprocessable Entity - POST https://registry.npmjs.org/-/stage/package/omniroute
  Error verifying sigstore provenance bundle: Unsupported GitHub Actions runner
  environment: "self-hosted". Only "github-hosted" runners are supported when
  publishing with provenance.

3.8.49 published fine on 2026-07-30 because it predates USE_VPS_RUNNER being
turned on (2026-08-02). 3.8.50 is the first release since, so the incompatibility
had been latent for four weeks with nothing to surface it.

Neither obvious fix works on its own:

  - dropping --provenance would regress supply-chain posture; 3.8.49 carries a
    SLSA attestation and 3.8.50 must not ship without one;
  - moving the whole job to a hosted runner reintroduces the failure that made it
    self-hosted in the first place — 16 GB is not enough for build:cli's
    next-build fallback (documented on the job's runs-on).

So the work is split by what each runner is actually needed for. The self-hosted
job keeps every heavy gate — build, artifact validation, boot-smoke, the
clean-install/upgrade proof — and then packs the tarball it just proved and hands
it over. A new `stage-npm` job on ubuntu-latest downloads those exact bytes and
performs the upload, which needs no memory at all.

`npm pack --ignore-scripts` on the producing side and `--ignore-scripts` on the
publishing side both matter: prepublishOnly is `build:cli-api && build:cli &&
check:pack-artifact`, and the job already runs all three as explicit steps (the
dist/ prune is logged twice today — once at Build CLI bundle, once redundantly
inside npm stage publish). Re-running them on the small hosted runner would
rebuild bytes that were already built, validated and boot-smoked.

The DIRECT emergency fallback moved too — it published with --provenance and
would have hit the identical 422.

* chore(quality): re-baseline zizmor for the new hosted publish job

The `stage-npm` job adds 2 zizmor findings (192 -> 194), both of the same
deliberate @vN convention every workflow in this repo already follows:
unpinned-uses on actions/download-artifact@v8 and actions/setup-node@v7, plus
the cache-poisoning that setup-node@v7 already raises on the two other jobs in
this very file. SHA-pinning only the new job would break the convention.

No new class: zero template-injection, artipacked, dangerous-triggers or
excessive-permissions. The job declares contents:read + id-token:write, which is
the minimum npm provenance needs.
2026-08-28 10:26:22 -03:00
Diego Rodrigues de Sa e Souza
44cb226b40 fix(quality): ratchet-bank verifier rejects object-valued notes (#11892)
Recreated from #11702 (MumuTW) onto the current release/v3.8.51 — fixes the object-note
comparator bug that has silently blocked the automated ratchet-bank lane since 2026-08-11;
24/24 focused tests + file-size gate green on this tip. Thanks for tracking this down!
2026-08-28 09:40:13 -03:00
MumuTW
394ae23720 fix(sse): harden session affinity key extraction (#11746)
Hardens session-affinity key extraction: no more JSON.stringify on arbitrary request objects,
recognizes bounded text from Responses/chat/Anthropic/Gemini/common string-root shapes, enforces
a shared 4096-char processing budget, and rejects oversized explicit session IDs before
trim/regex/hash work. 128/128 focused affinity/failover tests passing. Closes #11744. Thanks!
2026-08-28 09:31:08 -03:00
Diego Rodrigues de Sa e Souza
b45c97df08 fix(quality): reconcile provider-assets-provenance manifest to the real 142-file snapshot (#11876)
Self-authored follow-up fix, part of the merge-batch session that drained the provider-retirement/provenance sweep PRs this manifest went stale from. Validated (see PR body): gate passes 142/142, regression test 18/18, typecheck/file-size/changelog-integrity/tracked-artifacts all clean.
2026-08-28 06:56:55 -03:00
Diego Rodrigues de Sa e Souza
7d57d9f4a1 fix(providers): retire common ChatGPT Web provider (#11754)
Rebased onto the current release/v3.8.51 tip as part of a combined provider-retirement/provenance merge batch (Designer Web, Felo Web, Runtime, GPL-derived removal, Qwen Web already landed). Large conflict set (this is the biggest PR in the batch — the common ChatGPT Web provider touches chat, images, count-tokens, session leases, and combos). Conflicts resolved:

- `open-sse/config/providers/registry/chatgpt-web/*`, `open-sse/executors/chatgpt-web*`, `open-sse/handlers/imageGeneration/providers/chatgptWeb.ts`, and their tests: kept deleted, matching the PR's stated scope.
- `open-sse/config/providers/registry/minimax/web/index.ts`, `open-sse/handlers/imageGeneration/providers/geminiWeb.ts`, `open-sse/executors/gemini-web.ts`'s stale image-mode branch: base-drift collisions against already-merged sibling retirements (#11691, #11708) — kept deleted / dropped the dead code, since this PR's own branch forked before those merged.
- `src/shared/constants/reservedProviderPrefixes.ts`, `open-sse/executors/index.ts`, `executorProxy.ts`, `virtualFactory.ts`, `autoStrategy.ts`, `src/lib/db/providers.ts`, `src/sse/handlers/chat.ts`: combined the Designer + Runtime (Felo/Qwen) + common-ChatGPT-Web retirement guard calls at each shared chokepoint — compute-once-then-OR pattern, consistent with prior combinations in this batch.
- `src/sse/services/model.ts` / `src/sse/handlers/chatHelpers.ts`: adopted this PR's new `getModelInfoOrRetirementResponse()` central wrapper (a real improvement over ad-hoc try/catch), and extended it to also catch the Designer + Runtime retirement errors it didn't originally cover, so the consolidation doesn't regress the other two mechanisms.
- `src/app/api/v1/images/edits/route.ts`: this PR moved the retirement check earlier (before `enforceApiKeyPolicy`) but left the old later call+catch block in place from base drift — removed the now-redundant duplicate `resolveImageRouteModel()` call and merged the Designer catch into the earlier one.
- `open-sse/config/imageRegistry.ts`, `tests/snapshots/executors/executor-map.json` (`keyCount` recomputed to 133), `tests/snapshots/provider/translate-path.json`: same "both sides inserted a different retired provider at the same slot" pattern — resolved by dropping both.
- `tests/unit/chatcore-executor-proxy.test.ts`, `provider-node-reserved-prefix.test.ts`, `combo-auto-candidate-expansion.test.ts`, `messages-count-tokens-route.test.ts`, `virtual-auto-combo.test.ts`: split into independent per-mechanism test blocks (established pattern); `virtual-auto-combo.test.ts`'s old "includes cookie web-session providers" positive-inclusion test (which used chatgpt-web as its example) was retired along with the provider and replaced by this PR's negative-exclusion test for the same slot.
- `docs/architecture/ARCHITECTURE.md`, `CODEBASE_DOCUMENTATION.md` (+ 4 i18n mirrors), `README.md`, `FREE-TIERS-GUIDE.md`, `docs/diagrams/free-tier-budget.svg`, `docs/screenshots/free-tier-budget-card.svg`, `docs/reference/PROVIDER_REFERENCE.md`: recomputed every stale count from the real merged state — 104 executors (`countFiles` gate logic), 351 providers (regenerated via `gen:provider-reference`), 152/351 `hasFree` entries, 445/438/7 free-tier catalog rows, 13 ToS-avoid providers, budget-card regenerated via its real generator script. One doc conflict (`oauth/` module list) needed picking HEAD's side specifically — theirs still listed the already-removed `raycast` module instead of the real `openference`.
- `config/quality/test-masking-allowlist.json`: additive merge of the PR's 17 `_deletedWithReplacement` entries alongside the batch's existing ones (one real duplicate-key mistake in my first pass, caught and fixed via a `object_pairs_hook` duplicate-key check before finalizing).

Also fixed two real, unrelated-to-my-merge issues surfaced by the focused suite:
- `tests/unit/resolve-web-provider-host.test.ts`: the PR's own test had a typo — it asserted `perplexity-web`'s resolved host as `"perplexity.ai"`, but the provider's registered `website` is `"https://www.perplexity.ai"` and the resolver returns the URL's `host` verbatim (no www-stripping), so the correct value is `"www.perplexity.ai"` (consistent with the same test's own `url` assertion).
- `tests/unit/hard-session-lease-bypass-inventory.test.ts`: this golden call-site inventory was already stale on the pristine post-#11713 tip (confirmed via a throwaway probe worktree) — `src/lib/db/providers.ts`'s 3 connection-fallback sites and a third `src/app/api/providers/route.ts` site were never added to the golden list by the earlier-merged #11698/#11720 PRs. Updated it to the real current inventory (dated inline comments explain each delta and which PR introduced it), plus this PR's own legitimate deltas (image-edits duplicate-call removal, `ChatGptWebExecutor.execute()` site removed).

Focused suite green (433/433 across executor-proxy, reserved-prefix, hard-session-lease-bypass-inventory, resolve-web-provider-host, retirement/runtime-block/source-retirement/management-retirement/image-handler-retirement, migration-168, combo-auto-candidate-expansion, virtual-auto-combo, executor-map-golden and siblings), plus `typecheck:core`, `check-file-size`, and `check-changelog-integrity` clean. Thanks for the thorough provenance-hold retirement work — appreciated.
2026-08-28 06:52:46 -03:00
Diego Rodrigues de Sa e Souza
8e2fb04329 fix(build): drop Next trace manifests from the npm tarball (413 on publish) (#11864)
The v3.8.50 staged publish was refused by the registry:

  npm error code E413
  npm error 413 Payload Too Large - POST https://registry.npmjs.org/-/stage/package/omniroute

The tarball had reached 288.7 MB packed / 1.1 GB unpacked, against 174.5 MB /
792.3 MB for the 3.8.49 that published fine. 842 *.nft.json files accounted for
668.7 MB of that — 61% of the whole package — having doubled from the 325.0 MB
across 748 files shipped in 3.8.49.

Those are Next.js Node File Trace manifests: build-time metadata used to compute
the standalone bundle, never read while serving. Nothing under src/, open-sse/
or bin/ references them, which the new test pins.

Excluding them follows the negation pattern files[] already uses for
node_modules and test sources. Verified against an isolated package that the
glob drops page.js.nft.json while keeping page.js and other.json, so it does
not over-match.

Separately worth tracking: the compiled JS under dist/.build/next also grew 69%
between 3.8.49 and 3.8.50 (231.9 MB to 391.9 MB, +3695 files). That is not what
broke the publish and is left for its own investigation.
2026-08-28 05:29:52 -03:00