Commit Graph

1141 Commits

Author SHA1 Message Date
Bob.Hou
4be37d149b feat(dashboard): expose comboTimeoutMs next to Target timeout (#13857)
* feat(dashboard): expose comboTimeoutMs next to Target timeout

The runtime already applied config.comboTimeoutMs as the whole-combo
wall-clock budget (0 = 10-minute hang-stop). Schema treated it as an
unknown passthrough key and the dashboard only painted Target timeout,
so operators could not raise the 15-step failover ceiling from the UI.

Declare comboTimeoutMs on comboRuntimeConfigSchema, mount both knobs in
the combo editor Advanced panel and Combo defaults, and keep
comboTimeoutMs longer than targetTimeoutMs so failover still has time.

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

* docs(changelog): attach #13857 to comboTimeoutMs fragment

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

* test(dashboard): name comboTimeoutMs store as milliseconds

The input is seconds; the stored config field is milliseconds. The old
title said "in seconds" while asserting 1_200_000.

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

* chore(i18n,changelog): translate #13857 keys into all locales and drop the CHANGELOG hunk

---------

Signed-off-by: Minxi Hou <houminxi@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-09-17 18:47:07 -03:00
Bob.Hou
ec780ef2dc feat(compression): make Lite tool-result truncation length configurable (#13915)
* feat(compression): make Lite tool-result truncation length configurable

Lite truncated tool results at a hardcoded 2000 characters. Coding-agent
payloads (file reads, crash dumps) lost the middle of the content with no
supported way to raise the cap.

Honor lite.maxToolLength from settings, then OMNIROUTE_LITE_MAX_TOOL_LENGTH,
then 2000. Existing installs keep the old length.

Related to #13178.

Signed-off-by: Minxi Hou <houminxi@gmail.com>
#13178 stays open.

* compression/lite: keep a stored cap when a step or toggle write is incomplete

An out-of-range step maxToolLength was still a number, so it hid a valid
global cap and fell through to env. A toggle-only settings PUT replaced
the whole lite row and dropped the stored cap. Save treated an out-of-range
number like a cleared field. Reject the bad Save, merge omitted caps, and
use null to clear.

Related to #13178.

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

* compression/lite: stop dashboard copy from hard-coding a 2000-char cap

The page overlays schema descriptions from i18n. Updating only
LITE_SCHEMA left operators seeing "over 2,000 characters" after the
cap became configurable. Also assert the Save error string, not the
Save button.

Related to #13178.

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

* chore(changelog): move #13915 entry to a changelog.d fragment

---------

Signed-off-by: Minxi Hou <houminxi@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-09-17 18:46:45 -03:00
Fouad Salkini
ec60915d91 fix(api): accept blockedModels in the key permissions schema (#13666)
* fix(api): accept blockedModels in the key permissions schema

`PATCH /api/keys/[id]` already destructures `blockedModels`, forwards it
into the update payload, and `updateApiKeyPermissions()` writes it to the
`blocked_models` column. Only the first link was missing:
`updateKeyPermissionsSchema` never declared the field, so Zod stripped it
from the parsed body and the destructured value was always `undefined`.
The request answered 200 and wrote nothing.

The API Manager permissions modal sends `blockedModels` on every save
(ApiManagerPageClient.tsx), so the Claude-Code family-blocking control
silently did nothing and an existing deny-list could not be cleared.
`blockedModels` is the deny-list half of the model policy — read by
`isModelAllowedForKey()` before the allow-list and winning over it — so
that half was only reachable by editing the database by hand.

Declare the field mirroring `allowedModels` (trimmed, non-empty, max
1000) and count it in the "No valid fields to update" guard so a body
carrying only `blockedModels` is a valid update.

Left out of `createKeySchema` deliberately: the create route does not
read `blockedModels`, so declaring it there would be dead weight.

* docs(changelog): add fragment for blockedModels key schema fix

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

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-09-17 16:26:42 -03:00
Nguyen Thanh Dat
21d756d7f0 fix(combos): accept isHidden in updateComboSchema (#12898)
* fix(combos): accept isHidden in updateComboSchema

A combo's visibility is stored on the record and honoured by the builder
option list and the dashboard grid, but updateComboSchema never listed
isHidden. The PUT handler spreads the validated body, so zod stripped the
field: a visibility-only update was rejected as "No valid fields to
update", and a mixed update succeeded while dropping the visibility
change.

Closes #12836

* docs(changelog): fragment for #12898
2026-09-17 16:25:16 -03:00
Diego Rodrigues de Sa e Souza
83fa4328f3 feat(providers): add xKiro (#12648)
* test(catalog): pin the 2026-09-02 free-tier re-audit facts for gemini, ollama-cloud, groq, nara and mistral

* feat(providers): add xKiro (5M tokens/day free plan, 39 pinned free models)

* fix(catalog): re-audit gemini, ollama-cloud, groq, nara and mistral against official pages

* docs(providers): xKiro in the provider reference, counts and free-tier headline (~1.66B)

* fix(catalog): restore the console-verified Mistral 1B pool and harden its regression test

* docs(free-tiers): move headline to the re-audited ~1.50B and refresh pool counts

* chore(free-tiers): retire stale Groq free-tier text and preset model; fix catalog header

* docs(providers): align the remaining visible provider/executor counts with the catalog

* docs(providers): align remaining free-tier count chips and metadata

* docs(free-tiers): state the evidence-comment rule honestly and retire the last "14.4K RPD" Groq texts

* docs(free-tiers): retire the stale Gemini onboarding quota text

* docs(free-tier): refresh catalog-entry counts to 442 after base sync

* docs(providers): re-sync provider and free-tier counts after merging release/v3.8.51

* docs(providers): re-sync residual counts after the base merge

* docs(free-tiers): restore README spacing lost in the merge and re-sync the guide counts

* docs(free-tiers): re-sync numbers after merging release/v3.8.51 (Cerebras reclassified upstream)

* fix(docs): keep the NaraRouter plans endpoint out of the API-path checker; rebaseline gateways.ts (+3)

* chore(quality): rebaseline gateways.ts file-size cap for the xKiro entry (+20)

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-09-17 13:34:45 -03:00
Diego Rodrigues de Sa e Souza
b6975537c1 fix(providers): remove the chipotle/pepper provider (#13131) (#13913)
* fix(providers): remove the chipotle/pepper provider (#13131)

amelia.chipotle.com (the reverse-engineered Amelia chat-widget backend
chipotle/pepper-1 talked to) now returns 404 on every route, including
root, from its Azure Application Gateway — confirmed live 2026-09-15.
This regressed from a WS handshake timeout (#4037, June 2026) to a
fully decommissioned host, so the upstream protocol cannot be fixed.
Owner decided to retire the provider entirely (Option B), following
the phind/kluster quiet-removal precedent: no REMOVED_PROVIDERS.md
entry (reserved for operator takedowns), just a one-line note under
FREE_TIERS.md "Removed / no free tier".

Removed every surface: executor, registry entry, executors/index.ts
and providers/index.ts wiring, noauth provider catalog entry,
ProviderIcon generic-fallback set, the autoCombo exclusion-list
comment, the chipotle_error code from the sanitizer allowlist,
PROVIDER_REFERENCE.md (regenerated), and every doc/test reference.

Regression test: tests/unit/issue-13131-chipotle-provider-removed.test.ts
asserts the provider is fully gone from the executor registry, the
provider REGISTRY and the noauth catalog, and that the executor module
no longer resolves — not a live-network repro (flaky/third-party).

Several existing tests used "chipotle" only as a generic noAuth-provider
example (proxy scoping, error classification, onboarding, fallback
text) with no chipotle-specific behavior under test; those were
re-pointed at another still-existing noAuth provider
(cloudflare-playground / duckduckgo-web) rather than weakened.

* test(providers): document the agnes-cn/chipotle count coincidence (#13131)

provider-node-reserved-prefix.test.ts's REGISTRY id+alias walk was
already red on the base tip (414 vs. expected 412) from agnes-cn
(#13399, +id/+alias). Removing chipotle's REGISTRY id/alias in this
PR nets it back to 412, making the test pass again without a numeric
edit — record why in a comment so it doesn't read as an untracked
coincidence later.
2026-09-17 13:22:09 -03:00
initguru
3e080877f2 fix(sse): bound active streams without terminal events (#12913)
* fix(sse): bound active streams without terminal events

* fix(sse): derive the active-stream ceiling from the largest registered model budget

The watchdog is a hard lifetime cap that never resets on bytes, so a flat
15-minute default killed models the registry already allows to run for 20
minutes (the Codex entries declare timeoutMs: 1_200_000). The default is now
that maximum plus a one-minute margin, and a new test re-derives the maximum
from the registry so a future larger budget fails the gate instead of silently
re-opening the bug.

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

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-09-17 10:44:48 -03:00
initguru
4c4d5c7fbe fix(resilience): allow maxWaitMs=0 as disable sentinel for execution expiration (#12902)
* fix(resilience): allow maxWaitMs=0 as disable sentinel for execution expiration

maxWaitMs normalization clamped the value to min:1, silently rewriting
an operator's 0 ("disable the limiter-managed execution deadline") into
1 — a 1ms expiration that killed every long-running job instantly. This
broke long-running reasoning models (GLM-5.2 with reasoning.effort=max
spends minutes before the first token, exceeding any practical
maxWaitMs; the TTB safety net is FETCH_TIMEOUT_MS, default 600s).

Fix: lower the floor to min:0 so 0 is preserved as the disable sentinel.
Issue #4165 follow-up.

Tests: 7/7 (resilience-normalize-maxwaitms-disable 5 + rate-limit-
maxwaitms-disable-execution 2). typecheck:core clean.

* fix(resilience): relax requestQueueSettingsSchema.maxWaitMs to allow 0

normalizeRequestQueueSettings already treats maxWaitMs=0 as an explicit
disable sentinel (queue-wait budget off), but the settings API schema
still rejected 0 with min(1), so an operator could never actually reach
the fix through PATCH /api/resilience. executionMaxWaitMs is untouched
(stays min(1) — separate field, separate decision, see #12902 item 4).

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

* test(resilience): prove maxWaitMs=0 vs #12715's queue-wait gate behavior

Answers the open technical question from #12902's review: does a
GLOBAL maxWaitMs=0 reintroduce the unbounded-queue regression #12715
fixed (a request hanging ~6min until the client aborts)?

Evidence, exercising the real gate chatCore.ts actually calls
(accountSemaphore.acquireMany({ timeoutMs: requestQueue.maxWaitMs }),
not the Bottleneck reservoir the PR's own tests cover) under real
contention (maxConcurrency=1, two concurrent acquires):

  - No: it does not hang. setTimeout(reject, 0) fires on the next
    tick, so a second contending request is rejected with
    SEMAPHORE_TIMEOUT in low milliseconds, never minutes.
  - But it is also not a genuine 'no cap' — an operator setting 0
    expecting 'wait as long as it takes' instead gets near-zero
    tolerance for even momentary contention on any configured
    concurrency gate (global/provider/account). This is a real
    asymmetry vs. the Bottleneck reservoir path (where 0 truly means
    unbounded) left for the maintainer to decide how to resolve —
    not something this pass can decide unilaterally.

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

---------

Co-authored-by: Jihyun Son <jihyun.son@sk.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-09-17 10:44:30 -03:00
Nguyen Thanh Dat
53e3e23c13 fix(api): stop DISABLE_SQLITE_AUTO_BACKUP from turning off Redis rate limiting (#13329)
A backup flag was being used as a proxy for test mode, so `DISABLE_SQLITE_AUTO_BACKUP` also disabled Redis rate limiting — two unrelated concerns riding one variable. Probe on your head: 3/3 + 13/13 pass across the new test and the existing rate-limiter suite.

Thanks, @datrixlab — catching that the existing rate-limiter tests still pass is what shows this untangled the two without changing the intended behavior of either.

**Batch validation** — boarded with the other 10 PRs of your batch into one worktree cut from `release/v3.8.51`; every PR verified as an ancestor of the combined HEAD before validating.

- Focused tests across all 11 PRs: **104/104 pass** on the combined tree.
- Gates on the combined tree: `check-changelog-integrity` PASS, `check-complexity` PASS, `check-cognitive-complexity` PASS, `typecheck:core` PASS, `check:open-sse-typecheck` PASS.
- `check-file-size` is red, but reproduces with byte-identical line counts on the pure `release/v3.8.51` tip (`open-sse/handlers/imageGeneration.ts` 3304, `open-sse/services/combo/roundRobinCombo.ts` 1221, `open-sse/utils/stream.ts` 3115). Inherited base-red, nothing added by this batch — it is also why this PR's "Fast Quality Gates" check was red.
2026-09-16 21:03:09 -03:00
Nguyen Thanh Dat
bb45fb8ede fix(providers): let a dashboard OFF for private provider URLs beat the env opt-in (#13323)
Confirmed on the tip: `src/shared/network/outboundUrlGuardPolicy.ts` only checked `isTrueValue(dbValue)`, so an explicit dashboard OFF fell through to the env opt-in instead of overriding it — the operator turning something off in the UI had no effect. Probe on your head: 7/7 pass.

Thanks, @datrixlab — an explicit OFF in the UI losing to an env var is the kind of thing that erodes trust in the whole settings surface.

**Batch validation** — boarded with the other 10 PRs of your batch into one worktree cut from `release/v3.8.51`; every PR verified as an ancestor of the combined HEAD before validating.

- Focused tests across all 11 PRs: **104/104 pass** on the combined tree.
- Gates on the combined tree: `check-changelog-integrity` PASS, `check-complexity` PASS, `check-cognitive-complexity` PASS, `typecheck:core` PASS, `check:open-sse-typecheck` PASS.
- `check-file-size` is red, but reproduces with byte-identical line counts on the pure `release/v3.8.51` tip (`open-sse/handlers/imageGeneration.ts` 3304, `open-sse/services/combo/roundRobinCombo.ts` 1221, `open-sse/utils/stream.ts` 3115). Inherited base-red, nothing added by this batch — it is also why this PR's "Fast Quality Gates" check was red.
2026-09-16 21:02:35 -03:00
Dmitry Kuznetsov
2f0a01d75c fix(oauth): expose manual Codex callback entry (#9944)
Merged after batch validation on a combined worktree cut from `release/v3.8.51` with #9908 and #7138.

**Evidence**
- Focused tests: 36/36 pass on the combined tree (`oauth-modal-codex-lan-ip-8046`, `antigravity-image-credential-retry`, `antigravity-usage-service`, `generic-quota-fetcher`), including the 2 pre-existing anchor tests that assert `codex` stays in `PKCE_CALLBACK_SERVER_PROVIDERS` and that the `localhost:1455` redirect URI is untouched.
- Gates on the combined tree: `check-complexity` PASS, `check-cognitive-complexity` PASS, `typecheck:core` PASS, `check-changelog-integrity` PASS.
- `check-file-size` is red, but reproduces byte-identical on the pure `release/v3.8.51` tip (`imageGeneration.ts`, `roundRobinCombo.ts`, `stream.ts`) — inherited base-red, not from this PR.

**Reconciled**
- `changelog.d/fixes/codex-manual-loopback-action.md` did not start with a markdown bullet, which is the one thing `check-changelog-integrity` failed on. Fixed in your branch (d3dbb5b) so the fragment convention holds; nothing else in your diff was touched.

Thanks for this one, @Ardem2025 — exposing the manual callback entry that already existed in the code instead of adding a new flow is exactly the right shape for the LAN/remote case, and keeping every PKCE/state check untouched made it easy to verify.
2026-09-16 19:59:25 -03:00
Jan Leon
a16705344e feat(dashboard): add a dedicated API-key routing editor (#13555)
Merged. Moving rule editing out of the permissions modal into `/dashboard/api-manager/routing` fixes the real problem (a cramped modal for something with conditions, effects and three target kinds), and the contract tests pin what matters: persisted fields round-trip, combo names match without rewriting off-catalog IDs, failed saves stay editable, unsaved drafts survive key switches, and writes are disabled after a failed load. Rule evaluation and authorization are untouched.

Validated as a combined board first (this PR merged with the 4 siblings of the JxnLexn wave on the release tip): eslint with the frozen suppressions, typecheck:core, check:open-sse-typecheck, complexity, cognitive-complexity, changelog-integrity, docs-sync, migration-numbering, provider-consistency and a duplicate-identifier audit all green, plus 77 passing / 0 failing focused node:test cases across the test files the wave touches. The wave's i18n fill (new keys carried to all 66 locales), free-tier doc counts and file-size rebaseline land in one follow-up PR right after the wave, as with #13904.

Thank you.
2026-09-16 16:38:04 -03:00
Jan Leon
502e614850 fix(vertex): discover and route partner models correctly (#12471)
Merged. Root cause first: `publishers.models.list` was called with `pageSize=1000` against Google's hard maximum of 300, so every publisher answered 400 and discovery silently fell back to the stale static catalog. On top of that the PR separates API-key from Service-Account capabilities correctly (keys cannot list Model Garden — project-scoped curated catalog; SA tokens can — live catalog), stops treating the expected generativelanguage rejection of a Service Account as a discovery failure, replaces the speculative partner IDs with documented MaaS IDs, and rejects OAuth client-config JSON with a clear message instead of a misleading one.

The 5 ESLint errors flagged during the earlier fix sweep were fixed in your own follow-up commits; the branch was reconciled with the release tip and the 42 locale files were checked for lost keys before this merge.

Validated as a combined board first (this PR merged with the 4 siblings of the JxnLexn wave on the release tip): eslint with the frozen suppressions, typecheck:core, check:open-sse-typecheck, complexity, cognitive-complexity, changelog-integrity, docs-sync, migration-numbering, provider-consistency and a duplicate-identifier audit all green, plus 77 passing / 0 failing focused node:test cases across the test files the wave touches. The wave's i18n fill (new keys carried to all 66 locales), free-tier doc counts and file-size rebaseline land in one follow-up PR right after the wave, as with #13904.

Thank you — the credential-capability distinction and the retired/non-chat filtering are what make the Vertex listing trustworthy.
2026-09-16 16:37:41 -03:00
Markus Hartung
1cf8e4bcc6 fix(db): auto-clean terminal batch checkpoints and expired file content (#12999)
Merged after a maintainer rework that kept every one of @hartmark's commits intact.

**What the rework added:** the auto-clean of terminal batch checkpoints and expired file content is gated behind a default-off feature flag (`BATCH_AND_FILE_AUTO_CLEANUP_ENABLED`, `defaultValue: "false"`, documented in `docs/reference/FEATURE_FLAGS.md` and described in all 66 locales) so the release default keeps today's behaviour and operators opt in; the DB handle leak in the test was fixed so the Node runner exits cleanly.

Validated as a combined board first (this PR merged with the 21 siblings of the same wave on the release tip): eslint with the frozen suppressions, typecheck:core, check:open-sse-typecheck, complexity, cognitive-complexity, changelog-integrity, i18n new-key coverage, docs-counts, docs-sync, migration-numbering, provider-consistency and a duplicate-identifier audit all green, plus 176 passing / 0 failing focused node:test cases across the 25 test files the wave touches and the dashboard test under Vitest (2/0). Then re-validated alone on the fresh tip before this merge: conflicts re-resolved, file sizes rebaselined for this PR's own growth, eslint and this PR's focused tests re-run.

Thank you — the cleanup itself is exactly the kind of maintenance that stops a data dir from growing forever.
2026-09-16 15:45:34 -03:00
Patryk Kopyciński
23c5772ccb feat: adaptive reasoning effort (auto) — gateway-resolved, per-turn pinned, all harnesses (#13448)
Merged after a maintainer rework that kept every one of @patrykkopycinski's commits intact — including the two refactors you pushed later (extracting the adaptive-effort wiring out of `chatCore.ts` and reading `x-omniroute-effort` inside the wiring module), which were merged into the rework rather than overwritten.

**What the rework added:** the adaptive-effort wiring is scoped to OpenAI-dispatch requests only (the claim in `docs/routing` was corrected to match), and `defaultReasoningEffort` was widened to accept `auto` explicitly instead of relying on a loose string.

Validated as a combined board first (this PR merged with the 21 siblings of the same wave on the release tip): eslint with the frozen suppressions, typecheck:core, check:open-sse-typecheck, complexity, cognitive-complexity, changelog-integrity, i18n new-key coverage, docs-counts, docs-sync, migration-numbering, provider-consistency and a duplicate-identifier audit all green, plus 176 passing / 0 failing focused node:test cases across the 25 test files the wave touches and the dashboard test under Vitest (2/0). Then re-validated alone on the fresh tip before this merge: conflicts re-resolved, file sizes rebaselined for this PR's own growth, eslint and this PR's focused tests re-run.

Thank you — gateway-resolved, per-turn pinned effort is a real feature, and the header contract makes it usable from every harness.
2026-09-16 15:18:36 -03:00
Markus Hartung
ac32cea4f0 fix(logs): recover concatenated JSON objects in the Provider Event Stream viewer (#13115)
Merged. Concatenated JSON objects in the Provider Event Stream are a real wire shape, not a corruption — recovering them in the viewer instead of dropping the batch makes the tool trustworthy when it matters.

Validated as a combined board first (this PR merged with the 21 siblings of the same wave on the release tip): eslint with the frozen suppressions, typecheck:core, check:open-sse-typecheck, complexity, cognitive-complexity, changelog-integrity, i18n new-key coverage, docs-counts, docs-sync, migration-numbering, provider-consistency and a duplicate-identifier audit all green, plus 176 passing / 0 failing focused node:test cases across the 25 test files the wave touches and the dashboard test under Vitest (2/0). Then re-validated alone on the fresh tip before this merge: conflicts re-resolved, file sizes rebaselined for this PR's own growth, eslint and this PR's focused tests re-run.

Thank you.
2026-09-16 13:37:23 -03:00
lorenzozane
5847c43922 fix(resilience): name collision keys, surface header drops, retry embeds, skip far-reset pings (#13766)
Merged. Four small, independently justified changes, each with its own regression test — naming both sides of a case-insensitive key collision, surfacing the dropped-header count to the caller instead of only to the log, one retry before a memory is left unvectorized, and skipping warm pings for a window whose reset is more than 24h out. The first-seen-wins resolution and the caller-visible behaviour of everything else are unchanged.

Validated as a combined board first (this PR merged with the 21 siblings of the same wave on the release tip): eslint with the frozen suppressions, typecheck:core, check:open-sse-typecheck, complexity, cognitive-complexity, changelog-integrity, i18n new-key coverage, docs-counts, docs-sync, migration-numbering, provider-consistency and a duplicate-identifier audit all green, plus 176 passing / 0 failing focused node:test cases across the 25 test files the wave touches and the dashboard test under Vitest (2/0). Then re-validated alone on the fresh tip before this merge: conflicts re-resolved, file sizes rebaselined for this PR's own growth, eslint and this PR's focused tests re-run.

Thank you for keeping each of the four minimal and commented — that is what made this reviewable as one PR.
2026-09-16 13:36:35 -03:00
Bob.Hou
e7999c477b fix(db): bound health scans and isolate native diagnostics (#13717)
Merged after a maintainer rework that kept every one of @HouMinXi's commits intact.

**What the rework added on top of the contribution:** the new DB health-check behaviour is gated behind a default-off feature flag (`src/shared/constants/featureFlagDefinitions.ts`, `defaultValue: "false"`), documented in `docs/reference/FEATURE_FLAGS.md` with the description key carried into all 66 locales, so the release default is unchanged and the new bounds only apply when an operator opts in. The optional-FTS5 migration set was reconciled by hand with the "180" entry that landed meanwhile (`src/lib/db/migrationRunner/constants.ts`).

**Carried from your rebased head:** the `/api/db/health` local-only classification in `src/server/authz/routeGuard.ts` plus its `routeGuard` assertion — `runManagedDbHealthCheck()` forks native diagnostics into a child process, so Hard Rules #15/#17 apply. Re-verified here: 37 pass / 0 fail.

Validated as a combined board first (this PR merged with the 21 siblings of the same wave on the release tip): eslint with the frozen suppressions, typecheck:core, check:open-sse-typecheck, complexity, cognitive-complexity, changelog-integrity, i18n new-key coverage, docs-counts, docs-sync, migration-numbering, provider-consistency and a duplicate-identifier audit all green, plus 176 passing / 0 failing focused node:test cases across the 25 test files the wave touches and the dashboard test under Vitest (2/0). Then re-validated alone on the fresh tip before this merge: conflicts re-resolved, file sizes rebaselined for this PR's own growth, eslint and this PR's focused tests re-run.

Thank you for the depth of this one — the resource-bounds suite and the sql.js startup/backup coverage are the kind of tests that keep a database layer honest.
2026-09-16 12:59:45 -03:00
Bob.Hou
54f19c7742 feat(providers): fetch live xAI catalog for xai-oauth (#13518) 2026-09-16 08:08:49 -03:00
Bob.Hou
cdcde97c70 feat(providers): add Agnes AI (China) as agnes-cn on api.agnes-ai.cn (#13399) 2026-09-16 08:01:39 -03:00
Diego Rodrigues de Sa e Souza
675ab1875e fix(api): persist hideAutoCombos/hideNoThinkVariants in settings schema (#13562) (#13800)
Merged in the 2026-09-16 sweep of the maintainer's own open PRs, at the owner's explicit instruction. No push was made to the PR branch: the merge took the head as the owning session left it (verified OPEN, non-draft and MERGEABLE against the release tip immediately before merging).
2026-09-16 06:11:56 -03:00
Diego Rodrigues de Sa e Souza
4de71978b3 fix(security): random per-process self-loop admission bearer (#13679) (#13813)
Merged in the 2026-09-16 sweep of the maintainer's own open PRs, at the owner's explicit instruction. No push was made to the PR branch: the merge took the head as the owning session left it (verified OPEN, non-draft and MERGEABLE against the release tip immediately before merging).
2026-09-16 06:09:15 -03:00
Bob.Hou
f36fcd8c31 fix(resilience): extend process crash guard to combo hedge cancels and upstream fetch failures (#13636)
Closes a real process-killer: the direct-response start timeout could fire after the fetch promise had already settled, and aborting at that point delivered the abort reason to a promise nobody was awaiting — Node promotes that to an `unhandledRejection` → `uncaughtException` and the process dies (#12861). The timer is now a no-op once the attempt has settled, and the same guard is extended to combo hedge cancels and upstream fetch failures.

Validated as a combined board first (this PR merged with the 11 siblings of the same batch on the release tip): eslint on every changed file with the suppressions file, typecheck:core, check:open-sse-typecheck, complexity, cognitive-complexity, changelog-integrity, i18n new-key coverage, docs-sync, migration-numbering, provider-consistency and a duplicate-identifier audit all green, plus 275 passing / 0 failing focused node:test cases across the 28 test files the batch touches. Then re-validated alone on the fresh tip before this merge: conflicts re-resolved, file sizes rebaselined for this PR's own growth, eslint and this PR's focused tests re-run.

Thanks @HouMinXi!
2026-09-16 03:03:07 -03:00
Dizzle
997cd4d509 fix(sse): stop retry wave on rate-limited 429 and drain 429 once (#13657)
The opencode executor classifies rate-limited 429 bodies (`classify429`, with real tests) and, when a whole account wave is exhausted, returns the last real upstream 429 — status, body, `Retry-After` and quota headers intact — so the provider error rules (monthly-quota cooldown) keep working.

Maintainer rework before merge (kept the idea, no default behavior change):
- The original stopped the cross-account wave at the first classified 429 and replaced the response with a synthetic one that dropped the body and headers; stopping early is now opt-in behind `OPENCODE_RATE_LIMITED_429_EARLY_STOP` (default off), the rate-limited account is still cooled down, the body is read as a bounded 8 KiB prefix from a clone and the original is never consumed, and the unused `status` input is gone.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 22:01:56 -03:00
Dizzle
94d27e44fe fix(providers): stop parking Mistral connections on a bare 401 with no clear auth failure (#13609)
Behind the new `MISTRAL_AMBIGUOUS_401_SOFT_LOCKOUT` flag (default off), a bare Mistral 401 (`{"detail":"Unauthorized"}`, identical for a revoked key and an exhausted quota) gets a retryable cooldown instead of parking the connection as `expired`; after three soft strikes within an hour the next bare 401 parks it, so revocation still converges.

Maintainer rework before merge (kept the idea, no default behavior change):
- The predicate is shared with the connection-test module instead of duplicated; the squeezed 139-char line that dodged the file-size gate is formatted normally and the growth is rebaselined honestly with an annotation.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 21:19:47 -03:00
Dizzle
13f44af6e5 fix(sse): pause failover dispatch after repeated transient upstream failures (#13615)
Behind the new `OPENCODE_TRANSIENT_FAILOVER_BACKOFF` flag (default off), after two consecutive transient upstream failures the opencode rotation pauses before each later account (1.5s, 3s, 6s, capped at 10s per request) instead of hammering the upstream.

Maintainer rework before merge (kept the idea, no default behavior change):
- The pause honors the client abort signal (no dispatch after a disconnect), the failed attempt's body is cancelled before sleeping, `transientRetryDelayMs` now uses its arguments, and the sleep is injectable so the tests run without real timers.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 20:58:12 -03:00
Dizzle
e325888d78 fix(sse): fail over opencode request on refused-route 403 (#13498)
Behind the new `OPENCODE_USER_BLOCKED_ROTATION` flag (default off), a 403 or 451 carrying `user_blocked` on a proxied opencode account rotates at most once to the next account instead of being returned as-is.

Maintainer rework before merge (kept the idea, no default behavior change):
- 403 and 451 are handled by one predicate (the original returned 451 without rotation), the refused account gets a cooldown and joins the tried-set, and the response body of the attempt rotated away from is cancelled.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 20:39:39 -03:00
Dizzle
d8c0448293 fix(opencode): rotate once when a Responses stream stalls before its first byte (#13484)
Behind the new `OPENCODE_RESPONSES_STALL_ROTATION` flag (default off): a streamed Responses reply with no first body byte within `RESPONSES_FIRST_BYTE_TIMEOUT_MS` (15s) cools the account and rotates once; a second stall fails fast instead of waiting the 80s readiness timeout.

Maintainer rework before merge (kept the idea, no default behavior change):
- The TLS first-byte watchdog from #12656 is restored byte for byte (the PR had changed its pump, timer and cancel); the stall guard lives in its own module.
- Proxy-less multi-account setups now rotate the same way as proxied ones (the original threw for them), a client abort during the wait rethrows instead of rotating, and the env var is documented as flag-only.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 20:10:57 -03:00
Dizzle
5cf4316b67 fix(proxy-health): refused probe responses reset the consecutive-failure streak (#13608)
Behind the new `PROXY_HEALTH_BLOCKED_RESETS_STREAK` flag (default off), a probe the target refuses (401/403/429) resets the proxy's consecutive-failure streak, so a proxy that clearly relays is not marked dead by spaced-out real failures.

Maintainer rework before merge (kept the idea, no default behavior change):
- The original reversed the deliberate #10654 policy for everyone; with the flag off a refusal stays neutral, and the existing assertions are restored. The stale JSDoc and the wrong "any relayed response resets" comment are fixed (5xx stays inconclusive).
- The source-grep test became a real sweep test: a local relay answering 403 drives fail → blocked → fail with auto-disable, in both flag modes.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 20:01:25 -03:00
Dizzle
ca312d57aa feat(proxies): show how many egress IPs actually served a proxy pool (#13581)
Behind the new `PROXY_POOL_EGRESS_OBSERVATION` flag (default off): a line under each proxy pool showing how many distinct egress IPs actually served it over 24h, backed by `GET /api/settings/proxies/pool/egress-observation`.

Maintainer rework before merge (kept the idea, no default behavior change):
- The route validates its query with Zod (unknown `scope` → 400 instead of silently `global`), error bodies go through `errorResponse()`, the OpenAPI entry documents security, parameters and responses, and the three UI strings exist in every locale.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 19:48:52 -03:00
Dizzle
29d66cbf8c feat(proxies): stop re-serving a proxy that just failed (#13578)
Behind the new `PROXY_SKIP_RECENTLY_FAILED` flag (default off), pool rotation and the opencode account rotation remember a proxy that just failed (refused probe or 429) and skip it for a doubling cooldown instead of re-serving it immediately.

Maintainer rework before merge (kept the idea, no default behavior change):
- The original was on by default and re-queried the DB on every request while a member was set aside; selection now caches a refusal sequence number and re-runs the cascade once per set-aside event.
- `src/lib/db` no longer imports the heavy dispatcher for key normalization (a parity test guarantees the same key as `proxyConfigToUrl()`); `.env.example` and `ENVIRONMENT.md` document the default as false.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 18:09:02 -03:00
Diego Rodrigues de Sa e Souza
df87e9363b fix(auth): close the JWT_SECRET bootstrap chain — real-peer loopback, obsidian always-protected, DATA_DIR vault refusal (#13791)
GHSA-7pq4-8pvv-rx7r (critical). Every link of the reported chain held on the
release tip:

1. First boot without JWT_SECRET generates one and writes it in cleartext to
   $DATA_DIR/server.env.
2. With no password configured, isAuthRequired() returned false for
   POST /api/settings/require-login unconditionally — before the loopback
   check — so any network peer could switch requireLogin off.
3. With requireLogin off, POST /api/settings/obsidian/webdav accepted an
   arbitrary vault root and echoed freshly minted Basic credentials.
4. The WebDAV file service is served by the custom Node layer before Next.js,
   outside the authz pipeline.
5. Pointing it at DATA_DIR reads server.env, and JWT_SECRET forges an
   `{"authenticated":true}` admin session.

A second, worse problem surfaced while verifying: isLoopbackRequest() decided
"loopback" from nextUrl.hostname / the Host header, which the client controls.
`Host: localhost` from a remote address made the whole fresh-install bootstrap
reachable, not just the write path.

Three cuts, plus the root cause:

- isLoopbackRequest() now reads the trusted peer: the token-stamped real TCP
  peer the custom server writes (peerStamp), then the pipeline's own locality
  verdict once a stamp token exists, then a real socket peer. The bootstrap
  write path honours the same constraint instead of returning false, and
  managementPolicy hands down the peerContext verdict explicitly, because at
  policy time the original request still carries client-supplied headers.
- Host is consulted only when the process has no stamp token at all — no
  stamping server in front, which in practice means route handlers invoked
  directly by the unit-test harness. Every supported runtime (run-next dev and
  start, standalone-server-ws for Docker, the npm CLI and Electron) calls
  ensurePeerStampToken() at boot, so there a signal-less request fails closed.
  Without this fallback ~340 route tests that call handlers with
  `new Request("http://localhost/…")` turned into 401s.
- /api/settings/obsidian joins ALWAYS_PROTECTED_API_PATHS: issuing and rotating
  reusable WebDAV credentials is credential export, the same rationale as the
  GHSA-62vw entry for the password reveal.
- enableObsidianVaultSync() refuses a vault that is, sits inside, or contains
  DATA_DIR, comparing realpath-resolved paths so a symlink cannot dodge it.

Tests are red-first: remote stamped peer → auth required on the bootstrap
write; Host: localhost plus a forged locality header from a non-loopback
stamped peer → 401 through the full pipeline; the local operator keeps the
first-password flow; obsidian inventory and DATA_DIR overlap cases.
2026-09-15 16:58:24 -03:00
Dizzle
87d9d82b37 fix(sse): fail over to sibling connection on stream early EOF (#13153)
Behind `STREAM_EARLY_EOF_SIBLING_FAILOVER_ENABLED` (default off): after the bounded same-connection retry is spent, a stream that closed early fails over exactly once to a sibling connection.

Maintainer rework before merge (kept the idea, no default behavior change):
- The PR's own failover test was red on its head: the `/v1/chat/completions` route's early-stream keepalive dropped the `X-OmniRoute-Selected-Connection-Id` header on the first cold request. Tests now drive `handleChat()` directly; the assertion was kept.
- "One hop" was one hop per connection (a 3-connection pool made 4 dispatches); it is now a single sibling hop per request, and when the pool runs out the original `STREAM_EARLY_EOF` 502 is returned instead of a generic `bad_gateway`, so combo-level detection keeps working. The source-regex timeout test became a behavioral one; flag description in all 59 locales.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 16:49:06 -03:00
Dizzle
53ed8c4745 fix(stream-recovery): order-aware in-flight tool-call detection behind off-by-default flag (#13633)
Behind `STREAM_RECOVERY_TOOLCALL_ORDER_FIX` (default off), mid-stream continuation becomes tool-call safe: any tool call seen in the stream — in flight or finished — blocks a continuation, and an empty continuation stops after one attempt.

Maintainer rework before merge (kept the idea, no default behavior change):
- The empty-continuation short-circuit also ran with the flag off; it is now gated, so the flag-off path uses the whole budget exactly as before (regression test added).
- The latch re-arm that let a continuation fire after a completed `finish_reason: tool_calls` is gone; index-less tool calls on multi-choice payloads are now blocked too; ~150 lines of dead trace plumbing removed.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 16:10:52 -03:00
Dizzle
611342609f fix(combo): return 502 for non-quota protected-priority stops (#13439)
Behind the new `PROTECTED_PRIORITY_INFRA_502_ENABLED` flag (default off), protected-priority combo stops caused by provably non-quota infrastructure (provider circuit open, predictive-TTFT latency) surface as 502 instead of a quota-looking 503.

Maintainer rework before merge (kept the idea, no default behavior change):
- The original branch made 502 the default for every stop, including model lockouts and cooldowns, and removed the #8133/#1731 provider-wide skip for 401/5xx without a connection id; both are restored with their regression tests untouched.
- Nineteen cases cover eight gate causes plus predictive latency, flag off and on.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 15:46:57 -03:00
Dizzle
ac52d4d9ea fix(sse): omit synthetic Retry-After and mark retry provenance on drain path (#13672)
Behind the new `RETRY_AFTER_PROVENANCE_ENABLED` flag (default off): `unavailableResponse` omits the synthetic `Retry-After: 1` when there is no real retry signal, marks `retry_after_provenance` on its bodies, and both combo drain readers parse prose retry hints from plain-text bodies too. With the flag off, headers and bodies are exactly as before.

Maintainer rework before merge (kept the idea, no default behavior change):
- A past `Retry-After` date is no longer labelled as an upstream signal with `Retry-After: 1`; non-JSON bodies (HTML 502 pages) log at debug instead of warning on every request.
- The provenance claim is narrowed to responses built by `unavailableResponse`, documented in the flag row.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 15:25:43 -03:00
Dizzle
45c54ca8aa fix(dashboard): require provider free-tier for the Free badge (#13645)
The provider-page Free badge can require a free tier the provider actually honors: behind the new `FREE_BADGE_REQUIRES_PROVIDER_FREE_TIER` flag (default off) the display-name "free" heuristic, non-boolean `free` fields and `:free` suffixes on registered providers without a documented free tier no longer light the badge. With the flag off the historical rule is unchanged.

Maintainer rework before merge (kept the idea, no default behavior change):
- `:free` models on free-tier providers and on compatible nodes (OpenRouter-style endpoints) keep the badge in both modes — the original change dropped them.
- Test D derives its provider set from `FREE_MODEL_BUDGETS` instead of a hard-coded allowlist; a vitest render covers both sections with the flag endpoint on, off and erroring.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 13:57:39 -03:00
Diego Rodrigues de Sa e Souza
c8b24ffc30 fix(authz): gate the cli-tools status and skills execution routes to LOCAL_ONLY (#13745)
GHSA-35fw-cv32-2373 and GHSA-jx89-f37j-pq89 — the same defect class as
/api/acp/agents (GHSA-hf57): a route whose handler chain spawns a host process
was classified Tier 3 MANAGEMENT only, and requireManagementAuth() waives auth
when requireLogin=false. Hard Rules #15/#17 require the LOCAL_ONLY gate, which
runs on the stamped real peer before any auth check.

cli-tools (GHSA-35fw): 14 routes reach
getCliRuntimeStatus() -> locateCommand() -> runProcess("sh", ["-c",
'command -v -- "$1"']) -> spawn(), exactly like their six gated siblings
(forge/grok-build/jcode/qwen/omp/letta-settings):
all-statuses, status, and the claude/cline/codewhale/codex/crush/deepseek-tui/
droid/kilo/openclaw/pi/smelt-settings routes. The advisory counted 13; it
missed /api/cli-tools/detect, which is heavier — detectAllTools() runs
execFile(binary, ["--version"]) and execFile("which") per tool.

skills (GHSA-jx89): POST /api/skills/install stores the request's handlerCode
verbatim as the skill handler with no allowlist, so a value equal to a built-in
name (execute_command / eval_code) aliases the real sandboxed built-in;
POST /api/skills/executions then runs it. The sandbox is a real container, but
the spawn is transitive, which is why the 6A.8 source scan never flagged it.

Entries are exact paths, not a /api/cli-tools/ blanket prefix: apply, backups,
config, guide-settings, hermes-agent-settings, keys, logs, openclaw/auto-order
and codex-profiles do not spawn and remote dashboards use them. All 16 are
mirrored into SPAWN_CAPABLE_PREFIXES (no manage-scope bypass) and added to the
route-guard-membership roots so the gate enforces them from now on.

Functional trade-off, same one already accepted for grok/forge/jcode/qwen: a
dashboard served through a tunnel no longer shows the CLI Tools status badges.

Tests are red-first. Two existing negative controls pointed at routes that turn
out to spawn (/api/cli-tools/all-statuses, /api/skills/install); they now point
at routes that genuinely do not (/api/cli-tools/config, /api/skills/marketplace,
/api/skills/skillssh/install), so the non-over-gating assertions are kept.
2026-09-15 13:25:35 -03:00
Dizzle
215ac43a70 fix(proxy): keep proxy credentials holding a literal percent (#13605)
Proxy credentials containing a literal `%` no longer throw `URIError`: every `decodeURIComponent` on proxy user/password is guarded.

Maintainer rework before merge (kept the idea, no default behavior change):
- HTTP proxies still failed because undici's `ProxyAgent` decodes the credentials itself; the dispatcher now builds undici's `Basic` token with the safe decoder and passes it as `token`, so a literal `%` works there too.
- The three remaining unguarded sites (`mappers.ts`, `proxySubscription/parse.ts`, `subscriptionService.ts`) are guarded; tests run the real `createProxyDispatcher` against a local HTTP CONNECT proxy and a local SOCKS5 server that record what they received.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 12:59:27 -03:00
Dizzle
cb420db64b fix(call-logs): hide search rows without a live provider (#13641)
Search stats and recent searches stop surfacing ghost rows: NULL and `-` providers are always hidden, and, behind the new `SEARCH_STATS_HIDE_DELETED_CONNECTIONS` flag (default off), traffic of a keyed provider whose connection was deleted is hidden too. Totals use the same guard as the per-provider rows, so they always agree.

Maintainer rework before merge (kept the idea, no default behavior change):
- Keyless providers from the search registry (`duckduckgo-free`, `searxng-search`, anonymous `context7`) and providers served through a credential fallback (`perplexity-search` on a `perplexity` key) stay visible in both modes — the original filter dropped them because they have no `provider_connections` row.
- Tests use real registry ids and cover flag off (historical stats) and flag on, including the analytics route; #13281's changelog fragment restored.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 12:48:06 -03:00
Dizzle
aaf0777a98 fix(proxies): keep the stored status when a write does not send one (#13577)
A write that does not send a proxy `status` (subscription refresh, bulk import, PATCH) no longer turns a disabled or dead proxy back on; the stored status is kept. Real DB-backed tests through `upsertProxy`, the subscription sync against a local feed and `handleProxyUpdate`.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 11:05:01 -03:00
Dizzle
1d03ba0ed2 fix(api): keep omitted fields on partial updates (#13582)
Partial updates keep the fields the client left out. Update/PATCH schemas that carried `.default()` re-applied those defaults under `.partial()`, so renaming a disabled reasoning-routing rule turned it back on, a playground preset lost its params and a proxy edit reset `family` to `auto`. The new `partialWithoutDefaults` helper strips defaults before `.partial()`; a scan test fails if any exported update schema ever leaks a default again.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 10:04:14 -03:00
Diego Rodrigues de Sa e Souza
c0f92ec98a fix(security): resolve findings from omni-code-sec battery (fix/batches-delete-completed-authz) (#13684)
Batch sweep enforces the caller's API-key policy (allowedEndpoints/schedule/usage/rate limit; the /api/v1 pathname now resolves its endpoint category for every /v1 route), commits per 200-batch chunk in key mode, guards against no-progress loops, rejects a scope naming both a key and allTenants; 8 covering tests registered for the mutation gate. Remaining CI reds are release base-reds (#12732), reproduced identically on the base tip. Refs #12969, #13680, #13681, #13685, #13377
2026-09-15 01:06:16 -03:00
Koosha Paridehpour
cce3a958b5 fix(tests): add DISABLE_IOREG_STRATEGY env var for macOS test compatibility (fixes #13467) (#13539)
Adds a `DISABLE_IOREG_STRATEGY=1` escape hatch to the macOS `ioreg` strategy in `getMachineIdRaw()`, so `machineId.test.ts` can reach the fallback strategies on darwin instead of always resolving the real hardware UUID (#13467).

Maintainer note: this branch also carried the "force passed=false when upstream call failed" eval commit, which already landed in #13413. The branch was reset to the current release tip plus only the ioreg commit (authorship preserved) so the squash carries just this change.

Validated in one consolidated batch of this series (37 PRs boarded together on `release/v3.8.51`): `typecheck:core`, `check:open-sse-typecheck` and `check:dashboard-typecheck` clean; ESLint clean on every changed file; file-size, complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync and migration-numbering gates green (only the pre-existing `open-sse/utils/stream.ts` file-size red remains, inherited from the base); 3,743 focused `node:test` cases plus 34 vitest cases green.

Thanks @KooshaPari!
2026-09-14 23:25:33 -03:00
Koosha Paridehpour
f938a08686 fix(resilience): add CJK quota-exhaustion patterns to 429 classifier (#13412)
Adds CJK quota-exhaustion messages (GLM/z.ai 5-hour window, Kimi, Qwen/DashScope, MiniMax, plus Japanese and Korean phrasings) to the 429 quota classifier. They are now `quota_exhausted` (cooldown + failover) instead of a transient `rate_limit` retry loop (#13194). The patterns go into the recoverable `QUOTA_PATTERNS` only, not the terminal set, and a guard case keeps the Chinese transient "请求过于频繁" as `rate_limit`. Chosen over #13536.

Validated in one consolidated batch of this series (37 PRs boarded together on `release/v3.8.51`): `typecheck:core`, `check:open-sse-typecheck` and `check:dashboard-typecheck` clean; ESLint clean on every changed file; file-size, complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync and migration-numbering gates green (only the pre-existing `open-sse/utils/stream.ts` file-size red remains, inherited from the base); 3,743 focused `node:test` cases plus 34 vitest cases green.

Thanks @KooshaPari!
2026-09-14 23:18:07 -03:00
Diego Rodrigues de Sa e Souza
db6feb6fad fix(auth): dashboard session requires the authenticated claim — Cursor CLI tokens are not sessions (#13375)
Merged as part of the owner batch of 2026-09-11.

This PR had a live worktree in another session, so it sat outside the main 39. Merged on your explicit call, validated first rather than taken on trust: boarded with the other 10 worktree-held PRs into a consolidated worktree off `release/v3.8.51`.

- ESLint over every changed file: no errors
- `typecheck:core` clean; `check:dashboard-typecheck` OK; `check:changelog-integrity` OK
- complexity 2821 / baseline 3218 and cognitive-complexity 1272 / baseline 1437
- 203 of 208 assertions green. The 5 remaining (`guide-settings-route` ×4, `hard-session-lease-bypass-inventory` ×1) reproduce on the pure tip with nothing from this batch applied.
- `imageGeneration.ts` rebaselined 3259 → 3293 for #12945's image-only-model guard, landed separately in #13392 so nothing was pushed onto a live branch.

⚠️ base-red inherited: #12732 — provider count 356 vs 358 and `open-sse/utils/stream.ts` 3115 > frozen 3098, both reproducing on the pure tip.
2026-09-11 22:29:12 -03:00
Diego Rodrigues de Sa e Souza
1fb7d5dff2 fix(providers): refresh Trae's stale Referer/Origin and forward user timezone (#12190) (#13255)
Merged as part of the owner batch of 2026-09-11.

This PR had a live worktree in another session, so it sat outside the main 39. Merged on your explicit call, validated first rather than taken on trust: boarded with the other 10 worktree-held PRs into a consolidated worktree off `release/v3.8.51`.

- ESLint over every changed file: no errors
- `typecheck:core` clean; `check:dashboard-typecheck` OK; `check:changelog-integrity` OK
- complexity 2821 / baseline 3218 and cognitive-complexity 1272 / baseline 1437
- 203 of 208 assertions green. The 5 remaining (`guide-settings-route` ×4, `hard-session-lease-bypass-inventory` ×1) reproduce on the pure tip with nothing from this batch applied.
- `imageGeneration.ts` rebaselined 3259 → 3293 for #12945's image-only-model guard, landed separately in #13392 so nothing was pushed onto a live branch.

⚠️ base-red inherited: #12732 — provider count 356 vs 358 and `open-sse/utils/stream.ts` 3115 > frozen 3098, both reproducing on the pure tip.
2026-09-11 22:29:02 -03:00
Diego Rodrigues de Sa e Souza
1361a9dd88 fix(sse): add first-byte watchdog to the TLS-fingerprint transport (#12656) (#13272)
Merged as part of the 39-PR owner batch of 2026-09-11, validated as a unit.

Boarded into one consolidated worktree cut from `release/v3.8.51` with the other 38 — zero conflicts between them.

- ESLint over every changed file: no errors (the only finding was one suppression entry the batch emptied, pruned on #13243)
- `typecheck:core` clean; `check:dashboard-typecheck` OK (206 pre-existing, within baseline); `check:changelog-integrity` OK
- complexity 2821 / baseline 3218 and cognitive-complexity 1272 / baseline 1437 — both under baseline
- 256 assertions green: 246 under node:test and 10 under vitest, which is where `tests/unit/**/*.test.tsx` actually runs
- `check-file-size`: `chatCore.ts` rebaselined 6144 → 6146 for #13278 and #13276, annotated and landed on #13243

⚠️ base-red inherited: #12732 — the provider count (356 in the docs vs the 358 the modules define) and `open-sse/utils/stream.ts` at 3115 > frozen 3098 both reproduce on the pure tip with zero contribution from this batch.
2026-09-11 22:06:09 -03:00
Diego Rodrigues de Sa e Souza
bc28bb06b1 fix(api): enforce API key policy on ElevenLabs proxy routes (#12574) (#13244)
Merged as part of the 39-PR owner batch of 2026-09-11, validated as a unit.

Boarded into one consolidated worktree cut from `release/v3.8.51` with the other 38 — zero conflicts between them.

- ESLint over every changed file: no errors (the only finding was one suppression entry the batch emptied, pruned on #13243)
- `typecheck:core` clean; `check:dashboard-typecheck` OK (206 pre-existing, within baseline); `check:changelog-integrity` OK
- complexity 2821 / baseline 3218 and cognitive-complexity 1272 / baseline 1437 — both under baseline
- 256 assertions green: 246 under node:test and 10 under vitest, which is where `tests/unit/**/*.test.tsx` actually runs
- `check-file-size`: `chatCore.ts` rebaselined 6144 → 6146 for #13278 and #13276, annotated and landed on #13243

⚠️ base-red inherited: #12732 — the provider count (356 in the docs vs the 358 the modules define) and `open-sse/utils/stream.ts` at 3115 > frozen 3098 both reproduce on the pure tip with zero contribution from this batch.
2026-09-11 22:05:07 -03:00
Diego Rodrigues de Sa e Souza
480b190fdc fix(providers): minimax-m3 collapses manual thinking.type:enabled to adaptive (#12132) (#13249)
Merged as part of the 39-PR owner batch of 2026-09-11, validated as a unit.

Boarded into one consolidated worktree cut from `release/v3.8.51` with the other 38 — zero conflicts between them.

- ESLint over every changed file: no errors (the only finding was one suppression entry the batch emptied, pruned on #13243)
- `typecheck:core` clean; `check:dashboard-typecheck` OK (206 pre-existing, within baseline); `check:changelog-integrity` OK
- complexity 2821 / baseline 3218 and cognitive-complexity 1272 / baseline 1437 — both under baseline
- 256 assertions green: 246 under node:test and 10 under vitest, which is where `tests/unit/**/*.test.tsx` actually runs
- `check-file-size`: `chatCore.ts` rebaselined 6144 → 6146 for #13278 and #13276, annotated and landed on #13243

⚠️ base-red inherited: #12732 — the provider count (356 in the docs vs the 358 the modules define) and `open-sse/utils/stream.ts` at 3115 > frozen 3098 both reproduce on the pure tip with zero contribution from this batch.
2026-09-11 22:04:57 -03:00