Commit Graph

2477 Commits

Author SHA1 Message Date
diegosouzapw
a45076fa2a feat(radar): recognize supporters and add Intel UI 2026-08-14 11:00:12 -03:00
diegosouzapw
7859c843de feat(radar): sync signed Intel insights 2026-08-14 10:59:42 -03:00
diegosouzapw
f806740a2f feat(radar): add supporter offers dashboard 2026-08-14 10:59:37 -03:00
diegosouzapw
e2e589c79c feat(radar): sync signed supporter offers 2026-08-14 10:58:11 -03:00
diegosouzapw
ea95cc2937 feat(mcp): expose Radar catalog tool 2026-08-14 10:52:20 -03:00
diegosouzapw
a801abc8e9 feat(radar): add guided combos page 2026-08-14 10:52:14 -03:00
diegosouzapw
86caa94baa feat(radar): build guided combo suggestions 2026-08-14 10:48:50 -03:00
diegosouzapw
340edbc997 feat(radar): persist local catalog state 2026-08-14 10:48:48 -03:00
backryun
f06d5f20ed fix(types): restore custom model output limit contract (#10339)
`CustomModelEntry` never declared `outputTokenLimit`, but the DB persists it
(src/lib/db/models.ts) and the catalog reads it (src/app/api/v1/models/catalog.ts),
producing TS2551 under the open-sse typecheck gate.

Verified locally against release/v3.8.50 @ 90458a613c: TS2551 count in
models/catalog.ts goes 2 -> 0, and model-token-limit-catalog.test.ts passes 5/5
with the added max_output_tokens projection assertion.
2026-08-14 10:17:49 -03:00
backryun
4eac410c94 fix(types): narrow combo credential preflight (#10254) 2026-08-14 00:57:43 -03:00
Ke Jin
876cc42089 fix(models): make custom model overrides work consistently (#10248)
* fix(models): apply compatible provider context overrides

* refactor(models): remove synced model tombstones

* fix(models): prefer custom model metadata

* docs(changelog): describe custom model fixes
2026-08-14 00:57:38 -03:00
Xiangzhe
8101c879e8 fix(providers): save compatible provider data URL icons (#10247) 2026-08-14 00:57:33 -03:00
Yahoo
ed48328c7c fix(cli): preserve OpenCode JSONC configs (#10246) 2026-08-14 00:57:28 -03:00
Xiangzhe
8417ace4b3 feat(codex): add OAuth fingerprint convergence modes (#10243)
* fix(deps): bump nanoid, dompurify for 2 new Dependabot alerts (#189, #190)

Bumps: nanoid ^3.3.17 (was transitive, now overridden), dompurify ^3.4.13
(with monaco-editor scoped override). Closes Dependabot #189, #190.

Remaining #182-#188 (js-yaml + mermaid) already closed by #9651 merge —
awaiting Dependabot re-scan.

npm audit → 0 vulnerabilities.

* fix(repo): harden .gitignore to also ignore a _tasks symlink (/_tasks)

_tasks is a SEPARATE nested git repo (gitignored). The pattern _tasks/ (trailing
slash) ignores only a directory, not a SYMLINK named _tasks. A self-referential
_tasks symlink can slip in via git add -A and, once pulled, checkout materializes
it over the real _tasks repo (destroying plans/specs/hands-off). Anchored /_tasks
ignores the symlink too, preventing re-capture.

* feat(codex): converge OAuth fingerprints

* test(codex): preserve identity assertions

* fix(codex): preserve explicit off identity

* fix(codex): close fingerprint transport gaps

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@outlook.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-14 00:57:23 -03:00
adevwithpurpose
587e53a3c1 fix(compression): cap countTextTokens at 50k chars and strip base64 data URIs (#10118)
Fixes #10117 — countTextTokens can block the worker event loop for tens of
seconds when a Codex request carries a large base64 image payload, wedging
/healthz and every concurrent request.

- Strip base64 image data URIs before encoding (images are not text)
- Fast-path length guard: over 50k chars, skip the near-quadratic pure-JS
  tokenizer and return the chars/4 heuristic

Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-14 00:57:19 -03:00
Diego Rodrigues de Sa e Souza
97aac6ac6c fix(ci): clear base-reds on release/v3.8.50 (round 4) (#10260)
* fix(ci): clear base-reds on release/v3.8.50 (round 4)

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

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

Refs #9985

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

---------

Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
Co-authored-by: backryun <bakryun0718@proton.me>
2026-08-13 23:02:48 -03:00
DarkAngel
266e39d36d feat(i18n): complete Portuguese (PT-PT) translation (#10250)
- 12,141 strings translated to European Portuguese
- Built on top of the existing pt.json with full coverage of the v3.8.50 catalog
- Remaining ~440 strings are technical terms/brand names kept in English

Co-authored-by: DarkEsteves <DarkEsteves@users.noreply.github.com>
2026-08-13 12:40:36 -03:00
SAMUEL AUGUSTO GUIMARAES LOPES
081f482680 fix(providers): raise default provider probe timeout from 5s to 8s (#9283)
* fix(providers): raise default provider probe timeout from 5s to 8s

The validationRead and modelsProbe presets in safeOutboundFetch.ts used a
fixed 5000ms timeout for the periodic credential health check and on-demand
connection test. Several real free-tier providers (Cerebras, Cloudflare AI
observed in practice) routinely take close to 5s to answer a lightweight
/models probe, which is indistinguishable from a real outage under that
budget — the connection flaps between "active" and "error" in the
dashboard/topology view purely from being near the edge of the timeout, not
from any actual failure.

Raised the default to 8000ms and made it configurable via
OMNIROUTE_PROVIDER_PROBE_TIMEOUT_MS (validated: falls back to 8000ms for
non-numeric or sub-1000ms values) so it can be tuned per-deployment without a
code change. validationWrite and modelsPagination presets are untouched.

Added tests/unit/safe-outbound-fetch-probe-timeout.test.ts covering the
default, env override, invalid-value fallback, and that the other two
presets are unaffected.

* docs(.env.example): document OMNIROUTE_PROVIDER_PROBE_TIMEOUT_MS

* Merge branch 'release/v3.8.50' into fix/provider-probe-timeout

Resolved merge conflict in .env.example: kept both Provider probe section (PR)
and Proxy/relay fetch section (release branch).

Added docs/reference/ENVIRONMENT.md entry for OMNIROUTE_PROVIDER_PROBE_TIMEOUT_MS.

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-13 07:54:40 -03:00
小妍儿 ✨
4ff9f0df0a fix(dashboard): persist model param-filter edits on popover close (#8910) (#9013)
* test: reproduce model param filter close persistence

* fix(dashboard): persist model param filters on popover close (#8910)

ModelCompatPopover declared providerId/modelId in its props type but never
destructured them, so both param-filter fetches referenced undefined
identifiers (TS2304, frozen in the dashboard-typecheck baseline) and threw
into a silent catch. CustomModelsSection also never passed the two props.

- Destructure providerId/modelId; pass them from CustomModelsSection.
- Save pending block/allow drafts when the popover closes or unmounts, so an
  outside mousedown no longer discards them.
- Read drafts from refs at save time and guard concurrent saves, avoiding
  stale-closure payloads and duplicate PUTs.
- Keep dirty state and drafts on non-OK/failed GET or PUT instead of silently
  clearing them; skip state updates after unmount.
- Provider-level block/allow, autoLearn, and other model entries are preserved;
  an empty block+allow still removes only the selected model entry.
- Ratchet the three now-clean dashboard-typecheck baseline entries.

Compat-toggle and upstream-header paths are unchanged.

* fix(dashboard): avoid lost update and surface failed param-filter saves (#8910)

The close-time save could clear the dirty flag for a payload snapshotted
before the PUT resolved, silently discarding any keystroke that landed in
that window. Track a monotonic draft revision and only acknowledge the
revision that was actually written, re-running the save (bounded) otherwise.

A failed save previously stayed dirty to 'retry on a later close', but
reopening the popover reloaded server state and silently reverted the
draft. Keep a dirty draft for the same provider/model on reopen and show a
failure marker next to the saving indicator instead.

* fix(dashboard): protect dirty param-filter drafts from load-effect clobber (#8910)

The retained-draft guard in the param-filters load effect required
paramLoadedKeyRef to match the current target, but that ref was only
assigned after a successful GET. Any draft typed before a successful load
for that target was therefore unguarded, and the clean-slate write
overwrote both the text and the dirty flag:

- a draft typed while the INITIAL load GET was still in flight was
  overwritten and its dirty flag cleared, so the close-path save became a
  no-op and the keystrokes vanished with no feedback;
- after a FAILED initial load, the retained draft was destroyed by the next
  successful reopen load — the exact moment the user reopens to retry —
  and the failure indicator was cleared as if the save had succeeded.

Track the target on the dirty flag itself (paramDirtyKeyRef, set when the
draft is marked dirty) instead of deriving it from a completed load, and
re-check the guard after the GET await so a load result never overwrites
text, clears dirty, or clears the failure indicator for a draft that is
not on the server.

* fix(dashboard): bind the param-filter save to the draft's own target (#8910)

saveModelParamFilters guarded on paramDirtyRef alone and read the
providerId/modelId it closed over, never the target the draft was typed
for. ModelCompatPopover is not always keyed by a stable identity
(CompatibleModelsSection keys by `${alias}:${modelId}`,
PassthroughModelsSection by the full model string, and providerId is
threaded from route/page state), so a re-render can re-point a live,
mounted popover at a different provider/model. If the old target's save
had failed or never ran, the still-dirty draft was then PUT into the NEW
target — writing a filter list under a model/provider the user never
edited and destroying that target's real config.

Replace the dirty flag / revision counter / dirty-key trio with a single
ParamFilterDraft ref that carries the provider, model and both field
values captured at edit time. The save drives its GET, PUT and payload
from that draft instead of the current props, re-reads the ref after
each await (restarting the attempt if the draft was replaced by one for
another target), and only clears it when the exact draft object it wrote
is still pending. Object identity replaces the revision counter, keeping
the existing lost-update protection.

A load no longer clears the draft or the failure indicator: a draft
pending here belongs to another target and is still owed a write to it.
An orphaned draft is therefore neither dropped nor redirected — it keeps
its own provider/model, keeps the failure marker visible, and is retried
by the next blur/close/unmount save. The cleanup effect also depends on
the target key so re-pointing the popover flushes the old draft.

* fix(dashboard): keep param-filter fields and drafts bound to their own target (#8910)

Two remaining defects of the #8910 silent-data-loss family, both reached through
the re-point path of a live ModelCompatPopover.

1. The inputs render blockText/allowText, whose only writer was the load effect —
   and that effect early-returned whenever a draft was dirty for the target. So
   re-pointing A -> B -> A left B's server values on screen under A, and the next
   keystroke snapshotted them into A's draft, persisting B's content into A's
   entry. The fields are now a function of the target: on return to a target with
   a pending draft the draft is restored into the inputs, and on a target with no
   draft the previous target's values are cleared instead of being left behind.
   An edit also no longer trusts the counterpart field unless the values on
   screen belong to the target being edited.

2. The pending draft lived in a single slot that every edit overwrote, so typing
   into a newly pointed target destroyed the previous target's unsaved work while
   the new target's successful save cleared the failure indicator — a green UI
   over data that was never written. Drafts are now keyed by provider/model; the
   save drains every pending draft against its own target, and the indicator
   reflects unsaved work across all targets rather than the last write.

Regression tests: modelCompatPopover-param-filter-target-repoint.test.tsx
(3 cases, RED at ecd111489, GREEN here). Scope limited to this component.

* fix(dashboard): drain midflight param-filter drafts (#8910)

* fix(dashboard): serialize cross-row param-filter saves (#8910)

* docs(changelog): add fragment for #9013

* chore: remove debug console.log and O5 test prefix

---------

Co-authored-by: 千乘妍 (Xiaoyaner) <xiaoyaner0201@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-13 07:54:27 -03:00
小妍儿 ✨
767c01d195 fix(db): invalidate LKGP pins when their provider connection is deleted (#8887) (#8935)
* test(db): RED for LKGP pin invalidation on provider connection delete (#8887)

* fix(db): invalidate LKGP pins when their provider connection is deleted (#8887)

setLKGP() persists { provider, connectionId } under the `lkgp` namespace of
key_value, but none of the three delete paths in db/providers.ts touched that
namespace, so a pin outlived the connection it referenced and became unbounded
stale state.

- Add deleteLKGPByConnectionIds() to its owning module src/lib/db/settings/lkgp.ts
  (no raw lkgp SQL inside providers.ts). Pins without a connectionId and legacy
  plain-string pins are left untouched.
- Wire it into deleteProviderConnection, deleteProviderConnections and
  deleteProviderConnectionsByProvider.
- Add invalidateCachedLKGP() to readCache.ts so the 5s in-memory lkgpCache cannot
  serve a pin that was just deleted; called via the lazy-import pattern already
  used there, so no import cycle (npm run check:cycles OK, 391 files).

No change to updateProviderConnection semantics, no session_model_history change,
no new API route, no migration.

---------

Co-authored-by: 千乘妍 (Xiaoyaner) <xiaoyaner0201@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-13 07:54:20 -03:00
Aman
bf71656637 fix(deepseek-web): classify business auth rejection as 401 (#10218) 2026-08-13 07:53:43 -03:00
Aman
dc185e5aab fix(guardrails): support Responses input images in Vision Bridge (#10202)
* fix(guardrails): bridge Responses input images

* docs(changelog): add #10202 Vision Bridge fix fragment
2026-08-13 07:53:27 -03:00
backryun
9a3f550d88 chore(repo): remove stale one-shot and duplicate helper (#10187) 2026-08-13 07:53:22 -03:00
Dohyun Jung
de32d5ae58 fix(responses): preserve case-insensitive combo names before Codex rewrite (#10177)
* fix(responses): preserve case-insensitive combo names

* test(responses): guard case-insensitive combo rewrite

* test(responses): add case-insensitive combo SQLite guard coverage
2026-08-13 07:53:18 -03:00
Ravi Tharuma
4e1d21f756 docs(settings): Thinking Budget modes + fix Auto i18n collision (#10169)
Co-authored-by: RaviTharuma <RaviTharuma@users.noreply.github.com>
2026-08-13 07:53:12 -03:00
Jonathan Bailey
f5629d2166 fix(discovery): parse reasoning tiers nested under metadata.reasoning.supported_efforts (#10138)
neuralwatt's /v1/models wraps capabilities and reasoning under a metadata
object (metadata.reasoning.supported_efforts + metadata.capabilities
.reasoning_effort), one level deeper than the shapes detectSupported
ThinkingEfforts recognized. Synced openai-compatible rows therefore carried
no supportedThinkingEfforts and no effort aliases were advertised.

Recognize the metadata-nested shape with the same schema and validation as
the top-level #7694 reasoning.supported_efforts, placed right after it in
precedence so a top-level declaration still wins when both are present.
Covered by three regression tests (parse, precedence, malformed-degradation).
2026-08-13 07:53:07 -03:00
Xiangzhe
fbcd57db56 fix(streaming): honor body stream intent for early heartbeats (#10127) 2026-08-13 07:52:51 -03:00
Xiangzhe
9e86a738ed perf(logging): bound call-log rotation work (#10125)
* perf(logging): bound call-log rotation work

* refactor(usage): extract call-log rotation/pruning from callLogs.ts to satisfy the file-size gate

Move the bounded rotation scheduler, orphan-artifact scanner, and row/overflow
pruning helpers (deleteCallLogsBefore, trimCallLogsToMaxRows,
cleanupOverflowCallLogFiles, cleanupOrphanCallLogFiles, rotateCallLogs,
scheduleCallLogRotation) into a new src/lib/usage/callLogRotation.ts module.
Pure extraction, no behavior change — callLogs.ts re-exports the same public
symbols so existing importers (usageDb.ts, compliance/index.ts, the
purge-logs route, and the rotation/cap test suite) are unaffected. Brings
callLogs.ts from 1108 to 787 lines, under the 1000-line file-size cap.

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

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-13 07:52:46 -03:00
Jonathan Bailey
5d873e42d7 feat(crof): advertise reasoning effort tiers incl. max from live discovery and registry (#10062)
* feat(crof): advertise reasoning effort tiers incl. max from live discovery and registry

CrofAI's /v1/models exposes only a boolean reasoning_effort flag, so
discovery previously produced synced rows with no supportedThinkingEfforts
and the catalog/Combo Builder had nothing from which to derive -<tier>
aliases. Map the boolean to the full supported tier list (none/low/
medium/high/max) provider-scoped in discovery, thread providerId through
persistence, and declare the same tiers on every reasoning-capable seed
model (incl. glm-5.2, deepseek-v4-flash-0731, kimi-k3, and the rest of the
live roster) so stale synced caches still resolve effort aliases. max is
verified live: cache-bypassed fixed-seed requests produce distinctly more
reasoning than high, corroborating the Crof owner's statement.

* chore(changelog): add Crof reasoning effort feature fragment

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-13 07:52:32 -03:00
Ke Jin
1a8d38655d fix(reasoning): preserve and replay assistant turns (#10045) 2026-08-13 07:52:12 -03:00
Diego Rodrigues de Sa e Souza
d2dd39ea78 feat(dashboard): Kimi 15% first-top-up campaign — dedicated tracked link + discount-first banner copy (#10240)
Moonshot approved a 15% extra-credits offer for new users' first top-up,
attached to a dedicated tracked link issued for OmniRoute (valid through
2026-09-30). The dashboard banner and the three README API platform
placements now use that link, and the banner description leads with the
discount in all 43 locales, keeping the commitment made when the offer
was requested. The README CTA gains the 15% mention. A code comment
marks the strings to revisit after 2026-09-30 if the offer is not
renewed.

Co-authored-by: backryun <bakryun0718@proton.me>
2026-08-13 04:49:28 -03:00
Markus Hartung
d925f6bf73 fix(logging): document CHAT_LOG_MAX_BODY_KB, capture messageCount for Responses API bodies (#10038)
* fix(logging): document CHAT_LOG_MAX_BODY_KB, capture messageCount for Responses API bodies

Extracted from PR #9439 (agentic conversation tracking). Most of the
original scope this commit was cherry-picked from (CHAT_LOG_MAX_BODY_KB
env var support, the estimateSizeFast() earlyExitAt parameterization)
turned out to already be present on the current upstream/release/v3.8.50
tip -- confirmed via diff and by running check-env-doc-sync.test.ts /
tests/unit/chatcore-log-truncation.test.ts against pristine upstream
before making any changes here. Only two genuine gaps remained:

1. CHAT_LOG_MAX_BODY_KB was read by getChatLogMaxBodyBytes() but
   undocumented in .env.example and docs/reference/ENVIRONMENT.md --
   tests/unit/check-env-doc-sync.test.ts flags any env var read in code
   but missing from both doc files. Documented it (both required --
   the same test enforces the pairing).

2. truncateForLog()'s summary only computed messageCount from
   obj.messages (OpenAI-chat/Gemini field name) -- a large /v1/responses
   request (which uses input[], not messages[]) got summarized with no
   count at all, leaving the dashboard's "Full Conversation" panel
   nothing to base its "N messages not shown" placeholder on for any
   Responses-API conversation, even though the same summarization logic
   applies to it.

Test plan:
- TDD: tests/unit/chatcore-log-truncation.test.ts's new regression test
  ("captures a message count for Responses API bodies too") confirmed
  failing against the pre-fix code, passing after.
- tests/unit/check-env-doc-sync.test.ts confirms CHAT_LOG_MAX_BODY_KB no
  longer appears in codeMissingEnv (remaining drift in that test is
  pre-existing/unrelated -- ANTIGRAVITY_ALLOW_SIGNATURE_BYPASS,
  COMMANDCODE_API_URL, OMNIROUTE_STRICT_SYSTEM_PROVIDERS,
  TLS_FINGERPRINT_PROVIDERS -- confirmed identical on a pristine
  upstream/release/v3.8.50 checkout, base-red inherited: #9985).
- tests/unit/chatcore-log-truncation.test.ts -- 19/19 passing.
- npx tsc --noEmit / npm run lint -- clean.

⚠️ base-red inherited: #9985

* docs(logging): consolidate CHAT_LOG_MAX_BODY_KB into a single entry per file

The variable was already documented (with a stale src/lib/chatLogTruncation.ts
reference in .env.example); keep the new richer entries next to the CHAT_LOG_*
family and drop the old duplicates.

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

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-13 04:02:42 -03:00
Markus Hartung
2f264d96dc fix(dashboard): expose OpenAI Responses store toggle for non-Codex connections (#10121)
* fix(dashboard): expose OpenAI Responses store toggle for non-Codex connections

`EditConnectionModal` only rendered and saved the "OpenAI Responses store"
toggle (providerSpecificData.openaiStoreEnabled) inside the Codex-only
settings block, even though the backend policy that reads this flag
(open-sse/utils/responsesStatePolicy.ts::isOpenAIResponsesStoreEnabled,
applyResponsesPreviousResponseIdPolicy) is already fully provider-agnostic,
and the component already computes a generic `isResponsesConnection` flag
(provider === "openai" or any openai-compatible-responses-* connection, in
addition to codex) that the sibling `preserveEncryptedReasoning` toggle
already correctly uses.

Net effect: an operator with a plain OpenAI API-key connection, or any
generic OpenAI-Responses-compatible proxy connection, had no way anywhere in
the dashboard to opt that connection into `store`/`previous_response_id`
continuation — the policy layer was ready, the control just never rendered
for anything but Codex.

Move the toggle (and its save-time write) out of the isCodex-only block and
gate it on isResponsesConnection instead, matching preserveEncryptedReasoning.
Renamed the local formData field from codexOpenaiStoreEnabled to
openaiResponsesStoreEnabled since it is no longer Codex-specific.

Regression test added (TDD): renders the modal for a plain provider:"openai"
connection and asserts the toggle is present and reflects a persisted flag —
fails on the pre-fix code, passes after.

* fix(responses): stop store-marker leak into Chat Completions requests

The OpenAI Responses store toggle exposed in the previous commit was only
half the fix: the actual store functionality was broken for any model
routed to /v1/chat/completions instead of /v1/responses (e.g. gpt-5-nano,
which lacks the responses-only targetFormat capability). translateRequest
stashes the client's Responses-shaped store intent under an internal
_omnirouteResponsesStore marker so a later re-conversion back to Responses
shape can restore it as store -- but when the destination stays in Chat
Completions shape, that re-conversion never runs, nothing else consumed
the marker, and it leaked verbatim into the real upstream request body.
OpenAI's own API rejects it with 'Unknown parameter: _omnirouteResponsesStore'.
Confirmed live against the real OpenAI API.

Fix: drop the marker unconditionally at the end of translateRequest once
translation is complete, regardless of destination format. Chat Completions'
own store field means something different (dashboard eval storage, not
Responses-style previous_response_id continuation), so the client's intent
must not be silently remapped onto it either -- it's simply dropped.

Also fixes a real crash discovered while live-testing store-enabled
requests: src/sse/handlers/chat.ts referenced isProviderBreakerFailureStatus
without importing it (only the unused PROVIDER_BREAKER_FAILURE_STATUSES
constant was imported), turning a clean 429/'no credits' response into an
uncaught ReferenceError whenever all provider accounts were rate-limited.
Confirmed live (container logs showed the exact ReferenceError before the
fix, and clean error responses after).

Plus two small unrelated base-red fixes needed to get the test suite
running at all on this branch: a broken relative import in
conol-web/index.ts (one path segment short, pointed at a nonexistent
directory), and a real syntax error in gateways.ts (missing closing brace)
that broke esbuild's TypeScript transform for every test file that
transitively imports it, including the pre-existing combo-breaker-429
suite used to verify the isProviderBreakerFailureStatus fix doesn't
regress breaker classification.

Regression test: tests/unit/responses-store-marker-leak.test.ts (confirmed
failing before the translator/index.ts fix, passing after).

⚠️ base-red inherited: migration 143_job_registry.sql duplicated an
already-existing 146_job_registry.sql (byte-identical migration body,
confirmed via diff); the 143 file is deleted since 146 is canonical per
SCHEMA_VERSION_RENAMES. Needed for translateRequest's DB-backed model
capability lookup to run at all in tests.
2026-08-13 04:02:38 -03:00
Markus Hartung
c9daf99e37 fix(combo): clear LKGP pin when its target fails, not only set it on success (#10034)
setLKGP() was only ever called on success — nothing invalidated a "last
known good provider" pin once that provider started failing, so a
*separate* subsequent request kept re-selecting the same just-failed
target via applyStrategyOrdering.ts's LKGP reordering.

Live incident: an OpenClaw request to combo "default" (routerStrategy:
lkgp) got a real reasoning + apply_patch tool call from
opencode-zen/big-pickle, then 3 separate follow-up requests over the
next ~2 minutes each independently re-selected the same big-pickle
target and each timed out with "504 Stream produced no non-ping SSE
event within 95000ms" before the client gave up — instead of failing
over to any of the combo's other 12 models.

Root cause confirmed via code read: circuit breaker and model lockout
deliberately don't react to this failure class (isStreamReadinessFailureErrorBody
exempts STREAM_READINESS_TIMEOUT/combo_target_timeout 504s from tripping
the provider breaker, and REQUEST_SCOPED_UPSTREAM_ERROR_CODES suppresses
model-lockout recording for the same class — both intentional, to avoid
poisoning a healthy provider on request-specific timing). Nothing else
in the system was clearing the stale LKGP pin, so it kept winning
target-selection ordering for every new top-level request.

Fix: add clearLKGP(comboName, modelId) to src/lib/db/settings/lkgp.ts,
export it through settings.ts/localDb.ts, and call it (mirroring the
existing setLKGP-on-success call pattern exactly, same two keys) in both
combo.ts's per-target failure paths -- handleComboChat's "Done retrying
this model" block and handleRoundRobinCombo's structurally identical
twin -- right where a target is finally given up on and the loop moves
to the next one.

TDD: new regression test in tests/unit/combo-routing-engine.test.ts
("clears LKGP after the last-known-good target fails") reproduces the
exact live scenario -- confirmed failing against the pre-fix code,
passing after. Added direct unit coverage for clearLKGP itself in
tests/unit/db-settings-crud.test.ts (deletes only the targeted key,
sibling keys survive; no-op on an unset key doesn't throw) and
registered the new export in db-settings-split.test.ts's public API
surface characterization test.

Test plan:
- Full combo/LKGP-related suite (combo-routing-engine, db-settings-crud,
  db-settings-split, combo-strategy-fallbacks,
  combo-selected-connection-success,
  delete-provider-connection-invalidates-lkgp-8887, db-read-cache) --
  183/183 passing.
- npx tsc --noEmit -- clean for all changed files (pre-existing unrelated
  errors elsewhere in the same test files confirmed identical against a
  pristine upstream/release/v3.8.50 checkout, zero diff at those lines).
- npm run lint -- clean (new test's any usage properly typed, not left
  to inflate the file's frozen any-budget suppression).

⚠️ base-red inherited: #9985
2026-08-13 04:02:34 -03:00
Diego Rodrigues de Sa e Souza
32da2a1afe fix: repair Audio Bridge runtime multipart self-loop (#10229)
* fix(guardrails): serialize audio bridge multipart safely

* docs(changelog): record Audio Bridge multipart fix

---------

Co-authored-by: backryun <bakryun0718@proton.me>
2026-08-13 03:43:44 -03:00
AStupidBear
c2a8505bd9 fix(combo): preserve OpenCode Free oc/ prefix for connections (#10180) 2026-08-13 00:41:03 -03:00
ggdayup
7b2cc8c0f2 fix(sse): apply free-tier filter to auto/best-free on chat path (#10199)
classifyAutoModel in autoRouting.ts returned only {variant:"cheap"} for
auto/best-free, without the spec.tier="free" that builtinCatalog.ts
hardcodes. chat.ts routes via resolveAutoRoutingState (autoRouting.ts),
not createBuiltinAutoCombo (chatHelpers.ts), so the tier filter was
skipped entirely and auto/best-free behaved as plain auto/cheap — paid
backends (e.g. antigravity/gemini-3.6-flash-high) could be selected from
the full pool.

Mirrors the hardcoded spec from builtinCatalog.ts:120 in classifyAutoModel
so both paths apply the free-tier candidate filter consistently.

TDD: tests/unit/auto-best-free-tier-filter.test.ts (RED → GREEN).
2026-08-13 00:40:32 -03:00
Mr White
9a0b007e05 fix(api): custom-model delete no longer tombstones a same-id synced model (#10228)
* fix(api): custom-model delete no longer tombstones a same-id synced model

DELETE /api/provider-models is addressed by `provider` + `model` alone, so
it cannot tell a manually-added custom row from a provider-synced row that
shares the same id. It removed both unconditionally and, because the synced
removal reported success, wrote `isDeleted:true`.

That tombstone is permanent: replaceSyncedAvailableModelsForConnection
filters deleted ids out of every re-import via getModelIsDeleted, so the
provider can never resync. Model sync keeps reporting `added: N` while the
catalog stays empty and /v1/models never lists the model again — even
though routing to it still works, which makes the provider look broken
only in discovery.

Reachable via: eye-hide the synced models (#3782 keeps them in the synced
store), manually add custom models with the same ids, then delete those
custom models. The originals disappear from the catalog while the UI still
lists them.

Remove the custom row first and treat its presence as the operator's
intent, so only a synced-only delete tombstones. #3199 (deleted models stay
dropped) and #3782 (eye-hidden models survive re-sync) are unaffected.

Covered by tests/unit/synced-model-delete-custom-sibling-tombstone.test.ts,
which drives the real route handler: A fails without this change, B guards
the #3199 path.

* chore(changelog): rename fragment to the assigned PR number #10228
2026-08-13 00:39:51 -03:00
Mr White
f0dc77892a fix(api): reach DeepSeek V4's native max reasoning tier (#10230)
* fix(api): reach DeepSeek V4's native max reasoning tier

DeepSeek V4 accepts reasoning_effort low | high | max, defaults to high,
and maps medium and xhigh down to high
(https://api-docs.deepseek.com/api/create-chat-completion; the upstream 400
on an invalid value enumerates none, minimal, low, medium, high, xhigh, max).

OmniRoute's canonical vocabulary is none|low|medium|high|xhigh, where `max`
is an alias collapsing onto `xhigh`. DeepSeek then maps `xhigh` back down to
`high`, so a client sending {"effort":"max"} silently got high — the model's
top reasoning tier was unreachable through the canonical field, and the
catalog never advertised `max` as an available tier.

Mirror the existing extendCodexGpt56EffortValues precedent: expose the
provider-native tier for these models only, without widening the global
request vocabulary. CANONICAL_EFFORT_VALUES and normalizeEffort() are
unchanged, so every other provider keeps collapsing max -> xhigh.

Scoped to the native `deepseek`/`ds` provider. Routed namespaces that merely
carry "deepseek" in the id (openrouter/deepseek/..., tllm/deepseek_v4,
oc/deepseek-v4-flash-free) terminate at a different upstream whose effort
vocabulary we do not control, so they keep the canonical behavior. The
provider is not resolved yet where the canonical params are folded in
(chat.ts), so the check also accepts a `<prefix>/<model>` id.

An explicit client reasoning_effort / reasoning.effort still wins, as before.

Covered by tests/unit/deepseek-native-max-effort.test.ts: 4 of its 6 cases
fail without this change.

* chore(changelog): add fragment for #10230
2026-08-13 00:39:07 -03:00
Diego Rodrigues de Sa e Souza
d259d9fcba fix(ci): clear base-reds on release/v3.8.50 (round 3) (#10213)
* fix(ci): clear base-reds on release/v3.8.50 (round 3)

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

Refs #9985

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

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

Refs #9985

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

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

Refs #9985

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

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

Refs #9985

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

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

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

Refs #9985

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

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

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

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

Refs #9985

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

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

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

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

Refs #9985

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

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

Refs #9985

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

Refs #9985

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

Refs #9985

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

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

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

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

Refs #9985

---------

Co-authored-by: backryun <bakryun0718@proton.me>
2026-08-13 00:02:25 -03:00
Diego Rodrigues de Sa e Souza
f1eb0b8357 refactor(providers): remove the Puter provider at its owner's request (#10210)
Remove the Puter provider (id `puter`, alias `pu`) entirely, at the
request of Puter's owner, Nariman Jelveh:

- registry entry (open-sse/config/providers/registry/puter/) and
  PuterExecutor (open-sse/executors/puter.ts), with their registrations
- API-key preset card (gateways.ts), provider icon and public SVG asset
- 33 free-model catalog entries (pool `puter`)
- authHint i18n key across all 43 UI locales
- credential-requirement frozen-list entry and related comments
- docs: ARCHITECTURE, CODEBASE_DOCUMENTATION, FREE_TIERS (removal note),
  PROVIDER_REFERENCE regenerated (337 providers), translated doc mirrors,
  llm.txt + its 42 i18n mirrors, README/AGENTS/package.json counts
  (338→337 providers, 144→145 migrations) and the 5 canonical SVGs
- migration 152 cleans up stored puter connections/keys/custom models;
  historical usage records are preserved (same principle as migration 151)
- regression guard: tests/unit/puter-provider-removed.test.ts; puter
  fixtures in shared tests swapped for neutral providers; translate-path
  golden snapshot regenerated

Historical CHANGELOG mentions are intentionally preserved; the removal
carries its own CHANGELOG entry.

Co-authored-by: backryun <bakryun0718@proton.me>
2026-08-12 16:54:50 -03:00
Diego Rodrigues de Sa e Souza
ecc89eef14 feat(providers): integrate audited free-tier gateways (#9210)
* feat(providers): add Zylo UnoRouter and Poolside registries

* feat(providers): integrate audited free-tier gateways

* feat: add wave2 free-tier provider registries

* feat(providers): add Mixlayer Speka and TokenReply registries

* feat: add wave 2 free-tier provider registries

* fix: align meganova provider slug

* feat(providers): integrate wave2 free-tier gateways

* feat(providers): add Wave 3-A free-tier registries

* feat(providers): add HelyxAI Auriko and Poixe registries

* feat(providers): add Naga AI and Chat Oripe registries

* feat(providers): integrate wave3 free-tier gateways

* feat(providers): add FreeInference registry

* feat(providers): add Free.ai registry

* feat(providers): integrate wave4 free-tier gateways

* docs: synchronize provider and free-tier inventories

* refactor(providers): split audited gateway catalog

* feat(providers): add audited Void AI and HelixMind gateways

* feat(providers): finalize audited free-tier integration

* test(providers): update APIKEY split count to 229 after rebase onto release/v3.8.50

The rebase merged the release catalog (201 APIKEY providers) with the PR's
28 free-tier additions, yielding 229 total. Correct the characterization
count so the partition assertion reflects the true merged state.

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: backryun <bakryun0718@proton.me>
2026-08-12 16:19:25 -03:00
Diego Rodrigues de Sa e Souza
f6ccd3cf9f fix(quality): green release/v3.8.50 base-reds round 2 (#9985) (#10131)
* fix(quality): green release/v3.8.50 base-reds round 2 — gateways/conol/deepai corruption, migrations, docs, ratchets, dashboard-typecheck

Base-red fix for issue #9985 after the 2026-08-11 merge storm (99 PRs).

Real defects fixed:
- gateways.ts: close regolo entry (was swallowing naga-ac + chatanywhere from #9421), drop stale duplicate chatanywhere entry (#9594)
- conol-web + deepai registry: correct ../shared import depth + deepai executor:default
- modelSelectModalHelpers: close isProviderModelHidden (#9011)
- driverFactory.test.ts: restore eaten test-closing brace (#9173)
- usageTracking: remove duplicate cache_* props
- modelCapability{Overrides,ResolutionSnapshot,Capabilities}: max_token -> max_output_tokens (#9199 vs #8908) + test align
- videoGeneration: drop duplicate handleFalVideoGeneration import (mediaGeneration/fal canonical, #9982)
- responseSanitizer: cast input_tokens_details before .cached_tokens access
- EditConnectionModal: missing alibaba code fields, hoist validationPsd, providerPageHelpers Badge variant union
- FreeBudgetCard: t() -> labels.noApiKey
- peerRouting + cliRuntime: ProcessEnv typing
- image-combo.test.ts: type any -> unknown
- fal.test.ts: moved to tests/unit/services (collected path) 14 tests green
- remove duplicate 143_job_registry.sql (146 canonical), KNOWN_GAPS fix

Docs/ratchets (owner-authorized rebaselines, annotated):
- CHANGELOG 3.8.50 living section restored + 42 i18n mirrors
- MCP-SERVER.md 104->105 tools + i18n
- ENVIRONMENT.md/.env.example: ADOBE_FIREFLY_CHROME_HEADED + DEBUG_CLAUDE_NONSTREAM
- fabricated-docs allowlist: TELEGRAM proposal env vars
- file-size: 5 grown files + proxyFetch 1207->1220
- dead-code 230->248, codeql 2->9 (drift from merged PRs, not this PR)
- untrack _tasks symlink; agent-skills-sync --apply (config-codex-cli)

* fix(changelog): reformat two feature fragments to the bullet convention (#9239, #9490)

* fix(quality): prune stale ESLint suppressions (base-red)

* fix(quality): resolve open-sse type errors + catalog/build regressions (base-red round 3)

Storm-merge splices repaired in the base-fix PR #10131:
- doctor.ts: AppConfig missing brokerSocketPath
- conol-web.ts: Buffer not assignable to BodyInit (Uint8Array)
- tinycms.ts: TinyCmsExecutor.execute return matches BaseExecutor (response/url/transformedBody)
- tinycmsSigner.ts: encodeInto never-narrowing guard + dead wasm URL fallback (Turbopack)
- virtualFactory.ts: options slot for resolutionSnapshot
- bottleneckPatch.ts: insufficient-overlap casts (as unknown as)
- imageCombo.ts: narrow handleImageGeneration union result
- browser-worker.ts: AppConfig + turn.capabilities splice
- conolDiscovery.ts: getProviderOutboundGuard from Policy module
- catalog.ts: drop removed SWR hooks (getCatalogStaleWhileRevalidateMs + accessors), CatalogCachePolicy -> inline settings, resolve 4-arg call
- catalogCache.ts: remove dead inFlight/promise refs
- chat.ts: add isProviderBreakerFailureStatus import
- model-catalog-cache-swr-8728.test.ts: align to #9199 new API (policy injection removed)

* fix(quality): align UI test fixtures to current component contracts (base-red vitest)

- setup-wizard: provide required serverState prop (component gained it in a merged PR)
- grok-device-oauth-modal: next-intl stub resolves grok flow keys to EN labels
- provider-quota-widget: label now inline (PR #8916 removed AutoRefreshButtonLabel extraction) — test the widget
- use-provider-connections-cursor-refresh + phase1f: match /api/providers?provider=<id> query form; hoist heavy dynamic imports to module scope (timeout flake)
- home-topology: mock next/navigation useRouter (component added node-click navigation)
- cooling/lobe/AutoComboCatalog: raise cold-import describe timeouts to 30-60s
- request-logger-*: align to current detail-view contract

* fix(search): guard params.token undefined in serper headers (typecheck base-red)

* fix(search): guard token headers + non-null providerConfig (typecheck base-red)

* fix(changelog): restore base CHANGELOGs eaten by merge auto-resolve (43 files)

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: backryun <bakryun0718@proton.me>
2026-08-12 16:09:34 -03:00
Diego Rodrigues de Sa e Souza
423b616503 fix(dashboard): retarget Kimi promo CTA to the API platform aff link (#10200)
Moonshot cannot sell coding plan subscriptions to most new users, so the
Get Kimi Code traffic could not convert. At their request the dashboard
banner and the README promotional spots now point at
platform.kimi.ai?aff=omniroute with partner-approved copy, in all 43
locales. The dismissal key bumps to -v2 so the retargeted banner shows
once to users who dismissed the old one, and the in-app title finally
adopts the founding Open Source Friend framing agreed in July (title key
renamed to foundingFriendTitle per the value-drift gate rename rule).
Kimi Code provider pages keep kimi.com/code on purpose: they serve
existing coding plan subscribers. Also un-excludes the now-fixed
kimiSponsorBanner.test.tsx from vitest.config.ts (#8618 is closed).

Co-authored-by: backryun <bakryun0718@proton.me>
2026-08-12 15:58:53 -03:00
Diego Rodrigues de Sa e Souza
37d70785c7 fix(build): repair broken production build, red lint gate and SWR crash (#10198)
The Build CI job is advisory, so eight module-level defects from eight
different PRs accumulated on release/v3.8.50 until `npm run build` failed
with 7 Turbopack errors and `npm run lint` with 14.

Build (link-time):
- modelSelectModalHelpers.ts: a lost `}` swallowed PROVIDER_TEST_CHUNK_SIZE
  into isProviderModelHidden's body (#9011).
- videoGeneration.ts: handleFalVideoGeneration imported twice; the standalone
  falHandler.ts is superseded by the provider-neutral mediaGeneration/fal.ts
  and is removed here (#9982 over #9969).
- catalog.ts: re-exported and called the injectable SWR policy that #9199
  deliberately replaced with a fixed 30s bound. Fixed on the consumer side —
  restoring the accessor would resurrect the unbounded window #9199 removed
  after measuring a 41s catalog build in production.
- tinycmsSigner.ts: generated wasm-bindgen glue kept a sidecar
  `new URL('wasm_signer_bg.wasm', import.meta.url)` that no file backs;
  Turbopack resolves it statically. The module ships inlined as WASM_BASE64
  and the only caller always passes it explicitly (#8736/#10087).
- conolDiscovery.ts: imported getProviderOutboundGuard from outboundUrlGuard,
  which does not export it. Fixed on the consumer side: outboundUrlGuard.ts is
  loaded by the packaged CLI without a tsconfig, so it must stay free of
  `@/`-aliased imports (#7682).

Runtime (the build never caught this one):
- catalogCache.ts::scheduleBackgroundRefresh had two dangling statements
  referencing undeclared `inFlight`/`promise`, so EVERY stale-while-revalidate
  read threw a ReferenceError. Surfaced by realigning the #8728 suite, which
  #9199 left asserting a removed contract.

Lint:
- driverFactory.test.ts: a case inserted between the preceding test's `finally`
  and its `});` left the file unparseable, so the SQLite driver-cascade suite
  (26 tests) had not run since 2026-08-11 (#9173).
- providerModelsConfig.ts: imported an executor directly, crossing the G14
  boundary; routed through a new open-sse/services/zaiWebCredentials.ts (#8451).
- image-combo.test.ts: 11 `any` violations, now typed (#9499).

Validation: npm run build exit 0, npm run lint clean, typecheck:core clean,
41/41 tests green across the affected suites.

Refs #9011 #9982 #9199 #8728 #8736 #10087 #8974 #9173 #8451 #9499

Co-authored-by: backryun <bakryun0718@proton.me>
2026-08-12 15:58:40 -03:00
backryun
e9020f0c0c fix: enforce OpenAI model lifecycle without silent reroutes (#8627) 2026-08-12 09:04:41 -03:00
backryun
bd472200d5 [v3.8.50] Fix Z.ai web browser transport and model capabilities (#8451)
* fix: complete Z.ai web browser transport

* refactor: address Z.ai review feedback

* test(zai-web): reconcile the #8014 endpoint guard with the chats/new + signed flow

Rebasing onto release/v3.8.49 pulled in #8503, which repointed CHAT_URL to
/api/v2/chat/completions and added an endpoint probe. This branch already
targets v2, so the executor conflict resolved to this branch's superset
(NEW_CHAT_URL + signature constants alongside the same v2 CHAT_URL). The two
tests needed adapting, because #8503's assertions assume the pre-rework flow:

- executor-zai-web.test.ts: the completion URL now carries the request
  signature as a query string, so an exact-equality check on the endpoint can
  never match. Assert the v2 prefix instead.
- zai-web-chat-endpoint-8014-probe.test.ts: the probe drove the executor with a
  bare cookie credential and no captcha proof, which now routes through the
  browser transport — fetch was never called and the probe captured nothing.
  Supplied a direct-path credential, and matched on pathname across all
  requests (the executor also probes the homepage for the frontend version and
  calls /api/v1/chats/new first).

The guard's intent is unchanged and slightly strengthened: it now asserts no
request reaches the stale unversioned path and that exactly one completions
request is issued, against v2.

54/54 across the zai suites; typecheck:core and eslint clean.

* fix(zai-web): surface upstream error frames instead of finishing empty

Reported on this PR: HTTP 200, `out=0`, stream "complete", no content and no
diagnosis.

Cause. HTTP-level failures are already handled — fetchUpstream turns any !ok
response into a makeErrorResult with the sanitized body. The gap is a 200 whose
SSE body carries an error payload: parseZaiFrame returns null for it,
drainSseDeltas drops it, and buildZaiStreamingBody then closes with an empty
assistant message + stop + [DONE]. The caller reads that as a successful empty
completion, so a rejected signature, an expired captcha and a stale token all
look identical — which is why this had to be diagnosed by reading code rather
than logs. Hard Rule #6.

Fix. parseZaiFrame now classifies an affirmatively error-shaped frame
(`error` at the top level or under `data`, string or {detail|message|msg}) as a
terminal delta, checked before the delta paths so it cannot fall through to the
"no usable delta" null. The stream emits it as `[Z.ai error] <message>`,
matching the mid-stream convention the other web executors already use
(zed-hosted's createErrorChunk) — the 200 is on the wire, so the status cannot
change, but the caller must not be left reading a blank success. Content
streamed before the failure is preserved. Message goes through
sanitizeErrorMessage (Rule #12).

Deliberately NOT changed: a contentless frame still parses to null. That is
live-validated behaviour, not an oversight — z.ai emits phase frames with no
delta_content, and executor-zai-web.test.ts pins it ("returns null for frames
with no usable delta"). Treating "nothing parseable arrived" as a failure would
invent policy on top of an observed protocol and risk false errors on the happy
path, so this only adds recognition of explicit error frames.

Tests (TDD, RED then GREEN): zai-web-silent-empty-repro.test.ts — 7 cases.
Error frame classified and terminal; surfaced through the stream with the
upstream's own text; surfaced after partial content without losing it; plus a
REGRESSION GUARD that contentless/phase-only frames are still skipped, and two
controls that the happy path and reasoning-only output are untouched. The guard
and controls passed before the fix; the four error cases did not.

94/94 across the zai + stream suites; typecheck:core, eslint and check:file-size
clean.

* refactor(sse): extract the zai-web transports so the complexity ratchet holds

The v3.8.49 merge-train rebaseline (#8686) set the ceiling to the tip's own
measurement, leaving zero headroom, so this branch's +5 cyclomatic / +3 cognitive
own-growth had nowhere to sit once rebased onto it.

Eight violations, all in code this branch introduces, resolved by extraction —
no behaviour change:

- `execute` (152 lines, complexity 25, cognitive 20) now delegates to
  `resolveZaiRequest()` for the four client-error rejections and to a
  `fetchViaSignedApi()` method for the CAPTCHA/signature path, so it reads as
  "validate, pick a transport, shape the response".
- `fetchThroughBrowser` (126 lines, cognitive 16) hands its image decoding to
  `resolveZaiBrowserAttachments()`, its Playwright options to
  `buildZaiBrowserChatOptions()`, and its call-log payload to
  `buildZaiBrowserAuditBody()`.
- `configureZaiBrowserEffort` (cognitive 35 — the worst of the set) repeated a
  wrap-and-relabel try/catch four times inside an if/else. `runStage`, which
  already existed one function below, is now module-scoped and reused, and the
  toggle collapses to `checked !== config.enabled` (same four cases).
- `validateWebCookieProvider` (complexity 19) moves its can-we-probe-this
  cascade into `resolveWebCookieProbe()`, which returns either a rejection or
  the URL + headers to use.
- `acquireBrowserContext`'s creation closure (complexity 17) hands cookie and
  localStorage seeding to `seedContextSession()`.

That last extraction also clears a violation that predates this branch —
`acquireBrowserContext` was already over the 80-line ceiling — so cyclomatic
lands at 2187 against a baseline of 2188.

Verified: check:complexity-ratchets green both metrics; typecheck:core clean;
ESLint clean on all four files; 85 tests across the zai-web, web-cookie
validation, browser-pool and model-test-runner suites pass.

* fix(zai-web): surface upstream errors on the non-streaming path

collectZaiNonStreaming ignored delta.error — a 200 whose SSE body carries
an error frame (rejected signature, expired captcha, stale token) came
back as a successful empty completion. Now it throws on an error frame,
matching the streaming path's [Z.ai error] convention; the caller's
existing try/catch returns makeErrorResult(502) instead of an empty 200.

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

---------

Co-authored-by: backryun <busan011@ormbiz.co.kr>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-12 08:41:03 -03:00
backryun
acc066db3f [v3.8.50] feat(devin-desktop): replace public Windsurf provider (#8228)
* feat(devin-desktop): replace public Windsurf provider

* fix(migrations): renumber Devin Desktop migration to 151 (avoid 147 collision)

147_windsurf_to_devin_desktop.sql collided with the released
147_api_keys_model_access_mode.sql — getMigrationFiles throws
"Migration version collision detected" on every DB start. Base occupies
slots up to 150, so renumber the new migration to 151 and point the
windsurf→devin RENAMED_MIGRATION_COMPATIBILITY entries (and tests) at it.
147 is freed in KNOWN_GAPS since 147_api_keys now owns the slot.

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

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-12 08:40:18 -03:00
backryun
c9282248eb fix(types): restore provider breaker status guard (#10088) 2026-08-12 08:25:10 -03:00
Diego Rodrigues de Sa e Souza
b7b9fe0baa fix(ci): clear base-red typecheck + migration collisions (release/v3.8.50, #9985) (#10152)
* fix(ci): clear base-red typecheck + migration collisions on release/v3.8.50

Resolve 13 typecheck:core errors (deepai executor/import, responseSanitizer
cached_tokens typing, search.ts token headers, usageTracking duplicate props,
modelCapabilityOverrideKey max_token, executeWebSearch null) and remove the
stale duplicate 143_job_registry.sql (canonical is 146_job_registry per
RENAMED_MIGRATION_COMPATIBILITY), freeing the 147 KNOWN_GAPS entry.

Base-reds tracked by #9985.

* fix(changelog): reformat 9239/9490 feature fragments to bullet convention (base-red #9985)

---------

Co-authored-by: backryun <bakryun0718@proton.me>
2026-08-12 07:56:35 -03:00