Repository navigation
feat(providers): Cursor PKCE login with Bearer quota, auto router, and empty-turn errors - #9909
Conversation
8decab2 to
e765ed4
Compare
|
Thanks @yansigit — sizable Cursor PKCE feature. Unit coverage is strong; the live OAuth/PKCE browser flow needs a VPS smoke before merge (held in the release-drain). I'll also do a pass on access/refresh token storage. |
1 similar comment
|
Thanks @yansigit — sizable Cursor PKCE feature. Unit coverage is strong; the live OAuth/PKCE browser flow needs a VPS smoke before merge (held in the release-drain). I'll also do a pass on access/refresh token storage. |
|
Thanks for the thorough Cursor PKCE work — the focused suite (106 tests) and typecheck/provider-consistency all pass locally. Two concrete blockers before this can merge:
Separately — since the PKCE handshake and the Bearer usage-chain only run against real cursor.com/api2.cursor.sh, this needs a live VPS smoke test (login start→poll→persist, then Provider Limits) before merge. Worth a quick check too that the model-catalog rewrite doesn't silently orphan any ids still referenced by existing combos. |
Two independently-merged PRs (cheaper-inference#10263 agentic-conversation-tracking-v4 and cheaper-inference#10362 exclusive-managed-session-leases) each picked migration slot 155 against different base states, landing a real collision on release/v3.8.50 (155_agentic_conversations.sql vs 155_exclusive_connection_leases.sql; cheaper-inference#10263 also claimed 156 via 156_conversation_turn_nodes.sql). Renumbered cheaper-inference#10362's migration to the next free slot (157) and updated its own regression test (exclusive-connection-leases.test.ts) that asserted the literal filename/slot. No retroactive guard needed: CREATE TABLE IF NOT EXISTS is idempotent under either number. Confirmed via check-migration-numbering.mjs (154 migrations, 0 duplicates) and the full exclusive-connection-leases test suite (11/11 pass).
…d port (cheaper-inference#10517) * fix(deps): bump nanoid, dompurify for 2 new Dependabot alerts (cheaper-inference#189, cheaper-inference#190) Bumps: nanoid ^3.3.17 (was transitive, now overridden), dompurify ^3.4.13 (with monaco-editor scoped override). Closes Dependabot cheaper-inference#189, cheaper-inference#190. Remaining cheaper-inference#182-cheaper-inference#188 (js-yaml + mermaid) already closed by cheaper-inference#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. * Hide health-check excluded models from /v1/models catalog (cheaper-inference#10026) Mirror the request-time exclusion rule (provider_specific_data.excludedModels) in the unified catalog builder: a model is hidden when its provider has connections but none of them is eligible for it. Applied across the PROVIDER_MODELS, synced, custom, alias-backed, and managed-fallback loops so ghost models no longer appear as available. Co-authored-by: ritheshcn25 <ritheshcn25@users.noreply.github.com> * fix(models): memoize getModelsDevPricing (event loop / healthz) (cheaper-inference#10055) * fix(models): memoize getModelsDevPricing for /v1/models catalog resolveCatalogPricing called getModelsDevPricing once per model while building GET /v1/models. Each call re-scanned models_dev_pricing and JSON.parsed every row (~10k SQL scans + multi-GB parse work), pegging the event loop so even /healthz timed out (cheaper-inference#9685, cheaper-inference#10052). Memoize the parsed map until saveModelsDevPricing / clearModelsDevPricing and add a unit test for invalidation. Signed-off-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com> * fix(db): invalidate modelsDevPricing cache on DB reset (cheaper-inference#10055) Copilot review fixes: 1. Register invalidateModelsDevPricingCache() with DB state reset system so resetDbInstance() clears the process-local memo, preventing stale pricing data from surviving across DB reset/restore operations. 2. Add test assertion verifying DB reset bypasses the memo (Copilot cheaper-inference#10055). The process-local memo at modelsDevSync.ts:204 caches getModelsDevPricing() results until saveModelsDevPricing()/clearModelsDevPricing() to avoid re-scanning all pricing rows on every /v1/models request. Without this hook, backup restore and test DB resets would serve stale cached data from the previous connection. Tests: npm run test:unit:serial -- tests/unit/modelsDevSync-extended.test.ts --------- Signed-off-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com> Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> * fix(oauth): route Zed hosted sign-in callback back to the dashboard port Zed's native-app sign-in always redirects the browser to the loopback port sent as native_app_port (hardcoded default 58443), where nothing listens: the browser shows "site can't be reached" and the login looks broken even though the token is in the URL. The manual paste fallback was broken too - handleManualSubmit requires a ?code= param that Zed's callback (user_id + access_token) never carries, so the flow could never complete. - zed-hosted: derive native_app_port from the dashboard's own loopback port so the redirect lands back on OmniRoute; remote/LAN origins keep the old default port and the paste flow - app root: forward ?user_id=...&access_token=... to the /callback relay instead of dropping the query string on the /dashboard redirect - /callback relay: recognize the Zed payload (no code param) and relay the full URL as the exchange payload; allow postMessage to both loopback spellings (localhost/127.0.0.1) of the same port - OAuthModal: zed-hosted popup auto-completes on true localhost; the manual paste path passes the full URL through to the exchange instead of erroring with "No authorization code found" - manual input panel: zed-hosted-specific placeholder and hint - tests: extend the postMessage scope guard with the loopback same-port trusted origins * changelog: fragment for cheaper-inference#10517 * fix(oauth): derive Zed native_app_port from server config, not browser scheme/port resolveDashboardLoopbackPort() previously re-derived the dashboard's loopback port from the browser-supplied redirectUri (window.location.port || protocol === "https:" ? "443" : "80"), which produced http://127.0.0.1:443/ native-app redirects when the dashboard was reached over HTTPS on its implicit default port (e.g. behind a local TLS-terminating reverse proxy) - a scheme/port mismatch, since Zed's own redirect is always plain http and nothing serves plain HTTP on 443 in that scenario. This code runs server-side (in the OAuth authorize API route), so once the redirect URI's hostname is confirmed loopback it now uses the OmniRoute process's own authoritative listening port via getRuntimePorts() (OMNIROUTE_PORT/PORT/DASHBOARD_PORT) instead of re-deriving it from the browser-observed scheme/port. Non-loopback (remote/LAN) redirect URIs still return null and fall back to the manual paste flow. Adds tests/unit/zed-hosted-loopback-port-derivation.test.ts (8 cases) covering the port-derivation logic directly, including the HTTPS-default-port mismatch scenario that motivated this fix, env-var precedence, IPv6 loopback, non-loopback/remote fallback, and buildAuthUrl's native_app_port wiring. Also rebaselines config/quality/file-size-baseline.json for OAuthModal.tsx's own growth from this PR's earlier commit (1134->1149 gate units) - legitimate zed-hosted callback wiring at the existing provider-switch chokepoint, not extractable without a broader modal decomposition (tracked in cheaper-inference#3501). The live Zed OAuth handshake itself (root -> /callback -> OAuthModal exchange against the real zed.dev endpoint) still needs a documented VPS smoke test per Hard Rule cheaper-inference#18; this fix covers the TDD-able port-derivation logic that motivated the change. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Signed-off-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com> Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@outlook.com> Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> Co-authored-by: ritheshcn25 <rithesh.chandran@snb.ca> Co-authored-by: ritheshcn25 <ritheshcn25@users.noreply.github.com> Co-authored-by: Ravi Tharuma <25951435+RaviTharuma@users.noreply.github.com> Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
…em metadata (cheaper-inference#10655) `assertValidEngine()` valida id, apply, compress, getConfigSchema e validateConfig — não exige `metadata`. Uma engine registrada sem esse campo é, portanto, um registro legal. Mas `canRunAtCompressionStage` lia `engine.metadata.executionStages` sem guarda, então essa engine legal derrubava o pipeline inteiro com `TypeError: Cannot read properties of undefined` em vez de falhar aberto, que é o contrato da compressão. Metadata ausente é o mesmo caso de "não declarou executionStages" e passa a cair no mesmo fallback documentado: só pre-translation. Isso destravava também `tests/unit/compression/pipeline-circuit-breaker.test.ts`, que registra uma engine de teste sem metadata e vinha 8/9 na base — agora 9/9. O teste novo torna o contrato explícito, em vez de deixá-lo dependendo de uma reprodução incidental noutro arquivo. Co-authored-by: Xiangzhe <bakryun0718@proton.me> Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
…time registry (R0.3) (cheaper-inference#10633) * test(sse): golden characterization of the executor map before the R0.3 registry refactor Freezes the 137-entry provider-id → executor mapping (class, provider identity, backing PROVIDERS config), the no-shared-instances invariant, and the getExecutor() dispatch rules (memoized DefaultExecutor fallback, cloud-agent guard cheaper-inference#6699, search-provider guard cheaper-inference#10274) as stable JSON snapshots. The upcoming ExecutorRegistry must keep both snapshots byte-identical. * refactor(sse): route executor lookup through ExecutorRegistry (R0.3) Adds open-sse/executors/registry.ts (Map-based registry mirroring translator/registry.ts): the built-in table in executors/index.ts stays declarative, every entry is registered at module load, and getExecutor()/hasSpecializedExecutor() resolve through the registry. DefaultExecutor fallback, its memoization, and the cloud-agent (cheaper-inference#6699) / search-provider (cheaper-inference#10274) guards are unchanged. Also fixes a latent lookup leak: the old object-literal lookup treated Object.prototype names (constructor, toString, ...) as specialized executors; the Map registry resolves them to the DefaultExecutor fallback like any unknown provider. Parity proof: executor-map golden (137 entries, byte-identical before/after), check:known-symbols green, 1018 tests across the 65 executor test files green. Docs: OPEN_SSE_ARCHITECTURE factory section corrected (it claimed generation from providerRegistry). Refs cheaper-inference#3501 * test(executors): regenerate ExecutorRegistry golden snapshots after release sync release/v3.8.50 sunset mimocode and added cloudflare-playground + jina-search since this PR's snapshots were captured; refresh the golden fixtures to match. --------- Co-authored-by: Xiangzhe <bakryun0718@proton.me> Co-authored-by: Markus Hartung <mail@hartmark.se> Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
…lidade com evidência (cheaper-inference#10647) * feat(compression): target-wire OmniGlyph stage and transport fidelity gate Roda o OmniGlyph depois da tradução para o wire real do provedor, em vez do corpo de origem. Um cliente OpenAI roteado para Claude deixava de comprimir com skip:source_format_not_claude porque o corpo ainda estava em formato OpenAI quando a engine era avaliada. - dispatch nativo por wire: Anthropic Messages, OpenAI Chat Completions e OpenAI Responses (input[] preservado, sem achatar para messages[]); - estágio target-wire pós-translateRequest, com guarda contra dupla compressão no caminho Claude→OpenAI; - preserveSystemPrompt do OmniRoute mapeado para compressSystem: false; - imageTransportPolicy: fidelidade de bytes/dimensões separada de supportsVision; só Anthropic/Claude tem recibo byte-preserving, o resto é fail-closed; - contagem de tokens de data URL PNG no wire OpenAI (marcador ;base64,); - README e i18n en/pt-BR com claims escopados ao caminho medido. * feat(compression): adota omniglyph 1.4.0 e tira o gate de modelo da env do host O 1.4.0 introduziu escopos de segurança e passou a resolvê-los dentro de isOmniGlyphSupportedModel() lendo process.env.OMNIGLYPH_PROFILE. Somado ao OMNIGLYPH_MODELS que já existia, duas variáveis do ambiente do host decidiam em silêncio o gate de TODO request do OmniRoute: passthrough desligaria a engine inteira e OMNIGLYPH_MODELS admitiria modelos sem recibo medido, enquanto a UI segue prometendo "Claude Fable 5 na rota direta medida". O adapter passa a usar isOmniGlyphSupportedModelForScope() com escopo explícito e fixa o escopo mais restrito como teto: a env só pode ESTREITAR a allowlist, nunca alargar. Os dois wires compartilham a mesma lista no pacote desde o 1.4.0, então uma checagem cobre Anthropic e GPT. - omniglyph ^1.3.1 -> ^1.4.0 (lock em 1.4.0); - testes de regressão para os dois caminhos de sequestro por env; - teste de contrato dos exports novos (escopo, perfis, accounting). O 1.4.0 também traz, sem mudança de código aqui: correção do glyph K que era lido como H, remoção do backtracking polinomial no secret-guard, overrides do pnpm em pnpm-workspace.yaml e as transitivas vulneráveis resolvidas. * feat(compression): expõe os perfis semânticos do omniglyph nos três wires O 1.4.0 trouxe perfis nomeados (coding-safe, balanced, aggressive, passthrough), mas só transformAnthropicMessages() os resolve sozinho: os transformadores OpenAI recebem TransformOptions cru e ignorariam o campo. Um perfil escolhido pelo operador valeria no wire Claude e sumiria no OpenAI. O adapter passa a mesclar o perfil com mergeCompressionProfileOptions() antes de chamar Chat Completions e Responses. O default segue aggressive — a política que os recibos publicados mediram. Medido nesta base: com coding-safe/balanced, uma sessão sem histórico acumulado para em below_min_chars e a engine não faz nada, porque os dois fixam minCompressChars no máximo e desligam system/tools/tool-results. Como a engine é opt-in, um default assim entregaria "ligado, 0% de ganho". O perfil é TETO, não piso: mergeCompressionProfileOptions não deixa um override do chamador reabrir uma lane lossy que o perfil fechou. Coberto por teste, por ser contra-intuitivo. Também fecha um caminho em que o OmniRoute violaria a própria política: o wire OpenAI do pacote não tem compressSystem — honra apenas compressTools, gptHistory, minCompressChars e reflow, e sempre troca a instrução por um ponteiro para a imagem. Com preserveSystemPrompt ligado, imagear assim queimaria o prefixo quente que a decisão cache-aware está protegendo, sem nada no corpo devolvido denunciando. A engine agora pula com skip:system_preservation_unsupported_on_wire. * feat(compression): contabilidade física do omniglyph com grau de evidência O adapter descartava o TransformInfo inteiro, então a UI mostrava um número de economia sem dizer de onde ele vinha — contagem do provider, estimativa ou só diferença de bytes. O 1.4.0 expõe normalizeAccounting(), que classifica essa evidência e resolve a semântica de cache por família: Anthropic reporta input, cache-create e cache-read em buckets DISJUNTOS, enquanto OpenAI e xAI reportam cached como SUBCONJUNTO do input. Somar à mão dá double-count silencioso. O novo omniglyphTelemetry.ts não filtra por denylist — MONTA um objeto novo, campo a campo, só com número e enum. TransformInfo mistura contadores inofensivos com material que não pode ser persistido: bytes PNG, imageSourceText(s), recoverable[].text, os sha8 de system/CLAUDE.md/primeira mensagem, nomes de tags observadas e o bloco env (cwd, branch, versões). Copiar o objeto inteiro transformaria telemetria de compressão em vazamento de prompt. O teste de negação prova que segredo, caminho do operador, texto do system e base64 não aparecem, e varre a allowlist exigindo que toda string seja de um enum conhecido. - provider threaded do chatCore e do bridge Codex WS até a engine; ausente vira `unknown`, que faz o upstream recusar adivinhar buckets de cache; - contabilidade propagada para o engineBreakdown do passo (o agregado do pipeline soma todas as engines e não serviria); - skip não emite contabilidade: zeros ali seriam indistinguíveis de "a engine nem rodou". * feat(compression): perfil do omniglyph configurável, persistido e documentado Fecha o caminho do operador: o perfil já existia no adapter, mas só como default de código. Agora atravessa schema Zod, normalizador do banco, API de settings e a página dedicada do engine. - OmniglyphConfig tipado + omniglyphConfigSchema (z.enum dos quatro perfis); - normalizeOmniglyphConfig: nome desconhecido vindo do storage cai para o default em vez de virar "roda com a política padrão"; - seletor na página do engine, com PATCH próprio — o perfil vive fora do mapa `engines`, e mandá-lo junto reescreveria o mapa inteiro (o store persiste o mapa como uma linha JSON só); - i18n en/pt-BR descrevendo o custo medido de cada perfil, não só o nome; - README e COMPRESSION_ENGINES.md com a regra do teto e o motivo de o default não ser o perfil mais seguro. Corrige de passagem um teste-irmão que ninguém via: o gate de transporte na UI deixou de dizer "direct Anthropic" quando os wires OpenAI nativos entraram, mas tests/unit/ui/omniglyphContextPage.test.tsx continuou afirmando a cópia antiga. O arquivo inteiro estava excluído do vitest.config.ts como "cheaper-inference#8618 pre-existing failure", então a quebra passou silenciosa. Com a asserção alinhada o arquivo fecha 3/3, e a exclusão sai — o próprio comentário mandava removê-la quando corrigida. A doc não nomeia OMNIGLYPH_MODELS: o gate de docs fabricadas está certo em apontar que o OmniRoute nunca lê essa env — quem lê é o pacote. * fix(i18n): paridade do locale vi com as chaves novas do perfil do omniglyph `tests/unit/i18n-vi-completeness.test.ts` exige paridade ESTRITA de chaves entre en e vi — diferente do ratchet `i18n:check-ui-coverage`, que passa com 80%. As 11 chaves do seletor de perfil entraram só em en e pt-BR, e o gate de cobertura seguiu verde, então a quebra só apareceu na matriz completa do CI. --------- Co-authored-by: Xiangzhe <bakryun0718@proton.me> Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
A single orphaned "<<<<<<< HEAD" line (no matching =======/>>>>>>> pair) leaked into release/v3.8.50 via PR cheaper-inference#10039's merge-conflict resolution during this session's serial-merge sweep. Repo-wide sweep confirms no other stray markers exist. Table structure verified intact before/after removal.
…heaper-inference#10537) * feat(providers): refresh Gemini Flash catalogs and pricing * fix(providers): refresh gemini-web Flash catalog * chore(providers): eliminate Gemini 3.5/3.6 Flash models * feat(providers): refresh Perplexity Web model mappings * feat(providers): refresh PromptQL and Notion catalogs * feat(providers): refresh KIE TinyCMS and Conol catalogs * feat(providers): refresh OpenCode Zen catalog * chore(providers): finish Gemini Flash cleanup * chore(providers): retire Google Imagen 4
- ENVIRONMENT.md: CLI_ALLOW_CONFIG_WRITES default is true (matches cliRuntime), CLI_QODER_BIN default is qodercli (also in .env.example), CLI_GEMINI_BIN is server-side detection only (omniroute run resolves from PATH) - CLI-TOOLS.md: catalog counts 26 code / 8 agents (adds the missing zcode row), setup targets without auto-discovery list Qwen (not Gemini; Gemini is launch-only), hostSetupCommand only for the six tools with a host recipe, global env block uses GOOGLE_GEMINI_BASE_URL at the root, mention omniroute run as the generic launcher - CLI-INTEGRATIONS.md: manifest aliases, per-target --model wiring (openai/ and omniroute/ prefixes, qwen hard-requires --model), run exit-code contract, gemini child-env scrub notes - CODEX-CLI-CONFIGURATION.md: document omniroute configure codex / run codex - SETUP_GUIDE.md + QUICK-START.md: surface the generic omniroute run launcher - ENVIRONMENT.md: disambiguate OMNIROUTE_SMOKE_API_KEY (canary) from the OMNIROUTE_SMOKE_* CLI smoke-harness variables
… bins, stale front-matter Counts (check:docs-counts STRICT, 8 drifts → 0): - 153 → 154 migrations in README.md, AGENTS.md and llm.txt (+ its 42 i18n mirrors, which must stay byte-identical to the root file) - 340 → 341 providers in README.md, AGENTS.md, llm.txt, the package.json description and the 4 SVG diagrams; PROVIDER_REFERENCE.md regenerated via gen:provider-reference (the new entry is the cloudflare-playground no-auth provider) Undocumented environment variables: - 13 CLI_*_BIN vars that exist in cliRuntime but were in neither ENVIRONMENT.md nor .env.example (kilo, opencode, hermes, forge, jcode, deepseek-tui, codewhale, smelt, pi, crush, omp, letta, windsurf — windsurf ships no default command) - OMNIROUTE_DEBUG and OMNIROUTE_HEALTHCHECK_PATH, read in code but absent from .env.example - CLI_CURSOR_BIN documents both fallbacks (agent, then cursor) Front-matter: bump the seven CLI/reference docs this effort touched from the stale 3.8.40/2026-06-28 stamp to the current release. Out of scope but blocking check:docs-all, fixed with evidence: ENVIRONMENT.md and .env.example still documented the Adobe Firefly CDP Chrome runtime removed in cheaper-inference#9255. Seven of its variables are read nowhere in the codebase and its source file no longer exists; surviving vars are repointed at adobeFireflyBrowserLogin.ts and CHROME_PATH at its real readers. The two Gemini CLI auth vars the run launcher scrubs from the child env are added to the fabricated-docs external-tool allowlist, next to the existing CODEX_HOME/COPILOT_PROVIDER_BASE_URL entries. npm run check:docs-all now exits 0 for the first time on this base.
…ss all 42 locales The translated CLI docs predated the relay-like CLI work: every locale still shipped the legacy Codex `config.yaml` quickstart (dropped from the English source when the generator moved to TOML), none mentioned the `omniroute run` launcher or the Gemini target, and CLI-INTEGRATIONS.md existed only in Polish. Regenerated through the project pipeline (npm run i18n:run) for the two guides the CLI effort changed: - docs/i18n/*/docs/reference/CLI-TOOLS.md — 42 locales updated; the obsolete YAML quickstart is gone from all of them (the remaining config.yaml mentions mirror the English legacy note and Continue's own config) - docs/i18n/*/docs/guides/CLI-INTEGRATIONS.md — 42 locales, 41 of them new files ENVIRONMENT.md is deliberately not included: at ~26 chunks per locale it exceeds the pipeline's 60s per-chunk timeout and fails after retries. It needs a raised OMNIROUTE_TRANSLATION_TIMEOUT_MS, which is a separate maintenance run. Verified: check:docs-all exits 0, doc-links reports no broken internal links, and spot-checks confirm technical identifiers, front-matter and language bars survive translation intact.
…rence#10674) (cheaper-inference#10691) `open-sse/services/combo.ts` imported "../../src/lib/localDb.js" — a .js suffix on a module that only exists as .ts. Turbopack resolved it by accident until the dependency-tree change in cheaper-inference#10647; after that the instrumentation hook died at boot with MODULE_NOT_FOUND, breaking `npm run dev` and the production build (60 consecutive red `Build App` runs on release/v3.8.50). Fixes the same latent pattern in src/lib/usage/usageLedger.ts, which survived only because it is an `import type` and is erased before resolution. Adds a guard rejecting relative .js specifiers across open-sse/ and src/. Package specifiers are untouched: publishing ESM as .js is legitimate there (e.g. @modelcontextprotocol/sdk), and only first-party relative imports are first-party TypeScript. Closes cheaper-inference#10674 Co-authored-by: Xiangzhe <bakryun0718@proton.me>
…-inference#10692) (cheaper-inference#10695) * fix(sse): import localDb through its real .ts extension (cheaper-inference#10674) `open-sse/services/combo.ts` imported "../../src/lib/localDb.js" — a .js suffix on a module that only exists as .ts. Turbopack resolved it by accident until the dependency-tree change in cheaper-inference#10647; after that the instrumentation hook died at boot with MODULE_NOT_FOUND, breaking `npm run dev` and the production build (60 consecutive red `Build App` runs on release/v3.8.50). Fixes the same latent pattern in src/lib/usage/usageLedger.ts, which survived only because it is an `import type` and is erased before resolution. Adds a guard rejecting relative .js specifiers across open-sse/ and src/. Package specifiers are untouched: publishing ESM as .js is legitimate there (e.g. @modelcontextprotocol/sdk), and only first-party relative imports are first-party TypeScript. Closes cheaper-inference#10674 * fix(config): keep the SQLite driver out of the client bundle (cheaper-inference#10692) The `aihorde` entry in IMAGE_PROVIDERS imported its live-catalog service directly. IMAGE_PROVIDERS is reachable from "use client" dashboard pages — they read its KEYS to derive which providers support which media kind — so that import dragged aihordeImageCatalog → safeOutboundFetch → proxyFetch → featureFlags → db/core → sqljsAdapter into the browser graph. The build then tried to bundle fs/net/tls for the browser and failed with 28 Module not found errors, leaving `Build App` red for 60 consecutive runs and no artifact buildable from the branch. A dynamic import() does not fix this: the bundler still has to make the module browser-loadable. The dependency is inverted instead — the registry entry knows only a pure registration module, and the server-only service registers itself on import, which every server path needing live models already does. With nothing registered the getter yields [], exactly what the live catalog returned before its first poll. Validated by a full `npm run build:release`: 0 Module not found, artifact produced. Closes cheaper-inference#10692 --------- Co-authored-by: Xiangzhe <bakryun0718@proton.me>
…le (cheaper-inference#10698) `check:agent-skills-sync` fails on the release tip (22b89a2) with no local changes: the generator reports omni-inference and cli-resilience as stale. The `quota status` / `quota preview` / `quota ensure` subcommands were added to the CLI catalog without re-running the generator, so the committed SKILL.md files no longer match it. Output of `generate-agent-skills.mjs --apply`, additive only — no hand edits. Co-authored-by: Xiangzhe <bakryun0718@proton.me>
…me one (cheaper-inference#10697) * docs(guides): DASHBOARD_ALLOW_EMBED is build-time, not a runtime flag The VS Code guide told operators to "start OmniRoute with DASHBOARD_ALLOW_EMBED=vscode". Next.js compiles headers() into the route manifest, so next.config.mjs reads the variable while the bundle is built — exporting it in front of an already-built server does nothing, which is the exact trap anyone on `npm install -g omniroute` or the Docker image falls into. Documents the build-time nature, the working from-source recipe, and which install paths can enable it at all. ENVIRONMENT.md and .env.example already said build-time; this aligns the how-to with them and with the extension's own fallback message. * docs(changelog): announce the VS Code Copilot Chat integration The release notes only mentioned OmniCopilot in passing, inside the DASHBOARD_ALLOW_EMBED bullet — a reader would never learn the extension exists. Adds the fragment that says it plainly, with both store links. --------- Co-authored-by: Xiangzhe <bakryun0718@proton.me>
… code (cheaper-inference#10699) `npm install -g <tarball>` on the .17 gateway writes the whole package and then fails renaming the old tree into its staging directory (ENOTEMPTY, exit 217). The canary read that non-zero exit as "install failed", aborted before the restart, and discarded npm's stderr through execFileSync throwing — so on 2026-08-18 the deploy stopped half-done twice, each time leaving new files on disk under an old running process, with no clue in the log. The exit code is not trustworthy in either direction: the 2026-08-14 outage installed a package built from the wrong branch and exited 0. classifyInstallOutcome() therefore decides on the BUILD_SHA read back from the installed package, and fails closed when it is absent or does not match — a zero exit with the wrong artifact is still a failure. npm reuses the same staging directory name, so the orphan blocks the next install with the same error; orphanStagingDirFromStderr() surfaces the exact path. It is not removed automatically — that is an rm -rf under /usr/lib, not something a deploy script should decide on its own. Refs cheaper-inference#10429 Co-authored-by: Xiangzhe <bakryun0718@proton.me>
…y point (cheaper-inference#10692) (cheaper-inference#10700) The guard shipped with cheaper-inference#10695 watched two hand-picked modules. It now walks the static import graph from all 753 "use client" files in src/ (plus the two originally pinned entries), so the invariant is verified across the repo instead of where someone remembered to look. Full sweep runs in ~750ms. Two exclusions make that practical: - `import type` is not an edge — TypeScript erases it before the bundler sees it. Counting type imports turns 3 real findings into 29; a guard that cries wolf gets switched off. - Dynamic `import()` is still not followed. It does not break a bundle edge (that was tried for cheaper-inference#10692 and failed) but it does move the module into a chunk the browser fetches on demand, which is a legitimate boundary. The widened sweep immediately found what the narrow one could not: five value-form imports of `db/batches` / `db/files` across three files under dashboard/batch, each reaching db/core → the SQLite driver. All five bind only interfaces (BatchRecord, FileRecord) used in type position, so the compiler was eliding them and the build stayed green — the same latent shape as cheaper-inference#10692 before cheaper-inference#10647 removed the toolchain's tolerance. Marking them `import type` makes the elision explicit instead of incidental. Refs cheaper-inference#10692 Co-authored-by: Xiangzhe <bakryun0718@proton.me>
Co-authored-by: Cursor <cursoragent@cursor.com>
…stays CJS (cheaper-inference#10936) Validado no worktree combinado: typecheck:core, changelog-integrity, complexity, cognitive-complexity, file-size, lint e teste focado (colocate-standalone-esm-scope) todos verdes. Fix real, correção de regressão introduzida por cheaper-inference#10836 (server.js CommonJS quebrando com type:module reintroduzido). CI vermelho é o base-red já rastreado em cheaper-inference#9985. Obrigado!
…cards (cheaper-inference#10939) Validado no worktree combinado: typecheck:core, changelog-integrity, complexity, cognitive-complexity, file-size, lint todos verdes. Fix de UX real e bem documentado (cards de Account Split mostravam UUID cru em vez de email/nome da conta). CI vermelho é o base-red já rastreado em cheaper-inference#9985. Obrigado!
…letions, 1.5/2.0/2.5-flash) (cheaper-inference#10942) Validado no worktree combinado: typecheck:core, changelog-integrity, complexity, cognitive-complexity, file-size, lint e teste focado (agnes-provider, 11/11) todos verdes. Atualização de catálogo/dados alinhada à documentação oficial vigente. CI vermelho é o base-red já rastreado em cheaper-inference#9985. Obrigado!
…ted 429 (cheaper-inference#10920) Validado no worktree combinado: typecheck:core, changelog-integrity, complexity, cognitive-complexity, file-size, lint e testes focados (egress-ip-lock-10880, egress-lock-allowlist-10880, proxy-logs-egress-lookup-10880) todos verdes. Otimização de resiliência bem fundamentada (cooldown de conexões compartilhando IP de egress após 429 do allowlist). CI vermelho é o base-red já rastreado em cheaper-inference#9985. Obrigado!
… and 401 recovery (cheaper-inference#10944) Validado no worktree combinado: typecheck:core, changelog-integrity, complexity, cognitive-complexity, file-size, lint e 52 testes focados (kimi-jwt, kimi-credentials-extract, kimi-token-refresh, kimi-web-401-retry, provider-refresh-token-route, token-health-check-kimi) todos verdes. Implementação sólida e bem testada de ciclo de vida de token para Kimi Web. CI vermelho é o base-red já rastreado em cheaper-inference#9985. Obrigado!
…ed factory + wrappers (reopen) (cheaper-inference#10910) Validado no worktree combinado: typecheck:core (confirma que TODOS os símbolos exportados foram preservados — TlsClientHangError, TlsClientUnavailableError, looksLikeSse, isCloudflareChallenge continuam re-exportados em cada wrapper), changelog-integrity, complexity, cognitive-complexity, file-size, lint e testes focados via vitest (chatgptTlsClient, grokTlsClient) + node:test (chatgpt-web-handoff-resume, lmarena-provider, claude-web-live-alignment, chatgpt-web, claude-web-slow-first-byte, grok-web-cloudflare-classification, grok-web) todos verdes. Refactor de consolidação bem executado: -3379 linhas líquidas, zero mudança de comportamento, 6 clientes TLS quase idênticos viram uma factory + wrappers finos. CI vermelho é o base-red já rastreado em cheaper-inference#9985. Obrigado!
…er-inference#10888) Validado no worktree combinado: typecheck:core, changelog-integrity, file-size, lint todos verdes. Correção real dos 3 alertas CodeQL (HMAC em vez de hash bruto, URL parsing em vez de substring, dismiss documentado). CI vermelho é o base-red já rastreado em cheaper-inference#9985.
…r-inference#10727) (cheaper-inference#10916) Validado no worktree combinado: typecheck:core, changelog-integrity, file-size, lint e 2/2 testes focados passando. Diagnóstico bem investigado do timeout WS do Meta AI (readyState exposto no erro). CI vermelho é o base-red já rastreado em cheaper-inference#9985.
cheaper-inference#7592) (cheaper-inference#10921) Validado no worktree combinado: typecheck:core, changelog-integrity, file-size, lint e 7/7 testes focados passando. Investigação completa com verificação de ancestralidade via merge-base antes de fechar a issue original. CI vermelho é o base-red já rastreado em cheaper-inference#9985.
…ode_modules (cheaper-inference#7346) (cheaper-inference#10924) Validado no worktree combinado: typecheck:core, changelog-integrity, file-size, lint e teste focado passando. Root cause bem documentado (distDir customizado gera dois node_modules externalizados). CI vermelho é o base-red já rastreado em cheaper-inference#9985.
…per-inference#5483 regression) (cheaper-inference#10946) Validado no worktree combinado: typecheck:core, changelog-integrity, complexity, cognitive-complexity, file-size, lint e testes focados todos verdes. Regressão real corrigida (apiType=chat agora é honrado em vez de forçado para /responses). CI vermelho é o base-red já rastreado em cheaper-inference#9985.
… on (cheaper-inference#10945) (cheaper-inference#10951) Validado no worktree combinado: mesmos gates + testes focados verdes. Bug real e bem reproduzido (least-used nunca gravava lastUsedAt, sempre a mesma conexão escolhida). CI vermelho é o base-red já rastreado em cheaper-inference#9985.
Validado no worktree combinado: mesmos gates + teste focado verde. Preserva effort_tiers declarados pelo provider (Kimi k3) em vez de substituir pela lista canônica genérica. CI vermelho é o base-red já rastreado em cheaper-inference#9985.
… effort immediately (cheaper-inference#10957) Validado no worktree combinado: mesmos gates + testes focados verdes. Fix bem medido (context window real vs anunciado divergindo por até 24h para modelos sincronizados fora do ciclo). CI vermelho é o base-red já rastreado em cheaper-inference#9985.
… 404ing (cheaper-inference#10947) (cheaper-inference#10958) Validado no worktree combinado: mesmos gates + teste focado verde. Root cause medido na release publicada v3.8.49 (nome de artefato NSIS com espaço vs. hífen no manifest). CI vermelho é o base-red já rastreado em cheaper-inference#9985.
…inference#10926) Validado no worktree combinado: mesmos gates + testes focados verdes. Extensão opt-in bem desenhada sobre cheaper-inference#10909 (dimensão de uso real via call_logs). CI vermelho é o base-red já rastreado em cheaper-inference#9985.
…heaper-inference#10855) Tirado de Draft e validado no worktree combinado: mesmos gates verdes (mudança de UI/i18n sem cobertura automatizada dedicada, mas de baixo risco — só warnings e ocultação condicional de UI). Fix de UX real (cheaper-inference#10794 — 401 confuso ao pular senha no onboarding). CI vermelho é o base-red já rastreado em cheaper-inference#9985.
cheaper-inference#10948) Validado no worktree combinado: mesmos gates + 36 testes focados verdes. Feature bem documentada e testada (tool calling completo para copilot-m365-web via SignalR, incluindo keepalives e detecção de erro silencioso). CI vermelho é o base-red já rastreado em cheaper-inference#9985.
) Validado no worktree combinado: typecheck:core, changelog-integrity, complexity, cognitive-complexity, file-size, lint e teste focado (vps-compose) todos verdes. Bundle Docker aditivo, seguro-por-padrão (loopback, secrets obrigatórios, imagem pinada), bem documentado. CI vermelho é o base-red já rastreado em cheaper-inference#9985.
…est) (cheaper-inference#9985) (cheaper-inference#10778) Reconciliado com a release e revalidado: typecheck:core, check:dead-code (410 real vs 416 na baseline resolvida — a PR mede corretamente sua própria melhoria), lint (adicionei 1 entrada de suppression para GrokBuildToolCard.tsx, arquivo mergeado depois que esta branch nasceu, 2 violações novas de react-hooks/set-state-in-effect não capturadas pela contagem original), complexity, cognitive-complexity, file-size, changelog-integrity e 11/11 testes do tieredRotation todos verdes. Drena 3 dos 8 hard failures do cheaper-inference#9985. Obrigado!
c130f2a
into
cheaper-inference:release/v3.8.50
…d empty-turn errors (cheaper-inference#9909) ⭐5 — Cursor PKCE login com Bearer quota, auto router e empty-turn errors. Feature completa e testada (11 arquivos de teste, 133 testes focados, todos verdes). **Validação (worktree combinado `.claude/worktrees/fix-9909`, board sobre `origin/release/v3.8.50`):** - 3 conflitos reais resolvidos: `config/quality/eslint-suppressions.json` (aditivo), `open-sse/config/providers/registry/cursor/index.ts` (dedup de 208 entradas de catálogo, 0 IDs duplicados verificado), `open-sse/executors/cursor.ts` (imports aditivos). - `npm run typecheck:core`: limpo. - `check-changelog-integrity`, `check-file-size`, `check-complexity` (2615/2774), `check-cognitive-complexity` (1175/1223), `check-dead-code` (410/416): todos OK. - `check-public-creds`: 1 entrada obsoleta pré-existente na allowlist (`copilot-m365-web.ts:330`), já presente no tip da release — não é desta PR. - `npm run lint`: 0 errors (5 warnings pré-existentes). - Testes focados (`cursor-agent-cli-version`, `cursor-available-models`, `cursor-catalog-combo-compat`, `cursor-errors-classify`, `cursor-login-pkce`, `cursor-model-effort-suffix-7289`, `cursor-streaming`, `cursor-token-extractor`, `cursor-token-refresh-wiring`, `cursor-usage-fetcher`, `empty-stream-no-content-8649`): 133/133 verdes. - Corrigido durante a validação: 1 teste novo da própria PR (`cursor-model-effort-suffix-7289.test.ts`, "splits effort off legacy grok- ids") colidia com `CURSOR_MODEL_ALIASES` já mesclado na release (mapeia `grok-4.5-high` → `cursor-grok-4.5-high` antes do fallback legado rodar); ajustado para usar um id não-aliasado (`grok-3-high`) que de fato exercita o fallback — commit `68b58ed`. Obrigado pela contribuição, @yansigit — feature robusta com boa cobertura de testes.
Summary
cursor-agentimport.api2.cursor.sh(GetCurrentPeriodUsage→/api/usage/summary→/auth/usage) with cookie dashboard as last fallback.autoplus OpenCodex router modesauto-cost/auto-balance/auto-intelligence(wiredefault+optimizationModelParameter); live-catalog reconciliation allowlists them.NGHTTP2_CANCELafter tool suspend finishes cleanly.docs/providers/CURSOR-DOCKER.md.release/v3.8.50tip is red — do not chase inherited failures in this PR).Related Issues
Validation
npm run lint(changed files + pre-commit lint-staged)origin/release/v3.8.50; focused checks rerun afterwardFocused commands
Focused Cursor suites: pass.
typecheck:core: pass. Provider-consistency: pass.Tests Added Or Updated
tests/unit/cursor-login-pkce.test.ts(new)tests/unit/cursor-token-refresh-wiring.test.ts(new)tests/unit/cursor-available-models.test.ts(new)tests/unit/cursor-errors-classify.test.ts(new)tests/unit/cursor-usage-fetcher.test.ts(Bearer chain)tests/unit/cursor-agent-cli-version.test.ts(installer cache)tests/unit/cursor-model-effort-suffix-7289.test.ts/oauth-cursor-auto-import.test.tsCoverage Notes
:20129checks (Provider Limits UI,cu/auto, premium empty-turn wording) remain useful on a depleted Cursor account but are not merge blockers.Reviewer Notes
GetServerConfigdynamic agent host, full pre-commit transport-retry rewrite, replacing fix(providers): Cursor provider — pinned Claude/GPT models return an empty turn (effort suffix sent inside model_id instead of a separate ModelParameter) #7289 ModelParameter effort with flat wire ids./api/oauth/(remote-reachable by design); onlycursor/auto-importremains LOCAL_ONLY.CursorAuthModalexhaustive-deps suppression that no longer applied.Made with Cursor