Adds Fusion as a third combo strategy alongside fallback/round-robin. A
fusion combo fans the prompt out to all member models in parallel, then a
configurable judge model synthesizes one final answer from the panel.
- handleFusionChat in open-sse/services/combo.js: quorum-grace collection
caps the straggler penalty, anonymized sources prevent judge brand-bias,
degrades to a direct answer on single survivor and 503 on total failure.
- chat.js dispatches strategy==="fusion" at both combo entry points.
- Combos dashboard: per-combo strategy Select replaces the round-robin
toggle, fusion reveals a judge picker, plus a strategy/capacity explainer.
- tests/unit/combo-fusion.test.js covers fan-out, judge routing/default,
quorum-grace straggler drop, single-survivor and total-failure degradation.
Co-authored-by: Cursor <cursoragent@cursor.com>
Prefer edited name over provider email/displayName in Providers and
Quota Tracker; surface the provider-sourced email/display as secondary
text. Keeps fallbacks for legacy or unnamed connections.
Closes#1699
Co-authored-by: Cursor <cursoragent@cursor.com>
Adds long-lived API-key (ksk_) authentication for Kiro/AWS CodeWhisperer
and a direct claude:kiro / kiro:claude translation route that avoids the
lossy OpenAI two-hop pivot.
- translator: claude-to-kiro request + kiro-to-claude response translators,
registered on the exact source:target pair (direct route ahead of the
OpenAI pivot in index.js). claude-to-kiro uses shared schema constants
(ROLE/CLAUDE_BLOCK/DEFAULT_IMAGE_MIME) per app convention.
- auth: POST /api/oauth/kiro/api-key imports + validates a key via
ListAvailableProfiles, persists authMethod="api_key" (no refresh token).
- executor: send tokentype: API_KEY header and try *.amazonaws.com hosts
first for api-key creds; OAuth keeps kiro.dev first.
- fix: never inject the default placeholder profileArn for api-key auth
(CodeWhisperer 403s an ARN not owned by the key's account).
- ui: API Key method in the Kiro connect modal; surface api-key accounts
on the Quota Tracker and provider count.
- stream: env-overridable TTFT vs stall timeouts + Kiro keepalive frame.
- tests: claude-kiro-direct + kiro-profile-arn (11 tests).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Auto-sends a minimal request right after each Claude OAuth connection's 5h quota window resets, so a fresh window starts immediately without waiting. Per-connection toggle on providers and quota dashboards.
- claudeAutoPing scheduler (server-side, 60s tick) hooked into initializeApp
- per-connection enable map in settings.claudeAutoPing.connections
- toggle + tooltip in ConnectionRow and ProviderLimits (Claude OAuth only)
Co-authored-by: Cursor <cursoragent@cursor.com>
- /api/settings/database now requires current password (header for GET,
body for POST) in addition to session; CLI-token requests exempt
- add verifyDashboardPassword helper reusing login bcrypt check
- profile UI prompts password via modal before export/import
- /v1/web/fetch rejects internal/private/metadata targets via assertPublicUrl
Refs GHSA-qvfm-67h2-2qfx, GHSA-qj3v-64wj-q825
Co-authored-by: Cursor <cursoragent@cursor.com>
Switch all siliconflow endpoints from api.siliconflow.cn to .com (chat,
validate, test, models) — the .cn host rejected valid .com-issued keys.
Replace the model list with 13 strong models verified live against the API.
Co-authored-by: Cursor <cursoragent@cursor.com>
Bootstrap → JWT → chat flow with auto-retry. Inject the MiMoCode
system marker required to pass the upstream 403 anti-abuse gate,
derive JWT expiry from the exp claim, and use a stable per-machine
fingerprint as the bootstrap client.
Co-authored-by: Cursor <cursoragent@cursor.com>
Restore Cowork feature while closing the RCE vector: drop user-defined
stdio commands so only hard-coded preset plugins (browsermcp) may spawn.
Custom MCP now accepts remote URL only. Routes stay gated to localhost.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Add custom-server.js: inject unspoofable socket IP, strip client XFF
(wired into Docker CMD + CLI spawn + build-cli copy)
- loginLimiter: key on trusted x-9r-real-ip, TRUST_PROXY opt-in, global fallback
- Force password change on first remote login while default is in use
- Add /api/auth/reset-password (local-only) so CLI reset writes live SQLite
- CLI settings: reset via API instead of stale db.json
- Fix OAuth modals opening duplicate browser tabs on add-connection
- Add cli:pack / cli:publish scripts
Co-authored-by: Cursor <cursoragent@cursor.com>
Tailscale installed via apt/snap/brew was reported "Not Installed/Logged In"
because 9Router only probed its custom userspace socket. Probe the system
socket (/var/run/tailscale/tailscaled.sock) as fallback, add /usr/sbin and
/snap/bin to candidate paths and EXTENDED_PATH, and report separate
customDaemonRunning/systemDaemonRunning flags. Caching/non-blocking behavior
preserved.
Co-authored-by: Stefan Pirker <stefan.pirker86@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Add a "Bulk Add" button on /dashboard/providers/codex that imports
multiple OAuth accounts at once by pasting a JSON array, single object,
or { accounts: [...] } wrapper.
- New endpoint POST /api/oauth/codex/bulk-import (serial loop, no token echo)
- New BulkImportCodexModal component with JSON textarea + success/failure summary
- Persist idToken/lastRefreshAt on first insert via OPTIONAL_FIELDS
- Backfill email/chatgptAccountId/chatgptPlanType from JWT when missing
- Derive expiresAt from expiresIn when missing
- Gated to providerId === "codex" only; other providers unaffected
Co-authored-by: Cursor <cursoragent@cursor.com>
LLM combos created with kind:"llm" were hidden because the page only
listed no-kind combos. Include kind==="llm" while still filtering out
media provider (webSearch/webFetch) combos.
Closes#1682
Co-authored-by: Cursor <cursoragent@cursor.com>
Move shutdown into header popup + profile, move remote into sidebar above
settings, add flag-only language switcher in header, and add language card
plus shutdown/logout actions to the profile page.
Co-authored-by: Cursor <cursoragent@cursor.com>
Added a new translation for the message "Endpoint is exposed without an API key." to various language files, enhancing user awareness regarding API security. This update ensures that users are informed about potential risks associated with unprotected endpoints in their respective languages.
Add wenyan-lite/wenyan/wenyan-ultra levels for max token compression,
sync SHARED_EXAMPLES/AUTO_CLARITY/PERSISTENCE across all levels, and
expose 3 wenyan buttons in endpoint settings UI.
Co-authored-by: Cursor <cursoragent@cursor.com>
Add shared OAuth credential lifecycle manager with provider-aware refresh
decisions. Implement CodexExecutor.refreshCredentials so 401/403 retry
refresh works for Codex, track lastRefreshAt and refresh before the
upstream stale-token window, preserve omitted idToken, and add
per-connection single-flight refresh to avoid refresh-token rotation races.
Merged from PR #1664.
Co-authored-by: Cursor <cursoragent@cursor.com>
OpenAI/Anthropic Compatible and Custom Embedding nodes allow exactly one
connection each. The guards were dropped during the bun:sqlite refactor
(v0.4.28), so duplicate POSTs were accepted (201) instead of rejected (400).
Restore the per-node existing-connection check in the POST handler.
Test: tests/unit/compatible-provider-connections.test.js now passes.
Co-authored-by: Cursor <cursoragent@cursor.com>
The "opencode-free" suggested-models filter only kept models whose id ends
in "-free", so free-but-unsuffixed models like `big-pickle` (routed via
open-sse/executors/opencode.js MESSAGES_MODELS) never appeared in the
import UI.
Extract the filters into a testable module and include a
KNOWN_FREE_OPENCODE_MODELS allowlist (big-pickle) alongside the "-free"
suffix check.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Qoder's API returns `remaining` as an absolute credit count (e.g. 348
out of 3000), but ProviderLimits' getRemainingPercentage and QuotaTable
treat the `remaining` field as a 0-100 percentage. Result: "348%" with
red status.
Stop forwarding `remaining` from the qoder parser. The percentage is
computed from used/total via calculatePercentage, which gives the
correct ~12% remaining for the example case.
Wire Qoder credits into the Quota Tracker card grid:
- Add `qoder` to USAGE_SUPPORTED_PROVIDERS so the connection passes the
isUsageEligible filter at /api/providers/client and shows up in
providerOptions on the dashboard.
- Reshape getQoderUsage so quota records (user, organization) live under
`quotas` and scalar metadata (totalUsagePercentage, isQuotaExceeded,
expiresAt) are siblings — the parser used to walk Object.entries(quotas)
and would have rendered `totalUsagePercentage: 0.42` as a "0/0" row.
- Surface Qoder's expiresAt as resetAt on each quota record so the card
shows when credits reset.
- Add a parser branch in ProviderLimits/utils.js: rename internal keys
(user → "Personal", organization → "Organization"), drop empty org
buckets so personal accounts don't render a misleading "0/0 Organization"
row, and forward remaining/unit so the QuotaProgressBar can use them.
- Add Qoder's brand color (#EC4899) to ProviderLimitCard's color map.
42 tests still pass; build clean.
Correctness:
- testUtils: drop checkExpiry so the userinfo URL probe actually runs (revoked
tokens used to look "active" until local 30-day expiry passed)
- auth.parseExpiry: handle numeric expiresAt, swap parseInt before Date.parse
so "2026" doesn't get interpreted as year-2026, treat expires_in:0 as
already-expired instead of fabricating a 30-day default
- providers.mapTokens: synthesize email from userId when fetchUserInfo fails
so OAuth dedup works (re-logins no longer accumulate "Account N" rows)
SSE wrapper:
- wrapQoderSSE: add !doneEmitted guard on success branch (chunks could leak
past [DONE] when an error envelope shared a TCP packet with a valid one)
- flush(): finalize TextDecoder + drain trailing buffer so the chunk carrying
finish_reason is delivered when upstream closes without a final \n
- sanitize literal \n inside inner OpenAI body so SSE framing stays intact
Robustness:
- executor: wrap buildCosyHeaders in try/catch so a missing accessToken
returns 401 (re-auth) instead of bubbling as 500
- executor: short-circuit on missing accessToken before signing
- executor: plumb proxyOptions/signal through buildQoderRequestBody so
proxy-only networks can fetch the model_config catalog
- qoderModels: dedupe concurrent first-time misses with an in-flight Promise
map (parallel chat windows now do 1 upstream fetch instead of N)
- qoderModels: check signal.aborted before addEventListener so a pre-aborted
parent signal cancels the inner fetch immediately
- auth: AbortController + 15s timeout on pollDeviceToken / fetchUserInfo to
prevent hung sockets when openapi.qoder.sh stalls mid-response
UX:
- OAuthModal: derive polling deadline from device-code expires_in (qoder
publishes 300s; the previous fixed 120s caused timeouts when users took
more than 2 minutes on the consent page)
Cleanup:
- delete src/lib/oauth/services/qoder.js — referenced removed config fields
(clientId/clientSecret/tokenUrl/authorizeUrl) and was re-exported from
services/index.js, so any future caller would TypeError on first use
commit 6561679f5c396bb07f5f7ba5bc5ec75e81c803a4
Author: OpenClaw Patch <patch@openclaw.local>
Date: Tue May 19 16:26:01 2026 -0700
fix: never dedup access_token connections
Access tokens should always create new entries. User decides
which to keep (refresh-based OAuth vs no-expiry website token)
and removes the other manually.
commit d773451657999a2965ca4a094a7f0b7a54066693
Author: OpenClaw Patch <patch@openclaw.local>
Date: Tue May 19 16:24:30 2026 -0700
fix: support ChatGPT website token format (account_id, plan_type)
ChatGPT website access tokens use top-level 'account_id' and
'plan_type' fields, while OAuth id_tokens use nested claims
under 'https://api.openai.com/auth'. Now both formats are
handled, so workspace dedup works for website tokens too.
commit cb895a5f6be59c51267874f11567646fa1f43016
Author: OpenClaw Patch <patch@openclaw.local>
Date: Tue May 19 16:12:56 2026 -0700
fix: detect JWT in manual callback URL field
When user pastes a JWT access token (starts with eyJ) in the
'paste callback URL' input field, skip URL parsing and send
it directly to the exchange endpoint as the code. Fixes
'Failed to construct URL: Invalid URL' error.
commit 29650d4a6732e3cf0958c9963b53209e41c8281e
Author: OpenClaw Patch <patch@openclaw.local>
Date: Tue May 19 15:37:02 2026 -0700
feat: auto-detect access token in OAuth exchange
When the exchange endpoint receives a JWT (starts with eyJ)
instead of an OAuth authorization code, it detects this and
creates an access_token connection directly — skipping the
OAuth token exchange flow.
This lets users paste a ChatGPT access token where the OAuth
code would normally go, and have it work automatically.
commit e8e7c5709a783abd0c45246a44de1cc6abdba100
Author: OpenClaw Patch <patch@openclaw.local>
Date: Tue May 19 15:14:48 2026 -0700
feat: workspace-aware dedup + ChatGPT access token import
1. Dedup now checks email AND workspace (chatgptAccountId)
- Same email in different workspaces = separate connections
- Backward compatible: non-workspace providers still dedup by email
2. New authType 'access_token' for ChatGPT website tokens
- POST /api/oauth/codex/import-token accepts raw access tokens
- Extracts email, workspace, plan from JWT claims
- Deduplicates by email+workspace like OAuth
- No refresh token needed (avoids OAuth relogin issues)
## Features
- Xiaomi MiMo Token Plan: region selector (Singapore / China / Europe) — keys are cluster-specific
- Antigravity: risk confirmation dialog before first connection
- Gemini CLI: surface upstream retry delay on 429 errors
## Fixes
- MITM: cannot kill process on macOS under sudo (lsof not found in PATH)
- Stream: false-positive stall timeout on Claude reasoning / Kiro responses
- Tunnel: cannot re-enable after disable (stuck state)
- Tunnel: cloudflared error messages now include log tail for easier debugging
- Language switcher: applies selected locale immediately on close (#1234)
- Antigravity OAuth: metadata now matches the official client
## Improvements
- Gemini CLI: bump engine to 0.34.0
- Re-hide `qwen` (OAuth EOL) and `iflow` (not ready) providers
OpenClaw 2026.5.x writes agents[].model as either a plain string
or as an object { primary, fallbacks }. The status enrichment and
POST cleanup both called .startsWith() on the raw value, which threw
TypeError when the object form was present and made the dashboard
report 'not configured'.
Add a resolveAgentModel helper that accepts both shapes and returns
the string id (model.primary for the object form, empty string for
missing/invalid). Use it when enriching agents in GET so consumers
receive a string model field, and when filtering the list in POST.
Refs decolua/9router#1196
## Features
- Add Blackbox provider with `bb` alias (#1143)
- Add Xiaomi token plan provider
- Enhance model select modal UX + modal traffic lights (#1111)
- Default Usage dashboard period to Today (#1141)
## Fixes
- Fix Cowork model selection and Windows CLI packaging (#1129)
- Update provider name retrieval for compatibility provider (#1135)
- Update JWT_SECRET handling
Cherry-picked from upstream PR #1129 + local improvements:
- dedupe inline remove-model handler -> use handleRemoveModel
- add .next-cli-build/ and cli/.build-home/ to .gitignore
* feat(model-select-modal): highlight added models and support bulk selection
- Add addedModelValues prop to highlight already-added models with primary color
- Sort models alphabetically per provider, with added models floated to top
- Replace green highlight with primary brand color (orange #E56A4A)
- Use check icon (10px) inline with model name instead of check_circle
- Replace Done button with info bar explaining click-to-toggle behavior
- Add ProviderIcon to provider group headers replacing colored dot
- Import ProviderIcon, remove unused Button import
* feat(cli-tools): wire addedModelValues, onDeselect, and auto-save to model select modals
- Pass selectedModels as addedModelValues to ModelSelectModal in OpenCode and Copilot cards
- Add onDeselect handler to remove model from list on second click
- Set closeOnSelect=false to allow bulk model selection
- Remove manual setModalOpen(false) from onSelect callbacks
- Add saveModels() silent auto-save triggered on modal close (OpenCodeToolCard)
- Use useRef to track latest selectedModels in closure-safe way
* feat(modal): functional traffic light close button with hover icon and tooltip
- Make red dot a clickable button that closes the modal
- Show ✕ icon inside red dot on hover via group-hover opacity transition
- Gray out yellow and green dots (cursor-not-allowed, no tooltip)
- Increase dot size from w-3 h-3 to w-4 h-4
- Add Tooltip with brand-matched color #FF5F56 on red dot
- Remove X close button from modal header
* feat(tooltip): add color prop for themed tooltip backgrounds
* feat(i18n): add translations for model select info bar and close tooltip
- Add 'Click to add, click again to remove. Changes are saved automatically.' to all 32 locales
- Add 'Close' translation to all 32 locales
* fix(ui): address code review feedback on modal UX and auto-save
- Modal: remove showCloseButton prop, use showTrafficLights for header
condition, hide traffic lights on mobile (hidden md:flex), add mobile
X button (md:hidden) with aria-label, add aria-label and title on
traffic light close button
- OpenCodeToolCard: validate activeModel membership before saving —
fallback to models[0] or empty string; clear/reassign activeModel
on deselect when removed model was the active one
- CopilotToolCard: add useRef + selectedModelsRef, add saveModels()
using /api/cli-tools/copilot-settings, wire auto-save on modal close
- ModelSelectModal: fix JSX formatting — separate info bar closing div
from Search comment onto its own line
* feat(usage): add Today period option to Usage & Analytics
Bổ sung lựa chọn Today vào bộ lọc thời gian của trang Usage & Analytics
(trước đây chỉ có 24h, 7D, 30D, 60D).
Khác biệt với 24h:
- 24h: cuộn 24 giờ trước → hiện tại
- Today: cố định từ 00:00 hôm nay (giờ local) → hiện tại
Thay đổi:
- page.js, UsageStats.js: thêm option Today vào danh sách PERIODS,
đổi grid mobile từ 4 cột sang 5 cột để fit option mới.
- api/usage/stats, api/usage/chart: cho phép giá trị period today.
- usageRepo.js:
+ getUsageStats: dùng nhánh live history khi period = today,
cutoff lấy từ 00:00 hôm nay theo local time.
+ getChartData: thêm 24 bucket theo giờ từ 00:00 → 23:59 hôm nay.
* feat(usage): đặt mặc định period là Today khi mở dashboard/usage
Người dùng thường quan tâm usage trong ngày hôm nay nhiều hơn 7 ngày,
nên hiển thị Today ngay khi vừa mở trang để giảm 1 thao tác chuyển period.
Đồng bộ luôn fallback trong UsageStats để giữ nhất quán khi component
dùng standalone.
The visibility, copy, and delete buttons in the API Keys list used , which never reveals on touch devices because they don't fire :hover. Switch all three to — visible by default on mobile, hover-revealed on sm+ — matching the pattern already used in providers/ and media-providers/
Co-authored-by: Muhammad Ridwan Ramadhan <ridwanramadhan8888@gmail.com>
Bổ sung lựa chọn Today vào bộ lọc thời gian của trang Usage & Analytics
(trước đây chỉ có 24h, 7D, 30D, 60D).
Khác biệt với 24h:
- 24h: cuộn 24 giờ trước → hiện tại
- Today: cố định từ 00:00 hôm nay (giờ local) → hiện tại
Thay đổi:
- page.js, UsageStats.js: thêm option Today vào danh sách PERIODS,
đổi grid mobile từ 4 cột sang 5 cột để fit option mới.
- api/usage/stats, api/usage/chart: cho phép giá trị period today.
- usageRepo.js:
+ getUsageStats: dùng nhánh live history khi period = today,
cutoff lấy từ 00:00 hôm nay theo local time.
+ getChartData: thêm 24 bucket theo giờ từ 00:00 → 23:59 hôm nay.
- Fixed variable declaration order in CLIToolsPageClient.js (functions before useEffect)
- Added lazy initialization for useState in BasicChatPageClient.js to read from localStorage
- Reduced ESLint errors by ~23%
Co-authored-by: yuangejiaozhu <leegajone@email.com>
Internal model test routes fetched 127.0.0.1:UPDATER_CONFIG.appPort
(hardcoded 20128). When PORT env is set to a different value, the app
listens on PORT but the internal fetch still targets 20128, causing
"fetch failed" on /api/models/test and /api/providers/[id]/test-models.
Fall back to UPDATER_CONFIG.appPort only when process.env.PORT is unset.
## Features
- Add Cline & Kilo Code tool cards
- Tailscale TUN mode for stable Funnel TLS
- Sort APIKEY providers by usage, collapse to top 20
## Improvements
- Local Material Symbols font (no Google Fonts)
- Docker base: Bun → Node 22-alpine
- MITM reads aliases from JSON cache (no native sqlite)
- Stream stall timeout (2 min) in open-sse
## Fixes
- Fal.ai key test: use stable models endpoint
Two findings, neither blocked by anything else:
1. src/app/callback/page.js — the OAuth callback page posted the
{ code, state } payload to window.opener with targetOrigin "*", so any
page that opened the popup against the well-known redirect_uri received
the live OAuth code. The expectedOrigins list was already computed but
never used. Iterate over it and pass the origin per send.
2. open-sse/utils/proxyFetch.js — createBypassRequest() set
rejectUnauthorized: false on the HTTPS request that runs after the
Google-DNS-resolved real-IP fallback (used for cloudcode-pa.googleapis,
GitHub Copilot, Cursor, AWS LLM endpoints). Combined with servername:
parsedUrl.hostname this gave SNI-correct connections that nonetheless
ignored cert validation, so an on-path attacker could swap in their
own cert and read the user's API tokens / prompts. Drop the flag.
Detected by Aeon + semgrep (javascript.browser.security.wildcard-postmessage-configuration
+ problem-based-packs.insecure-transport.js-node.bypass-tls-verification).
Severity: HIGH (#1) / MEDIUM (#2).
CWEs: CWE-1385 (#1), CWE-295 (#2).
Co-authored-by: aeonframework <aeon@aeonframework.dev>
- Add global CSS rules for select elements in dark mode
- Use color-scheme property to signal dark mode to browser
- Explicitly style option elements with dark theme colors
- Fix UsageStats dropdown to use correct CSS variables (bg-surface, text-text-main)
Fixes dropdown text being unreadable in dark theme on usage page:
- Provider filter dropdown
- Table view selector (Model/Account/API Key/Endpoint)
- Pagination page size selector
Tested in Chrome and Firefox with both light and dark themes.
## Features
- Add bun:sqlite adapter with automatic runtime detection (Bun/Node)
- Add bulk API key import (format: `name|sk-key`, one per line)
## Fixes
- Fix add API key for custom providers
- Introduced OllamaLocalExecutor to handle requests for the "ollama-local" provider.
- Removed the direct URL construction for "ollama-local" from BaseExecutor.
- Updated index.js to include the new OllamaLocalExecutor in the executors mapping.
- Enhanced the ProvidersPage component to support dynamic addition of OpenAI/Anthropic compatible providers.
- Implemented handleDeselectModel function to allow users to deselect models in both ComboFormModal and ComboDetailPage.
- Updated ModelSelectModal to handle deselection and visually indicate selected models.
- Enhanced user experience by allowing models to be removed from the selection without closing the modal.