Commit Graph

2220 Commits

Author SHA1 Message Date
danscMax
ac20a3bd48 fix(api): allow text-to-image on dual-modality models + revive HuggingFace image host (#7648)
* fix(api): allow text-to-image on dual-modality models + revive HuggingFace image host

Two image-generation regressions surfaced while testing /v1/images/generations:

1. Dual-modality models (inputModalities ["text","image"]) were rejected with
   "Image input is required" because the gate treated any "image" modality as
   mandatory. That blocked pure text-to-image on 41 models (Together x10,
   Stability x10, LMArena x15, NVIDIA x3, BFL x2, NanoGPT x1). Only edit-only
   models (modalities ["image"] with no "text") should require an image input;
   extract modalitiesRequireImageInput() and gate on that.

2. The HuggingFace image provider pointed at api-inference.huggingface.co, which
   HF retired (DNS-dead -> "fetch failed" 502). Route through
   router.huggingface.co/hf-inference/models, matching the chat provider which
   already migrated.

Regression guard: tests/unit/image-text-to-image-modality.test.ts (fails on base
-- the helper did not exist and the baseUrl was the retired host).

* fix(api): keep Stability edit/control/upscale endpoints image-required

modalitiesRequireImageInput() correctly stopped gating dual-modality
(text+image) generation models on an image input, fixing pure
text-to-image for 41 models. But 10 of those dual-modality entries are
Stability AI's dedicated /v2beta/stable-image/{edit,control,upscale}/*
endpoints (inpaint, outpaint, search-and-replace, search-and-recolor,
replace-background-and-relight, creative, sketch, structure, style,
style-transfer) — they accept a text prompt too, but mechanically
require an input image upstream. The blanket modality-based inference
silently dropped OmniRoute's client-side gate for exactly those 10,
trading a clean 400 for a confusing upstream Stability error.

Add an explicit `imageRequired` override on the registry entry, decided
by the model's actual endpoint rather than inferred from its listed
modalities, and combine it with modalitiesRequireImageInput() at the
route gate: `imageModelEntry?.imageRequired || modalitiesRequireImageInput(...)`.

Extracted the Stability AI model list into
providers/registry/stability-ai/imageModels.ts (mirroring the existing
kie/segmind pattern) — imageRegistry.ts sits right at the 800-line
file-size cap and the extra flags would have pushed it over.

Extended tests/unit/image-text-to-image-modality.test.ts: the previous
"no dual-modality model is gated as image-required" assertion was
exactly the bug (it would have passed even with the regression); new
assertions cover the 10 Stability edit/control/upscale models by id
(still require an image) alongside the true dual-modality generation
models (BFL Kontext, NVIDIA, NanoGPT — still accept text-only).

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

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 15:14:57 -03:00
Ronaldo Davi
589dbde2e6 fix(db): dedupe bulk-imported proxies by full credential tuple (#7594) (#7644) 2026-07-18 15:14:53 -03:00
Paijo
16e481ba3e perf(db): add jitter to stagger due-on-restart connections (#6919)
* perf(db): add jitter to stagger due-on-restart connections

- Add MIN_RESTART_REFRESH_JITTER_MS=500 and MAX_RESTART_REFRESH_JITTER_MS=5000
- Replace fixed stagger delay with stagger + random jitter in sweep()
- Export sweep() for testing (marked @internal)
- Test: 3 connections with 100ms base stagger, verify all processed
- Uses Promise.withResolvers() pattern

* fix(test): make the sweep jitter test actually assert the jitter floor

The "sweep processes all connections with stagger + jitter delay" test
asserted elapsed >= 50ms, which was already trivially satisfied by the
pre-existing fixed stagger alone (3 connections -> 2 gaps * 100ms =
200ms), so the test passed identically whether or not the jitter change
was present and never actually exercised the new behavior.

Tighten the bound to >= 1000ms: with MIN_RESTART_REFRESH_JITTER_MS=500
and MAX=5000, the true floor with jitter is 2 * (100 + 500) = 1200ms —
a hard guarantee (setTimeout never fires early), not a probabilistic
one. Verified this fails (205ms) with the jitter term zeroed out and
passes (7-9s, within the [1200ms, 10200ms] range) with it restored.

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

* fix(health): make jitter configurable via env vars and tighten test assertion

- Replace hardcoded jitter [500, 5000)ms with HEALTHCHECK_JITTER_MIN_MS /
  HEALTHCHECK_JITTER_MAX_MS env vars (defaults 500/5000).
- In test: set HEALTHCHECK_STAGGER_MS=1, HEALTHCHECK_JITTER_MIN_MS=100,
  HEALTHCHECK_JITTER_MAX_MS=100 (fixed jitter), assert elapsed >= 190ms.
- Without jitter: 2 gaps * 1ms = ~2ms. With jitter: 2 gaps * 101ms = ~202ms.
  The assert proves jitter is applied.

Fixes #6919

* refactor(health): compact jitter await + tighten comments (file-size cap)

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

---------

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 15:14:48 -03:00
Rafael Dias Zendron
d415baa026 fix(6848): auto-cleanup for telemetry tables causing OOM (#6988)
* fix(6848): add auto-cleanup for telemetry tables that grow without bound

Add retention-based cleanup for 4 tables that had no prune policy:
- domain_cost_history (timestamp INTEGER, unix epoch)
- compression_cache_stats (created_at DATETIME)
- xp_audit_log (created_at TEXT)
- compression_run_telemetry (timestamp INTEGER, unix epoch)

All default to 30-day retention, integrated into runAutoCleanup()
which runs on startup + every 6h via startCleanupScheduler().
Also runs VACUUM after startup cleanup to reclaim disk space.

6 unit tests covering retention boundary, no-op on recent data,
and DEFAULT_DATABASE_SETTINGS key existence.

Closes #6848

* ci: retrigger CI for Electron Package Smoke flaky test

* ci: retrigger flaky integration test (batch-e2e timeout)

* test(#6848): rewrite tests to call real cleanup functions with seeded DB data

Replace mock-only assertions with integration-style tests that seed data
into the isolated test DB, call the actual cleanup functions from
src/lib/db/cleanup.ts, and verify rows are correctly deleted.

All 6 tests now exercise real code paths:
- cleanupDomainCostHistory: verify old rows deleted, recent preserved
- cleanupCompressionCacheStats: verify old rows deleted, recent preserved
- cleanupXpAuditLog: verify old rows deleted, recent preserved
- cleanupCompressionRunTelemetry: ensure table + verify cleanup
- Combined: all 4 functions return 0 deletions when data is within retention
- DEFAULT_DATABASE_SETTINGS: verify new retention keys exist with value 30

* test(#6848): self-contained DATA_DIR isolation for the cleanup test

Builds on the existing rewrite (already correctly importing and calling
the real cleanupDomainCostHistory/cleanupCompressionCacheStats/
cleanupXpAuditLog/cleanupCompressionRunTelemetry from src/lib/db/cleanup.ts
with real seeded-row assertions instead of re-implementing the DELETE
inline) and closes the remaining gap: DATA_DIR isolation relied entirely
on the test:unit harness's `--import ./tests/_setup/isolateDataDir.ts`,
which is invisible from the test file itself.

Per CONTRIBUTING.md/CLAUDE.md, a single test file is documented to run
directly as `node --import tsx/esm --test tests/unit/<file>.test.ts` —
without the harness's isolation import, getDbInstance() resolves to the
developer's real ~/.omniroute/storage.sqlite, and this file's DELETE-based
cleanup calls operate on real rows, not test rows. Confirmed by running it
that way before this fix: it deleted 238 real compression_cache_stats rows
and 53 real xp_audit_log rows (assertions failed on the row counts, which
is how the gap surfaced) instead of the 2/3 rows the test itself inserted.

Fix: mkdtempSync + DATA_DIR override before the first src/lib/db/* import
(self-contained, matches the pattern in
tests/unit/duckduckgo-vqd-429-misclassification-6996.test.ts), plus
test.after() calling resetDbInstance() and removing the temp dir — the
repo's DB-test-cleanup rule (a dangling handle can hang the native test
runner). Re-ran after the fix: same 6/6 pass, now against an isolated
/tmp DB with the expected 3/2/3/2 row counts, in ~3s instead of ~15s.

Red-first proof: reset cleanupDomainCostHistory's cutoff to a no-op
(`cutoffEpoch = 0`, never matches a real row) — the dedicated
"cleanupDomainCostHistory: deletes rows older than retention window" test
failed as expected; restored and reran clean (6/6).

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

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 15:14:41 -03:00
nguyenha935
12bf0ed077 fix(ui): improve React Flow dark theme (#7553)
* fix(ui): theme provider topology in dark mode

* fix(i18n): isolate topology translation keys

* refactor(i18n): reuse existing topology labels

---------

Co-authored-by: nguyenha935 <208228297+nguyenha935@users.noreply.github.com>
2026-07-18 15:14:28 -03:00
nguyenha935
a7d08a43c3 fix(cli): refresh runtime detection accurately (#7552)
Co-authored-by: nguyenha935 <208228297+nguyenha935@users.noreply.github.com>
2026-07-18 15:14:23 -03:00
nguyenha935
a7dba3bbcc fix(dashboard): prefer public endpoint URLs (#7547)
* fix(dashboard): prefer public endpoint URLs

* docs: add changelog fragment for #7547

* test(dashboard): cover onboarding public endpoint

* refactor(hooks): split display-URL predicates below complexity gate

Decompose isPrivateIpv4 and isPublicDisplayBaseUrl (both over the ESLint
complexity gate of 15) into small named predicates. Behavior is unchanged:

- isPrivateIpv4 now checks a PRIVATE_IPV4_RANGES table (RFC1918 +
  special-use ranges) through isInIpv4Range instead of one long chain
  of ||/&& comparisons.
- isPublicDisplayBaseUrl now delegates to isSupportedProtocol,
  isLoopbackHostname, isMulticastDnsHostname and isNonPublicIpv6 (itself
  split into isIpv6LoopbackOrUnspecified / isIpv6UniqueLocal /
  isIpv6LinkLocal), preserving the isIpv6 gate so hostnames that merely
  start with "fc"/"fd" (e.g. fdroid.example.com) are not misclassified
  as IPv6 unique-local addresses.

Adds IPv4 range-boundary and IPv6-gate regression tests; all existing
assertions are unchanged.

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

---------

Co-authored-by: nguyenha935 <208228297+nguyenha935@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 15:14:18 -03:00
Bob.Hou
65fbba4893 fix(antigravity): wrap Pro fallback chain in try/catch for timeout resilience (#7290)
* fix(antigravity): wrap executeOnce in try/catch for Pro fallback chain

When a Pro-tier candidate times out or throws a network error, the

exception now continues to the next candidate instead of aborting

the entire chain. Includes diagnostic logging and unit tests.

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

* fix(antigravity): propagate abort signal in Pro fallback catch block

Re-throw AbortError and signal.aborted immediately instead of
retrying the next candidate. Prevents wasted upstream requests
after client disconnect.

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

* fix(mitm): skip DNS modification when sudo unavailable (container)

In containers (USER node, no sudo, not root) provisionDnsEntries() now detects the condition up-front and logs a clear message instead of attempting sudo and silently swallowing the error. Adds canElevate() to the injectable deps interface for testability, and supports SKIP_ANTIGRAVITY_DNS=true for explicit opt-out.

* fix(antigravity): improve abort detection and fallback error handling

Check Error.name === 'AbortError' for non-DOMException environments

(polyfills, test harnesses). Capture first 400 from any candidate

(not just i===0) so mixed paths surface the 400 instead of a

generic error. Return firstResult when last candidate throws,

consistent with the all-400 case.

* test(mitm): add coverage for container-skip DNS provisioning

* test(mitm): harden container-skip DNS test assertions

The SKIP_ANTIGRAVITY_DNS=true and canElevate()=false tests used empty
agentStates/customHosts, so they could not distinguish 'all steps
skipped' from 'only the default step skipped'.  Provide non-empty mocks
and assert addHostsDns was NOT called.

Also add a SKIP_ANTIGRAVITY_DNS=false boundary test confirming the
strict === "true" comparison does not block normal provisioning, and
verify sudoPassword passthrough in the canElevate=true happy-path test.

* refactor(mitm): split provisionDnsEntries below complexity gate

provisionDnsEntries() (complexity ~18, this PR's try/catch/log
additions pushed it over check-complexity.mjs's threshold of 15)
and execute()'s Pro-fallback loop (complexity 27, from wrapping
executeOnce() in try/catch for timeout resilience) were both over
the gate. Decomposed each into small named helpers, no behavior
change:

- provision.ts: split into provisionDefaultDns/provisionAgentDns/
  provisionCustomHostsDns, each wrapping one best-effort DNS step.
- antigravity.ts: extracted the fallback-chain catch/400-handling
  decisions (handleAntigravityFallbackChainError,
  isAntigravityAbortError, handleAntigravityFallback400) into a new
  antigravity/proFallbackChain.ts submodule (pure, no executor
  instance state), mirroring the existing antigravity/sseCollect.ts
  submodule pattern. Also fixes the antigravity.ts file-size cap
  (was pushed to 1854 lines > 1813 frozen ceiling by this PR's own
  try/catch addition; now 1771).

execute/provisionDnsEntries no longer appear with ruleId complexity
or max-lines-per-function in the check-complexity.mjs report.

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

* refactor(antigravity): drop redundant loop continue (cognitive-complexity gate)

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

* refactor(antigravity): fold fallback outcome dispatch into switch (cognitive gate)

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

---------

Signed-off-by: Minxi Hou <houminxi@gmail.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: HouMinXi <19586012+HouMinXi@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 15:14:14 -03:00
Bob.Hou
88c28428e1 feat(providers): add Agnes AI native provider support (#7035)
Add built-in provider registry entry for Agnes AI (agnes-ai.com),
a permanently free OpenAI-compatible API by Sapiens AI.

Models:
- agnes-2.0-flash: 256K context, 64K output, thinking mode
  (reasoning_content), vision, tool calling
- agnes-1.5-flash: 256K context, 64K output, vision

The baseUrl uses the full /v1/chat/completions path. This is the
standard pattern for registry entries (115 built-in providers use
the same convention). The default executor's buildUrl() routes
registry entries through normalizeOpenAIChatUrl(), which detects
the existing /chat/completions suffix and returns the URL as-is
without appending. Only openai-compatible-* connections (dashboard-
added custom providers) unconditionally append the path.

Specs verified against MODEL_CATALOG.md v2026.06.28
(github.com/AgnesAI-Labs/AgnesAI-Models) and live API
testing at apihub.agnes-ai.com/v1 (2026-07-13).

Fixes #5580

Signed-off-by: Minxi Hou <houminxi@gmail.com>
2026-07-18 15:13:52 -03:00
KooshaPari
f8e56e4615 fix(router-eval): retained-optimization gate cleanup (#7318)
* feat(eval): add router-eval harness (AIQ scoring, regression gate, Pareto search)

Extracts a standalone router-eval evaluation tool that replays routing
decisions (from NDJSON corpora or the usage_history/call_logs SQLite
tables) into an AIQ (success/latency/cost) score, compares baseline vs.
candidate router configs with a retained-run regression gate, and ranks
Pareto-optimal candidates across a search space — a sibling to the
existing eval:compression harness.

New scripts: scripts/router-eval/{index,compare,patch-compare,search,
trends}.ts, scripts/check/check-router-eval-regression.ts, and
src/lib/routerEval/index.ts, wired via 6 new package.json entries
(eval:router, eval:router:compare, eval:router:patch-compare,
eval:router:search, eval:router:trends, check:router-eval).

Reconstructed onto current release/v3.8.49 from the original ~142-commit
stale PR branch: only the genuinely new router-eval payload (17 files)
was extracted — the other ~560 changed files in the original diff were
base-drift already present on release in newer form. The new package.json
scripts now invoke `node --import tsx` instead of `bun`, matching the
`eval:compression` precedent (Bun is reserved for a closed 5-script
allowlist). The runtime-detection shim (`"Bun" in globalThis`) already
present in the harness gracefully falls back to better-sqlite3 under
Node, so no logic changes were needed there; the retained-run manifest's
previously-hardcoded `runtime: "bun"` field and matching CLI help text
were corrected to reflect the actual invocation.

All 27 existing router-eval unit tests pass unchanged.

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

* refactor(router-eval): decompose toRouterObservation below complexity gate

toRouterObservation had cyclomatic complexity 18 (gate max is 15). Extract
the per-field parsing/normalization into pure helpers (sampleId, model
fields, latency, cost derivation, success) so the entry point is a plain
sequential assembly of a RouterObservation. Behavior is unchanged — same
tests pass, same fallbacks, same precedence between input aliases.

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

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 15:13:39 -03:00
KooshaPari
f657c7865a feat(issue-agent): surface RecordedTriageTimeoutError as 504 (#7315)
* chore(ci): add .mergify.yml to main — Mergify only reads config from the default branch (#7168)

* feat: scaffold issue agent and router eval provenance

* feat: wire recorded issue triage runner

* feat: ingest recorded issue context

* feat: persist issue agent audit log

* feat: import recorded github issue exports

* docs: document issue agent env toggle

* fix(issue-agent): validate run requests

* fix(ci): add the auto-enqueue pull_request_rule to the Mergify config (queue_conditions alone are eligibility-only) (#7179)

* fix: validate issue agent run requests

* docs: add issue agent execution traceability

* feat(issue-agent): route recorded triage through chat

* test(issue-agent): verify recorded triage through chat route

* docs(issue-agent): add executable triage session artifacts

* fix(ci): migrate Mergify auto-enqueue to merge_protections_settings.auto_merge_conditions (rules-based path is EOL 2026-07-16) (#7216)

* fix(ci): drop Mergify batch settings (batching is a paid-tier feature; free plan queue is serial) (#7220)

* feat(issue-agent): surface RecordedTriageTimeoutError as 504

When the recorded-triage chat completion times out, the AbortController
fires an AbortError that previously surfaced as a generic 400 to the
caller. This change:

  * Adds a `RecordedTriageTimeoutError` that wraps the AbortError
    with the timeoutMs context.
  * Re-throws it from `executeRecordedTriageChatCompletion` so the
    caller can distinguish timeouts from other failures.
  * In the runs route, catches it and returns a 504 with code
    `ISSUE_AGENT_TIMEOUT` so clients can render a useful error.

Tests:
  * issue-agent-execution.test.ts        — verifies the typed error
  * issue-agent-route-execution.test.ts  — covers timeout path
  * issue-agent-runs-route.test.ts       — verifies 504 mapping

* fix(ci): merge queue tolerates the advisory dast-smoke failure (its GH-hosted build hang dequeued every attempt) (#7225)

* test(ci): make the #6634 selfref guard hermetic — main's copy hard-fails every PR (#7341)

main's copy of this test still does git I/O inside a unit test:

    const baseSrc = git(['show', 'origin/main:' + FILE]);

Runners check out a shallow single ref, so origin/main does not resolve and the
test dies with 'fatal: invalid object name origin/main'. Every PR into main
fails Unit Tests (7/8) on it — today that is #7313, #7315, #7316, #7334, #7336
and #7337, six PRs red on a defect none of them introduced. #7313 has no other
red at all.

release/v3.8.49 already carries a fix (2e42b8efc, #7174: try/catch, fetch
origin/main on demand, t.skip() when unreachable), but it only reaches main at
release time — so main stays broken for the whole cycle. Cherry-picking it would
also import a new problem: PR Test Policy classifies t.skip() as a silenced
assertion, which we watched it correctly catch on #7300 today.

This is the hermetic version instead (ported from #7327, which does the same for
the release branch): read the file straight off disk, compare against an empty
base so baseTaut/baseExtTaut are 0 — the strictest possible comparison point —
and call evaluateMasking() directly. No git ref, no fetch, no skip, nothing the
runner's checkout depth can break.

The #6634 regression stays covered: the guard's logic lives in
SELF_TEST_FIXTURE_RE (check-test-masking.mjs:337), not in the test. Proven both
ways on main before committing — neutralise SELF_TEST_FIXTURE_RE to /$^/ and
the test FAILS; restore it and it passes 2/2, with check-test-masking.mjs left
byte-identical.

Co-authored-by: growab <nekron@icloud.com>

* chore(quality): tighten main's coverage baseline to the CI's real numbers (#7347)

main's ratchet had been failing --require-tighten on every PR: 11 metrics
improved but the baseline was never tightened. Same class as the #6634
selfref guard — an infra fix that lands only on the release branch leaves
main red for the whole cycle, and every PR into main pays for it.

Values are the merged-coverage numbers from a run on main itself (a local
run measures ~68% vs CI's ~80%; the baseline's own note warns about that
gap). Only the 11 coverage values change — gitleaks and semgrepFindings
keep main's own state.

No changelog fragment: #7326 carries it on release/v3.8.49, and a second
one here would double the entry at release time.

* fix(issue-agent): dryRun-explicit test fixture + sanitize the generic error catch

Two independent Hard Rule #12/#18 fixes on the recorded-triage runs route:

1. tests/unit/issue-agent-route-execution.test.ts's "preserves the normal
   chat route provider failure response" test omitted dryRun from its
   request body. createRecordedTriageRun() computes `dryRun: input.dryRun
   !== false`, so an omitted dryRun defaults to true (dry-run mode) and the
   route returns the deterministic dry-run summary WITHOUT ever calling
   executeRecordedTriageChatCompletion() -- the mocked 429 fetch was never
   invoked, so the test always observed the 200 dry-run response instead.
   Add the missing `dryRun: false`, matching the sibling test above it. The
   normal chat-completions route also enriches upstream errors with a
   "[provider/model] [status]:" prefix and a connection-cooldown hint
   (RESILIENCE_GUIDE.md) rather than passing them through byte-for-byte, so
   the assertion now checks the original message survives (status +
   substring) instead of exact-matching the mocked JSON shape.

2. src/app/api/issue-agent/runs/route.ts's generic (non-timeout) catch
   returned raw `error.message` straight to the client. Validation failures
   thrown by this module (bad issue URL, malformed GitHub export) are safe,
   curated messages -- but appendIssueAgentAuditRecord()'s mkdir/appendFile
   under DATA_DIR can throw a real Node fs error (ENOENT/EACCES/EEXIST, ...)
   whose raw `.message` embeds the server's absolute filesystem path. Add
   isNodeSystemError() (keys off NodeJS.ErrnoException's `.code`, which only
   Node's own fs/system errors set) to replace that class of error with a
   generic message, and route everything else through sanitizeErrorMessage()
   per Hard Rule #12. Add a regression test that forces a real audit-write
   failure (pre-creating a file where audit.ts expects to mkdir) and asserts
   the response contains neither the errno code nor the DATA_DIR path.

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

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: KooshaPari <koosha@example.com>
Co-authored-by: growab <nekron@icloud.com>
2026-07-18 15:13:35 -03:00
KooshaPari
8febd55e44 feat(sidecar): support conditional provider manifest refresh (#7130)
* feat(sidecar): support conditional provider manifest refresh

* fix(sidecar): accept weak manifest validators

* perf(sidecar): cache provider manifest payload

* docs(sidecar): describe manifest conditional refresh

* test(sidecar): restore CORS preflight and manifest-content coverage

The ETag/conditional-refresh rewrite of this test file dropped two
pieces of coverage without replacing them: the CORS OPTIONS-preflight
test, and the 200-response test's providers.length>100 /
clientSecret-not-leaked assertions. This is the only test file for
the provider-plugin-manifest route, so none of that was covered
anywhere else afterward.

Restore both: fold the providers.length/openai-presence/clientSecret
assertions back into the "stable ETag" 200-response test alongside
the new ETag checks, and add back a dedicated OPTIONS test asserting
the CORS preflight headers.

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

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 15:13:31 -03:00
KooshaPari
0bbdb839d9 Explain effective auto-combo scoring weights (#7087)
* fix(inspector): report effective auto scoring weights

* fix(inspector): check options.combos before the health-signal short-circuit

resolveConfiguredCombos() returned [] unconditionally whenever
healthResponse, forecastResponse, and either skipAutopilot or
autopilotReport were all supplied -- before it ever looked at
options.combos. That is exactly the call shape
comboHealthDashboard.ts::buildComboHealthDashboardResponse() always
uses (it resolves combos/health/forecast/autopilot once, then passes
all of them into buildComboScoringInspectorResponse together), so
through the real dashboard integration the caller-supplied combos
were silently discarded every time. Since combosById/combosByName
(built from resolveConfiguredCombos()'s return value) are what
resolveInspectorWeights() uses to report a combo's actual configured
modePack/weights, this meant the dashboard's weightSource/modePack
fields always came back "default", even for a combo with an explicit
mode pack configured.

Check options.combos first, unconditionally, and only fall back to
the health-signals short-circuit (skip an unnecessary getCombos() DB
round-trip) or a fresh getCombos() call when the caller didn't supply
combos at all.

Adds a regression test that drives the real
buildComboHealthDashboardResponse() end-to-end with a combo configured
for modePack "ship-fast", proving the inspector now reports the
correct weightSource/modePack through that call path -- the exact
scenario the PR's own tests didn't cover (they only exercised
buildComboScoringInspectorResponse() directly with comboId+combos,
never combos alongside a pre-resolved healthResponse+forecastResponse).

Also reconciles the existing "skipAutopilot avoids rebuilding
autopilot report" test: it asserted options.combos was never even
read in this scenario via a throwing property getter, which pinned
the exact short-circuit-wins-always bug this fix removes. The test's
options object never supplied a real combos array in the first place,
so the fixed code still takes the same DB-free path -- only the
poison-pill mechanism (which trapped a mere property read rather than
an actual getCombos() call) no longer applies.

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

* refactor(usage): split resolveInspectorWeights below complexity gate

resolveInspectorWeights had cyclomatic complexity 16 (gate max is 15).
Extract the auto-config precedence resolution (autoConfig -> config.auto
-> config -> {}), the mode-pack name lookup, and the explicit-weights
validation into small pure helpers, each returning early instead of
nesting ternaries. Behavior is unchanged, including the fallback warning
that fires when a mode pack or explicit weights were configured but
could not be resolved.

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

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 15:13:26 -03:00
Jan Leon
c711ed257b Restore proxy navigation and sidebar accordion state (#7381)
* fix(sidebar): restore proxy navigation and accordion state

* fix(proxy): prevent free pool translation crash

* fix(settings): guard protected sidebar items and hydration state

* test(sidebar): cover proxy visibility and expansion state

* chore: restart pull request checks

* refactor(sidebar): extract group item visibility control

* refactor(sidebar): move group visibility control to module scope

* fix(sidebar): preserve collapsed state on initial load

* fix(proxy): collect free pool UI regression test

* chore(test): unfreeze free-pool-tab.test.tsx from test-discovery baseline

The move to tests/unit/ui/free-pool-tab.test.tsx (this branch) relinks
it to the test:vitest:ui runner, so the tests/unit/free-pool-tab.test.tsx
entry in the frozen orphan baseline is now stale and fails
check:test-discovery. Removes the stale entry (60 -> 59 known orphans).

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

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 15:13:19 -03:00
Jan Leon
d470526031 Honor provider proxies for The Old LLM Vercel blocks (#7380)
* fix(theoldllm): honor provider proxy for Vercel blocks

* fix(theoldllm): fail closed when assigned proxy is unavailable

* refactor(theoldllm): extract proxy guards into dedicated module
2026-07-18 15:13:13 -03:00
Jan Leon
78d2eee914 Add per-connection Provider Quota visibility (#7360)
* feat(dashboard): add Provider Quota visibility toggle per connection

* refactor(dashboard): extract provider quota visibility controls

Move quota visibility UI and update logic into reusable components, add Portuguese translations, and remove the stale migration gap allowlist entry.

* Hide quota visibility controls for unsupported providers

* chore(ci): retrigger GitHub checks

* fix(db): renumber quota-visibility migration past release tip (121→125)

122_free_proxy_sync_errors.sql, 123_quota_auto_ping.sql, and
124_generic_session_affinity_ttl.sql have since landed on
release/v3.8.49, so 121 is now out-of-sequence and would not apply on
databases already past 122+. Renumbers to 125 (the next free slot past
the current release tip) and restores "121" in check-migration-numbering's
KNOWN_GAPS allowlist, since 121 remains a genuine unfilled gap once this
migration moves off that number.

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

* chore(quality): rebaseline file-size + complexity for resync merge

The release-resync merge unions two already-compliant features in the
same god-component (ConnectionRow.tsx/ConnectionsListPanel.tsx): this
PR's per-connection quota-visibility wiring and release's confirm-
delete-account wiring (#7361). Both were individually within budget
(785/786 lines); combined they land at 791. Complexity count moves
2058->2059 for the same reason (2 previously-compliant .map() render
callbacks in ConnectionsListPanel.tsx now marginally exceed the
80-line function cap). No new logic was written — see the
_rebaseline_2026_07_18_pr7360_quota_visibility_resync justification
entries in both baseline files for the full accounting. Verified via
a byte-for-byte diff of the violation lists between origin/release/
v3.8.49 tip and this merge. Structural shrink stays tracked in #3501.

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

* refactor(db): split _updateConnectionRow update assembly (complexity gate)

_updateConnectionRow grew past the 80-line max-lines-per-function ceiling
after this branch added quota_visible column handling. Extract the
`.run()` params assembly (field mapping/normalization, unchanged) into a
module-private `_buildUpdateConnectionRowParams` helper in the same file
so the SQL statement + call site stay in `_updateConnectionRow` while the
function itself drops back under the gate. No behavior change.

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

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 15:12:52 -03:00
Jan Leon
5f04d5bcbd feat: add principal-scoped CCR MCP lifecycle (#7282)
* feat: add principal-scoped CCR MCP lifecycle

* refactor: extract CCR MCP schemas

* refactor: reduce CCR store complexity

* fix: preserve CCR retrieval feedback

* fix: keep CCR expiry/accounting scoped to accessed entries

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 15:12:47 -03:00
Jan Leon
c78f150ac3 feat(compression): support RTK TOML schema v1 filters (#7281)
* feat(compression): support RTK TOML filters

* chore(ci): sync RTK skill and dependency allowlist

* refactor(compression): reduce RTK import complexity

* fix(i18n): add Portuguese RTK import translations

* fix(compression): improve RTK TOML import validation feedback
2026-07-18 15:12:43 -03:00
Jan Leon
dc0dec46c7 Add cache-aligned Live Zone compression (#7280)
* feat(compression): add cache-aligned live-zone processing

* refactor(compression): satisfy complexity ratchets

* fix(compression): handle tool_result outputs in live zone

* fix(i18n): add live-zone pt-BR strings
2026-07-18 15:12:38 -03:00
Jan Leon
9088151043 Fix Codex Responses compression analytics (#7273)
* fix compression analytics for Codex responses

* preserve Responses tool output fields

* Test compression analytics cost failure isolation
2026-07-18 15:12:33 -03:00
Jan Leon
c1a3d83c27 fix(combo): reject known context overflow without exhausting providers (#7177)
* Fix context-window exhaustion classification

* fix(combo): keep chat.ts/comboStructure.ts under the file-size ratchet + fix context-overflow boundary bug

- Extract getKnownContextOverflow (+ its KnownContextOverflow type) out of
  comboStructure.ts into a new open-sse/services/combo/knownContextOverflow.ts
  leaf, so the file-size ratchet (cap 800 for new files) passes.
- Extract the skipConnectionDisable predicate out of handleSingleModelChat in
  chat.ts into open-sse/services/combo/comboPredicates.ts::shouldSkipConnDisable,
  and consolidate the new combo-failure-handling imports, to keep chat.ts under
  its frozen file-size baseline (1796) after the #7177 request-scoped-failure
  wiring.
- Fix a real boundary bug in getKnownContextOverflow surfaced by the merge:
  estimateRequestInputTokens counted a caller-omitted `messages: []` (which some
  combo entrypoints default in) as real content, charging a few phantom
  "structural" JSON.stringify tokens toward the estimate. That was enough to
  falsely trip the new known-context-overflow rejection for a request with no
  real input when max_tokens exactly equals the target's context window (a
  common config where limit_input === limit_output === limit_context),
  regressing tests/unit/combo-routing-engine.test.ts's pre-existing #3587
  "non-reasoning model does not get max_tokens buffer" case. Empty
  arrays/objects no longer count as estimable content.
- Add a regression test for the exact-boundary empty-content case.

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>

* refactor(combo): move overflow logic into knownContextOverflow module (file-size cap)

comboStructure.ts is not frozen in the file-size baseline but is capped at 800
lines; this PR's net +29 on that file alone would push it over once merged.

knownContextOverflow.ts already exists in this PR as the dedicated home for
"known context limit" logic, so move the genuinely new pieces there instead
of leaving them in comboStructure.ts:

- hasEstimableContent (new): its own doc comment already frames it purely in
  terms of the known-context-overflow boundary check, so it belongs next to
  that check, not in the general request-compatibility file.
- getKnownContextLimit (new, requestedOutputTokens-aware): this *is* the
  "how big is a target's known context window" primitive knownContextOverflow
  already consumes; hosting it there is a better fit than comboStructure.ts.
- getLegacyKnownContextLimit: kept alongside its sibling rather than split
  across two files, since both are alternate implementations of the same
  concept (used only by comboStructure.ts's hasKnownCompatibleContextLimit).

comboStructure.ts now imports all three back for its own internal callers
(estimateRequestInputTokens, getTargetCompatibilityFailures,
hasKnownCompatibleContextLimit). deriveRequestCompatibilityRequirements and
the RequestCompatibilityRequirements type stay in comboStructure.ts exactly
as this PR already has them (still consumed internally there), so
knownContextOverflow.ts keeps importing those two, same as before.

No behavior change — pure relocation, verified by the existing PR test
suite (combo-context-window-filter, combo-breaker-429, combo-failure-log-message,
combo-target-exhaustion, diagnostics) plus the pre-existing
combo-vision-aware-routing/combo-context-requirements/combo-roundrobin-compat-fallback-6238
suites, all green.

Net effect on open-sse/services/combo/comboStructure.ts vs. this PR's merge
base: -2 lines (was +29). typecheck:core, lint, and the complexity ratchets
(check:complexity, check:cognitive-complexity) are unchanged from this PR's
current HEAD — the 4 pre-existing complexity/max-lines findings in
valueContainsImagePart/filterTargetsByRequestCompatibility are untouched by
this move (same violations, same total ratchet counts, just shifted line
numbers).

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

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 15:12:24 -03:00
Jan Leon
d760169d9a feat(usage): add Codex reset credit picker (#7154) 2026-07-18 15:12:19 -03:00
Jan Leon
95e537307b fix(models): preserve direct-model combo metadata (#6993) 2026-07-18 15:12:15 -03:00
Ronaldo Davi
0730eee9f2 fix(api): await params in Agent Bridge DNS route (Next.js 16) (#7271) (#7492) 2026-07-18 11:35:26 -03:00
danscMax
858d762918 fix(oauth): repair qwen + codebuddy-cn device-code endpoints (#7517)
Both device-code providers failed at the upstream request (surfacing in the
companion extension as 'ошибка сервера OmniRoute'). Diagnosed by calling the
upstream endpoints directly:

- qwen: QWEN_CONFIG used the bare host qwen.ai, whose /api/v1/oauth2/device/code
  and /token paths return 404 Not Found. The working qwen-code device flow lives
  at chat.qwen.ai (verified: 200 + a valid device_code/user_code). Point both
  URLs at chat.qwen.ai.
- codebuddy-cn: the Tencent state endpoint reads 'platform' from the QUERY
  string, not the JSON body. Sending it only in the body returned
  400 {"code":10001,"msg":"platform is empty"}. Passing ?platform=CLI
  returned 200 with {code:0, data:{state, authUrl}}. Build the stateUrl with the
  platform query param (body kept as-is).

Validation: live upstream calls returned 200 for both corrected requests (device
flow can't be hit from CI). Regression guard: tests/unit/oauth-device-code-endpoints.test.ts.
2026-07-18 11:35:09 -03:00
danscMax
7ff2e5c0b5 fix(oauth): surface sanitized device-code error instead of a generic 500 (#7511)
The dynamic OAuth GET handler swallowed every thrown error into a generic
`{ error: "Internal server error" }` 500, so a device-code upstream failure
(qwen → qwen.ai, codebuddy-cn → copilot.tencent.com — geo-block / outage / bad
client) surfaced in the extension as an indistinguishable 'ошибка сервера
OmniRoute', hiding WHY it failed.

Route the caught error through sanitizeErrorMessage() (already imported, hard
rule #12) so the real reason ("Device code request failed: …", "CodeBuddy
state request failed (403)") reaches the client, falling back to the generic
only when the sanitizer yields nothing.

Regression guard: tests/unit/oauth-device-code-error-transparency.test.ts
(source-level — the route needs the full Next request/auth/upstream graph, so
behavioural validation belongs on a real build/VPS).
2026-07-18 11:35:05 -03:00
danscMax
d7726ef80a fix(db): stop a 'latest' path segment from disabling backups and migrations (#7359)
Eleven subsystems answered "am I running under a test runner?" with
`process.argv.some((arg) => arg.includes("test"))`. JavaScript agrees that
'latest'.includes('test') is true, so ANY argv carrying a `latest` segment — a release symlink
like /opt/omniroute/latest/server.js, an npm/npx cache path, a `--model=latest` flag — silently
put the process into test mode.

The worst consequence is src/lib/db/backup.ts: isSqliteAutoBackupDisabled() returns true, so
SQLite auto-backup simply never runs — no warning, no log. The same substring decides whether
migrationRunner runs its pending-migration check, whether cloud sync initialises, and whether
the local/token health checks, quota recovery, model-lockout settings and the WS live server
consider themselves live. All of them fail silent, which is the dangerous kind.

Replaces the eleven copies with one helper, src/shared/utils/testProcess.ts:
- env first (NODE_ENV=test, VITEST) — unchanged;
- argv: `test`/`tests` only as a WHOLE token delimited by a path separator, dot or dash, so
  `--test`, `tests/unit/x.test.ts` and `src/x.test.ts` still match, while `latest`, `protest`,
  `contest` and `attestation` no longer do;
- argv: runner binaries (vitest/jest/mocha/ava/tap), which have no delimiter before "test";
- execArgv as well as argv — `node --test x.js` puts `--test` in execArgv, and
  modelLockoutSettings was the only copy that remembered to look there.

argv/env are parameters rather than globals so the negative cases are testable: under a test
runner the globals always say "test", which is precisely why this bug could never be caught.

tests/unit/test-process-detection.test.ts guards the regression (a `latest` path is not a test
run) alongside the positives that must keep working.
2026-07-18 11:35:00 -03:00
Adam
b71790bb7e fix(providers): accept m365.cloud.microsoft for copilot-m365-web token (#7078) (#7166)
* fix(providers): accept m365.cloud.microsoft for copilot-m365-web token (#7078)

* test: regression for #7078 m365.cloud.microsoft token extraction

* fix(7078): match on url.hostname and anchor path with startsWith

* test(7078): cover explicit :443 port via url.hostname
2026-07-18 11:34:56 -03:00
Adam
178496fd92 fix(providers): AgentRouter model import applies Claude Code wire image to /v1/models (#7016) (#7060)
* fix(providers): AgentRouter model import applies Claude Code wire image to /v1/models (#7016)

* test: regression guard for AgentRouter /v1/models discovery (#7016)

* fix(7016): case-insensitive Authorization strip + bare-array parseResponse

* test(7016): assert no Authorization variant + bare-array parse
2026-07-18 11:34:47 -03:00
Paijo
f287f42f15 perf: wrap ComboCard, HeroSection in React.memo (#7070)
* perf: wrap ComboCard, HeroSection in React.memo

* fix(#7070): add test coverage for React.memo changes; fix selfref test in fork CI

- Add smoke tests for combos page and EvalsTab to satisfy PR Test Policy
  requiring tests for production code changes
- Fix selfref test (check-test-masking-selfref-6634) to try upstream/main
  first, falling back to origin/main, since origin/main may not exist in
  fork CI environments

* fix(#7070): bump frozen baseline for combos/page.tsx 4655->4656 after React.memo wrapping

The file-size checker's split('\n').length convention now counts 4656
for src/app/(dashboard)/dashboard/combos/page.tsx after wrapping
ComboCard in React.memo (+1 effective line).

---------

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
2026-07-18 11:34:06 -03:00
Paijo
a0cff84339 perf(db): add temp_store=MEMORY pragma to SQLite init (#6921)
* perf(db): add temp_store=MEMORY pragma to SQLite init

Store temp tables/indices in memory instead of disk for faster
query execution (GROUP BY, ORDER BY, subquery materialization).
The two other optimized PRAGMAs (synchronous=NORMAL, cache_size=-16384)
were already set.

* test(db): add temp_store MEMORY pragma test

Verifies PRAGMA temp_store = 2 (MEMORY) after initDb() runs.

---------

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
2026-07-18 11:33:49 -03:00
Paijo
e79de5c294 perf(startup): warm model catalog cache at module init (#6920)
* perf(startup): warm model catalog cache at module init

Fire-and-forget call to getUnifiedModelsResponse after DB ready so first
GET /v1/models request doesn't pay cold-build cost (15-30s). Non-fatal —
if warmup fails the next request builds fresh.

* test: add warm catalog cache source-pattern test

Verifies registerNodejs() includes the model catalog warmup import
and call to getUnifiedModelsResponse.

* fix(perf): warm the durable OpenRouter catalog cache, not just the 1.5s TTL Response cache

The warmup called getUnifiedModelsResponse() with no Authorization header,
so it only ever populated the top-level per-key Response cache
(catalogCache in catalog.ts) at key "|0|" — a real client sending an
apiKey gets a different key and misses that cache entry. But that cache
also has only a 1.5s TTL (CATALOG_CACHE_TTL_MS, a #6408 burst-dedup
window for concurrent requests, not a startup-warm cache), so even a
perfectly key-matched entry would almost always have expired before real
traffic arrives regardless.

The one genuinely durable, apiKey-independent cost in the catalog build
is getOpenRouterCatalog()'s 24h disk-cached network fetch
(src/lib/catalog/openrouterCatalog.ts) — buildUnifiedModelsResponseCore()
calls it unconditionally whenever an OpenRouter connection is configured,
fully decoupled from the per-key Response cache. Extract the warmup into
an exported warmModelCatalogCache() (testable in isolation, without
exercising all of registerNodejs()) that explicitly warms this cache too,
guarded on an OpenRouter connection actually existing so deployments that
never use OpenRouter don't pay an unconditional third-party network call
at every boot.

Replace the source-text-grep test with a behavioral one: warm once with a
mocked fetch, confirm exactly one network call, then confirm a real
request using a DIFFERENT apiKey than the warmup reuses the cache instead
of re-fetching — the actual, durable, apiKey-independent benefit. Also
covers the no-connection-configured and fetch-failure-is-non-fatal cases.

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

---------

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 11:33:44 -03:00
Paijo
6e71553797 perf(db): project columns + composite index in getProviderConnections (#6918)
* perf(db): project columns + composite index in getProviderConnections

- Add  param to avoid  (scans ~200MB/query)
- Add  WHERE clause support (was silently ignored)
- Add composite index  on
  (auth_type, is_active, refresh_token)
- Update health check caller to request only needed columns
- Test: authType filter, column projection, default full fetch

* fix(db): dedupe authType filter, allowlist columns projection in getProviderConnections

The branch was rebased on top of #6946 (already merged, same author,
same authType-filter fix), leaving a duplicate `if (filter.authType)`
block in getProviderConnections. Harmless at runtime (SQLite tolerates
the repeated named param) but dead code — remove the newer duplicate,
keep the one already merged via #6946.

The `columns` projection param is interpolated directly into the SQL
SELECT clause via `.join(", ")` with no validation. No current caller
passes untrusted input, but it's a live SQL-injection footgun for
whichever future caller wires it up: reproduced a working exfiltration
via a single-statement subquery column name (no semicolon/stacked-query
needed, so better-sqlite3's single-statement restriction doesn't help)
that leaked an unrelated connection's api_key through the response.
Add an allowlist validated against the real provider_connections schema
(core.ts's SCHEMA_SQL) — rejects any non-listed column, and re-quotes
the reserved "group" keyword so it stays usable. Verified the fix
blocks the exact reproduced exfiltration.

Add a regression test asserting invalid/injection-shaped column names
are rejected, a mixed valid+invalid list still rejects (fail-closed,
not a silent partial projection), and the legitimate "group" column
still round-trips correctly when requested.

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

---------

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 11:33:32 -03:00
Paijo
a2ebc343d4 fix: add re-entrancy guard to token health check sweep (#6917)
* fix: add re-entrancy guard to token health check sweep

Adds an in-flight guard to prevent overlapping sweep() executions.
Uses global state sweeping flag that is set before the first await
and cleared in a finally block. Subsequent calls while sweeping
return early with a debug log line.

Test coverage:
- skips when a previous sweep is still in flight
- resets sweeping flag after normal completion
- resets sweeping flag on empty connections

* fix(test): move sweep re-entrancy test to node:test, wire CI correctly

tests/unit/token-health-check-sweep.test.ts used vitest syntax while
living directly under tests/unit/, which is exactly the glob
`npm run test:unit` (node's native runner) scans — running it there
threw "Vitest mocker was not initialized" and failed the file outright.

Separately, the vitest.config.ts include-array edit didn't wire the
test into any CI-blocking script either: `npm run test:vitest` runs
vitest.mcp.config.ts (a different config, not this path), and
test:vitest:ui is scoped to tests/unit/ui only — so the 3 tests never
ran in CI at all while node's runner actively failed on the file.

Rewrite the test to node:test, matching the
tests/unit/apikey-connection-health-check.test.ts /
tests/unit/token-health-check.test.ts convention (real temp-dir SQLite
DB rather than vi.mock, since mock.module() is unavailable in this
tsx/ESM + Node native test-runner setup). The re-entrancy scenario now
drives the real, unmocked sweep() with real OAuth connections
(healthCheckInterval: 0 keeps checkConnection() a fast no-op) and
asserts on wall-clock elapsed time + the shared sweeping flag instead
of a mocked call count. Verified this fails without the guard (909ms,
~3x the stagger) and passes with it restored.

Remove the now-unused vitest.config.ts include entry since the test no
longer needs it.

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

* refactor(health): compact sweep guard + restore one-line stagger delay (file-size cap)

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

---------

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 11:33:28 -03:00
Diego Rodrigues de Sa e Souza
cab9e5f0c0 fix(dashboard): cut UI import chain from connection persist module (CI shard base-red) (#7677)
Base-red unblock (CI Unit shard 2/4 red on EVERY PR since #7653). Validated locally: test 6/6 under the exact shard harness; persist module proven to load without the UI chain; full static-gate set green (complexity 2056≤2058, cognitive 889≤890, file-size/test-discovery/dashboard-typecheck/changelog OK).
2026-07-18 07:15:56 -03:00
Diego Rodrigues de Sa e Souza
d9f3699b4e feat(sse): quota tracking for AgentRouter, v0 (Vercel), FreeModel (#6850, #6845, #7075) (#7653)
Validated in merge-train --fast @ 6cafcbb (static gates + 9 changed test files + vitest green, 2m35s; full suite ran today on train 2c tip)
2026-07-18 03:17:09 -03:00
Diego Rodrigues de Sa e Souza
60955975e4 feat(usage): add TTFT/E2E-latency/tokens-per-second to model latency stats (#6875) (#7635)
Validated in merge-train --fast @ 6cafcbb (static gates + 9 changed test files + vitest green, 2m35s; full suite ran today on train 2c tip)
2026-07-18 03:17:04 -03:00
Diego Rodrigues de Sa e Souza
38dd62819b feat(providers): Speechmatics STT, gTTS, VibeProxy preset (#6659, #6667, #6874) (#7655)
Validated in merge-train --fast @ 6cafcbb (static gates + 9 changed test files + vitest green, 2m35s; full suite ran today on train 2c tip)
2026-07-18 03:16:58 -03:00
Diego Rodrigues de Sa e Souza
9e084e18a7 feat: OpenRouter quota tracking (key/credits + free-window counter) (#6842) (#7651)
Validated in merge-train --fast @ 4ed4498 (static gates + changed tests 29/29 + vitest green, 2m39s; full suite ran today on trains 1/2c)
2026-07-18 03:11:01 -03:00
Diego Rodrigues de Sa e Souza
b28331307e feat: per-model default reasoning_effort + no-think none on OpenAI path (#6879) (#7631)
Validated in merge-train 2026-07-18 @ 9084b408b: 12198/12201 pass; single red = earlyStreamKeepalive timer test, confirmed load-flake (6/6 green isolated on release tip AND the merged tree; no boarded PR touches keepalive)
2026-07-18 03:07:37 -03:00
Diego Rodrigues de Sa e Souza
2cca081b3c feat: import providers from CSV/JSON file (#6836) (#7636)
Validated in merge-train 2026-07-18 @ 9084b408b: 12198/12201 pass; single red = earlyStreamKeepalive timer test, confirmed load-flake (6/6 green isolated on release tip AND the merged tree; no boarded PR touches keepalive)
2026-07-18 03:07:32 -03:00
Diego Rodrigues de Sa e Souza
94509c0b5f feat: confirm before removing a single connection (#7361) (#7640)
Validated in merge-train 2026-07-18 @ 9084b408b: 12198/12201 pass; single red = earlyStreamKeepalive timer test, confirmed load-flake (6/6 green isolated on release tip AND the merged tree; no boarded PR touches keepalive)
2026-07-18 03:07:25 -03:00
Diego Rodrigues de Sa e Souza
13e57b2b35 feat: rate-limit queue admission control (maxQueueDepth + 15s default) (#6593) (#7649)
Validated in merge-train 2026-07-18 @ 9084b408b: 12198/12201 pass; single red = earlyStreamKeepalive timer test, confirmed load-flake (6/6 green isolated on release tip AND the merged tree; no boarded PR touches keepalive)
2026-07-18 03:07:20 -03:00
Diego Rodrigues de Sa e Souza
735e2d0783 feat(sse): generalize session affinity TTL to all providers (#7274) (#7650)
Validated in local merge-train @ 8f27177d1 (full parity suite green: typecheck+file-size+complexity+cognitive+changelog+unit shards 1&2+vitest)
2026-07-17 22:09:44 -03:00
Diego Rodrigues de Sa e Souza
8bf2e6929f feat(providers): add g4f.space no-key gateway (groq/gemini/pollinations/ollama/nvidia) (#6650) (#7647)
Validated in local merge-train @ 8f27177d1 (full parity suite green: typecheck+file-size+complexity+cognitive+changelog+unit shards 1&2+vitest)
2026-07-17 22:09:37 -03:00
Diego Rodrigues de Sa e Souza
f5d705c277 feat(dashboard): in-product guidance for prompt compression engines (#7530) (#7634)
Validated in local merge-train @ 8f27177d1 (full parity suite green: typecheck+file-size+complexity+cognitive+changelog+unit shards 1&2+vitest)
2026-07-17 22:09:21 -03:00
Diego Rodrigues de Sa e Souza
fb612867fd feat: add Segmind image+video provider (#6656) (#7608)
* feat(providers): add Segmind image+video provider (#6656)

Segmind exposes 200+ hosted image/video models under a single
`POST https://api.segmind.com/v1/{model}` REST shape: x-api-key auth,
JSON request body, raw media bytes response (no JSON envelope).

- New IMAGE_PROVIDERS + VIDEO_PROVIDERS registry entries (format:
  "segmind") with a curated starter model list (Flux, SDXL, SD3.5,
  Kandinsky for image; Wan, Hunyuan, LTX, Kling for video).
- New connection-metadata entry in specialty-media.ts; segmind added
  to IMAGE_ONLY_PROVIDER_IDS and VIDEO_PROVIDER_IDS.
- Dedicated handlers (imageGeneration/providers/segmind.ts,
  videoGeneration/providers/segmind.ts) built on a shared REST client
  (utils/segmindClient.ts) that centralizes the fetch/error/log path
  so both stay under the complexity/max-lines ratchets.
- Extracted the pre-existing Alibaba DashScope video handler out of
  the frozen videoGeneration.ts into videoGeneration/providers/
  dashscope.ts (no behavior change) to make room for the new Segmind
  dispatch branch under the frozen file-size baseline.
- Error responses route through sanitizeErrorMessage() (Hard Rule
  #12) — verified by dedicated no-leak tests.
- Regenerated docs/reference/PROVIDER_REFERENCE.md (250 -> 251
  providers) and synced the plain-text provider counts in README.md/
  AGENTS.md/CLAUDE.md (anchors/badges left untouched).

Tests: tests/unit/segmind-image-video-provider-6656.test.ts (11
cases — registry shape, connection metadata, IMAGE_ONLY/VIDEO_
PROVIDER_IDS membership, mocked-fetch request mapping for both
image and video, and sanitized-error-path assertions for both
upstream error bodies and network exceptions). No live Segmind key
required; response shape (raw media bytes, x-api-key auth) is
sourced from https://docs.segmind.com/ and corroborated against
https://www.segmind.com/models/flux-schnell/api,
https://www.segmind.com/models/sdxl1.0-txt2img/api, and
https://www.segmind.com/models/wan2.1-t2v/api.

Gates run clean: check-file-size, check:complexity-ratchets
(2055/889, both under baseline), typecheck:core,
typecheck:noimplicit:core (no new errors), lint (targeted files),
check:cycles, check:docs-counts (STRICT provider-count drift
resolved), check:docs-sync, check:any-budget:t11,
check:tracked-artifacts, check:provider-consistency,
check:known-symbols.

* test(providers): align APIKEY_PROVIDERS count 167→168 for the new segmind provider (#6656)

Adding segmind to specialty-media.ts grows APIKEY_PROVIDERS by one;
providers-constants-split.test.ts hardcodes the family-partition total.
Legitimate count alignment, not a weakened assertion — all 4 partition/
dedup checks still enforced.
2026-07-17 12:13:47 -03:00
Diego Rodrigues de Sa e Souza
5bacb719d3 feat: add Microsoft Designer as image provider (#6672) (#7609)
* feat(sse): add Microsoft Designer as image provider (#6672)

Adds `microsoft-designer-web` — an unofficial, reverse-engineered
Bearer-token web-session image provider, modeled on the existing
`chatgpt-web`/`copilot-m365-web` "-web" provider category.

- Registers the provider in WEB_COOKIE_PROVIDERS (src/shared/constants/
  providers/web-cookie.ts) and IMAGE_PROVIDERS (open-sse/config/
  imageRegistry.ts, new "designer-web" format).
- New handler open-sse/handlers/imageGeneration/providers/designerWeb.ts
  implements the submit-then-poll DallE.ashx flow (Bearer access_token +
  ClientId/SessionId/UserId headers -> form POST -> poll for
  image_urls_thumbnail), wired into handleImageGeneration()'s dispatch.
- The upstream ClientId header is a fixed, publicly-shared value (not a
  secret) — routed through resolvePublicCred() per Hard Rule #11, never
  as a string literal.
- Registers the token-based credential requirement in
  webSessionCredentials.ts so the provider-connect UI asks for the
  right field; connection validation falls back to the existing generic
  web-cookie session-ping validator (no dedicated validator needed).
- Extracted the KIE image-model catalog into a co-located
  open-sse/config/providers/registry/kie/models.ts module (mirrors the
  existing lmarena/directModels.ts pattern) to keep imageRegistry.ts
  under the file-size cap while adding the new provider entry.
- Regenerated docs/reference/PROVIDER_REFERENCE.md (250 -> 251
  providers) and updated the plain-text counts in README.md, AGENTS.md,
  CLAUDE.md.

Tests (tests/unit/microsoft-designer-web-6672.test.ts, 16 cases):
registry-entry shape assertions, the resolvePublicCred() shape
assertion (Hard Rule #11), and the pure header/form-body/response-
parsing helpers plus the handler's submit/poll/error/timeout paths
against a mocked fetch — no live Designer session required.

Reverse-engineered from the g4f MicrosoftDesigner.py provider reference
(researched during #6672 triage); the exact upstream response shape has
not been validated against a live Designer session, so the poll-loop
implementation follows the documented g4f contract as closely as
possible without a live capture.

* fix(providers): satisfy web-cookie executor contract + document designer-web env vars (#6672)
2026-07-17 12:09:12 -03:00
Diego Rodrigues de Sa e Souza
93e217e763 feat(video): add Novita AI as video-generation provider (#6658) (#7606)
Adds Novita AI to the video-generation subsystem (VIDEO_PROVIDERS), alongside
its existing text/chat gateway registration. Novita's async video APIs are
per-model (POST /v3/async/<model-slug>, e.g. wan-t2v, kling-v1.6-t2v) sharing
one poll endpoint (GET /v3/async/task-result?task_id=...) — confirmed against
Novita's published API reference. Seeds Wan 2.1 T2V and Kling V1.6 T2V models;
reuses the stored novita provider Bearer apiKey (no separate credential flow).

To stay under the frozen videoGeneration.ts file-size cap, extracted the
existing Alibaba/DashScope handler into a co-located sibling module
(videoGeneration/dashscopeHandler.ts) alongside the new Novita handler
(videoGeneration/novitaHandler.ts) and its pure helpers (videoGeneration/novita.ts).

Also tags novita in VIDEO_PROVIDER_IDS (src/shared/constants/providers.ts) so
it surfaces as a video-capable provider in PROVIDER_REFERENCE.md and A2A
provider-discovery, and regenerates the provider reference doc.

Tests: tests/unit/video-novita-6658.test.ts (18 cases) covering registry
shape, pure helpers (URL building, param normalization, task-id/result
parsing), and full handler wiring (submit->poll->mp4, missing credentials,
missing task_id, task FAILED, task timeout).
2026-07-17 11:54:56 -03:00
Diego Rodrigues de Sa e Souza
76c3b3b8d3 feat: add Freepik (Magnific Mystic) image generation provider (#6654) (#7597)
* feat(providers): add Freepik (Magnific Mystic) image generation provider (#6654)

Adds an official, API-key-based Freepik image-gen provider using the Mystic
endpoint (POST /v1/ai/mystic -> async task_id -> GET /v1/ai/mystic/{id}
polling), modeled on the existing leonardo.ts generationId adapter pattern.

- open-sse/config/providers/registry/freepik/index.ts: new registry module
  (kept separate to avoid pushing the frozen imageRegistry.ts over the
  file-size cap) with the 6 real Mystic style models (realism, fluid, zen,
  flexible, super_real, editorial_portraits) — not the "Flux/Imagen3" list
  from the original feature request, which independent research showed was
  stale.
- open-sse/handlers/imageGeneration/providers/freepik.ts: submit+poll
  adapter; all error paths route through sanitizeErrorMessage() (Hard Rule
  #12), configurable poll interval/timeout via body.poll_interval_ms /
  poll_timeout_ms for fast, deterministic tests.
- Registered in providers.ts (IMAGE_ONLY_PROVIDER_IDS) and
  apikey/specialty-media.ts (catalog metadata), with the corrected free-tier
  note (one-time ~€5 credit, not a recurring "100/month" allotment).

Drops the "100 free credits/month" and "Flux/Imagen3 selectable models"
claims from the original issue - verification showed the free tier is a
one-time ~€5 API credit and Imagen 3 only underlies the `fluid` style, not a
separately selectable model. Domain: api.freepik.com is still live as of
this writing despite Freepik's April-2026 API-docs rebrand to Magnific
(docs.freepik.com -> docs.magnific.com); noted inline for future
re-verification.

Closes #6654

* test: align APIKEY_PROVIDERS count to 171 after freepik + release merge (#7597)
2026-07-17 11:49:50 -03:00