Commit Graph
100 Commits
Author SHA1 Message Date
Jason 16e7ff1c9d feat(deeplink): let links declare the API format, import Pi providers, and leave activation to the user
- Add a single `apiFormat` parameter (anthropic / openai_chat /
  openai_responses / gemini_native) that maps to each app's own field:
  meta.apiFormat for Claude Code, Codex and Grok Build (non-native formats
  need local routing), `npm` for OpenCode, `api` for OpenClaw and Pi,
  `api_mode` for Hermes. Unsupported combinations are rejected while
  parsing; links without it keep the previous defaults byte for byte.
- Support Pi provider links. They write a models.json node and must carry
  a model.
- The confirmation dialog now has a switch for "switch after import"
  (providers), "add to the app config" (additive apps, on by default) and
  "enable after import" (prompts). The link's `enabled` only sets its
  initial state.
- Turning the switch off really only saves the row: a new
  `ProviderService::add_without_switching` skips the first-provider path
  in `add` that would otherwise write the client config. The deep-link
  golden rows now have is_current false accordingly.
- Reject link values that additive apps would expand: Pi keys starting
  with `!` and `$NAME` / `${NAME}`, OpenCode `{env:}` / `{file:}`, and
  `${...}` for OpenClaw and Hermes. These would run a command or send
  local secrets and files to the link's endpoint. Checked when parsing,
  when merging `config`, and when building the provider.
- Update the deep-link manual in zh/en/ja.
2026-10-12 23:26:43 +08:00
Jason 85e574b47e chore(presets): show YYLX as 鱼鱼连线 in Chinese
Give the YYLX presets a nameKey so the picker shows the site's Chinese
name, 鱼鱼连线 (魚魚連線 in zh-TW), while en and ja keep "YYLX". All nine
preset files get the key; MiniMax Code inherits it from the Pi preset.

The preset name and the inner settingsConfig.name stay "YYLX": the
former keys the search alias table and the latter is the provider name
written into client configs.
2026-10-12 23:26:43 +08:00
Jason e0a03184d4 chore(presets): point OpenModel sign-up links at the cc-switch referral URL
Replace the campaign page link with https://www.openmodel.ai?ref=cc-switch
in every OpenModel preset's apiKeyUrl (Claude Code, Claude Desktop, Codex,
Gemini CLI, Grok Build, OpenCode, OpenClaw, Hermes, Pi; MiniMax Code
reuses the Pi preset) and in both links of the four README sponsor rows.
websiteUrl stays on the plain homepage, and the "cc-switch" promo code
already in the READMEs is unchanged.
2026-10-12 23:26:43 +08:00
Jason db93ac2083 fix(grokbuild): point session_summary at the active provider and keep a link's model profile
Session titles: when `models.session_summary` is unset, Grok Build
(verified on 1.0.50) generates titles with the built-in `grok-4.6`
model but sends the request to the default model table's endpoint, so
third-party upstreams reject it and title generation fails. Switching
never wrote this key, so fixing the row templates alone would not
reach live. The Grok switch patch now makes session_summary follow the
provider when it is unset or names a table CC Switch wrote (the
target, the write record, or a table retired in this switch): it points
at the target table, or is removed when switching to the official
account. A user's own choice (a built-in model or their own table) is
left alone.

Deep links: a Grok Build link carrying a config.toml no longer gets
rebuilt into a fixed template that dropped the profile's display name,
description, API backend and context window. The table named by
`[models] default` is kept through an allowlist of Grok 1.0.50 profile
fields; the endpoint, key and model come from the link parameters.
Links whose table uses auth_provider, env_http_headers or
mtls_cert_dir are rejected, and env_key is still dropped. Only one
model table is imported, since a provider owns one table in live.
Links without a config keep the old template byte for byte.

Fixes #7003
Refs #6457
2026-10-12 23:26:43 +08:00
Jason c63c702c70 fix(proxy): bump the Claude Code haiku alias to claude-haiku-5-5
Routing mode writes stable role aliases into settings.json, and Claude
Code looks the alias up in its own model table to decide how to send
thinking. claude-haiku-4-5 is listed there as a model whose thinking can
be turned off and that lacks adaptive thinking, so haiku-tier requests
went out with thinking disabled and upstreams that require thinking
rejected them. claude-haiku-5-5 rejects disabled thinking and uses
adaptive thinking, matching what the sonnet and opus aliases already
send to the same upstreams.

The proxy still maps by the "haiku" role keyword, so routing is
unaffected; the new alias is written the next time the contract is
applied.

Fixes #8118
2026-10-12 23:26:43 +08:00
Jason 11e1326bf5 fix(deeplink): keep a Codex link's provider config instead of rebuilding a fixed template
A Codex provider link could carry a full config.toml, but import only
pulled the key, endpoint and model out of it and rebuilt a fixed
template, silently dropping the provider's auth mode, headers, websocket
support and model catalog URL.

When a link carries a config, keep the parts that belong to the
provider, using the same field lists the live projection writes on
switch:

- from the selected [model_providers.*] table: connection and auth
  fields (wire_api, requires_openai_auth, http_headers, query_params,
  supports_websockets, retries/timeouts, capabilities, ...); the route
  is always renamed to `custom`
- model_catalog_url only on the same origin as the endpoint, since
  Codex sends the provider's credentials when fetching it
- top-level key and provider-exclusive fields (model, reasoning effort,
  web_search, context window, subagent model)

Global settings ([features], [tools], MCP, approval_policy, notify,
local file paths) are not imported. Links whose provider table makes
Codex run a command or read local credentials (auth, aws,
gateway_oauth, env_http_headers) are rejected; env_key is dropped in
favor of the link's own key, and an env_key-only link is rejected.
URL params still take priority, and a table's bearer token is reset to
the final key.

Links without a config keep the old template byte for byte; a new
golden snapshot locks the shape of config-carrying imports. The confirm
dialog notes that only provider settings are imported, and the manual
documents the rules (and that configUrl is rejected).

Refs #7438, #7942, #5510
2026-10-12 23:26:43 +08:00
Jason 450bc039f0 feat(deeplink): enable imported usage scripts by default
A provider link that carries a usage script now enables usage query
unless it says `usageEnabled=false`. Site owners attach the script so
users see their balance; making them switch it on again after import
was an extra step with little safety benefit: the script runs in the
QuickJS sandbox, sends a single request, and deep-link scripts have no
template type, so that request must be same-origin HTTPS with the base
URL.

Links without a script still import usage fields disabled. The
confirmation badge follows the same rule as the backend, and the
script body is still shown in full before the user imports.
2026-10-12 23:26:43 +08:00
Jason 98222d19e3 fix(deeplink): stop losing links on cold start and tighten import checks
Deep links that arrived before the frontend was listening were dropped:
on Windows/Linux the plugin emits the startup URL before our handler is
registered, and Tauri events fired before the JS listener exists (cold
start, window rebuilt in lightweight mode) go nowhere.

- Keep the parsed link (or parse error) in a backend pending slot with a
  monotonic id and emit `deeplink-pending`. The dialog peeks on mount and
  on every notification, and acks by id only after the link is shown, so
  an interrupted read never loses it and a stale ack never clears a newer
  link.
- Read `get_current()` after registering the URL handler so the startup
  URL is handled on Windows/Linux.
- Drop our own `RunEvent::Opened` handling on macOS; the deep-link plugin
  already emits it, which made links open twice.
- Process pending links serially in the dialog (rerun on notifications
  that arrive mid-processing) so a late read of an older link can't
  replace a newer one, including during config merge. The effect no
  longer depends on `t`, so the startup language sync can't interrupt it.
- Reject `configUrl` up front instead of failing after the user confirms.
- Accept `mcode` for prompts and `pi`/`mcode` for MCP apps.
- Warn on the confirm dialog when the provider will be switched to right
  away (`enabled=true`), and refresh the tray menu after a provider import.
2026-10-12 23:26:43 +08:00
Jason fac8bfd8ba fix(installer): don't recreate a deleted desktop shortcut on MSI upgrade
A major upgrade removes the old product and installs the new one from
scratch, and the desktop shortcut component was unconditional, so every
update put the shortcut back even after the user had deleted it.

Look for the existing shortcut during AppSearch (before the old version
is removed) and only install the desktop shortcut on a fresh install or
when it is still present on an upgrade.
2026-10-12 23:26:43 +08:00
Jason 04bd15c9f8 fix(i18n): use 詳細オプション for the advanced options toggle in Japanese
高級オプション reads as "luxury options" in Japanese. Rename the toggle and the docs that quote it (README_JA, the Japanese routing guides and the user manual FAQ).
2026-10-12 00:20:53 +08:00
Jason 3302cc2dfd feat(presets): add NekoCode presets
Add NekoCode (nekoapi.ai) back as a non-sponsor preset for Claude Code,
Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi. MiniMax Code
picks it up from the Pi preset. There is no Gemini CLI or Grok Build
preset: the site serves only Claude and GPT models.

The API now lives on nekoapi.ai. The old nekocode.ai host, which the
site's docs still name, answers /v1/messages and /v1/responses with
404 "Invalid URL", while nekoapi.ai returns the gateway's own 401 for
both. Chat Completions is not served.

- Claude Code uses https://nekoapi.ai without model overrides, as the
  site recommends for its default "max" group.
- Codex uses https://nekoapi.ai/v1 with gpt-5.6-sol over Responses.
- The site restricts its default groups to Claude Code and Codex CLI.
  Other clients need a key from the "max (external)" group and a
  claude-cli User-Agent, so OpenClaw and Pi carry it in headers,
  OpenCode in options.headers and Hermes in extra_headers.
- Claude Desktop goes direct with Sonnet 5.5 / Opus 5.5 / Haiku 4.5.
  Its preset cannot set a User-Agent, so it may still be refused.
- apiKeyUrl points at the token page without the old aff code.

The nekocode icon kept registered when the sponsor entry was dropped in
996d512f6 is reused.
2026-10-12 00:10:39 +08:00
Jason 8845b1ef79 docs(release): add v4.0.9 release notes 2026-10-11 23:29:48 +08:00
Jason 5e57b1ecf1 chore(release): v4.0.9 2026-10-11 23:26:46 +08:00
Jason b17250a302 feat(presets): add TokenFlux presets
Add TokenFlux (tokenflux.dev, a Sub2API gateway) as a non-sponsor
preset for Claude Code, Claude Desktop, Codex, Gemini CLI, OpenCode,
OpenClaw, Hermes, Pi and Grok Build. MiniMax Code picks it up from the
Pi preset.

Models follow the site's public marketplace listing, which names each
group's models and the protocols it serves:

- Anthropic clients use https://tokenflux.dev with Opus 5.5,
  Sonnet 5.5 and claude-haiku-4-5. The main "Claude Max" groups list
  Haiku without the date suffix; only the Kiro group has the dated id.
- Codex defaults to gpt-5.6-sol over Responses.
- Gemini CLI defaults to gemini-3.1-pro, the id the "Google" group
  serves (no preview id there).
- Grok Build defaults to grok-4.5; the Grok groups list
  openai_responses explicitly.
- No endpoint candidates: tokenflux.dev is the only reachable host.

The icon is the site logo rendered from its 272 KB vector SVG to a
180px JPG tile, since the artwork carries its own background.
2026-10-11 23:08:55 +08:00
Jason 2b0ec81bb2 fix(codex): rewrite call_id-less tool outputs for xAI native Responses
Codex Desktop injects function_call_output receipts (create_thread,
automation_update, heartbeat, cross-thread messages) with only an fco_ id
and no call_id. xAI's Responses API rejects the whole request with 422
(missing field call_id), and since the receipt stays in history the thread
never recovers.

Inside the xAI-only compat layer, rewrite these items into user messages
with the same marker the Chat bridge uses, keeping text and image parts.
Items with a call_id are untouched, and other upstreams are unchanged.

Refs #7074
2026-10-11 22:51:29 +08:00
Jason 644b7d5c04 feat(forms): show model parameters in the picker and fill picks quietly
Each model in the picker dialog now shows its context window, reasoning
and image input, resolved with the same lookup as the parameter fill
(same-endpoint preset, then models.dev by endpoint, then the vendor's own
entry), so what the dialog shows is what Apply fills in. Prices are left
out: models.dev only has them when the endpoint is recognized. The vendor
column has a fixed width so the parameters line up. The dialog body mounts
only while open, which is also when it subscribes to the models.dev query.

Since the dialog already showed the values, models added from it are
filled without the "parameters filled, please check" toast. The fill
function of useModelMetadataFill now takes { baseUrl, silent } instead of a
bare base URL (the Pi form passes its per-model address that way). Picking
a model from a row's dropdown still shows the toast.

ClaudeStackModelsField takes the provider's base URL so its dialog can
show parameters too; its rows have no such fields to fill.
2026-10-11 22:51:29 +08:00
Jason 8347e1ab03 refactor(forms): drop the fetch-count toast when the model picker opens
A successful fetch from a model list header now opens the picker dialog,
which already shows how many models came back, so the "fetched N models"
toast only repeated it. Claude Code Aggregation, Codex with a model list
and OpenCode / MiniMax Code no longer show it there. The fetch buttons that
don't open the picker (the Codex default model field, the Claude Code model
mapping) keep it, and the empty-list and error toasts are unchanged.
2026-10-11 22:51:29 +08:00
Jason f724474590 feat(forms): pick fetched models in a dialog that can also remove them
The fetched-model picker was an inline box under the model list that could
only add models: configured ones showed as checked and disabled. It is now
a dialog opened from the list header (Claude Code Aggregation, both Codex
model list layouts, OpenCode and MiniMax Code). A successful fetch opens it;
after that the button reads "Select models" and opens it without fetching
again, with a refresh button inside.

A checkbox now means "in the list": configured models start checked and
unchecking removes their rows on Apply, while Cancel changes nothing. Rows
that stay keep their order and fields, new picks are appended and get their
parameters filled. Configured models the provider did not return are listed
first in their own group so they can be removed too. In Codex, removing the
default model's row moves the default to the new first row, as deleting
the row does. The dialog has search, select all / none for the visible
models, a selected-only filter and a selected count.

Replaces fetchedModelsTitle, modelAlreadyAdded and addSelectedModels with
providerForm.modelPicker.*.
2026-10-11 22:51:29 +08:00
Jason 1afa90af58 fix(forms): align the thinking level picker with the model list row
The thinking level trigger in the Codex model list still used the old
field style (h-9, rounded-md, shadow, up-down chevron), so it stood taller
than the inputs next to it and the row looked misaligned. It now shares
fieldClass and the h-8 height with Input and SelectTrigger, uses the same
chevron and placeholder color, and the delete button drops its h-9
override for the default icon size.
2026-10-11 22:51:29 +08:00
Jason ac2eebc004 revert(forms): drop the Codex main reasoning effort picker
The picker for the top-level model_reasoning_effort duplicated what Codex
users already pick in /model, and the line stays visible and editable in
the TOML editor at the bottom of the form. It also did nothing for the
Aggregation case, where the key is written regardless and the picker was
hidden. Removes the field, its state and TOML helpers, tests and locale
keys. The subagent effort select keeps the shared effort select.
2026-10-11 22:51:29 +08:00
Jason 28ee933ccf refactor(claude-desktop): explain the model mode in plain words
The model configuration hint listed role ID patterns and how the mapping
works, which users could not act on. It is now one sentence for both
modes: use Direct when the provider offers native Claude models, and
Model Mapping for third-party models such as DeepSeek or Kimi. Replaces
modelMappingOnHint / modelMappingOffHint with modelModeHint.
2026-10-11 22:51:29 +08:00
Jason 1e045d2ef7 refactor(forms): move the full URL toggle into advanced options
The toggle only matters for gateways whose endpoint path can't be built
from a base URL, and only in routing and Aggregation modes, yet it sat next
to every endpoint field. It now lives in the advanced options of the Claude
Code and Codex forms (both layouts; Grok Build reuses the Codex fields),
with its explanation in a help tip that also says it is not usable in
direct mode. A provider that already has it on opens with the advanced
section expanded, so it can still be turned off.

The toggle is a shared FullUrlToggle; EndpointField no longer carries it.
Removes fullUrlHint, fullUrlHintGeminiNative and the unused
fullUrlEnabled / fullUrlDisabled keys in favor of fullUrlHelp and
fullUrlHelpGeminiNative.
2026-10-11 22:51:29 +08:00
Jason 5673145647 refactor(forms): shorten the legacy provider fields notice
The panel listing fields an older version saved in a provider, which
switching no longer writes out, now says so in one sentence and drops the
count (the fields are listed right below). Claude Code and Gemini CLI keep
"click to add above, applies to every provider after saving"; Codex and
Grok Build keep "click to copy the TOML".
2026-10-11 22:51:29 +08:00
Jason f59bba5466 refactor(forms): move Claude form explanations into help tips
The full Claude Code form (direct and routing) now follows the same rule
as the Aggregation layout and the Codex form: field explanations live in
the help tip after the field name, and only consequences stay on the page.

- Endpoint, upstream format, auth field and model mapping explanations
  move into help tips; the collapsed advanced-section hint is removed.
- User-Agent and request overrides share one block with the Aggregation
  layout: a single "only in routing and Aggregation modes" line, with each
  field's purpose in its help tip. The shared fields now require `help`.
- The default fallback model explanation moves into a help tip (direct
  and routing versions). Its consequence is shown only when it applies:
  on third-party endpoints, when the Haiku tier is empty in direct mode
  or the fallback model is empty in routing mode.
- Removes unused locale keys: apiFormatHint, customUserAgentHint,
  localProxyRequestOverridesHint, advancedOptionsHint, fallbackModelHint,
  fallbackModelHintDirect.
2026-10-11 22:51:28 +08:00
Jason 6e7811e5e0 fix(proxy): send Bearer to OpenAI-format Claude upstreams and merge stack switches
Claude routing picked the auth header from the env var name alone, so a
provider with ANTHROPIC_API_KEY and an OpenAI Chat or Responses upstream sent
x-api-key, which those protocols reject. OpenAI formats now always use
Authorization: Bearer; Anthropic Messages upstreams keep the env var
semantics. The full form no longer shows the auth field for those formats
(Copilot cards keep it, where it picks the placeholder variable).

In aggregation mode the Claude contract only carried the default provider's
compatibility switches, so picking another member's model could send
features that member rejects. Switches of the other publishing members are
now merged by kind: disabling experimental betas, Artifact or the auto mode
server applies when any member asks for it; tool search, always-on effort
and fine-grained tool streaming stay on only when every member turns them on.
Extra body, thinking and window switches still follow the default provider.

Add a reasoning effort picker to the full Codex form for the top-level
model_reasoning_effort, sharing the select with the subagent effort; picking
the model default removes the key.
2026-10-11 22:51:28 +08:00
Jason 29998ed934 fix(i18n): apply the saved language on cold start
The first-screen language is read from localStorage, which is only a
cache of the language saved in settings.json. When the cache is empty or
stale (dev and release builds use different WebView origins, WebView data
was cleared, ~/.cc-switch was copied to a new machine), the UI fell back
to the system language and only switched to the saved one after opening a
page that mounts useSettings, such as Settings.

Fetch settings once during bootstrap (sharing the ["settings"] query
cache) and, if the saved language differs, switch to it and refresh the
localStorage cache.
2026-10-11 22:51:28 +08:00
Jason e615a6f460 refactor(forms): make provider form hints follow the app's actual mode
The full form serves both direct and routing, so pass the app's actual mode
in: when the app is in direct mode and the upstream format needs conversion,
warn under the format that saving the in-use card breaks requests, or that
other cards need routing first. The Claude fallback model explanation now
describes the main model in direct mode.

Also fix wording that no longer matched behavior (takeover, "direct" for
pass-through formats, route-only hints that also apply in aggregation),
show the Codex default model field in the aggregation layout when the model
list is empty, hide fields the backend ignores on managed-account cards
(Codex/xAI OAuth endpoint, format and auth field; Copilot user agent and
request overrides), and remove 12 unused codexConfig strings.
2026-10-11 22:51:28 +08:00
Jason 5b78fc6298 docs(flatpak): explain skipping updater artifacts without a signing key 2026-10-11 22:51:28 +08:00
Jason 84ca4f0069 refactor(stack): move aggregation layout explanations into help tips
Merge the model list explanation and the default-row rule into one help tip
for both the Claude and Codex aggregation layouts, keep only the restart
reminder visible, and drop the collapsed advanced-section hints. Claude's
upstream format, auth field and endpoint explanations move into help tips in
the aggregation layout only, so the Claude direct form is unchanged. The
route-only line now says the two settings apply in routing and aggregation
modes.
2026-10-11 22:51:28 +08:00
Jason 36e2ce2c78 refactor(codex): move field explanations into help tips
The Codex edit page carried a grey explanation under almost every field.
Following the v7 help rules, explanations of what a field is and when to
use it now sit behind a "?" after the field name, and only consequences
stay on the page: restart Codex after changing the model list, a max
output above the real limit returns 400, User-Agent and request overrides
only apply in routing mode, and Copilot needs routing in every mode.

The collapsed advanced-section summary and the reasoning group note are
gone; the Chat and Anthropic options already say they need routing.
The shared endpoint, User-Agent and request override fields take an
optional help text, so the other provider forms are unchanged.
2026-10-11 22:51:28 +08:00
Jason 9f7ac1fa67 refactor(codex): make the first row of the model list the default model
With a model list, the separate default model field duplicated the list:
the first row is now the default model, as in the Aggregation layout, and
a help tip next to "Model list" says so. Renaming or removing the first row
updates the model; an older card whose default model sits further down
shows that row first without counting as an edit.

Cards without a list keep the default model field below the empty list.
They rely on Codex's built-in /model menu, and adding a list row just to
set the model would shrink that menu to the listed entries. Adding the
first row to an empty list seeds it with the current default model.
2026-10-11 22:51:28 +08:00
Jason 0dae89e25c refactor(codex): move the model list out of the advanced options
The default model and the sub-agent model are picked from the model
list, but the list sat collapsed inside the advanced options below them.
Show it right after the connection fields, before the default model, and
call it "Model list" as the Aggregation layout does.

The advanced section no longer opens on its own for native Responses
providers or cards with a list: that only existed to reveal the mapping.
The default model drops its own fetch button when the list above already
has one; Grok Build has no list and keeps it.
2026-10-11 22:51:28 +08:00
Jason 4f08989a66 refactor(stack): explain the default model with a help tip instead of stars
The star column in the Aggregation model lists of Claude Code and Codex
cluttered every row. The first row has always been the default model, so
drop the stars and the "make default" action, and explain the rule with a
help tip next to the "Model list" label instead.

The Codex direct form lets the default model be any row of the mapping.
When such a card opens in the Aggregation layout, move that row to the top
for display only: saving without edits keeps the stored order and model.
2026-10-11 22:51:28 +08:00
Jason c4770472ed feat(presets): add YYLX presets
Add YYLX (yylx.io, a Sub2API gateway served from app.yylx.io) as a
non-sponsor preset for Claude Code, Claude Desktop, Codex, Gemini CLI,
OpenCode, OpenClaw, Hermes, Pi and Grok Build. MiniMax Code picks it
up from the Pi preset.

- Endpoint candidates cover the site's three published nodes: app
  (auto), global and cn.
- Sub2API keys are bound to one group and the groups carry different
  models. The site points third-party agents at its "Claude API"
  group, which has Opus 5.5 and Sonnet 5 but no Sonnet 5.5, so the
  non-Claude-Code Anthropic presets use Opus 5.5 / Sonnet 5 /
  Haiku 4.5, which every Claude group serves. Claude Code keeps the
  client defaults.
- Codex defaults to gpt-5.6-sol over Responses as the site's guide
  says, Gemini CLI to gemini-3.1-pro-preview, Grok Build to grok-4.5.
- The API key link is the console's /keys route.
- Search alias adds the Chinese name 鱼鱼连线.

The icon is the site's line-art fish, inlined with currentColor so it
stays visible in dark mode, cropped to its bounds and with path
precision reduced to one decimal (27 KB -> 12 KB).
2026-10-11 22:51:28 +08:00
Jason 886915a797 feat(presets): add BotCF presets
Add BotCF (botcf.com, a New API gateway) as a non-sponsor preset for
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes, Pi and
Grok Build, appended after RunToken. MiniMax Code picks it up from the
Pi preset.

- Anthropic clients use https://botcf.com with Opus 5.5, Sonnet 5.5
  and Haiku 4.5; Codex, OpenCode and Grok Build use /v1.
- Codex defaults to gpt-5.6-sol over Responses, matching the site's
  own Codex guide; Grok Build defaults to grok-4.5.
- No Gemini CLI preset: /v1beta is answered by nginx with 405 and
  never reaches the gateway.
- No endpoint candidates: botcf.com is the only reachable host.

The icon is the site's "BF" letter-mark, inlined with its brand
orange baked in since the original relied on an outer CSS color.
2026-10-11 22:51:28 +08:00
Jason 233310d7fb feat(presets): add OpenModel sponsor presets across ten apps
OpenModel (openmodel.ai), a multi-model API gateway with client-specific
discounts for Codex and Claude Code, joins as a sponsor, placed directly
below RunAPI in the sponsor order (preset files and the four README
sponsor tables stay in sync; Gemini has no RunAPI preset, so it follows
AICodeMirror there).

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi on api.openmodel.ai; MiniMax
  Code picks it up from the Pi preset. The gateway serves Anthropic
  Messages, OpenAI Responses and Gemini native (no Chat Completions), so
  every preset uses one of those. Anthropic- and Gemini-style clients use
  the bare host; Codex, Grok Build and the OpenCode Anthropic SDK use /v1.
- Model lineup is Claude Opus 5.5, Sonnet 5.5 and Haiku 4.5, defaulting
  to Opus 5.5. Codex defaults to gpt-5.6-sol (one of its Codex-discounted
  models), Gemini to gemini-3.1-pro-preview, Grok Build to grok-4.5.
- Campaign link only in apiKeyUrl and README hrefs.
- 180px tile icon: the official logo is black on transparent with no
  vector source, so it sits on a white tile to stay legible in dark mode.
- Four-locale README sponsor rows and a 1200px banner.
- Claude preset rows in the selector test go from 77 to 78.
2026-10-11 22:51:28 +08:00
Jason 89add5de39 feat(presets): add Grok Build presets for SoruxGPT, IKunCode and RunToken
All three relays list grok-4.5 on their public pricing pages, so they now
get Grok Build presets on their /v1 hosts, with the same endpoint
candidates as their Codex presets.
2026-10-11 22:51:28 +08:00
Jason 2ff37a76f4 feat(codex): set a default sub-agent model and reasoning effort
The Codex provider form gains a sub-agent model field and a reasoning
effort picker, written to `[agents] default_subagent_model` and
`default_subagent_reasoning_effort` in the provider's config.toml.
Clearing a value removes the key, and an `[agents]` table left empty is
removed with it. The backend already treated both keys as key fields, so
switching providers swaps them and leaves the rest of `[agents]` alone.

In Aggregation mode the two keys are not written, as with Claude Code:
the row names the default provider's model, so a main agent moved to
another provider's aggregated model would send its sub-agents back to
the default provider. Sub-agents follow the main model instead.

When the model is outside the provider's model mapping the form says so
and offers to add it: Codex can't get its context window otherwise, and
routing that converts to Chat or Anthropic replaces unmapped models with
the default one. The field is hidden for Grok Build, official cards and
the aggregation layout.
2026-10-11 22:51:28 +08:00
Jason d25e8d5425 feat(presets): add RunToken presets
Add RunToken (www.runtoken.ai, a New API relay) as a non-sponsor preset
for Claude Code, Codex, Gemini CLI, OpenCode, OpenClaw, Claude Desktop,
Hermes and Pi, with fast.runtoken.ai as an endpoint candidate and the
site's tile logo as its icon.
2026-10-11 22:51:28 +08:00
Jason fec99c5606 feat(presets): add IKunCode presets and SoruxGPT/IKunCode icons
Add IKunCode (a New API gateway) as a regular aggregator preset for Claude
Code, Codex, Gemini CLI, OpenCode, OpenClaw, Claude Desktop, Hermes and
Pi, mirroring the SoruxGPT layout. The base URL is api.ikuncode.ai, the
newer domain the site recommends; api.ikuncode.cc, which its docs still
use, is listed as an endpoint candidate. Codex defaults to gpt-5.6-sol,
Gemini CLI to gemini-3.1-pro-preview, and the other apps get Claude Opus
5.5, Sonnet 5.5 and claude-haiku-4-5-20251001.

Both sites serve New API's stock logo and favicon, so the icons come from
their own brand images instead: SoruxGPT's dragon mark and IKunCode's
avatar, downscaled to 180px JPEGs and registered as tile icons.
2026-10-11 22:51:28 +08:00
Jason 5b708ebd5e feat(presets): add SoruxGPT presets
Add SoruxGPT (ai.soruxgpt.com, a New API gateway) as a regular aggregator
preset for Claude Code, Codex, Gemini CLI, OpenCode, OpenClaw, Claude
Desktop, Hermes and Pi.

Claude Code and Gemini CLI use the native Anthropic and Gemini endpoints
at the site root; Codex uses /v1 with gpt-5.6-sol. The accumulative apps
and Claude Desktop speak Anthropic Messages with Claude Opus 5.5, Sonnet
5.5 and claude-haiku-4-5-20251001 (the undated Haiku id is only enabled
in the default group). The four soruxgpt.com mirror nodes are listed as
endpoint candidates where the preset type supports them.
2026-10-11 22:51:28 +08:00
Jason df40d53c36 docs(release): add v4.0.8 release notes 2026-10-10 23:32:26 +08:00
Jason ed15cd0141 chore(release): v4.0.8 2026-10-10 23:32:26 +08:00
Jason 2e20cdcb5c fix(codex): explain why the official model list is unavailable
When the Codex Stack page fell back to the bundled list or had no list
at all, every cause collapsed into one generic notice: the login check,
the official fetch and the bundled CLI run all returned a bare None, and
skipped logins were not even logged.

Carry the reason instead:
- Official list: no usable ChatGPT login, login not refreshed for over
  8 days, login missing the workspace or user ID, no Codex version,
  fetch failed, or an invalid list.
- Bundled list: no Codex CLI found, the CLI failed to print a list, or
  the list has no usable model. A missing CLI is now told apart from a
  failing one, and the failure detail reaches the default log level.

The Stack view exposes the codes as `noticeReasons`, and the notice
lists them in all four locales. Each fallback also logs one warning.

Recover automatically once the official list is usable again. The
background check only rewrote the client when the cached list changed,
so after a fallback (for example an expired login that Codex later
refreshed) a 304 or an unchanged list left the notice in place until the
next manual switch. A refresh after a fallback now always rewrites the
client, and while the cached entry is still fresh the check rewrites
from it without fetching.

Refs #8087, #7993
2026-10-10 23:23:54 +08:00
Jason 979ce6a60b fix(codex): find the CLI bundled with the Codex desktop app
The Codex Stack page reports "official models unavailable" when both the
online fetch and `codex debug models --bundled` fail. Users who only
installed the desktop app have no `codex` on PATH, so the bundled source
always failed for them.

Add the desktop app's own CLI as the last fallback candidates:
- macOS: ChatGPT.app and the pre-rename Codex.app under /Applications
  and ~/Applications, current `codex-cli/bin/codex` layout before the
  older `Resources/codex`; ChatGPT.app always wins so a leftover
  Codex.app cannot shadow the newer model list.
- Windows: %LOCALAPPDATA%\OpenAI\Codex\bin\<version>\codex.exe, newest
  modification time first (version names do not sort as strings).

Standalone installs still take precedence.

Refs #8087, #7993
2026-10-10 23:23:54 +08:00
Jason 3784fd9e4d fix(ui): soften dark-mode text colors to reduce glare
The three dark-mode text tiers all sat close to white (15.5 / 11.1 / 8.4
contrast on the app background), so screens full of table data and labels
looked harsh and the hierarchy flattened.

- --text-1 #f4f4f5 -> #e4e4e7, and the shadcn --foreground family follows
  so inherited body text matches
- --text-2 #d0d0d8 -> #ababb3, --text-3 #b5b5bd -> #9a9aa2
- --muted-foreground aligned with --text-2

Contrast is now about 13.4 / 7.5 / 6.1; the secondary and tertiary tiers
stay at or above 4.5:1 on card and selected backgrounds. Light mode is
unchanged.
2026-10-10 23:23:54 +08:00
Jason 4215f7ee29 feat(settings): add an Open log button to the diagnostic log section
Reveals cc-switch.log in the system file manager (falls back to the
logs directory when the file has not been created yet), so users can
attach it to issues without hunting for the hidden config folder.
2026-10-10 23:23:54 +08:00
Jason 1f786dadd0 docs(release): expand the Codex session compression section in v4.0.7 notes
Explain how the switch maps to config.toml, when Codex actually compresses
sessions, how resuming and CC Switch's session list and usage handle
compressed rollouts, and what to watch out for (Codex version, startup
notice, turning it off, other tools that read .jsonl). Move it to the top
of the new features.
2026-10-10 18:44:33 +08:00
Jason 0b8662a411 fix(i18n): align quota help and backup hint wording with UI labels
The ja and en quota display help referred to the Accounts page by an old
name, and the en, ja and zh-TW backup hints named the unified Codex
session history switch differently from the setting itself. Use the
labels the UI actually shows.
2026-10-10 16:58:21 +08:00
Jason 790ed8009d docs(release): add v4.0.7 release notes 2026-10-10 16:53:17 +08:00
Jason fda103e9b1 chore(release): v4.0.7 2026-10-10 16:53:17 +08:00
Jason e049660a50 fix(codex): explain that old third-party sessions cannot continue on the official route
When Codex is on the official provider, a request carrying the proxy
placeholder key comes from one of two places: a Codex process that has
not reloaded its config, or a session created under a third-party
provider. Codex resumes a session with the provider id it recorded, so
the latter keeps using the dormant `custom` table that points at the
local proxy with the placeholder key, and restarting Codex does not
help.

The error used to suggest restarting first, which sent users of the
second case into a loop. It now says that an old third-party session
keeps its original provider and needs a new session, and falls back to
the restart hint otherwise.
2026-10-10 16:36:44 +08:00
Jason b7d548b67c feat(quota): let quota windows show what is used instead of what is left
Since 4.0 every quota window reads "x% left". Some users had grown used
to the 3.x "x% used" and asked for a way back, while others had asked
for "left" to match Codex itself, so this becomes a setting instead of a
flip: Settings > General > Appearance gains "Quota display" with Left
(default) and Used.

The setting is stored as quotaDisplay in the device-local settings.json
so the tray, rendered in Rust, follows it too. Only per-window
percentages change: balances, Codex Credits and saved resets keep
showing what is left, and a used-up window still reads "used up".
Colors, the "almost out" hint and which windows the card keeps are
still decided by what is left, so 94% used is orange just like 6% left.

The expanded bars now take their length from the number shown, so a
window at 30% used draws a 30% bar instead of a 70% one; balances keep
drawing what is left. SubscriptionQuotaView stays a pure view and gets
the mode from its wrappers.

Closes #8024
2026-10-10 16:36:44 +08:00
Jason 0fc67c3b35 fix(codex): warn about stale Codex clients on Windows without a process table
The stale-client check reads the process table, which Windows does not
have, so neither the model list notice nor the cached-account notice
ever appeared there, and the restart button always reported that the
daemon was not running.

On Windows the managed daemon is now found through its own daemon.pid:
the process is opened by pid and counts as running only while it is
still active and its creation time matches the recorded one, so a
reused pid is not mistaken for it. Restart is only offered for a daemon
known to be running, since Codex's restart command starts one otherwise.

The desktop app and editor plugins cannot be seen there, so the notice
falls back to "changed since you last dismissed it": the model list and
login fingerprints are recorded in codex-client-ack.json when the user
closes the notice, through the new acknowledge_codex_stale_clients
command, and a later change shows it again with wording that says
CC Switch cannot tell whether those clients are open. Without a record
the current state is taken as acknowledged, so upgrading does not show
it, and the first login seen becomes the baseline, so signing in is not
reported as an account switch.

Closing the notice used to hide it until the app, mode or provider
changed, so changing the model list again on the same provider stayed
silent on every platform. The stack view now carries a revision derived
from the same fingerprints, and the notice reappears once it changes.
2026-10-10 16:36:44 +08:00
Jason e5b1e0966b fix(codex): tell ChatGPT workspaces of the same user apart when switching official cards
Since 4.0, an official Codex login was identified by the id_token subject
alone. The Plus and Team workspaces of one OpenAI user share that subject
and differ only in tokens.account_id, so switching between their cards
was treated as "already signed in as this person": auth.json was never
rewritten and the restart notice never fired. The device login stash was
keyed the same way, so all of that user's workspaces collapsed into one
entry. 3.20.4 wrote the card's login without comparing owners, which is
why downgrading helped.

A login is now identified by user plus workspace, matching how Codex
itself decides whether the account changed.

The stash file gains a version. An older stash is upgraded once on load:
its entries are rekeyed by the new identity, and workspaces that were
merged away are restored from the official cards, but only for users
still present in the stash or in the live auth.json. A user found in
neither has logged out and is not brought back. Switching still restores
a login only from the stash, never from a card snapshot, so a logout is
not revived later either.

Refs #8054
2026-10-10 16:36:44 +08:00
Jason e06ba573d2 fix(windows): keep non-ASCII paths out of generated batch files
cmd.exe decodes a .bat file with the console code page, but the batch
files are written as UTF-8. With a non-ASCII Windows username, paths
under the user profile were garbled.

Tool install/update scripts now start with `chcp 65001`. They run in a
hidden console of their own, so the code page change does not reach
the user's terminal. This fixes upgrades failing with "The system
cannot find the path specified" for npm installs under the profile.

The Claude provider launcher refers to the temporary settings file as
%~dp0<name>, since it sits next to the batch file, so the --settings
path no longer goes through the file bytes and Claude stops silently
starting with its default config. A non-ASCII project directory is
passed through CC_SWITCH_INTERNAL_CWD instead of being embedded. ASCII
directories are still written literally, so terminals that do not
inherit the caller's environment keep working as before. chcp is not
used here because it would persist in the user's interactive session.

Fixes #8034
2026-10-10 16:36:44 +08:00
Jason 69dbb307e5 fix(i18n): scope unified Codex session history copy to the CLI
The Codex desktop app now lists sessions from every provider, so the
unified history toggle only matters for the CLI resume list, which still
filters by the current provider id. Reword the setting description and
both enable/disable confirmations in all four locales to say so, and
note that desktop-only users don't need to turn it on.
2026-10-10 16:36:44 +08:00
Jason 84457b5105 fix(codex): keep a top-level base_url when saving a provider
A Codex provider whose config carries base_url (and wire_api) at the top
level with no model_provider selector was projected as a route-less card:
the editor showed an empty request address, and saving stripped both keys
as key fields with nothing to write them back, so the proxy then failed
with "Codex Provider 缺少 base_url 配置". Switching to such a card in direct
mode also sent Codex to the built-in OpenAI route.

The projection now treats a top-level base_url like the legacy
openai_base_url shape and normalizes it into [model_providers.custom], so
the editor shows the address and saving keeps it.

The form no longer produces this shape: with no selector, setCodexBaseUrl
selects a custom table and writes the address into it. The line-based
rewrite is checked against the parsed TOML and falls back to the old
top-level write (which the backend now normalizes) when it cannot be done
safely, e.g. inline tables, commented table headers or multi-line strings.

Fixes #8039
2026-10-10 16:36:44 +08:00
Jason 347c60c3f7 docs(aggregation): add Codex troubleshooting for sub-agent tasks and the official quota lock
- Explain why Codex sub-agents on another provider cannot read encrypted tasks
  and how the classic sub-agent tools setting avoids it
- Explain that Codex locks the picker to Luna Reserve once the official 5-hour
  quota runs out, and describe the workaround of temporarily making a
  third-party provider the default (#7945, #7962, #7974)
- Update the Chinese guide and the Chinese, English, and Japanese manuals
2026-10-10 10:49:23 +08:00
Jason 8641cd1d6b feat(codex): read compressed session rollouts and add a compression toggle
Codex can compress rollouts untouched for 7 days into a sibling
rollout-*.jsonl.zst and delete the original, either through
[features] local_thread_store_compression or through the app-server
rollout/compress RPC, which ignores that flag. It decompresses a rollout
back to .jsonl when the session is resumed. CC Switch only read .jsonl,
so compressed sessions vanished from the session list, their usage was
never imported, and history bucket migrations skipped them while still
recording themselves as done.

- Add codex_rollout_file, shared by the readers below: recognise both
  forms, read .jsonl.zst transparently, write a rollout back in its own
  form, and keep only the plain file when both forms briefly coexist.
- Session list: decompress only the head of a compressed rollout. Use
  its mtime, which Codex preserves, as the last active time and show no
  last-reply preview, so scanning tens of GB stays fast. Content refs are
  resolved against the decompressed stream. A stale source path falls
  back to the other form, and deleting a session removes both forms.
- Usage import: a cursor is inherited from any path with the same
  logical file name, taking the furthest line offset, in any directory
  and in both directions. Request-id dedup only covers the 30 days of
  detail rows kept before rollup, and compressed rollouts are older, so
  this is what prevents double counting across compress and resume.
  Compressed files persist their on-disk size so unchanged ones are
  skipped without decompressing.
- History bucket migrations rewrite compressed rollouts in place, keep
  them compressed, and keep their mtime so old sessions do not jump to
  the top of the list.
- Settings > Codex gets a "Compress old Codex sessions" switch backed
  directly by config.toml (write true to enable, remove the key to
  disable) through the live write engine, showing the current size of
  the sessions directories. It re-reads when the saved Codex directory
  changes.
2026-10-10 10:49:22 +08:00
Jason ef24a01912 test(codex): skip the thread version SQLite fixture on WSL temp dirs
The test added in dc93c4962 builds an on-disk SQLite fixture in a temp
dir. The WSL2 nightly points TEMP at a \\wsl.localhost 9P share, where
Windows cannot take SQLite locks, so creating the table failed with
"database is locked" on every retry. Guard it with
sqlite_unsupported_in_temp_dir() like the other SQLite fixture tests.
2026-10-10 08:06:30 +08:00
Jason c261126630 docs(release): add v4.0.6 release notes 2026-10-09 22:15:03 +08:00
Jason 314968b65d chore(release): v4.0.6 2026-10-09 22:15:02 +08:00
Jason ee50c12b81 test(apps): expect the action color on the apps page visibility switches
8919d05cb dropped the neutral switch tone but left the apps page test
asserting data-tone="neutral". Assert that the visibility switches no
longer carry a tone.
2026-10-09 22:15:02 +08:00
Jason c5efe0baaa feat(providers): add a search button to the provider page header
Provider search could only be opened with Cmd/Ctrl+F, so users with many
providers did not know it existed.

- Add a search button next to "Add provider" in the page header. It
  toggles the existing search panel; the panel closes when switching apps
  or leaving the provider page.
- Match the API address as well as name, notes, and website. The address
  is read from every app's settingsConfig shape by a new shared
  extractProviderBaseUrl, which ProviderCard now also uses.
- Only filter while the panel is open. Closing it with Esc used to leave
  the list silently filtered.
- Portal the search panel to document.body. As a child of the list's
  space-y container it counted as a sibling, so opening it pushed every
  provider card down by 1rem.
- Add a "Show provider search" setting (on by default, device-local).
  Hiding the button keeps the Cmd/Ctrl+F shortcut.
2026-10-09 21:50:56 +08:00
Jason dc93c4962d fix(codex): keep official models visible in the aggregation catalog
With the official account as the aggregation default, CC Switch fetches
the official model list with the version of the `codex` on PATH. The
server trims the list by client_version, and for 0.140 or older it only
returns hidden entries (gpt-5.5, codex-auto-review). Desktop-only users
often keep an old CLI on PATH, so the catalog was written with every
official row hidden and the picker showed only the aggregated third-party
models.

- Use the newest Codex in use: the PATH CLI or the cli_version recorded
  on the 20 most recently created threads in Codex's state DB, which also
  covers the desktop app and editor extensions on every platform.
- A source with no `visibility = "list"` row is not usable. When the
  fetched list has none, fall back to the bundled list (no catalog if that
  has none either) and show a new notice asking to update Codex.

Fixes #8014
2026-10-09 21:30:23 +08:00
Jason 4629ee2f27 fix(tray): show every quota tier in the app row title
The app row title kept only the two tiers with the least quota left. With
three windows (5-hour / weekly / monthly, e.g. Volcengine Agent/Coding
Plan, Zhipu, MiniMax) the 5-hour window resets most often and usually has
the most left, so it was almost always dropped. 3.x listed every tier.

Write all tiers in their original order. The "almost out" suffix still
follows the tier with the least left. Remove the now unused pick_lines.

Closes #8011
2026-10-09 21:11:44 +08:00
Jason 8919d05cb3 fix(ui): use the action color for the apps page visibility switches
The neutral switch tone drew the checked track in dark gray next to a
medium-gray unchecked track, so on and off were nearly indistinguishable
in light mode. Drop the neutral tone (its only caller was the apps page
"Show" column) and let those switches use the same action color as every
other switch.

Closes #8010
2026-10-09 21:09:53 +08:00
Jason 033017b0be fix(skills): stop the import dialog from locking while discover refetches
The import mutation's onSettled awaited every affected refresh, including
the discover list, which downloads each repository from GitHub (up to 60s
per repo). TanStack Query only settles a mutation after onSettled resolves,
so isPending stayed true and the import dialog could not be cancelled or
closed until discover finished. The discover segment stays mounted once
opened, so this hit anyone who had visited it on a slow GitHub connection.

Only await the installed and unmanaged refreshes the dialog depends on;
repos and discover are still invalidated in the background.

Fixes #7994
2026-10-09 21:07:30 +08:00
Jason c1cb531fc9 feat(skills): update all Skills from the check button and list them before confirming
Updating meant checking, then filtering the table to "updates", then
finding "Update all" in the filter row. The confirm dialog also only gave
a count, and it stayed open until every Skill had finished downloading.

- The "Check updates" button turns into "Update all (N)" once updates are
  known; re-checking moves to the "..." menu. A check that finds updates
  opens the confirm dialog directly instead of a toast.
- Drop the duplicate "Update all" button from the filter row.
- The confirm dialog lists each Skill with a clickable source repo that
  opens the same page as the row link. V7ConfirmDialog gains a `details`
  slot because the body renders inside a <p>.
- Confirming closes the dialog at once; the toolbar button shows
  "Updating 1/2..." and the row menu spins until the batch is done.
- Remove the now unused `skills.updatesFound` string; add
  `skillsPage.confirm.updateListAria` and `skillsPage.updatingProgress`
  in all four locales.
2026-10-09 20:41:37 +08:00
Jason ae934663b5 fix(codex): build third-party catalog entries from the bundled classic tool template
ProxyChat and Copilot catalogs cloned the gpt-5.5 entry from Codex's own
models_cache.json (falling back to `codex debug models --bundled`, then the
bundled copy). The template only supplies the tool contract — slug, name,
context window and visibility are all overwritten — but every change
OpenAI made to that cached entry leaked into third-party models: the
hidden flag behind #7924/#7954, and newer entries carry
`tool_mode: code_mode_only`, v2 sub-agents and experimental tools the
Chat converter was never built for. The cache also announces GPT-5.5's
retirement on 2026-10-14, after which every switch would fall through to
spawning the Codex CLI.

- Always use the bundled template for ProxyChat / Copilot; drop the cache
  and CLI lookups and the per-process template cache.
- Refresh the bundled template to match Codex 0.162's own gpt-5.5 entry:
  `shell_type: unified_exec`, and include the skills / plugin usage
  instructions (both default to false in codex-rs when absent).
- Rename the loader to `load_codex_classic_tool_template` and document
  why it stays on the classic gpt-5.5 shape.
- Tests lock the classic tool contract and that a hostile
  models_cache.json no longer affects generated entries.

Existing ProxyChat sessions lose the cached `comp_hash` and get compacted
once on resume.
2026-10-09 17:21:20 +08:00
Jason 1beb8fa881 feat(ui): make the CC Switch update dot green and open About from it
The sidebar used the same neutral dot for two different things: tool
updates on "Apps" and a new CC Switch release on "Settings". The dot was
barely darker than the icon, and clicking "Settings" landed on General,
so the update button was still two steps away.

- The CC Switch update dot on "Settings" and on the About row is now the
  success green. Tool updates on "Apps" keep the neutral dot.
- Clicking "Settings" in the sidebar while an update is available opens
  About directly. Cmd+, still opens General.
2026-10-09 16:56:59 +08:00
Jason 449c7e78af docs: add aggregation mode guide and update user manuals
- Add the Chinese aggregation guide with the final cover, client screenshots, and request-routing diagram
- Update Chinese, English, and Japanese manuals to distinguish model fetching, models.dev metadata completion, and manual entry
- Clarify official subscription support, default models, and client restart requirements; add README links
- Validate 90 local documentation links, five images, and Git diff formatting
2026-10-09 16:51:08 +08:00
Jason 4aeaccf125 fix(codex): unbind providers when a managed account is removed
Deleting a managed ChatGPT account (or logging out of all of them) left
every Codex provider bound to that account pointing at a dead local ID.
#7395 made such cards recoverable by hand (switch away, pick another
account); this removes the dangling bindings where they are created.

- Remove/logout detach the affected Codex providers in one SQLite
  transaction: drop the binding, store an empty auth like a manual
  unbind, backfill category=official for legacy cards, keep unknown
  meta keys. The card then follows the native Codex login.
- Only this device's account IDs are unbound. Provider rows sync across
  devices, so a row bound to another device's account stays bound.
- The command reports "account removed but unbinding failed" separately.
  Any row still left dangling (failed unbinding, older versions, synced
  rows) keeps going through the #7395 recovery path, whose tests now
  create such rows directly.
- Frontend invalidates the Codex provider list after remove/logout,
  including on partial failure.

Thanks @SaladDay for the report and the first fix in #7060.

Fixes #7055
Refs #6969
2026-10-09 16:51:08 +08:00
Jason c37a26259e fix(proxy): explain unreadable sub-agent tasks instead of emptying them
A v2 sub-agent's task arrives as the trailing agent_message whose
payload only the main agent's backend can decrypt. The opaque-state
rectifier used to replace that payload with a placeholder like any
other foreign ciphertext, so a retry sent the sub-agent an empty task.
The upstream also often rejects it inside a 200 stream, which bypassed
the rectifier entirely and left Codex retrying the same failure.

- Keep the current task untouched when rectifying; earlier agent
  messages and other foreign state are still cleared.
- For native Responses passthrough requests ending in an encrypted task,
  hold the stream until the first non-lifecycle event under one fixed
  deadline (first-byte timeout, at most 10s), so heartbeats cannot extend
  it. A self-identified rejection before output joins the normal
  rectifier path; anything else replays byte for byte.
- When nothing else is left to clear, or the retry is still rejected,
  return a neutral 400 unreadable_encrypted_agent_task that names the
  classic sub-agent toggle, instead of letting Codex retry.
2026-10-09 16:51:08 +08:00
Jason 6760cfbbe1 feat(codex): add an opt-in classic sub-agent tools toggle for aggregation
Codex multi-agent v2 has the main agent's backend encrypt the task it
hands to a sub-agent, so in aggregation mode a sub-agent on a different
provider cannot read its task. Codex picks the tool version from each
catalog row's multi_agent_version, which the aggregated catalog copies
from the official catalog (v2 for Sol and Terra).

Add a device-level setting, off by default, that stamps
multi_agent_version = "v1" on every row of the aggregated catalog
(official rows included: ciphertext minted by OpenAI is just as
unreadable elsewhere). Classic tools pass the task as plain text at the
cost of v2-only tools such as followup_task and interrupt_agent. With
the setting off the catalog is unchanged.

Changing the setting rewrites the catalog right away when Codex is in
aggregation mode and rolls the setting back if the write fails. When
config.toml forces [features] multi_agent_v2, which Codex applies before
catalog rows, the settings row says the toggle has no effect.
2026-10-09 16:51:08 +08:00
Jason 18bdea7c73 chore(icons): update DMXAPI logo 2026-10-09 16:51:08 +08:00
Jason 0be33a8442 fix(quota): bring back colored quota numbers and warn below 20%
The all-gray quota text made it hard to tell a healthy limit from one
that is running low. Restore the v3 green / orange / red, but color only
the number ("88%", "¥82.10", "$2500", the reset count) so tier names and
"left" stay gray. Lines with no number (used up, expired, query failed)
are still colored as a whole.

- Warn once a tier has less than 20% left (was 10%), in the card and in
  the tray's "almost out".
- The "used $X" line has no total, so it stays uncolored.
- The expanded bars and the auth center keep neutral numbers while
  normal, since the bar already carries the color.
2026-10-09 16:51:08 +08:00
Jason 2db86e94da docs(release): add v4.0.5 release notes 2026-10-08 23:00:52 +08:00
Jason cf02f670ff chore(release): v4.0.5 2026-10-08 23:00:52 +08:00
Jason 5d24fdf9a6 feat(usage): show all history in the all-time heatmap
The all-time heatmap only covered the last 53 weeks, so older usage was
cut off. It now keeps the last 53 weeks as the default view and adds a
"Show more" toggle that lists earlier history in contiguous 53-week
spans, back to the first recorded day. Each span is titled with its
exact date range and totals, and shading is bucketed across all spans
so they stay comparable.

A new get_usage_first_date command returns the earliest local day across
request logs and daily rollups, honoring the dashboard's app, provider
and model filters, so the heatmap knows how many spans to fetch in one
trends query.
2026-10-08 22:48:02 +08:00
Jason 5ae6ad3888 ci(wsl): retry flaky lib tests in the WSL2 nightly
The 9P share sometimes aborts a write mid-test with os error 995
(ERROR_OPERATION_ABORTED): a Claude Desktop config test passed in 1.3s
on one run and failed after 17s on the next with no code change.

Retry up to twice in the nightly only. The ci profile used by PRs keeps
failing on the first error, and nextest still lists retried passes as
FLAKY so repeat offenders stay visible.
2026-10-08 11:25:51 +08:00
Jason 7d8004c408 test(wsl): skip three lib tests the 9P share cannot host
The WSL2 nightly now reaches the lib suite: 3299 of 3302 pass. The rest
are limits of \\wsl.localhost, not product bugs:

- Windows cannot create symlinks on the share ("Incorrect function"),
  so the catalog escape and grokbuild symlink-cycle tests cannot build
  their fixtures. Skip when the link fails under a WSL temp dir and
  keep failing anywhere else.
- Extracting 13,248 files takes over the 180s per-test timeout on 9P.
  Production extracts to the system temp dir, so skip the entry-limit
  test when TEMP is a WSL share.
2026-10-08 11:11:24 +08:00
Jason faae0e4d7e ci(wsl): run the WSL2 nightly from a nextest archive
The nightly has failed since 10-05 with LNK1327: the contract step
re-invoked cargo with TEMP on \\wsl.localhost, cargo re-linked the lib
tests, and mt.exe cannot create its manifest temp file on a UNC path.

Build the lib tests once into a nextest archive with the native TEMP,
and run both the contract test and the lib suite from that archive, so
nothing is built while TEMP points at the WSL share. The archive is
extracted to the runner temp, and --no-tests fail replaces the manual
check that the contract test was discovered.

Supersedes #6856.
2026-10-08 10:43:48 +08:00
Jason efd236a474 docs(manual): update the user manual for v4.0
Bring the zh, en and ja user manuals up to the v4.0.4 interface: the
sidebar layout, the six Settings groups, the Direct / Routing /
Aggregation connection modes, Accounts, and the Apps page. Old
Settings paths (Advanced, Routing, Auth) and takeover wording are gone.

- Add 4.6 Aggregation Mode in all three languages and list it in the
  manual indexes.
- Rewrite 1.3 Interface Overview and 1.5 Settings around the new
  layout; refresh provider, extension, routing, usage and FAQ pages.
- Use the v4.0 release screenshots where they fit and leave
  TODO(v4 screenshot) markers elsewhere.
- Update the version section of each manual index to v4.0.4.
- Remove the 40 v3 screenshots that are no longer referenced.
2026-10-07 20:42:08 +08:00
Jason a40adef3f7 fix(usage): import Grok Build session usage after 60s instead of 10min
The settle window that delays Grok Build session imports reused the
±10-minute takeover guard window, so a finished turn in Direct mode took
10-11 minutes to show up in usage stats. The settle window only needs to
cover the few seconds between turn_completed being written and the proxy
rows for that turn landing in the database, so cut it to 60 seconds and
keep the guard window unchanged.

A compile-time assertion keeps the settle window below the guard window,
and two tests cover a 90-second-old turn importing and a proxy row that
lands a few seconds after the event still blocking the import.
2026-10-07 20:16:35 +08:00
Jason a7f66764e3 chore(github): list all ten apps in issue templates
Add Claude Desktop, Grok Build, Hermes, Pi and MiniMax Code to the app
dropdowns, bump the version examples to 4.0.4, and point to
Settings → About for the version number.
2026-10-07 20:02:36 +08:00
Jason 5dd824c730 docs(readme): update all four READMEs for v4.0
- Replace the v3.8 screenshots with six v4.0 screenshots (main, apps,
  aggregation, Codex aggregated models, session reader, usage)
- Rewrite Quick Start around the sidebar and the Direct / Routing /
  Aggregation tabs, add the Apps page and Accounts, and note that the
  project switcher is now hidden by default
- Add an Aggregation column and section; tray switching now covers
  Claude Desktop and Pi now supports MCP
- Replace v3 settings paths in the FAQ with the v4 locations and add an
  entry on restarting Codex after the aggregated model list changes
- Point the release notes link at v4.0.4
2026-10-07 20:02:36 +08:00
Jason 01ee685da8 docs(release): lead with usage stats in the v4.0.4 notes 2026-10-07 19:09:33 +08:00
Jason a29a4f3868 docs(release): add v4.0.4 release notes 2026-10-07 18:34:16 +08:00
Jason c2282a1073 chore(release): v4.0.4 2026-10-07 18:34:16 +08:00
Jason 5b10d1996c fix(usage): keep the speed column visible in a narrow window
At the minimum window width (900) with the sidebar expanded, the request
log table area is only 644px wide, but the table had min-w-[700px]. The
rightmost speed column was pushed into the table's horizontal scroll,
whose scrollbar sits below the last row and is usually off-screen, so
users saw the column as missing.

Lower the minimum to 620px, matching the provider, model and pricing
tables. The provider and model columns already truncate (max-w-0), so
they absorb the squeeze.
2026-10-07 12:37:37 +08:00
Jason e0b36500dc feat(profiles): hide the project switcher by default
The project switcher showed on the providers page header and in the tray
for every new install, even though most users never create a project.
Flip the default so it stays out of the way until turned on in Settings.

- Use plain #[serde(default)] for show_profile_switcher and drop the
  default_show_profile_switcher() helper; AppSettings::default() now
  sets it to false
- Change the frontend fallbacks in App.tsx and GeneralSection from
  ?? true to ?? false so the header does not flash the switcher before
  settings load

Existing users keep their current behavior: the field has been
persisted to settings.json since v3.17.0, so anyone who has saved
settings already has an explicit true.
2026-10-07 12:31:44 +08:00
Jason e09fdf046c feat(settings): give the About card's star prompt its own dismissible strip
The star prompt sat at the end of the link row with ms-auto, so it
wrapped onto a line of its own and read like a stray underlined caption.

- Move it into a tinted strip at the bottom of the card with a slowly
  spinning star (stops under prefers-reduced-motion) and a "Star on
  GitHub" button that opens the repo
- Drop the separate GitHub button while the strip is shown, since the
  star button goes to the same place
- Add a close button; dismissal is remembered per device in
  localStorage, and the plain GitHub button returns to the link row
- Add settings.starOnGithub to all four locales
2026-10-07 09:16:14 +08:00
Jason f61dc5f520 fix(macos): relaunch via LaunchServices so the window comes to the front after restart
tauri::process::restart spawns the binary directly. Since macOS 14 app
activation is cooperative, so the new process's activation request is
denied and its window stays behind other apps after an in-app update or
a config-dir restart.

restart_process now launches the bundle with `open -n` on macOS, letting
the still-frontmost old instance hand activation to the new one, and
falls back to tauri::process::restart if that fails. restart_app uses
restart_process instead of app.restart() so both restart paths behave
the same.
2026-10-06 23:52:59 +08:00
Jason d35726e286 test(hermes): batch the scan-limit fixture inserts in one transaction
first_user_messages_is_scoped_to_listed_sessions inserts 1002 rows, each
in its own autocommit. On the Windows runner every commit flushes to disk,
so the test took 150-180s and intermittently hit nextest's 180s timeout
(2.5s on Linux). Wrap the fixture inserts in a single transaction.
2026-10-06 22:14:45 +08:00
Jason 8596a233b2 docs(release): add v4.0.3 release notes 2026-10-06 21:20:09 +08:00
Jason 014a33a42b chore(release): v4.0.3 2026-10-06 21:20:09 +08:00
Jason f3e3a95560 docs(manual): add the auto mode server-check toggle to the Claude quick toggles table
The toggle shipped without a manual row. Describe what it changes, the
per-session notice it avoids on gateways, the new-provider default and
that official endpoints should leave it unchecked; the per-provider note
now covers three options instead of two.
2026-10-06 21:13:04 +08:00
Jason 65e79de845 feat(claude): explain each quick toggle with a help tip
The quick toggles above the Claude config editor had no explanation,
except a native title tooltip on the auto mode one. Put the standard "?"
help tip after each label: one sentence on what the toggle does, why
you would want it, and whether it is global or follows the provider.

Render the toggles from one list so every entry gets the same
checkbox + help layout, with copy under claudeConfig.<key> and
claudeConfig.<key>Help. The help button sits outside the <label>, so
opening the tip never flips the checkbox.
2026-10-06 21:13:04 +08:00