Repository navigation
Conversation
…namically ANTHROPIC_MODEL and ANTHROPIC_SMALL_FAST_MODEL were missing from defaultModels, so they never got written to ~/.claude/settings.json on Apply. Add both entries with dynamic defaults sourced from REGISTRY.claude.models via getClaudeCodeDefaultModels(). Also fix handleApplySettings to fall back to model.defaultValue when modelMappings[alias] is empty.
|
Important Review skippedToo many files! This PR contains 300 files, which is 150 over the limit of 150. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (300)
You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces significant updates to OmniRoute, including MCP server integration, A2A protocol support, and a major overhaul of the documentation and environment configuration. Key feedback points out critical errors in the Dockerfile base image and .npmignore exclusions that would break the production build and native dependencies. Additionally, improvements are suggested for Node.js version compatibility logic, environment variable handling in the CLI, and more robust process spawning.
| @@ -1,13 +1,20 @@ | |||
| FROM node:22-bookworm-slim AS builder | |||
| FROM node:24.14.1-trixie-slim AS builder | |||
There was a problem hiding this comment.
The Dockerfile specifies node:24.14.1-trixie-slim as the base image. However, Node.js version 24 does not exist yet (the current stable versions are 20, 22, and 23). Additionally, trixie is the codename for Debian 13, which is currently in the testing phase and not recommended for production environments. This will cause the build to fail as the image cannot be found on Docker Hub.
|
|
||
| # Build artifacts (pre-built goes inside app/) | ||
| .next/ | ||
| node_modules/ |
There was a problem hiding this comment.
The global exclusion of node_modules/ in .npmignore will also exclude app/node_modules/. Since the project uses a Next.js standalone build located in app/, and bin/omniroute.mjs (line 199) explicitly looks for the better-sqlite3 native binary inside app/node_modules/, the published package will be broken as it will lack its required native dependencies.
| export const RECOMMENDED_NODE_VERSION = "24.14.1"; | ||
| export const SUPPORTED_NODE_RANGE = ">=20.20.2 <21 || >=22.22.2 <23 || >=24.0.0 <25"; |
There was a problem hiding this comment.
The RECOMMENDED_NODE_VERSION is set to 24.14.1 and the SUPPORTED_NODE_RANGE includes 24.x. As Node.js 24 has not been released yet, these values are incorrect and will lead to misleading runtime warnings or incorrect compatibility checks for users.
| export const RECOMMENDED_NODE_VERSION = "24.14.1"; | |
| export const SUPPORTED_NODE_RANGE = ">=20.20.2 <21 || >=22.22.2 <23 || >=24.0.0 <25"; | |
| export const RECOMMENDED_NODE_VERSION = "22.14.1"; | |
| export const SUPPORTED_NODE_RANGE = ">=20.20.2 <21 || >=22.22.2 <23"; |
| API_PORT: String(apiPort), | ||
| HOSTNAME: "0.0.0.0", | ||
| NODE_ENV: "production", | ||
| NODE_OPTIONS: `--max-old-space-size=${memoryLimit}`, |
There was a problem hiding this comment.
The NODE_OPTIONS environment variable is being overwritten with only the --max-old-space-size flag. This will discard any existing NODE_OPTIONS set by the user (e.g., for debugging or custom loaders). It is better to append to the existing variable.
NODE_OPTIONS: `${process.env.NODE_OPTIONS || ""} --max-old-space-size=${memoryLimit}`.trim(),| }; | ||
|
|
||
| const server = spawn("node", [serverJs], { | ||
| const server = spawn("node", [`--max-old-space-size=${memoryLimit}`, serverJs], { |
There was a problem hiding this comment.
…n loads stop leaking temp files (cheaper-inference#8749) * fix(plugins): delete the host script synchronously and stop two tests leaking child processes Three related leaks in the plugin child-process lifecycle, found while tracing 27 node processes on a developer machine. 1. loader.ts removed the generated omniroute-plugin-host-*.mjs with a fire-and-forget `rm(...).catch(() => {})`. That unlink loses the race against process exit: test:unit runs with --test-force-exit, which tears the process down before the promise settles, so every plugin load leaked one temp .mjs into TMPDIR. Measured at 6 files per full-suite run, 40 accumulated over a handful of local runs. rmSync closes the race; the throw stays swallowed because an exception raised from a child "exit" handler would take the server down, and a leftover temp script would not. 2. plugins-manager-lifecycle.test.ts "activates an installed plugin" called activate() -- which spawns the plugin's child process -- but never deactivate(). deactivate() is the only path that reaches the loader's cleanup(), so the child outlived the test and its IPC channel kept the test process's event loop alive. 3. plugins-manager-restart-reload-7806.test.ts simulateRestart() deleted the entry from loadedPlugins without calling cleanup(), dropping the only handle that can kill child #1. The reload then spawned child #2, and the finally block's deactivate() could reach only child #2 -- one dangling child per test. A real restart takes the whole process tree down, so calling cleanup() here is both the faithful simulation and the fix. Combined effect: a run without --test-force-exit deadlocks. The test process cannot exit while its child holds the IPC channel open, and the child waits for messages that never come. Observed as three plugin hosts alive for 4h51m under a runner that never finished. Validation (Hard Rule cheaper-inference#18, TDD): the new plugins-loader.test.ts case fails against the old async unlink ("must delete the host script synchronously, not on a later tick") and passes with rmSync. It redirects TMPDIR/TEMP/TMP to a private directory before counting, because test:unit runs at --test-concurrency=20 and a concurrent file's host scripts would otherwise land in the counted directory and flake the assertion. After: 19/19 pass across the three files, 0 temp scripts created, 0 orphan processes. tests/unit/build/** 334/334; typecheck:core and eslint clean. * docs(changelog): add fragment for plugin host script sync delete
…routing (cheaper-inference#9195) Three linked bugs prevented the Custom Models 'Vision capable' toggle from affecting Combo routing, causing 400 capability_mismatch on image requests sent through Combos targeting a custom vision model. Bug #1 (catalog, dead guard): modelType === 'chat' was always false for chat models because modelType was only assigned 'embedding', 'rerank', 'image', or 'audio'. Changed the guard to !modelType || modelType === 'chat' so getCustomVisionCapabilityFields() fires for custom chat models. Bug #2 (catalog, synced-first ordering): When a model appeared in both syncedAvailableModels (from discovery) and customModels, the custom row was skipped entirely, losing the vision override. Now merge vision fields into the existing synced entry when the custom model has an explicit supportsVision boolean. Bug #3 (routing capabilities): getResolvedModelCapabilities() / resolveVisionCapability() had no path to consult the customModels supportsVision flag. Added a sync DB lookup helper and a new customVisionOverride parameter so the dashboard toggle affects Combo routing. Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
…n package (cheaper-inference#8299) * 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 cheaper-inference#4: Add comment clarifying async __resetBrowserPoolMetricsForTest vs upstream sync Issue cheaper-inference#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>
…erence#9485) * feat(providers): add DeepSeek V4 thinking effort aliases * docs(changelog): add DeepSeek effort alias entry * fix(catalog): scope effort-tier fallback to declared models and harden resolver Addresses reviewer findings on cheaper-inference#9485: - CRITICAL #1: catalog no longer synthesizes unresolvable effort aliases for static reasoning models without declared tiers (cheaperinference, cline, etc.) - CRITICAL #2: tiered static models survive synced-coverage suppression so normal installs with synced DeepSeek base models still expose aliases - WARNING #3: registry suffix resolution short-circuits when the raw id matches a direct custom or synced model, preserving custom apiFormat/targetFormat - WARNING cheaper-inference#4: empty synced effort array no longer erases the registry fallback - WARNING cheaper-inference#5: isFlash check is robust to suffixed/prefixed model ids - Added regression tests for blast radius, custom-model shadowing, none-path, and suffixed isFlash * fix(combos): expose static registry effort tiers in Combo Builder (cheaper-inference#9485) Static provider registry models (e.g. DeepSeek V4 Flash/Pro) declare supportedThinkingEfforts, but buildModelOptions() only ran appendSyncedEffortVariants() over DB-synced rows. Synced metadata for a DeepSeek connection can omit supportedThinkingEfforts, so the catalog/ Playground surfaced the declared aliases while the Combo Builder picker showed only the bare base ids. Feed builtInModels with declared effort tiers through the same appendSyncedEffortVariants() utility used for synced rows, inheriting the base entry's contextLength/outputTokenLimit/supportedEndpoints/ supportsThinking and preserving its source. DeepSeek is not skipped by shouldExposeSyncedEffortVariants(), so Flash (none/low/high/max) and Pro (none/high/max) aliases now appear in the Combo Builder for any connection whose synced rows omit effort metadata. Regression test seeds a DeepSeek connection with effort-less synced rows and asserts the exact alias sets, source preservation, and metadata inheritance.
…e#9584) * feat(providers): add Zylo UnoRouter and Poolside registries * feat(providers): integrate audited free-tier gateways * feat: add wave2 free-tier provider registries * feat(providers): add Mixlayer Speka and TokenReply registries * feat: add wave 2 free-tier provider registries * fix: align meganova provider slug * feat(providers): integrate wave2 free-tier gateways * feat(providers): add Wave 3-A free-tier registries * feat(providers): add HelyxAI Auriko and Poixe registries * feat(providers): add Naga AI and Chat Oripe registries * feat(providers): integrate wave3 free-tier gateways * feat(providers): add FreeInference registry * feat(providers): add Free.ai registry * feat(providers): integrate wave4 free-tier gateways * feat: add RTL layout compatibility CSS (fixes #7680) (#7987) Co-authored-by: Austin Liu <austinliu@Austins-MacBook-Air-3.local> * [v3.8.50] feat(ci): extend i18n glossary-consistency gate to ko (#8244) * fix(dashboard): correct machine-translated Korean UI strings in ko.json Fix 527 mistranslated values in the Korean locale, all verified against the en.json source: - Restore protected product/protocol names garbled by machine translation (응록→ngrok, 인류/인류학→Anthropic, 쌍둥이자리→Gemini, 반중력→Antigravity, 꼬리비늘 깔때기→Tailscale Funnel, 진공→VACUUM, 우편번호→ZIP) - Fix wrong-sense homonym translations (달리기→실행 중 for Running, 장애인→비활성화됨 for Disabled, 열쇠→키 for Key, 안타→적중 for Hits, 유물→아티팩트 for Artifacts, 건강검진→상태 확인 for Healthcheck) - Repair translated identifiers that broke literal values (양말5→socks5, 볼록-세션-id→convex-session-id, 채팅/완료→chat/completions, 메시지/보내기→message/send JSON-RPC methods) - Replace key-name dumps shipped as values ("Table Name", "Overview Title", "Cli Tools Redirect Title" etc.) with real Korean translations - Unify ngrok casing (Ngrok→ngrok) and trailing punctuation with the English source; align terminology across fixes (공급자, 폴백, 사용자 정의) All {placeholder} tokens, markdown, and protected terms preserved verbatim; i18n UI coverage and ko validation gates pass. * feat(ci): extend i18n glossary-consistency gate to ko Follow-up to #8224 (ko.json mistranslation cleanup): the glossary gate only checked zh-CN, leaving the Korean catalog unguarded against the next machine-translation run reintroducing the garbage it fixed. - Add scripts/i18n/glossary/ko.json: 9 canonical concepts (provider, fallback, running/disabled states, key, export, healthcheck, port, artifacts) plus protectedTermMistranslations for 10 verified garbled renderings (응록→ngrok, 인류→Anthropic, 쌍둥이자리→Gemini, 반중력→Antigravity, 꼬리비늘→Tailscale, 진공→VACUUM, 양말5→socks5, 우편번호→ZIP, 클로드→Claude, 옴니루트→OmniRoute) - Extend check-glossary-consistency.mjs to merge per-locale protectedTermMistranslations from the glossary file with the legacy zh-CN KNOWN_MISTRANSLATIONS map (behavior for zh-CN unchanged) - Add ngrok/Anthropic/Claude/Gemini/Antigravity/Tailscale/VACUUM/ socks5/ZIP to protected-terms.json - Wire --locale=ko into the i18n-glossary CI job and add the i18n:check-glossary:ko npm script - Tests: merge semantics (3 new unit tests), #8224 regression guards for src + bin/cli ko catalogs, and real-file pass assertions for ko Every enforced synonym/mistranslation was verified to have zero occurrences in both real ko catalogs; collision-prone candidates (안타 ⊂ 안타깝게도, 배우 ⊂ 배우기) were deliberately excluded. * test(tail): retire stale i18n __MISSING__ repro + fix qianfan website URL (#8263) Base-red slice 6, rebased onto the advanced release/v3.8.49 (91fd5f9). The oauth grok-cli #7610 guard was already fixed on the base by #8027 (it reads the warning from grokCliAuthJson.ts) — dropped from this slice to avoid a conflicting duplicate. Remaining two, still red on the current base: - i18n #7258: the "focused repro" asserted zh-TW.json STILL carries raw __MISSING__: placeholders. That backlog was filled (the "no locale has a raw __MISSING__: leaf" invariant is the durable guard); retired the now-inverted repro. - qianfan: Baidu renamed the product page (product/wenxinworkshop -> product-s/ qianfan_home); updated the expected website URL. Validated (clean env): i18n 4/0, qianfan 5/0; oauth-modal-grok 2/0 already green on base. Co-authored-by: Probe Test <probe@example.com> * [v3.8.50] feat(ui): add global model search to Combo builder (#8285) * Feat: Busca Global de Modelos no Combo Builder * Fix: assembleStandalone src and dest equality check on Windows * fix(ui): i18n global model search + drop pnpm-lock + extract search panel - Drop pnpm-lock.yaml (repo is npm-workspaces; package-lock.json is canonical). - i18n: replace hardcoded Portuguese strings in the new global model search UI (Combo Builder) with getI18nOrFallback()/t() EN-fallback calls; add the 10 new keys (builderModeStep, builderModeGlobal, builderGlobal*) to en.json and propagate __MISSING__ placeholders to all 42 locales. - Extract the mode-toggle + global-search panel JSX into a new GlobalModelSearchPanel component, and the allGlobalModels/ filteredGlobalModels/add-step/add-all logic into pure, unit-tested helpers (buildGlobalModelList, filterGlobalModelList, addGlobalModelStep, addAllGlobalSearchMatches) in src/lib/combos/builderDraft.ts, keeping combos/page.tsx under its frozen file-size budget. - Revert the unrelated local-tooling .source/dynamic.ts one-liner to match origin/release/v3.8.49. - Add unit tests for the new builderDraft helpers. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Gleisson de Jesus Santos <T034183@embasanet.ba.gov.br> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: ikelvingo <im.kelvinwong@gmail.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> * [v3.8.50] feat: extract CloakBrowser/browser-pool into optional plugin package (#8299) * 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> * [v3.8.50] fix(adobe-firefly): live x-arp-session-id / Arkose wire (stop HTTP 408) (#8571) * feat(adobe-firefly): reference image attach + /v1/images/edits (follow-up #8006) Upload source images to Firefly storage (POST /v2/storage/image) and attach them as referenceBlobs on generate-async, matching live firefly.adobe.com captures (usage:general for nano multi-ref; usage:subject for gpt-image). Also wire built-in adobe-firefly through OpenAI-compatible POST /v1/images/edits (multipart or JSON data URLs, up to 4 refs) so Media edit-with-references and Open WebUI image-edit hit the same path as image2image generate. Unit suite: tests/unit/adobe-firefly.test.ts 41/41. * test(api): add route-level coverage for Adobe Firefly /v1/images/edits + fix typecheck/file-size drift Covers the referenceBlobs upload path, the 4-reference cap error, and the credentials/rate-limit branches added to the /v1/images/edits route for adobe-firefly (#8510). Also fixes a Buffer/BodyInit typecheck mismatch in uploadAdobeFireflyImage and corrects the adobeFireflyClient.ts file-size baseline entry to match the gate's actual LOC count (it counts the trailing newline, so the frozen value is 2317, not 2316), plus a testFrozen entry for adobe-firefly.test.ts's own +159 line growth from this PR. Moves the handleAdobeFireflyImageGeneration re-export out of the middle of the import block in imageGeneration.ts for readability. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * [v3.8.50] fix(adobe-firefly): durable session, Chrome recovery, browser sign-in (#8578) * fix(adobe-firefly): durable session, off-screen Chrome recovery, browser sign-in Rebuild x-arp-session-id from Cookie pieces (sid/ark/forter) so aux_sid is never sent as ARP. Sticky ARP + submit spacing reduce mid-batch colligo 408 thrash. Add optional managed Chrome warm (off-screen headed by default; Forter rejects headless) and POST /api/providers/{id}/login browser sign-in that returns JWT+Cookie after a fresh SSO. Visible sign-in resets off-screen window placement and clears prior Adobe session when adding another account. * fix(adobe-firefly): cast Node Buffer to ArrayBuffer and harden chrome runtime null close Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(docs): sync docs-counts gate and env var contract for adobe-firefly Update executor/OAuth-provider counts in ARCHITECTURE.md and CODEBASE_DOCUMENTATION.md to match the real code (89 executors, 21 OAuth providers), and document the Adobe Firefly Chrome-driven session-refresh env vars in .env.example and ENVIRONMENT.md so the env/docs contract tests pass. Co-authored-by: artickc <artickc@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: artickc <artickc@users.noreply.github.com> * fix(github): honor per-model targetFormat override for Copilot custom models (#8713) GithubExecutor.buildUrl() only consulted the static PROVIDER_MODELS registry via getModelTargetFormat("gh", model), so a custom Copilot model (e.g. gpt-5.6-terra/gpt-5.6-luna) with its dashboard "Target Format" set to OpenAI Responses API always still routed to /chat/completions and got rejected upstream with "model ... is not accessible via the /chat/completions endpoint" — the setting had no effect on real routing. chatCore already resolves the correct per-request targetFormat (including the custom-model override) via resolveChatCoreTargetFormat(), but that value was never threaded past chatCore into the executor's own URL-building decision. Mirrors the zai/glm-coding-apikey fix (#7364) for the identical class of bug: chatCore/executionCredentials.ts now surfaces the resolved override onto providerSpecificData.targetFormat when it resolves to openai-responses for the github provider, and GithubExecutor.buildUrl() prefers that value over the static registry lookup when present. Verified: 6 new regression tests plus all 95 pre-existing github/executor tests green. Co-authored-by: Wital <wital@example.com> * fix(test): revive orphaned vitest tests and fix CI routing (#8718) * [v3.8.50] fix(api): serve stale model catalog during refresh (#8728) * fix(api): make model catalog refresh response-safe * fix(api): invalidate model catalog mutation paths * fix(db): preserve aliases backup import after catalog rebase --------- Co-authored-by: Erick Kinnee <erick@ekinnee.dev> * fix(antigravity): quota-aware account selection and projectId persistence (#8891) * fix(antigravity): per-model quota + 30min credits_exhausted reprobe - accountFallback.ts: hasPerModelQuota() now treats antigravity/agy as per-model quota. A single-model 429 no longer cascades to all models in the provider. - connectionRecovery.ts: credits_exhausted removed from terminal set; isCreditsExhaustedReprobeCandidate() with 30min default. Loads active+inactive rows so inactive credits_exhausted accounts can recover. - tests/unit/quota-connection-recovery.test.ts: 6 cases covering pure helpers + tick wiring. * fix(antigravity): persist projectId and prefer healthy accounts Save Cloud Code projectId after runtime discovery, skip accounts missing projectId when alternatives exist, and mark missing_project_id on 422. * fix(antigravity): skip quota-exhausted models during account selection Avoid repeatedly dispatching to Antigravity models that already report exhausted quota, reducing wasted upstream calls and combo fallback latency. --------- Co-authored-by: hermes <hermes@nous.local> * feat(alibaba): free-tier routing with live quota sync (#8893) * 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> * feat: improve provider quota layouts (#8916) * feat: improve provider quota layouts (#8916) Adds Full/Compact layout toggle for provider quota cards. Compact mode shows condensed card grid with key metrics; Full mode shows expanded detail. Toggle persists via localStorage. Changes: - ProviderLimits/index.tsx: layout mode state + toggle button - QuotaCardGrid.tsx: compact/full card rendering - ProviderQuotaWidget.tsx: compact/home view - HomePageClient.tsx: minor wiring fix - tests/unit/quota-card-grid-compact-layout-8916.test.ts: structural guard - file-size-baseline.json: rebaseline for ProviderLimits/index.tsx (1163) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(ci): restore providerId contract + reorder grid source + rebaseline translator drift - ProviderQuotaWidget.tsx: restore size={18} on non-compact ProviderIcon to satisfy base-branch test #3064 pinned contract. - QuotaCardGrid.tsx: reorder branches so non-compact (default) layout renders first in source. Same runtime behavior; satisfies base tests #3520/#6815/#7072 that inspect the first div/grid-cols class. - file-size-baseline.json: bump testFrozen translator-openai-to-gemini 1619->1622 (+3 upstream drift absorbed in merge of release/v3.8.50). Closes upstream CI: Unit Tests 2/4, 3/4, 4/4 + Fast Quality Gates. codeql-ratchet is upstream repo-wide (not our code) — external. --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(i18n): localize SubscriptionTab UI strings instead of hardcoded Chinese (#8930) The proxy subscription tab (System -> Proxy -> Subscriptions) displayed Chinese text regardless of the selected language. The component called useTranslations("settings") but bypassed t() for all ~50 UI strings. - Replace every hardcoded Chinese string in SubscriptionTab.tsx with t("proxySubscription.<key>") calls - Add 53 new keys under settings.proxySubscription to en.json (English) and zh-CN.json (Chinese) with full manual translations - Propagate to all 41 other locales via generate-multilang.mjs (Google Translate), per docs/guides/I18N.md workflow All 42 locales at 100% i18n coverage with zero __MISSING__ markers. * Fix custom tool output pairing during context compression (#8933) * Fix custom tool output pairing during compression (#8932) * Bypass proxy compaction for native Codex context * fix(sse): extract Codex tool-call output repair to leaf module for file-size gate repairMissingCodexToolCallOutputs (added by #8932 for custom_tool_call pairing) pushed codex.ts past the frozen file-size baseline. Extract it to open-sse/executors/codex/toolCallRepair.ts, leaving only the wiring call in codex.ts. Rebaseline the test file's genuine +41 line growth from #8932's new custom_tool_call_output coverage. Co-authored-by: JxnLexn <JxnLexn@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: JxnLexn <JxnLexn@users.noreply.github.com> * feat(combos): let combo builders test providers and add only working models (#9011) * fix(sse): preserve Claude Code tool-name casing via Gemini/Antigravity (#9008) (#9016) Stop blindly lowercasing PascalCase tool_use names on the Gemini→Claude path so Claude Code no longer rejects Read/WebSearch as missing tools. * fix(vision): preserve images for text-only routes (#9037) * fix(vision): preserve images for text-only routes * fix(i18n): complete Vietnamese vision bridge copy * fix(ci): drain prerelease tag input --------- Co-authored-by: rinseaid <rinseaid@rinseaid.net> * feat(i18n): complete zh-CN localization for compression engines and dashboard UI (#9038) * feat(i18n): complete zh-CN localization for compression engines and dashboard UI - Translate all compression engine names and descriptions (Caveman, Lite, Aggressive, Ultra, OmniGlyph, Headroom, Session Dedup, RTK, CCR, LLMLingua) - Translate all __MISSING__ entries (50+ strings) across settings, cache, OAuth, compression exclusions, and provider onboarding - Translate hardcoded dashboard UI strings (analytics tables, playground, cliproxy/9Router exposure cards, Qdrant config, OneProxy, forgot-password) - Localize PWA manifest and A2A agent card (manifest.ts, agent.json route) - Add missing translation keys (hermes roles, API protocol, embedded services, memory/Qdrant, Obsidian, Codex auto-ping, reasoning routing) * fix(i18n): restore cliCommon.comparison.acp keys dropped in the release merge The release merge kept only the author's translated `flow` value and dropped `title`, `desc` and `examples`, which exist on every sibling entry (code/agent). Restore the three from the release while keeping the author's `flow` translation. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(resilience): recover idle-capacity limiter wedges early (#9041) * fix(resilience): recover idle-capacity limiter wedges early * docs(changelog): note limiter wedge recovery * fix(resilience): harden limiter wedge recovery * fix(resilience): close limiter recovery review gaps * test(resilience): preserve scoped exhaustion guards * docs(changelog): remove self-credit suffix * test: include limiter regressions in mutation coverage * chore(quality): reconcile v3.8.50 file-size baselines * fix(docs): add WAF MDX title frontmatter * fix(docs): complete WAF frontmatter metadata * fix: skills & memory — tool-name encoding, schema normalization, warm-cache, combo id, Ponytail catalog (#9058) * 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> * fix(translator): honor Chat targets for Responses clients (#9161) Honor explicit Chat targets for Responses-shaped clients while preserving native Responses providers and selecting token fields from the outbound protocol. Includes focused regression coverage and the required changelog fragment. * test(mcp): guard Node 24 bundled MCP startup (#9162) * feat(cursor): proactively renews Cursor sessions and fixes manual refresh (#9173) * 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). * fix(dashboard): make connection Default Model editable and optional (#9172) (#9179) * fix(dashboard): make connection Default Model editable and optional * docs(changelog): retitle fragment with PR number --------- Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> * fix(combo): recover provider circuit breaker from HALF_OPEN on success (#9207) The combo success path called recordProviderSuccess (cooldown-only) without notifying the circuit breaker. When a provider breaker entered HALF_OPEN after repeated failures, successful probe requests never transitioned it back to CLOSED -- the breaker stayed stuck indefinitely. Production evidence: agy breaker HALF_OPEN with 699 requests at 98% success rate, never recovering. Root cause: combo.ts calls recordProviderSuccess from providerCooldownTracker.ts (resets cooldown failureCount only) but never calls breaker._onSuccess(). The failure path in accountFallback.ts calls breaker._onFailure(), creating an asymmetry. Fix: add recordProviderSuccess to accountFallback.ts as the symmetric counterpart of recordProviderFailure. Uses getProviderBreaker (not configureProviderBreaker) to avoid overwriting the breaker's resetTimeout with default profile values. Calls breaker._onSuccess() for all non-OPEN states (CLOSED/DEGRADED/HALF_OPEN), matching execute()'s behavior. * fix(command-code): preserve literal max effort for command-code provider (#9257) * fix(command-code): preserve literal max effort for command-code provider * test(command-code): type the new sanitizeReasoningEffortForProvider assertions The 3 new command-code reasoning-effort test cases cast the function's unknown return value with `as any`, which pushes the file's frozen no-explicit-any suppression count (48) to 51 and trips the "No new ESLint warnings" gate. Use a minimal EffortCarrierResult shape instead of any, matching the fields the assertions actually read. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * test(v1-models): type the API key lookup in the #9320 auth-leak regression test The release-tip test file added by #9320 used `(k: any)` in an Array.find callback, which is not covered by config/quality/eslint-suppressions.json (the file was added after the suppressions snapshot was frozen). That leaves the "No new ESLint warnings" gate red for any branch that merges this exact release/v3.8.50 tip, unrelated to this PR's own diff. Fixing it here with a minimal derived type (Awaited<ReturnType<typeof getApiKeys>>[number]) unblocks the gate without touching the frozen suppressions baseline. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * feat(sse): server-side template expansion for combo system prompts (#5501) (#9414) * feat(sse): server-side template expansion for combo system prompts (#5501) * fix(quality-gates): register combo-system-prompt-templates-5501 test in stryker tap.testFiles check:mutation-test-coverage --strict flagged tests/unit/combo-system-prompt-templates-5501.test.ts as covering src/shared/utils/circuitBreaker.ts without being listed in stryker.conf.json tap.testFiles, so its mutant kills wouldn't count. Co-authored-by: maxmad64bis <maxmad64bis@users.noreply.github.com> --------- Co-authored-by: Max <maxmad64@gmail.com> Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: maxmad64bis <maxmad64bis@users.noreply.github.com> * fix(translator): normalize streamed optional tool arguments (#9423) * fix: preserve Codex cache usage for Claude suggestions Co-Authored-By: Claude <noreply@anthropic.com> * fix: normalize streamed optional tool arguments Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Kittisak Tangsiri <kittisak@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> * ci: re-trigger checks after GitHub Actions incident (2026-08-07, resolved) (#9441) * fix(sse): preserve client cache boundaries when hoisting system roles (#9457) Hoisting a mid-conversation `system`/`developer` message into the top-level `system` field carried its `cache_control` marker along. Anthropic assembles the cache prefix as tools -> system -> messages, so the marker ended the cached prefix at the system block and left the accumulated conversation without a breakpoint: that turn was billed as fresh input and the next one rebuilt the cache. `relocateHoistedCacheBoundary` moves the marker to the nearest preceding block that can carry a breakpoint, skipping thinking blocks, empty text and anything the upstream normalisation discards or empties out. If that block already carries the client's own marker, both are kept - unless the hoisted one, now ahead of the target in `system[]`, would put a 5m breakpoint before a 1h one, which Anthropic rejects; it is dropped in that case. Either way the breakpoint count never grows. normalizeClaudeUpstreamMessages rewrites tool_result and inlined file/document blocks into plain text after the hoist, which silently discarded any marker on them - including a relocated one. The replacement block now inherits it. Both hoisting implementations share the helper; a fix touching only claudeSystemRole.ts would leave extractSystemMessagesToBody broken, and the native Claude path reaches the former through normalizeClaudeUpstreamMessages. Capability-gated hoisting for strict providers (#7293) is unaffected. Fixes #9436 Co-authored-by: LeonG606 <leongudat01@gmail.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> * fix(adobe-firefly): harden CDP warm, risk session, and browser sign-in (#9549) * fix(adobe-firefly): open browser sign-in and resolve provider slug in /login POST /api/providers/[id]/login passed the connection DB id to inAppLoginService.startLogin, but TOKEN_EXTRACTION_CONFIGS is keyed by provider slug — so browser login never launched for web-cookie providers. Adobe Firefly also cannot use cookie extraction: the IMS JWT only appears on Authorization headers to firefly-3p.ff.adobe.io. Add a dedicated Playwright interceptor and persist credentials with camelCase keys that updateProviderConnection actually reads. * fix(adobe-firefly): use system Chrome/Edge CDP for browser sign-in Playwright is not available inside the pkg-packaged VibeProxyServices.exe, so import('playwright') always failed with 'Playwright not installed' and never opened a window. Launch Chrome/Edge with --remote-debugging-port and capture the firefly-3p Authorization Bearer via pure CDP WebSocket instead. * fix(adobe-firefly): live x-arp-session-id / Arkose wire (stop 408 under load) Browser generate-async requires x-arp-session-id as base64({sid,ark,ftr}) with a real Arkose blob (sherlockToken). JWT alone frequently returns colligo HTTP 408 system under load while credits still work. - Match live ftr magic __UDF43-m4_31ck + Arkose pk in synthetic ARP fallback - Ranked extract of sherlockToken / x-arp from Cookie, HAR, fetch() paste, and space-joined JWT+ARP (PasswordBox newline collapse) - Reuse one ARP for storage upload + generate-async - Clearer 408 errors when browser ARP is missing vs stale - Unit suite 42/42 * fix(adobe-firefly): durable session ARP rebuild and aux_sid false-positive Rebuild x-arp-session-id from forterToken/arkose/ff_session_guid instead of ranking long Cookie pairs (e.g. aux_sid=…) as opaque ARP, which caused colligo HTTP 408. Cache IMS JWT + cookie sessions, rotate ARP on 408 retries, and keep Playwright warm-up opt-in only (headless Forter is rejected). Also expand synthetic ARP shape with bfp/fpjs to match live successful captures. * fix(adobe-firefly): renew sessions through durable CDP * fix(adobe-firefly): isolate browser sessions per account * fix(adobe-firefly): make account login fresh and deterministic * docs(adobe-firefly): document renewal controls * fix(adobe-firefly): harden CDP warm, risk session, and browser sign-in Stop colligo 408 thrash from stale Forter and frozen Google login during Sign in with browser: - CDP warm: clear Firefly origin storage + risk cookies (keep SSO); require forter age under 10 minutes on loop and timeout paths; dual CDP queues; await Runtime.runIfWaitingForDebugger; profile-lock launch retries - Session: connectionId fingerprint; write-back JWT+Cookie; warm-fail cooldown; fail closed risk_session_stale when forter is known-stale - Client: submit gate around generate-async; max 2 attempts when forter known-stale; poll 401 one refresh; pass sessionBrowserKey through handlers - Login route: pure system Chrome/Edge CDP only; camelCase credential persist - Unit: browser-login + firefly suites green (60) * fix(adobe-firefly): dedupe CDP session hardening blocks after rebase Remove duplicated guard blocks and test bodies introduced when rebasing the CDP session hardening work onto release/v3.8.50, which already carries the hardened implementation. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(translator): preserve Kimi K3 Responses reasoning (#9556) * fix(translator): preserve Kimi K3 Responses reasoning * fix(translator): make K3 reasoning preservation model-driven * fix(translator): replay cached Kimi reasoning before fallback * fix(translator): keep authentic K3 reasoning through cleanup * refactor(reasoning): use replay policy for K3 * fix(settings): use provider prefixes in model overrides (#9569) * [v3.8.50] feat(providers): add support for TinyCMS Web (#8736) * 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> * [v3.8.50] fix: passthrough non-standard cache token fields for DeepSeek / MiniMax / Bedrock across streaming, non-streaming, and Dashboard paths (#8591) * fix(#8171): map DeepSeek prompt_cache_hit_tokens into prompt_tokens_details.cached_tokens DeepSeek native API returns cache stats in flat top-level fields (prompt_cache_hit_tokens / prompt_cache_miss_tokens) instead of the standard prompt_tokens_details.cached_tokens. The usage sanitizer (sanitizeUsage / sanitizeResponsesUsage) was stripping these non-standard fields, so clients never received real cache hit counts even when the upstream served cached responses. Changes: - sanitizeUsage(): map prompt_cache_hit_tokens into prompt_tokens_details.cached_tokens when the latter is unset - sanitizeResponsesUsage(): same mapping for input_tokens_details - filterUsageForFormat(): add prompt_cache_hit_tokens and prompt_cache_miss_tokens to the default format allow list so they survive field-level filtering * fix: passthrough non-standard cache token fields for DeepSeek / MiniMax / Bedrock across streaming, non-streaming, and Dashboard paths * fix(sse): shrink cache-hit token passthrough to fit file-size gate PR #8591 added a DeepSeek/MiniMax/Bedrock flat cache-hit-token -> nested prompt_tokens_details.cached_tokens mapping (#8171) that grew responseSanitizer.ts and stream.ts past their frozen file-size baselines. - Extract the chat-completions/Responses-API mapping logic into a new leaf module (responseSanitizer/cacheHitTokens.ts). - Move the streaming-path rebuild into filterUsageForFormat() (usageTracking.ts), the single conversion chokepoint both stream.ts call sites already used, eliminating the duplicated stream.ts patch entirely. - Rebaseline responseSanitizer.ts by the 2 lines that remain irreducible (the mandatory ES import for the extracted helper). Behavior verified unchanged via the existing response-sanitizer and stream-handler unit suites. Co-authored-by: ikelvingo <ikelvingo@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: ikelvingo <ikelvingo@users.noreply.github.com> * docs: fix stale tool count (105 -> 104) in MCP server docs (#10002) The doc's own breakdown at line 11 (42+3+4+3+6+8+8+6+22+2) sums to 104, matching the two existing '104 unique tools' mentions. The '105 tools' mentions in the intro and cardinality-reduction section were stale and inconsistent with the documented source of truth. * refactor(providers): remove retired GitHub Models (#9023) * docs: clarify free-provider model refresh outcomes (#9087) * 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 Naga.ac and ChatAnywhere aggregator providers (#6674) (#9421) Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> * fix(providers): switch minimax from claude to openai format so images work (#9463) * fix(providers): switch minimax from claude to openai format so images work The Anthropic-compatible /anthropic/v1/messages endpoint rejects image input with 403. MiniMax's OpenAI-compatible /v1/chat/completions endpoint supports image_url natively for MiniMax-M3. - minimax + minimax-cn: format claude→openai, baseUrl→/v1/chat/completions - Remove Anthropic-Version header + ?beta=true suffix (not needed for openai) - Remove minimax/minimax-cn from ?beta=true executor case - Update cache-control tests (openai format uses different caching path) - Fix reasoning-split test names (no longer claude format) TDD: 2 registry tests assert format=openai (red→green). Refs: Hermes Agent #15715, MiniMax OpenAI-compatible API docs. * fix(sse): re-align stream-readiness-policy tests with minimax's openai format PR #9463 switched minimax/minimax-cn from claude to openai format so images work. The stream-readiness bump for Claude-format replicas is keyed off the registry's format field (single source of truth), so minimax legitimately falls out of that group now. Swap the "Claude-format replica" test fixtures to agentrouter (still format: "claude") and add explicit coverage that minimax no longer gets the claude_format_heavy_reasoning bump. --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> * fix(providers): reject the dashboard password as a connection API key (#9572) * fix(providers): refuse to store the dashboard password as a connection API key A browser autofilled the management password into a connection's API-key field. The resulting credential authenticates against nothing, so every request routed through that connection came back 401, and because the field looks like any other password input the same autofill fired again while the connection was being repaired by hand. The refusal belongs on the write path rather than in the form. Twenty routes create or update connections and all of them funnel through createProviderConnection and updateProviderConnection, so one check there covers every entry point including a future one. The two other places that write api_key are left alone on purpose: one re-encrypts rows that already exist and the other is the one-time db.json import, and neither takes a value an operator just typed. Update checks the incoming value, never the merged one. A connection that already holds the password has to stay editable or the operator cannot repair the exact state this prevents, and re-checking the merged value would spend a bcrypt round on every unrelated field edit. Only a real match blocks the write. An unreadable settings row or a throwing bcrypt call logs and allows, because a guard against one specific mistake must not turn into a way to lock out every connection write. Signed-off-by: Minxi Hou <houminxi@gmail.com> * fix(providers): compare the untrimmed credential, and cover the guard's branches The guard trimmed the incoming value before comparing it, which catches a paste carrying whitespace the password does not have. It missed the mirror case: neither the login route nor the set-password route trims, so a dashboard password may itself begin or end with a space, and an autofill reproducing it exactly was trimmed into a value that no longer matched the stored hash. The write then went through, which is the state this guard exists to prevent. Both forms are compared now, the second only when the first fails on a string that differs, so an ordinary key still costs a single bcrypt round. Two branches carried no coverage and both are load-bearing. The catch that logs and allows is the only path that lets a write through; a stored hash bcrypt cannot parse reaches it without needing a mock, since the shape check accepts an impossible cost factor that the comparison then rejects. The early return is what keeps a token renewal -- a write carrying tokens but no apiKey -- from paying for a settings read and a bcrypt round every time it fires, and the same unparseable hash makes that path observable, so an absent warning is proof the return happened. The narrower scope is deliberate and now says so in the code: the OAuth tokens arrive from a provider's token endpoint rather than from a form, so extending the comparison to them would charge every renewal for a field no autofill can reach. Signed-off-by: Minxi Hou <houminxi@gmail.com> --------- Signed-off-by: Minxi Hou <houminxi@gmail.com> * fix: restore unorouter api and catalog metadata (#9594) Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> * ci(test): route orphaned Vitest tests through blocking CI (#9605) * ci(test): route orphaned Vitest tests through blocking CI * docs: fix advisory status in AGENTS.md and refresh baseline note * fix(changelog): fix fragment format for #9415 * fix(changelog): preserve upstream fragment format --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> * fix: pass max reasoning effort through by default, add global model registry fallback (#8057) (#9612) * feat(db): add a job registry for scheduled background work (#9631) * feat(db): add a job registry for scheduled background work Background jobs each ship their own timer today, so there is no list of what is scheduled, no history of what ran, and no way to pause one without an environment variable and a restart. The registry gives them one home: a jobs table holding the schedule, a job_runs table holding the outcomes, and a loopback-only API to inspect and control both. Cron jobs read their expression through an optional cronGetter rather than the stored column, so an operator changing OMNIROUTE_WARMUP_CRON does not need the row rewritten. register() is an idempotent upsert that refreshes the schedule but never overwrites `enabled` or `created_at`, which is what lets a job be re-registered on every boot without discarding the operator's toggle. Run history is pruned per job rather than globally, and safeRun records a failure for a handler that throws as well as one that returns success:false, so a crashing job leaves a trail instead of a gap. The API is under /api/jobs and gated to loopback in the route guard. It can trigger a run and flip a job off, which is runtime administration and does not belong on a remotely reachable surface. Signed-off-by: Minxi Hou <houminxi@gmail.com> * feat(jobs): move the budget reset and token health check onto the registry Both jobs owned their own timer and started themselves as an import side effect, so nothing could report whether they were running, when they last ran, or why a run failed. They now register with the job registry and are started from it, which also means their schedule and run history are visible through /api/jobs. startAll() runs each interval job's first tick synchronously, so both entry points start the registry only after initializeCloudSync() has been awaited. The old wiring reached that ordering two different ways: the budget reset was started after the init call, and the health check's first sweep sat behind a 10s timer. Replacing both with one startAll() would otherwise have moved the two handlers in front of the initialisation they run against. Both entry points also register the same pair of jobs. Registering one and not the other is how a background job goes missing without anything failing. sweep() now returns how many connections it swept, so the health check can record a real records_affected the way the budget reset does. The migration documents that column as a per-job count, and hardcoding zero would have left one of the two jobs reporting a number the schema promises but the code never produces. A skipped or empty sweep reports zero. Every existing caller ignores the return value. The token health check keeps its own disable semantics: the handler still calls isHealthCheckDisabled() before sweeping, so OMNIROUTE_DISABLE_TOKEN_HEALTHCHECK, the production-build phase and the automated-test guard behave as before. Its registry adapter lives in src/lib/jobs/ next to the budget reset rather than in tokenHealthCheck.ts, which is already above its frozen size ceiling on the base branch and should not grow further. The adapter lets a failing sweep throw rather than reporting it itself, matching the budget reset: safeRun records a thrown error as a failure run with its message. The warmup job is seeded disabled. Its handler arrives with the warmup scheduler, and startAll() filters on enabled before it looks for a handler, so seeding it enabled here would warn about the missing handler on every boot. * fix: allowlist cron-parser dep and document OMNIROUTE_RUNNOW_TIMEOUT_MS env var Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> * chore: align rebased branch with release tip (migration renumbered 139->146 in release; feature already cherry-picked in #9886) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Signed-off-by: Minxi Hou <houminxi@gmail.com> Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> *…
…ck-boot) check:pack-boot booted the packed tarball fine but failed its version assertion: `version "undefined" (expected "3.8.50")`. The artifact was not broken — the health payload was. cheaper-inference#11040 (GHSA-mvf8-qc78-5mxm) reduced GET /api/monitoring/health to a liveness-only view for non-management callers. But that route is classified PUBLIC (src/shared/constants/publicApiRoutes.ts:67) and runAuthzPipeline deletes CLI_TOKEN_HEADER from the forwarded headers for EVERY route class (src/server/authz/pipeline.ts), so a handler can only learn that a local CLI authenticated from the subject the policy stamped. publicPolicy stamped `anonymous` unconditionally, so requireManagementAuth in the route 401'd even for a valid loopback machine token and every caller got `{status, setupComplete}` — no `version`. Verified on the installed tarball before the fix: the same token that returns 200 on /api/cli/whoami (MANAGEMENT, where the pipeline does stamp the subject) got the anonymous body on /api/monitoring/health. publicPolicy now stamps the same loopback-gated `local-cli-token` subject the MANAGEMENT policy already produced. The shared verdict (peer-locality resolution + constant-time token compare) moves to src/server/authz/peerContext.ts so both policies use one implementation — no behavior change on the MANAGEMENT side. Anonymous callers keep the liveness-only view. Tests: publicPolicy stamps the CLI subject only for a loopback peer with a valid token (both negative controls asserted; the positive case fails on the pre-fix policy), plus a health-route pin that a stamped local-CLI caller receives `version`. check:pack-boot green end to end (boot #1, machine-token contrast, sql.js round trip, boot #2 persistence).
Summary
defaultModels— never written to~/.claude/settings.jsonon ApplyREGISTRY.claude.modelsviagetClaudeCodeDefaultModels()handleApplySettingsto fall back tomodel.defaultValuewhenmodelMappings[alias]is emptyFiles changed
open-sse/config/providerRegistry.ts—getClaudeCodeDefaultModels()derives latest opus/sonnet/haiku from registrysrc/shared/constants/cliTools.ts— addedmodel+smallFastdefaultModels entriessrc/app/.../ClaudeToolCard.tsx— fallback todefaultValuewhen Apply has no selectionCLAUDE.md— added missing test commands from copilot-instructions.mdTest plan
ANTHROPIC_MODELandANTHROPIC_SMALL_FAST_MODELappear in~/.claude/settings.jsonproviderRegistry.ts— confirm it propagates as default