* feat(api-manager): add provider-level model permissions
Persist canonical provider wildcards alongside exact model grants and
preserve explicit restricted-empty deny-all semantics across API, SQLite,
JSON import, sync, runtime policy, and the dashboard.
Invalidate filtered model catalogs on permission changes and guard against
stale in-flight catalog builders repopulating invalidated cache entries.
* fix(api-manager): show provider and model counts separately in summary
Provider wildcard selections (provider/*) are no longer counted as
individual models in the Selected Models Summary. The header now shows
"N providers · M models" when both are present, or just the non-empty
category when only one type is selected.
* fix(api-manager): separate provider and model permission displays
* fix(api-manager): separate provider wildcard permissions in UI
* fix(cache): add latency marker + per-key bypass for semantic cache
Semantic cache silently corrupts latency measurements: a 10s upstream
call served from cache looks like 19ms. Three fixes:
A. Latency marker: cache HIT responses now carry
X-OmniRoute-Cache-Latency: synthetic so measurement tools can
distinguish real vs cached latency.
B. Per-key bypass: new apiKeys.cacheDefaultMode ('legacy' | 'bypass')
lets latency-sensitive clients opt out of cache reads entirely.
- DB column + migration (134)
- rowParser parseCacheDefaultMode
- API create default + PATCH update
- checkSemanticCache returns null on bypass
C. Type safety: ApiKeyRow/ApiKeyView/params updated, superRefine
guard includes cacheDefaultMode.
Cache write path intentionally unchanged: apiKeyId is already in the
cache signature (semanticCache.ts:140), so per-key isolation prevents
cross-key pollution.
Changed test files:
- tests/unit/chatcore-semantic-cache.test.ts (3 new tests)
Signed-off-by: Minxi Hou <houminxi@gmail.com>
* docs: document semantic cache latency impact + bypass configuration
---------
Signed-off-by: Minxi Hou <houminxi@gmail.com>
* Bypass proxy compaction for native Codex context
* Add native ChatGPT Web provider pipeline
* Add managed browser and tunnel deployment
* Add ChatGPT Web setup and doctor UI
* Document and test ChatGPT Web integration
* fix(security): register chatgpt-web-codex-doctor in LOCAL_ONLY_API_PATTERNS
The diagnostic route under /api/providers/{id}/chatgpt-web-codex-doctor
was not registered in the spawn-capable route guard. Adding it for
parity with the existing /login pattern.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix(providers): route chatgpt-web-codex admin routes through a service boundary
The provider CRUD/doctor routes imported chatgpt-web-codex helpers
(finalizeValidatedChatGptWebCodexSecrets, encode/decodeChatGptWebCodexSecrets,
getChatGptWebCodexDoctorStatus) directly from open-sse/executors/**, which
no-restricted-imports (EXECUTOR_IMPORT_RESTRICTION) forbids for src/app/**
files — executor implementations must stay behind an open-sse handler or
service boundary.
Add open-sse/services/chatgptWebCodexAdmin.ts as a thin re-export boundary
(mirroring the existing tokenRefresh.ts re-export pattern) and import from
there instead. No behavior change.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
---------
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* feat(providers): add Conol web support
* fix(conol): preserve sessions and image turns
* fix(conol): pin session model and effort via /model endpoint
Conol ignores agentModel/agentEffort on POST /api/sessions, so every
session silently ran on the downgraded account default (the create
response reports modelDowngraded: true / effectiveModel).
Sessions are now created empty and configured out-of-band against
POST /api/sessions/{id}/model before the first turn is submitted, in the
order the web client uses: modelPreset, then agentModel, then agentEffort.
The ordering is load-bearing because the model call resets agentEffort to
null server-side.
Effort now defaults to xhigh when the caller does not pin one via the
-<effort> model suffix, and is clamped onto the ladder each model actually
advertises, so xhigh degrades to high on claude-sonnet-5 and is skipped
entirely for models without an effort ladder such as openrouter/fusion.
Model and effort are also dropped from the session binding key so switching
models re-pins the existing session instead of stranding it and losing the
conversation history. Re-pinning only happens on an actual change, so
steady-state follow-ups cost no extra round trips.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add a per-target priority option that advances only after trusted quota exhaustion while preserving retry, nested Combo, quality, and Global Fallback semantics.
* docs: document provider model refresh fix
Document the verified live-model refresh path for stale provider catalogs,
record the current Pollinations anonymous-access limitation, and sync the
provider-count references after regenerating the provider reference.
Co-Authored-By: Oz <oz-agent@warp.dev>
* docs: note codex local env and mac path
Co-Authored-By: Oz <oz-agent@warp.dev>
---------
Co-authored-by: Oz <oz-agent@warp.dev>
* feat(providers): add support for TinyCMS Web including WASM-based cryptographic signing and Proof-of-Work emulation
* feat(providers): add unit tests, ESLint suppressions, and fix hardcoded userid for TinyCMS Web
- Add unit tests for WASM init, UUID validation, challenge flow (15 tests)
- Add WASM source comment explaining binary origin
- Replace hardcoded userid with dynamic provider-specific data
- Add ESLint suppressions for no-explicit-any in WASM bridge code
- Add explanatory comments for DOM shim (runtime WASM-bindgen, not test mocks)
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* refactor(providers): extract TinyCMS DOM shims into an explicit setup function
tinycmsSigner.ts installed its window/document/HTMLCanvasElement/
CanvasRenderingContext2D shims for the wasm-bindgen glue as a module-load
side effect. That meant merely importing the module (even transitively,
e.g. through the provider registry from an unrelated test) mutated
global state for the rest of the test process.
Extract the shim installation into setupDomMocks(), which returns a
restore callback:
- initTinyCmsWasm() calls it once before instantiating the WASM module
(production path — unchanged behavior, still automatic).
- tests/unit/provider-tinycms-web.test.ts now calls it explicitly in a
`before` hook and restores the previous globals in `after`, so the
shims never leak into other test files.
As a side effect, replacing five separate `as any` casts with a single
typed `global as Record<string, any>` handle drops the file's
no-explicit-any count from 5 to 1; eslint-suppressions.json updated to
match.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* docs(providers): regenerate PROVIDER_REFERENCE.md for tinycms-web
Mechanical `npm run gen:provider-reference` run after merging release/
v3.8.50 into this branch — the generated table was stale for both the
new tinycms-web entry this PR adds and the release's own cheaperinference
addition. Total providers 290 -> 292, Web Cookie Providers 31 -> 32.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
---------
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* refactor(cursor): extracts token extraction into shared lib
Moves tryIdeAuth/tryAgentAuth and supporting helpers out of the
auto-import route into src/lib/cursor/tokenExtractor.ts, and adds
an agent-cli-state.json fallback candidate path to tryAgentAuth
(alongside the existing auth.json candidate) so the extraction
logic can be reused by the upcoming renewal orchestrator.
* feat(cursor): adds cursor-agent-backed token renewal orchestrator
Builds the renewal orchestrator in src/lib/cursor/renewal.ts: a
bounded, unattended-safe --list-models nudge, a side-effect-free
status availability check, an in-flight spawn lock keyed by
command, and renewCursorConnection() which nudges cursor-agent
then independently re-scrapes the IDE and cursor-agent credential
sources to detect whichever refreshed. Extends cursorAgent.ts's
binary resolution and spawn helper with fixed-paths-only mode and
a SIGKILL follow-up for background use. Adds a generic keyed-mutex
utility (src/shared/utils/keyedMutex.ts) for serializing a
connection's renew-then-persist cycle, and forwards a busy-timeout
through driverFactory's node:sqlite fallback path.
* feat(cursor): proactively renews Cursor sessions in the sweep
Adds src/lib/tokenHealthCheckCursor.ts, sweep-side glue that calls
the renewal orchestrator and persists the result, wired into
tokenHealthCheck.ts's checkConnection() via a new Cursor-specific
branch placed ahead of the generic no-refresh-token fallthrough.
Carves out a non-terminal exception for a Cursor connection that
already landed at testStatus "expired" via the request-time 401
path, excluding permanently-dead account_deactivated connections.
Extends buildRefreshFailureUpdate() with an overrides param so
Cursor's failure path can use a distinct, non-terminal errorCode
instead of the generic refresh_failed/expired taxonomy.
* feat(cursor): adds local-only manual refresh route
Adds POST /api/providers/[id]/refresh-cursor, a dedicated
loopback-only route that calls the renewal orchestrator on demand
for a single Cursor connection, bounded by a 30s per-connection
cooldown. Classifies the new route in LOCAL_ONLY_API_PATTERNS and
closes the manage-scope-bypass gap for dynamic-segment spawn-capable
routes under /api/providers/ via a new SPAWN_CAPABLE_PATTERNS /
SPAWN_CAPABLE_PATTERN_ANCESTORS mechanism, which also retroactively
covers the pre-existing /login route. The existing shared
/api/providers/[id]/refresh route is untouched and stays
remote-reachable for every other provider.
* feat(cursor): surfaces a dismissible cursor-agent nudge
Adds GET /api/providers/cursor/agent-availability, a credential-free
LOCAL_ONLY route returning only { cursorAgentAvailable: boolean },
backed by a 5-minute cached wrapper around the renewal orchestrator's
existing availability check. Surfaces a dismissible dashboard banner
on the Cursor provider page suggesting cursor-agent installation
when it isn't detected, following the existing dismissible-banner
convention. Also fixes a pre-existing bracket character in a
routeGuard.ts comment that was silently truncating
check-openapi-security-tiers.mjs's view of LOCAL_ONLY_API_PREFIXES.
* fix(cursor): wires manual refresh button to the new route
Branches handleRefreshToken to call the dedicated Cursor refresh
route instead of the generic /refresh route, which silently 502s
for Cursor connections today since they carry no refresh token.
Every other provider's refresh behavior is unaffected. Adds the
cursorSessionUnchanged i18n key and syncs it (plus a pre-existing,
unrelated 28-key backlog) across all 42 locale files.
* fix(cursor): addresses Phase 4/4.5 review findings
Restores the legacy stdout/stderr auth-pattern fallback in
checkCursorAgentAvailability() that the plan's Task 2 Step 4
required but the implementation had dropped. Threads an optional
deps parameter through checkCursorConnectionIfNeeded() so its
error branch is reachable in tests, and switches both it and the
manual-refresh route to exhaustive switch statements over the
renewal result. Adds a short-lived host-keyed dedup cache around
tryIdeAuth() so multiple due Cursor connections sharing a host
don't each open the same state.vscdb file in one sweep tick.
Adds opportunistic eviction to the manual-refresh cooldown map,
an outer try/catch to the availability route for defense-in-depth
consistency with the plan's other routes, and corrects a stale
JSDoc claim about the /login route's auth check. Documents the
now-empirically-confirmed agent-cli-state.json schema mismatch
found while validating against a real cursor-agent install.
* docs(cursor): adds changelog fragments for the renewal plan
Adds one fragment per user-facing outcome per changelog.d/README.md's
convention for a PR that both fixes and adds. PR number placeholder
to be filled in once the PR is opened.
* fix(i18n): translates the new Cursor keys into Vietnamese
The i18n:sync-ui run in an earlier commit left __MISSING__
sentinels for the 4 new Cursor keys in every locale, but
Vietnamese has a dedicated completeness test requiring zero
internal missing markers. Provides real translations for
cursorSessionUnchanged, cursorAgentNudgeTitle,
cursorAgentNudgeBody, and cursorAgentNudgeDismiss.
* fix(cursor): addresses quality-gate Layer 1.5 findings
Restores a comment that misrepresented execFile's actual argv shape
after an earlier bracket-removal fix, this time avoiding literal
closing-bracket characters entirely so the openapi checker's naive
array parser can't be broken by either version. Bounds the sweep-
and manual-route-triggered tryIdeAuth() busy-timeout to 250ms
(down from the interactive auto-import path's 2000ms), since both
share the main event loop with all other in-flight requests and
should fail fast on a WAL-lock collision rather than block the
whole instance for up to ~4s. Has the manual refresh route bypass
the sweep's IDE-auth dedup cache so a click always sees a fresh
read, consistent with this plan's existing "manual actions never
see stale cached data" convention. Documents the previously-missing
agent-availability route in ROUTE_GUARD_TIERS.md's spawn-capable
table.
* fix(cursor): adds SIGKILL follow-up to the status-check spawn
Matches the nudge spawn's existing SIGTERM+SIGKILL pattern so an
unresponsive cursor-agent status check can't leak a lingering
process if it ignores SIGTERM.
* docs(cursor): fills in the PR number for changelog fragments
Renames the 3 changelog.d fragments to their PR-numbered filenames and replaces the (#PR) placeholder with #9173, now that the PR exists.
* fix(cursor): corrects changelog fragments to reference PR #9173
The prior commit only staged the git mv rename — a git add invocation with a stale (pre-rename) pathspec aborted before the actual (#PR) -> (#9173) content edit was staged, so the rename landed without the fix it was meant to carry. This captures the actual content change.
* docs(cursor): regenerates the agent-skills catalog for the new route
check:agent-skills-sync (CI's Merge integrity gate) requires SKILL.md files to stay in sync with the live route catalog. Adding /api/providers/cursor/agent-availability in an earlier commit needed a regen this branch never ran.
* chore(quality): rebaselines file-size caps grown by agentrouter merges
Two already-merged agentrouter commits (564c204ef, ec150a006) on release/v3.8.50 grew open-sse/executors/base.ts, open-sse/handlers/chatCore.ts, and tests/unit/chatcore-translation-paths.test.ts past their frozen caps before this PR branched — unrelated to the Cursor renewal changes here. No PR branch is left to fix the growth in-place, so the caps are bumped to the current real sizes, following the existing release-green rebaseline precedent in this file.
* fix(sse): imports getModel helpers from db/models, not localDb
A recently-merged agentrouter commit added a @/lib/localDb import in chatCore.ts, violating the no-restricted-imports rule (Hard Rule #2 — never barrel-import from localDb.ts). Points the import at the owning module, src/lib/db/models.ts, where both functions are actually defined, and prunes the now-stale suppression entry.
* fix(sse): scopes CC-relay anthropic-beta to its own requestDefaults
Two already-merged agentrouter commits widened usesClaudeCodeProtocol()'s native-Claude system-transform block (billing header + selectBetaFlags-derived anthropic-beta) to also run for generic CC-compatible relay connections, not just real claude traffic and agentrouter's own wire-image mimicry. selectBetaFlags() has no visibility into a relay's own providerSpecificData.requestDefaults, so its header replacement silently wiped out an earlier context-1m append and force-included redact-thinking regardless of the relay's own opt-in. Restores both for plain CC-compatible relays only; real claude/agentrouter traffic is unaffected.
Also bumps four stale hardcoded Codex/Claude Code CLI version-string test assertions (0.144.1->0.146.0, 2.1.219->2.1.220) that drifted when the same two commits bumped the version constants without updating their tests, and rebaselines base.ts's frozen file-size cap for this fix's own +35 lines.
* fix(sse): preserves bare CC-relay native treatment and context-1m
The previous commit's fix was too broad in one direction: excluding ALL CC-compatible relays from the native-Claude header block broke two pre-existing tests (cc-compatible-provider.test.ts, v3.6.6) that rely on that treatment for a 'vanilla' relay with no providerSpecificData.requestDefaults configured.
Refines the gate to this whole native-Claude header-replacement block: replace headers for real claude traffic, agentrouter's wire-image mimicry, OR a CC-relay with no requestDefaults at all — only a relay with EXPLICIT requestDefaults (context1m/redactThinking/summarizeThinking) gets to keep buildHeaders()'s own correctly-computed header set. A redact-thinking-beta strip (unconditional, a no-op when native treatment didn't apply) covers the one remaining gap: selectBetaFlags() force-includes it for a bare relay's opaque client, which a bare relay never explicitly opted into.
Verified against all three previously-conflicting pre-existing tests simultaneously: executor-default-base.test.ts's '1M beta' test, both cc-compatible-provider.test.ts SSE-forcing tests, and provider-request-failure-pipeline.test.ts's 'keeps request beta headers' test (the last of which was already broken by the raw agentrouter merge, confirmed via direct comparison against that exact commit).
* fix(sse): fills in remaining stale CLI version literals
The same two agentrouter commits bumped Codex/Claude Code CLI version constants (0.144.1->0.146.0, 2.1.219->2.1.220) without updating every hardcoded test assertion. This round covers the ones the previous version-string commit missed: the anthropic-cache-fingerprint billing-version constant, a cc-bridge-transforms body assertion, the UI-mirror parity test's own snapshot plus its RoutingTab.tsx source of truth, an integration test's User-Agent assertion (inconsistent with its own dynamic Version assertion two lines up), and the translate-path golden snapshot. Also updates a stale doc comment referencing the old literal by value instead of by constant name.
* fix(cursor): imports from db/ modules, not the localDb barrel
Both files violated Hard Rule #2 (never barrel-import from localDb.ts) — a genuine lint error that had gone uncaught locally. refresh-cursor/route.ts imported getCachedProviderConnectionById from @/lib/localDb instead of its owning module, @/lib/db/readCache. tokenHealthCheckCursor.ts copied the same pattern from its sibling tokenHealthCheckCopilot.ts (an existing, already-suppressed violation) for updateProviderConnection; imports it from @/lib/db/providers instead, with no circular-import fallout (verified via the existing token-health-check-cursor and refresh-cursor-route test suites).
* fix(db): removes stale raw-SQL allowlist entry for cursor route
The cursor auto-import route no longer contains raw SQL — that query
now lives in src/lib/cursor/tokenExtractor.ts, outside the
route/handler scope check-db-rules scans. The allowlist entry was
stale, tripping the stale-enforcement gate.
* fix(test): registers cursor test files in stryker tap.testFiles
Three unit test files covering mutation-tested modules
(route-guard-cursor-agent-availability, route-guard-cursor-refresh,
cursor-renewal) were missing from stryker.conf.json's tap.testFiles,
tripping the mutation-test-coverage gate's drift detection.
* chore(ci): retriggers checks (stuck GH Actions runner on shard 2/4)
* fix(sse): restores CC-relay context1m/redact-thinking test coverage
Rebasing onto release/v3.8.50's new tip (35405be60, an unrelated
agentrouter protocol-inference commit) silently flipped two assertions
this branch's own earlier fix (687fbda62) depends on, in the same test
files that commit touched for other reasons:
- executor-default-base.test.ts: calls[0] (a bare CC-relay with no
requestDefaults) expected redact-thinking-beta absent; flipped to
present. calls[1] (context1m+redactThinking requestDefaults) expected
the context-1m beta preserved; flipped to absent.
- provider-request-failure-pipeline.test.ts: expected Accept:
text/event-stream and the context-1m beta present for a relay with
explicit requestDefaults; flipped to application/json and absent.
35405be60 did not touch open-sse/executors/base.ts at all, so these
were test-only edits made without visibility into the still-unmerged
CC-relay header-preservation fix on this branch — they quietly matched
the assertions back to the pre-fix (buggy) behavior instead. Restores
the original, validated expectations; all three interdependent test
files (executor-default-base, cc-compatible-provider,
provider-request-failure-pipeline) verified passing together again.
* ci: re-trigger checks after GitHub Actions incident (2026-08-07, resolved)
* ci: re-trigger checks (previous push event was dropped)
* fix(quality): restore dropped vi.json cursor-renewal keys + rebaseline test growth
vi.json was missing 4 keys (cursorSessionUnchanged, cursorAgentNudgeTitle/Body/Dismiss) that this PR's own pre-merge branch had translated -- the original merge's 'git checkout --theirs' resolution for the 7 conflicted locale files discarded them since upstream's vi.json has no cursor-token-renewal feature. Restored from pre-merge tip a38003e30. Also rebaselines combo-routing-engine.test.ts (3457->3464) for the comment growth from the ALL_ACCOUNTS_INACTIVE fix, caught by CI's PR-mode check:file-size.
* chore(tests): drop explanatory comments on ALL_TARGETS_SKIPPED assertions
Kept the assertion value fix (ALL_ACCOUNTS_INACTIVE -> ALL_TARGETS_SKIPPED); the comments were unnecessary. Reverts the file-size baseline bump these comments caused (combo-routing-engine.test.ts back to its original 3457).
* feat(skills): add Ponytail minimalism skill as external catalog entry
- Add 'external' SkillCategory + SkillArea
- Register ponytail (MIT, DietrichGebert/ponytail) in CURATED_SKILLS
- Generator: external skills carry content in custom block, no api/cli body
- Generate skills/ponytail/SKILL.md with original content preserved
- Update catalog test counts 45 -> 46
* fix(skills+memory): builtin handler fallback in executor, skip vector upsert for deleted memories
- skills: Next.js compiles SkillExecutor into multiple chunks (own singleton
each); route chunk lacked builtin handlers registered at startup via
instrumentation. execute() now falls back to builtinSkills registry, so
POST /api/skills/executions works for file_read/web_fetch/etc.
- memory: scheduleVectorUpsert is fire-and-forget and embeddings are slow;
health-check verify (create->delete test memory) left queued upserts
failing with 'memory not found' every 30s. Check existence before embedding
and skip quietly.
* fix(skills): encode tool names with @ and . for providers rejecting them
Skill tools were advertised as 'name@version' (e.g. test-fr2@1.0.0), but
DeepSeek/Groq/OpenAI reject function names not matching ^[a-zA-Z0-9_-]+$.
Names already valid are left untouched; invalid ones are reversibly encoded
as omr_skill_<base64url> and decoded in interception before registry lookup.
* fix(combos): include DB id column in combo records for dashboard links
getCombos() selected only data/sort_order/context_cache_protection, so
combos whose JSON blob lacked an id field returned id: undefined. The
dashboard then linked to /dashboard/combos/undefined and Combo Control
Center failed with 'Combo not found'. Merge the id column into parsed
rows (authoritative, only when the blob has no id).
* fix(skills): normalize flat skill schemas to object schema for Gemini/Claude
Stored skill schemas are flat property maps ({ text: { type: string } }),
which OpenAI-compatible providers tolerate but Gemini
(function_declarations[].parameters) rejects with 'Unknown name ... Cannot
find field'. Wrap bare maps into { type: 'object', properties: {...} } for
all three tool formats.
* fix(skills): warm registry cache before skill injection in chat path
injectSkills() lists the in-memory skillRegistry, which is empty after a
cold start until something calls loadFromDatabase(). The interception path
already warms the cache (#2815); the injection path did not, so skills
were silently skipped (no_enabled_skills) for the first requests after
restart. Warm the cache for the chat owner before injection.
---------
Co-authored-by: Egor <egorich-print@users.noreply.github.com>
* feat(alibaba): add free-tier routing with console quota and builtin allowlist
Classify DashScope free vs paid models via console quota API, a hardcoded
operator allowlist fallback, and per-connection drained tracking. Wire wildcard
combo expansion, model refresh, combo exhaustion, and audit redaction for
Alibaba console credentials.
* fix(routing): reset forced connection pin and persist Alibaba free-tier drain
Drop session affinity pins when a forced connection is excluded after 429,
and record Alibaba free-tier exhaustion on upstream 403 so per-key drained
lists stay accurate without blocking sibling keys.
* fix(alibaba): prefer live quota sync over static free-tier allowlist
Stop unioning the builtin text allowlist when a console quota snapshot exists,
treat expired quotaValidityPeriod as not_capable, and add a dated JSON pack plus
sync-alibaba-allowlist script for operator refresh without code edits.
* docs(alibaba): document free-tier console path + allowlist env overrides
Adds the 4 ALIBABA_FREE_TIER_*_FE_PATH / ALIBABA_FREE_TIER_ALLOWLIST_PATH
env vars (referenced by alibabaFreeTierQuotaFetcher.ts and
alibabaFreeTierAllowlist.ts) to .env.example and
docs/reference/ENVIRONMENT.md so the env/docs contract check passes.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* refactor(open-sse): split alibabaFreeTierQuotaFetcher.ts under file-size cap
Extract pure parsing/classification/eligibility-filtering logic into
alibabaFreeTierQuotaClassify.ts and shared types/primitives into
alibabaFreeTierQuotaTypes.ts, leaving the HTTP/console-fetch flow in the
original file. Public API is unchanged (re-exported), behavior is identical.
Co-authored-by: AndrianBalanescu <AndrianBalanescu@users.noreply.github.com>
* fix: resolve typecheck errors in alibaba-free-tier routing
---------
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: AndrianBalanescu <AndrianBalanescu@users.noreply.github.com>
Co-authored-by: AndrianBalanescu <andrian@balanescu.dev>
* fix: align three stub implementations with original code
- chatUrlMatcher: restore original 3-arg signature (u, matchDomain, chatUrl)
with PLACEHOLDER-aware path segment matching
- shouldUseGrokBrowserBacked: remove required param, restore env-var logic
checking both WEB_COOKIE_USE_BROWSER and OMNIROUTE_BROWSER_POOL
- browserPool.ts: add Turbopack rationale comment and join-trick helper
to satisfy the optional-import test assertions
- browserBackedChat.ts: replace any types with typed BrowserPoolModule interface
Verification: 40/40 browser node:test pass, typecheck:core 0 errors
* fix: remove duplicate getMod/modPromise in browserBackedChat stub
Two copies of the module proxy got committed — the typed BrowserPoolModule
version at lines 50-56 and a stale any-typed duplicate at lines 64-71.
Removed the duplicate, keeping the typed version.
Verification:
- 40/40 browser tests pass (both previously-failing suites now green)
- typecheck:core: 0 errors
- env kill switch (OMNIROUTE_BROWSER_POOL=off): verified
* fix(pr-8299): address all 5 review issues
Issue #1: Add @omniroute/browser-pool path to root tsconfig.json paths
Issue #2: Fix tryBackedChat fallback — call browserBackedChat outside if(loaded) guard
Issue #3: Fix grokClearance stub signature (signal?: AbortSignal) → string|null
Issue #4: Add comment clarifying async __resetBrowserPoolMetricsForTest vs upstream sync
Issue #5: Add test case for package-absent fallback in tryBackedChat
All 25 browser tests pass across 4 suites. typecheck:core passes.
* chore: move sqlite-vec to optionalDependencies, fix js-tiktoken static import
Both changes ensure native binary dependencies are properly categorized as optional:
- sqlite-vec: moved from dependencies to optionalDependencies. Only used via
lazy _require("sqlite-vec") in vectorStore.ts — zero static imports.
- js-tiktoken: already in optionalDependencies, import changed to createRequire
pattern to avoid crash when package is not installed (same pattern as sqlite-vec
in vectorStore.ts).
Resolves ScoutDeps findings from browser-pool pluginization audit.
* docs(issues): fix stale interfaces.ts path in browser-pool proposal
The proposal originally planned open-sse/interfaces/browserPool.ts for
the BrowserPoolProvider interface, but the shipped implementation puts
it in packages/browser-pool/src/interfaces.ts instead. Update the
references so the doc matches what was actually built — the stale
path was tripping check:fabricated-docs (--strict).
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix: sync package-lock.json with playwright 1.62.0
Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
* test: keep browser warmup disabled in tryBackedChat unit tests
* fix(pr-8299): keep grokClearance on the evolved release implementation (rebase reconciliation)
---------
Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
* fix: add per-connection virtual admission lanes (#9654)
Worst-day-ever analysis to harden AdaptiveAdmissionController:
- Guard expireEntry() against null entry (CRITICAL null deref)
- Add deleteLane() to drain+reject on LRU eviction (HIGH orphaned promises)
- Fix Map mutation during evictIdleLanes iteration (MEDIUM safety)
- Add ADMISSION_LANE_EVICTED reject code (MEDIUM clarity)
- Pass sessionId to admitChatRequest in route.ts
- virtualLanes defaults to false in validateConfig
- 7 new controller tests + 14 new byte-level admission tests
- Assertions tightened from >= to === (Matt Pocock methodology)
Debunked 2 false positives: concurrency race (single-threaded JS)
and memory amplification (FairCostQueue bounds per-lane).
Fixes#9654
* fix(admission): restore bounded queue-wait on per-connection lanes (#9654)
The per-connection lane refactor dropped the bounded queue-wait
(acquireHeavyWithin / #waiters / queueMs). #9654's acceptance criteria and
#9608 section C prefer server-side wait/pacing up to defaultMaxWaitMs over
an instant retryable 503.
- ChatAdmissionController: re-add #waiters FIFO + acquireHeavyWithin(timeoutMs);
queueMs: 0 preserves the instant-503 path
- admitChatStructure and admitChatRequest.reserve are async again and take queueMs
- route: pass CHAT_ADMISSION_QUEUE_MAX_MS and await the admission calls
- per-connection lane tests await the async admitChatStructure
Admission suite: 114/114 pass (bun test, 7 files).
* chore: re-trigger CI after dast-smoke infra cancellation (#9654)
* feat(admission): cancel queue-wait on client abort (#9654)
U2 from KC plan 2026-08-09-001. Thread the request AbortSignal through
acquireHeavyWithin so a disconnected client stops parking in the FIFO
for the full queueMs.
- acquireHeavyWithin(timeoutMs, signal?): on abort the waiter is removed
from the FIFO immediately and the promise resolves null early;
pre-aborted signals never park; the deadline timer is cleared when
abort/release wins the race
- admitChatRequest reserve() passes request.signal; admitChatStructure
gains options.signal; the route threads request.signal
- 5 exact-assertion tests (settle-early, pre-aborted, byte-heavy,
structural, FIFO-preservation): 119/119 across the 7-file suite
* fix(admission): bound queued bytes for the queue-wait heap valve (#9654)
U3 from KC plan 2026-08-09-001. The restored queue-wait parks fully-buffered
bodies; without a cap, several large coding-agent bodies (~750 KB) waiting at
once recreates the #4380 heap amplification this module was built to stop.
- acquireHeavyWithin(timeoutMs, signal?, queuedBytes): each parked waiter is
charged its buffered size against CHAT_ADMISSION_MAX_QUEUED_BYTES (default
4 MB); over-budget waits reject immediately with a retryable 503 and never
park. The charge is released on wake, abort, or timeout.
- Real sizes threaded from admitChatRequest (declared length / sniffed bytes);
structural waits charge the conservative 256 KB weight.
- Lower default OMNIROUTE_CHAT_ADMISSION_QUEUE_MS to 2000ms (was 5000ms).
- Env vars documented in .env.example; 6 exact-assertion tests: 125/125 across
the 7-file admission suite (was 119).
* docs: map the two admission-lane systems for operators (#9654)
U5 from KC plan 2026-08-09-001. Verifies lane metrics are exposed by the health
payload (GET /api/monitoring/health -> adaptiveAdmission -> lane* fields) and
records which lane system reports where: byte-level per-connection lanes (always
on, memory scope) vs adaptive virtual lanes (opt-in via OMNIROUTE_CHAT_VIRTUAL_LANES,
dispatch scope) plus the explicit opt-in ops note.
* docs: add required frontmatter to admission-lanes doc (dast-smoke build fix)
* docs: sync env vars with .env.example and ENVIRONMENT.md (docs gate fix)
* fix(admission): complete REJECT_MAP, literal lane env read, split oversized test file
Three CI-gate fixes surfaced by the post-merge check run (head 3de77166e):
1. open-sse-typecheck (TS2741): REJECT_MAP was missing the ADMISSION_LANE_EVICTED
entry that controller.ts:662 emits on lane eviction. Add the 503 mapping so the
Record<AdmissionRejectCode, RejectHttpMapping> is total.
2. Docs Gates fabricated-claim: OMNIROUTE_CHAT_VIRTUAL_LANES was read dynamically
via ENV_KEYS.virtualLanes (env[key]), invisible to the literal env.X scanner.
Read it literally — behavior-identical, doc claim now verifiable.
3. check:file-size: chat-body-admission.test.ts (1307 lines) exceeded the 1000-line
new-file cap. Split the queue-wait/abort/heap-valve section into
chat-body-admission-queue.test.ts (818 + 513 lines, both under cap).
Suite: 125/125 across 8 files. All three checkers pass locally.
* refactor(admission): drop dead ENV_KEYS.virtualLanes entry + lock lane-evicted mapping test
Code-review follow-up on 50c93d266:
1. ENV_KEYS.virtualLanes is now unreferenced since the literal env read landed;
remove it so the config map only lists keys actually read through the map.
2. Add an exact-assertion runtime test for the ADMISSION_LANE_EVICTED mapping:
a queued lane waiter evicted by the 60s idle TTL rejects with 503 /
admission_lane_evicted / Retry-After 1 / sanitized body (no raw tenant key).
Proves the REJECT_MAP entry end-to-end through buildAdmissionRejectResponse.
Suite: 126/126 (17 in runtime file, 125 in the 8-file admission suite).
---------
Co-authored-by: Brandon Bennett <brandonbennett@macbookair.myfiosgateway.com>
Wire Bearer /alpha billing credits and 5h/weekly windows into Provider
Limits and genericQuotaFetcher so dashboard and preflight see live CC quotas.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(sse): grace period before finalizing a client disconnect as 499 (#9653)
A client that closes its connection right after reading a fully-completed
SSE stream can race OmniRoute's own completion bookkeeping: the bytes
already reached the client, but the transform stream's own completion
callback (onStreamComplete, which flips streamCompletionRecorded) hasn't
finished bubbling up when the disconnect handler fires, so the request gets
persisted as a false 499 with zero token usage even though it delivered its
full response.
Confirmed live on real traffic before this fix: a request whose server log
showed "disconnect: request_signal_aborted" at 18236ms was persisted with
status 200 and full token usage (82814/1292) once the grace period let the
real completion win the race, matching what the client actually received.
createClientDisconnectGraceHandler (new leaf in
streamFailureFinalization.ts) polls isStreamCompletionRecorded() for up to
STREAM_DISCONNECT_GRACE_PERIOD_MS (default 10s, env-configurable, 0
disables) before finalizing as a failure. If a real completion lands within
the window, handleStreamFailure's own guard is a no-op and the genuine 200
stands.
Covered by tests/unit/stream-disconnect-grace-period-9653.test.ts (fake-timer
driven: already-recorded completion short-circuits, disabled-grace-period
finalizes immediately, a completion landing mid-window skips finalize
entirely, and no completion ever landing finalizes once the deadline
passes).
(cherry picked from commit 5d0fe28c42)
* chore(quality): rebaseline chatCore.ts for the disconnect grace-period fix
Own growth from the disconnect grace-period fix: 5030->5039 (+9, the
createClientDisconnectGraceHandler wiring at the existing
onClientDisconnectFinalize call site).
---------
Co-authored-by: Markus Hartung <mail@hartmark.se>
* fix(compression): add Lite tool truncation toggle
* fix(antigravity): add missing antigravityProjectPersistence.ts module
The quota-strategy engine (quotaStrategies.ts) imports from
antigravityProjectPersistence.ts, but only antigravityProjectPersist.ts
existed in the tree. Add the missing module with the expected
preferAntigravityConnectionsWithStoredProject() helper and re-export
the existing persistDiscoveredAntigravityProjectId().
Co-authored-by: diegosouzapw <diegosouza.pw@outlook.com>
* fix(file-size): rebaseline strategySelector.ts for Lite truncation toggle
The PR adds one line to threading options?.config?.lite into
applyLiteCompression. Update the frozen size from 1060 to 1061.
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Refs #9629
---------
Co-authored-by: Xiangzhe <xiangzhedev@gmail.com>
Co-authored-by: xz-dev <xz-dev@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
* feat(api): add GET /api/resilience/connections for per-account state
The three temporary-failure mechanisms each have their own scope -- the
provider circuit breaker covers a whole provider, connection cooldown covers
one account, model lockout covers a provider/connection/model triple -- and
until now nothing showed them side by side. Diagnosing "why is this key being
skipped" meant reading three separate surfaces and correlating by hand, which
is exactly what the docs' own debugging guidance asks an operator to do.
The route returns all three keyed by connection, plus the breaker's transition
history so a flapping provider is visible as a sequence rather than a single
current state. getStatus() already assembled everything except that history;
it now returns a copy of it and carries an explicit CircuitBreakerStatus type
instead of an inferred one.
Reading raw connection rows for this meant widening getRawProviderConnections'
column projection, so the existing allowlist is exported and the route selects
through it. A test asserts every column the route names is in that allowlist,
which turns a future typo into a failure here rather than a silent empty field.
Each of the three data sources is wrapped independently: one of them throwing
degrades that section and sets meta.degraded rather than failing the whole
response, since a partial view still answers most of the questions the page
exists for.
Loopback-gated. It spawns nothing, unlike every other entry on that list, but
it exposes per-account operational state and the comment says so to keep it
from being read as precedent for gating read-only routes generally.
Tests are real isolated-DB integration tests rather than mocks -- ESM mocking
is unavailable here (no mock.module, non-configurable exports) and the
codebase already has the isolated-DB pattern, which exercises more than a mock
would anyway.
Signed-off-by: Minxi Hou <houminxi@gmail.com>
* feat(dashboard): add the per-account resilience connections page
Renders what the API added: every connection with its cooldown, its provider
breaker, and its model lockouts in one table, with a detail view per connection
and the breaker's transitions drawn as a timeline. The timeline is the part that
is hard to get from the existing surfaces -- a breaker sitting at CLOSED right
now looks healthy, and only the sequence shows it has opened four times in the
last hour.
Polls rather than streams. The state it displays changes on the order of
seconds to minutes and the page is loopback-gated, so an SSE channel would buy
nothing over an interval.
ModelCooldownsCard had its own formatRemaining. The new table needs the same
countdown format and two copies would drift, so it moves to
shared/utils/formatRemaining.ts and both import it -- behaviour unchanged, the
extracted version differs from the deleted one only in local variable names.
DataTable's column and row interfaces are exported for the same reason: the new
table types against them rather than restating their shape.
Signed-off-by: Minxi Hou <houminxi@gmail.com>
* fix(i18n): translate new resilience-connections screen strings
PR #9510 added the "Connection Resilience" dashboard screen but the
sync-added i18n keys (sidebar.resilienceConnections/Subtitle and the
full resilienceConnections namespace) were left as __MISSING__: in
every non-English locale, dropping i18nUiCoverage.pct below the 99
ratchet baseline.
Translate all ~78 new leaf strings into all 41 non-English locales.
Pre-existing unrelated __MISSING__ debt (hermesRole*, apiProtocol*,
grokAutoTopUp*, featureFlagExposeFunctionalGatewayMirrorsDescription)
is left untouched — out of scope for this fix.
Co-authored-by: HouMinXi <HouMinXi@users.noreply.github.com>
---------
Signed-off-by: Minxi Hou <houminxi@gmail.com>
Co-authored-by: Minxi Hou <houminxi@gmail.com>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: HouMinXi <HouMinXi@users.noreply.github.com>
* fix(providers): add per-provider opt-out for anonymous no-auth fallback
API-key providers with anonymousFallback: true (opencode-go, opencode-zen,
pollinations, kilocode) receive a synthetic "noauth" connection whenever all
real connections are terminal (credits_exhausted/banned/expired) or
unavailable. The opencode upstream now rejects anonymous requests with
401 Missing API key, so the fallback adds a guaranteed-failing round trip
and health/reconnect noise before the combo moves on.
Add a noAuthFallbackDisabledProviders settings array (zod-validated,
persisted via /api/settings, following the blockedProviders pattern).
When a provider is listed, maybeSyntheticNoAuthFallback returns null for
anonymousFallback-only providers, so exhausted providers are skipped
immediately as allExpired/allRateLimited while real keyed connections keep
working and recover automatically once quota state clears. True no-auth
providers are unaffected; blockedProviders remains their disable mechanism.
Default (absent/empty list) preserves current behavior.
Provider detail pages for anonymousFallback providers gain an
"Anonymous fallback" toggle (default ON) backed by the new setting.
Refs #9674
* fix(auth): reduce file size
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
---------
Co-authored-by: Hermes Agent <hermes@hermes-chloe.hyades.io>
Wire Openference as a first-party OAuth gateway (PKCE, rotating refresh)
and an API-key catalog entry on api.openference.com, with live model
discovery, connection testing, free-tier badges, and regression tests.
Co-authored-by: Anh Tran <anhlead@outlook.com>
A phone that previously loaded a production build on this origin (or
an old dev build from before the registration was gated) kept an
active service worker across dev restarts. It intercepted every
navigation/asset fetch, occasionally serving a JS chunk that didn't
match the running dev server, which tripped Next's dev-client
chunk-mismatch auto-reload — visible as an unexplained, unstoppable
refresh loop on that device only (confirmed via a clean private tab
on the same phone/URL not looping).
PwaRegister now actively unregisters any existing service worker
registrations and clears their caches outside production, instead of
just skipping a new registration.
(cherry picked from commit 66a2515cbc)
Co-authored-by: Markus Hartung <mail@hartmark.se>
* feat(logging): make the chat-log truncation limit configurable, bumped default 128x
The 8KB cap on logged request/response bodies
(open-sse/handlers/chatCore/logTruncation.ts::truncateForLog()) was
hardcoded — trivially exceeded by any real multi-turn agentic
conversation, meaning the dashboard's "Full Conversation" panel could
only ever show a placeholder instead of the actual messages for nearly
every logged row of any conversation with real substance.
- Added CHAT_LOG_MAX_BODY_KB env var (src/lib/logEnv.ts::
getChatLogMaxBodyBytes()), default 1024 KB (1MB) — a 128x bump from
the old hardcoded 8KB — following the same configurable-limit pattern
as the sibling CHAT_LOG_TEXT_LIMIT/CHAT_LOG_ARRAY_TAIL_ITEMS/etc. vars.
- Documented in .env.example and docs/reference/ENVIRONMENT.md.
estimateSizeFast() (open-sse/utils/estimateSize.ts) has been
substantially rewritten upstream since this bug was first found (now an
iterative Frame-based walker with a separate node-visit budget, not the
simple stack loop originally patched) — re-implemented the fix against
the current algorithm rather than porting the old diff: the byte
early-exit was unconditionally the module-level ESTIMATE_SIZE_BYTE_LIMIT
(256 KiB) with no way for a caller to raise it, so any caller comparing
against a bigger configured threshold could never see a size above
~256 KiB — every payload between 256 KiB and the caller's real limit
looked "under threshold" and truncation never fired, the opposite of
intended. Added an optional byteLimit parameter (default unchanged at
ESTIMATE_SIZE_BYTE_LIMIT, so isSmallEnoughForSemanticCache's existing
behavior is untouched) threaded through both the byte-check early-exit
and the node-budget-exhaustion fail-closed fallback, with
truncateForLog() now passing its own configured getChatLogMaxBodyBytes()
value through.
* feat(dashboard): show conversation session tag in request detail metadata
Adds a "Conversation" field to the request detail panel's metadata
grid (after "Combo"), showing the request's conversation id
(sessionTag) for quick reference/copy.
---------
Co-authored-by: Markus Hartung <mail@hartmark.se>
* fix(deps): bump transitive deps for 6 Dependabot + remaining audit vulns on main
Same overrides as #9464 (ip-address, hono, fast-uri, socket.io-parser, undici)
applied directly to main. Also covers brace-expansion (scoped), js-yaml v4 copies,
and mermaid.
npm audit: 6→0 vulnerabilities.
Closes Dependabot #161-#166.
* fix(deps): bump nanoid, dompurify for 2 new Dependabot alerts (#189, #190)
Bumps: nanoid ^3.3.17 (was transitive, now overridden), dompurify ^3.4.13
(with monaco-editor scoped override). Closes Dependabot #189, #190.
Remaining #182-#188 (js-yaml + mermaid) already closed by #9651 merge —
awaiting Dependabot re-scan.
npm audit → 0 vulnerabilities.
* fix(repo): harden .gitignore to also ignore a _tasks symlink (/_tasks)
_tasks is a SEPARATE nested git repo (gitignored). The pattern _tasks/ (trailing
slash) ignores only a directory, not a SYMLINK named _tasks. A self-referential
_tasks symlink can slip in via git add -A and, once pulled, checkout materializes
it over the real _tasks repo (destroying plans/specs/hands-off). Anchored /_tasks
ignores the symlink too, preventing re-capture.
* feat(telegram): Mini App chat bridge — initData auth, update webhook, chat proxy
Implements the Phase-1 slice of the Telegram Mini App integration
(docs/proposals/TELEGRAM-MINIAPP.md):
- src/lib/telegram/initData.ts — dependency-free WebApp initData HMAC-SHA256
verification (Telegram Bot API spec), with auth_date freshness check.
- src/lib/telegram/config.ts — TELEGRAM_BOT_TOKEN / model / API base / timeout
env config; token format validation; enabled gate.
- src/lib/telegram/botApi.ts — minimal fetch-based Bot API client
(sendMessage, editMessageText, setWebhook) + update shape helpers.
- src/lib/telegram/chatProxy.ts — maps a Telegram user to a per-user
OmniRoute API key (createApiKey, name telegram:<userId>) and proxies
prompts through the existing handleChat pipeline.
- src/app/api/telegram/update/route.ts — inbound endpoint serving both the
Bot API update webhook (/start + chat replies) and the Mini App direct
path (initData HMAC verified → 401 on mismatch). Public route prefix;
own auth only.
- src/app/miniapp/page.tsx — Telegram WebApp SDK chat UI.
- Tests: telegram-init-data (7), telegram-botapi (5) — 12/12 pass.
- Env docs: TELEGRAM_* vars in .env.example + ENVIRONMENT.md (sync ✓).
- Route-validation check: PASS (body validated via Zod).
---------
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: benzntech <bensonkbmca@gmail.com>
Agent clients (OpenCode, Claude Code, Cursor) fan out heavy sub-requests
that land on the admission gate together. With the single heavyweight
slot, concurrent heavy requests were rejected immediately with a
retryable 503; clients burn their retry budget in seconds and the agent
dies mid-task.
Heavy requests now wait up to OMNIROUTE_CHAT_ADMISSION_QUEUE_MS (default
5000ms) for a slot before the 503, served FIFO; 0 restores the legacy
immediate-reject behaviour. Applied to both the byte-based path
(admitChatRequest) and the structure-based path (admitChatStructure, now
async).
Co-authored-by: herjarsa <herjarsa@users.noreply.github.com>