Commit Graph
100 Commits
Author SHA1 Message Date
Jason f3b18df120 docs(release): add v3.20.2 release notes 2026-09-07 21:54:01 +08:00
Jason b6254432a9 chore(release): v3.20.2 2026-09-07 21:54:01 +08:00
Jason 389dd96cb8 feat(provider): add SoleAPI sponsor presets across all nine apps
SoleAPI (soleapi.com) joins as a sponsor, placed directly above Micu in
the sponsor order (preset files and the four README sponsor tables stay
in sync).

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi. Endpoints follow the
  vendor's CC Switch setup page: Anthropic/Gemini-style clients use the
  bare host, OpenAI-style clients (Codex, Grok, Responses) use /v1.
- Haiku's upstream ID carries a date suffix
  (claude-haiku-4-5-20251001), so the Desktop preset uses mappedRoutes
  and the multi-model presets list the dated ID.
- Codex defaults to gpt-5.6-sol (vendor config.toml), Gemini to
  gemini-3.8-flash.
- No OpenClaw/Hermes cost fields: site prices are official rates times
  a per-channel multiplier and change with promotions.
- Referral link only in apiKeyUrl and README hrefs; request endpoints
  stay bare.
- Inline currentColor icon traced from the vendor SVG, metadata entry,
  four-locale partnerPromotion copy, and a 1920x786 banner.
2026-09-07 20:23:32 +08:00
Jason e724270dbb fix(codex): proxy image edits endpoint
Codex's built-in ImageGen switches from `/images/generations` to
`/images/edits` whenever it references existing images (explicit paths or
the last N generated images). After #7036 the generations route is proxied,
but edits still hit an empty local route and the tool fails with 404.

Add `CodexStandaloneEndpoint::ImagesEdits` and route the four local aliases
through the same standalone passthrough as generations. The full-URL sibling
derivation now uses a shared suffix table for the Images family, so a
provider configured with either `/images/generations` or `/images/edits` as
a complete URL derives the other route; opaque URLs still fail closed.

Tests cover the effective-endpoint match, full-URL rewrites in both
directions, and an end-to-end alias test that asserts auth replacement,
body preservation (data-URL images) and usage logging with the real Images
API `input_tokens_details` shape.
2026-09-07 20:23:32 +08:00
Jason 648d89cd7b fix(provider): pin models URL for JieKou and Novita Claude presets
Both serve the Anthropic-compatible API under /anthropic but expose the
OpenAI-compatible model list under /openai/v1. The candidates derived
from the base URL (/anthropic/v1/models, and /v1/models and /models at
the stripped root) all return 404, so "Fetch models" could never
succeed for either provider.

Also drop the JieKou assertion that pinned the absent modelsUrl, which
locked the broken behaviour in.
2026-09-07 20:23:32 +08:00
Jason ccc140a2c0 feat(pricing): refresh seed for September 2026 vendor price changes
Audit run of /jason-update-model against models.dev, every change confirmed
on the vendor's own pricing page.

New row:
- qwen3.8-flash: 0.15 / 0.47 flat across the 1M window, cache 0.016 / 0.20.

The other two rows this audit found have since landed through contributed
PRs, so only the annotations remain here:
- gpt-6-astra came in via #7162; this commit keeps its literal "12.5" and
  adds the note that low/medium/high/xhigh fall back to the base row.
- gemini-3.8-flash came in via #7164; this commit adds the note that
  0.75 / 3.75 / 0.075 is introductory through 2026-12-31 and reverts to
  the 1.50 / 7.50 / 0.15 list price on 2027-01-01.

Price changes (seed + repair, guarded on the previous values):
- gemini-3.6-flash: Google now lists the same 0.75 / 3.75 / 0.075
  introductory price through 2026-12-31.
- gpt-5.6-sol plus the bare gpt-5.6 row and its five effort-suffix rows:
  promotional 4 / 20 / 0.40 / 5, "available at least through November 21,
  2026". The alias rows must move in lockstep because exact-id lookup
  runs before suffix stripping.
- minimax-m2, minimax-m2.1, minimax-m2.5: the official pay-as-you-go
  page lists all three at 0.3 / 1.2 with cache read 0.03 and write 0.375.

The new repair entries are appended after the existing chain so old
databases hop through the intermediate prices (gpt-5.6-sol cache-write
backfill, minimax-m2.5 0.12 -> 0.15). The repair-order test now locks
both chains alongside the DeepSeek one.
2026-09-07 20:23:32 +08:00
Jason 2cd4006456 fix(codex): stamp requires_openai_auth on takeover writes to match the live login state
With official-login preservation off (the default), a direct switch to a
third-party provider deletes ~/.codex/auth.json. Enabling proxy takeover
afterwards rebuilt config.toml from the stored card, which still carried the
pre-0.149 `requires_openai_auth = true`, so Codex >= 0.149 trapped the TUI in
the login screen even though the proxy placeholder bearer token was in place.
The same gap hit hot switches after `codex logout` mid-takeover.

The takeover writer now stamps the active custom table's flag from the login
state Codex itself would observe, mirroring the direct-switch plan:

- Resolve the auth mode exactly like `AuthDotJson::resolved_mode` (explicit
  `auth_mode` first, then personal_access_token > bedrock_api_key >
  bedrock_access_keys > OPENAI_API_KEY > ChatGPT) before checking the
  credential, so Bedrock credentials next to a stale API key are never
  promoted to an OpenAI login (`account_state` would fail with
  UnsupportedBedrockApiKeyAuth and abort TUI startup).
- Decide `cli_auth_credentials_store` before touching auth.json: keyring and
  auto stores are undecidable from disk (the card's flag is left alone),
  ephemeral is always signed out, and only the file store reads the file.
  A missing, unreadable or corrupt file means signed out, as it does for
  Codex, and no longer fails the takeover write.
- Proxy-injected OAuth cards (xai_oauth, github_copilot) keep the neutralized
  `false` regardless of what is on disk; the official login is never their
  credential.

Official passthrough and managed-official branches are untouched.
2026-09-07 20:23:32 +08:00
Jason b1250dc7ac fix(tools): read Hermes latest version from GitHub Releases instead of PyPI
The tools panel asked PyPI for Hermes' latest version, but upstream
stopped publishing there after 0.19.0 (2026-07-20) and now ships only
through GitHub Releases. Users saw "latest 0.19.0" forever, often below
their installed version, and the upgrade button never appeared because
it only shows when latest is strictly newer than current.

PyPI was never the channel cc-switch installs or upgrades Hermes
through: install runs the official install.sh (git clone of main) and
update runs `hermes update` (git pull). Probe the same channel.

- Query GitHub Releases first and fall back to PyPI only when GitHub is
  unreachable or rate-limited.
- Parse the semver from the release name ("Hermes Agent v0.21.0
  (v2026.8.31)") since the tag is calendar-versioned; reject calendar
  numbers on both paths so the frontend never treats 2026.8.31 as a
  version and reports a permanent update.
- Hide the PyPI fallback value when the local version already leads it,
  so a degraded probe shows "unknown" rather than "latest < current".
- Add a 15s request-level timeout to the GitHub and PyPI probes; the
  shared proxy client allows 600s, which would stall the Hermes card
  and the refresh/upgrade-all buttons when api.github.com hangs.

Refs: #6475, #6618, #7033
Supersedes: #6621
2026-09-02 09:56:07 +08:00
Jason 4f62f676bd fix(presets): let the 9527CODE icon follow the theme
The nine 9527CODE presets pinned iconColor to #4F46E5, which came from
the docs site favicon rather than the logo itself. The vendor mark is
two-tone black (#000000 over #2D2D2D).

The fix is not to swap in #000000. ProviderIcon resolves colour as
preset iconColor -> metadata defaultColor -> undefined, and only the
undefined case leaves style.color unset, letting the inline SVG's
fill="currentColor" inherit the surrounding text colour. Any hex opts
out of theming, and a black one disappears in dark mode: the icon sits
in a bg-muted container at 18% lightness there, against a --foreground
of 98%, and nothing in the stylesheet inverts icons for dark mode.

Dropping iconColor and marking the metadata defaultColor currentColor
renders near-black on light, matching the official logo, and near-white
on dark. The traced SVG already suits this — its two tones are a
0.82-opacity base layer plus an opaque overlay, so the depth
relationship is relative and survives the inversion. Hard-coding both
tones would have foreclosed it.

This follows packycode and the 26 other currentColor icons.
2026-09-02 09:17:35 +08:00
Jason 68d71cc639 feat(presets): add 9527CODE presets across nine apps
9527CODE (9527.codes) is a sponsored enterprise-grade relay running New
API, fronting Claude, GPT, Gemini, Grok and Chinese models behind a
single key. It lands across Claude Code, Claude Desktop, Codex, Gemini
CLI, Grok Build, OpenCode, OpenClaw, Hermes and Pi, and is inserted
after APIKEY.FUN in both the preset files and the sponsor table so the
README order stays the ordering SSOT.

The gateway accepts every protocol with or without the /v1 prefix, but
the presets are written to match what each client actually appends
rather than relying on that fallback:

- Claude Code / Desktop / Gemini CLI:  9527.codes       (client adds /v1)
- Codex / Grok Build:                  9527.codes/v1    (responses)
- OpenCode:                            9527.codes/v1    (@ai-sdk/anthropic)
- OpenClaw / Hermes / Pi:              9527.codes       (anthropic-messages)

OpenCode and the other Anthropic-protocol clients look inconsistent but
are not: the AI SDK appends /messages to its baseURL while OpenClaw,
Hermes and Pi append /v1/messages themselves.

Three non-obvious details:

- endpointCandidates carry only the two live backups, api.9527.codes
  (overseas) and cdn.9527.codes (Asia-Pacific). The vendor docs list a
  fourth route, txcdn.9527.codes, which does not resolve; including it
  would offer users an endpoint that cannot connect.
- No cost is recorded for the OpenClaw and Hermes models. What a user
  actually pays is the official price times a token-group multiplier
  that varies per group and plan, so quoting list prices would mislead.
- The icon is an inline SVG traced from the vendor logo, drawn as a
  full silhouette at 0.82 opacity with the black regions layered on top.
  Stroking the two tones as separate adjacent paths leaves a hairline
  seam between them; the base layer covers it.
2026-09-01 23:47:14 +08:00
Jason cbbf727914 feat(presets): add AICodeWith presets across eight apps
AICodeWith (aicodewith.ai) is an aggregator exposing 30 models — Claude,
GPT, Gemini, GLM, Kimi, DeepSeek and Grok — behind a single key and
balance. It lands across Claude Code, Claude Desktop, Codex, Gemini CLI,
OpenCode, OpenClaw, Hermes and Pi.

The vendor exposes one endpoint family per protocol and its own docs warn
against reusing the generic /v1. All four are transcribed from the
official client guides:

- Claude Code / Desktop: api.aicodewith.ai             (Anthropic, no /v1)
- Codex:                 api.aicodewith.ai/chatgpt/v1  (responses)
- Gemini CLI:            api.aicodewith.ai/gemini_cli
- OpenCode:              api.aicodewith.ai/v1          (@ai-sdk/anthropic)

The first and last look inconsistent but are not: Claude Code appends
/v1/messages itself while the AI SDK appends /messages to its baseURL,
so both land on the same route.

Three non-obvious details:

- Claude Desktop uses mappedRoutes rather than passthroughRoutes. The
  upstream Haiku id is claude-haiku-4-5-20251001, which differs from the
  claude-haiku-4-5 role route id, so passthrough would point the Haiku
  route at a model that does not exist.
- OpenClaw, Hermes and Pi have no official integration guide. They reuse
  the Codex endpoint with their respective Responses protocol instead of
  guessing at /v1/chat/completions, for which the vendor publishes no
  example anywhere. Endpoint probing cannot settle this either: the auth
  middleware runs before route matching, so every path returns 401
  unauthenticated.
- Model ids come from GET /v1/models, which is unauthenticated and more
  current than the pricing page; the OpenCode guide still advertises
  kimi-k2 and glm-4.6, both retired.

The only logo the vendor ships is a 128KB PNG whose 2664 colours are
almost entirely compression noise over a two-tone image. It is vectorised
to a 4.9KB inline SVG using currentColor, so the icon follows the theme
rather than carrying an opaque white background.
2026-09-01 23:47:14 +08:00
Jason 6d25f34eaf feat(presets): add QwenCloud presets across seven apps (#6214)
QwenCloud is DashScope's international site — a separate deployment from
the existing Bailian presets, with its own domain, console and API keys.
It lands as new entries rather than a rename, so existing Bailian users
keep working unchanged.

Each of the three plans gets its own preset because their base URLs and
keys are not interchangeable; the vendor docs state that a mismatched
key/base_url pair fails with 401:

- Pay-as-you-go: dashscope-intl.aliyuncs.com
- Coding Plan:   coding-intl.dashscope.aliyuncs.com
- Token Plan:    token-plan.ap-southeast-1.maas.aliyuncs.com

Endpoints and model metadata are transcribed from the official client
docs. Two non-obvious details:

- The Coding Plan's OpenAI endpoint is a bare /v1 with no compatible-mode
  segment, and only supports Chat Completions, so its Codex preset is
  marked openai_chat rather than inheriting the Responses default.
- qwen3.8 models expose a 983616-token context window and accept only
  low/medium/xhigh reasoning levels, so the Claude Desktop Token Plan
  routes are deliberately not flagged 1M-capable.
2026-09-01 10:14:41 +08:00
Jason 21fda0ea01 test(codex): align grok-4.5 reasoning tier expectations with 4-tier presets
c08040e92 declared xhigh for the grok-4.5 xAI presets but missed the
test expectations, leaving main's frontend checks red and dragging
every PR's Frontend Checks down with the same two failures.
2026-08-31 18:02:21 +08:00
Jason 054673e0b8 fix(codex): reject 2^64 in whole-float integer rewrite
u64::MAX as f64 rounds up to 2^64, so the > guard let exactly 2^64
through; the saturating cast then produced u64::MAX, which rounds back
to 2^64 and passes the round-trip check - silently rewriting the value
off by one. Guard with >= instead.
2026-08-31 09:55:35 +08:00
Jason d05a11cc21 refactor(codex): drop unused namespace SSE restore helpers
create_namespace_restore_sse_stream and restore_sse_block lost their
last caller when the xAI response path moved to the sanitize rewrite
stream; delete them instead of shipping #[allow(dead_code)].
2026-08-31 09:55:35 +08:00
Jason 9e1100534b fix(codex): address xAI native Responses review findings
- Remap unknown request models before sanitizing, so a subagent SKU
  that lands on grok-4.5 gets its unsupported sampling fields stripped.
- Intersect union-branch required lists instead of unioning them, so a
  flattened oneOf does not force fields the chosen branch lacks.
- Let grok-prefixed request models bypass the catalog remap: real Grok
  SKUs the catalog has not caught up with must pass through.
2026-08-31 09:55:35 +08:00
Jason c08040e926 fix(codex): declare xhigh reasoning tier for grok-4.5 xAI presets
Probed 2026-08-30 against native /v1/responses: grok-4.5 accepts
low/medium/high/xhigh and rejects max with HTTP 400 "Invalid reasoning
effort". Codex does not clamp out-of-catalog efforts, so the preset
list must match what the upstream actually accepts.
2026-08-30 23:53:14 +08:00
Jason c88b00faf6 fix(codex): neutralize managed-OAuth official-auth fallback flag
The v3.20.1 config-only switch refactor extended the keyless safety gate
to all write paths, which broke switching to proxy-managed OAuth cards
(xAI Grok OAuth): they are keyless by design — the local proxy injects
the real token per request — yet their preset snapshot inherited the
pre-0.149 template's requires_openai_auth = true, so the gate refused
the switch with provider.codex.config.official_auth_fallback.

Fix the snapshot instead of exempting the gate: force the flag to false
on the active custom table for proxy-injected OAuth providers
(uses_proxy_injected_oauth: xai_oauth, github_copilot — codex_oauth is
deliberately excluded, the official login IS its credential). The
neutralization hooks into build_effective_provider_for_live so
preflight, normal writes, and proxy backup/takeover projections all see
the same safe shape, and 0.149 resolves the provider as unauthenticated
without ever reading auth.json. The OAuth preset now emits
requires_openai_auth = false at the source; stored cards self-heal via
backfill on the next switch.
2026-08-30 23:27:03 +08:00
Jason 3217f72596 docs(release): add v3.20.1 release notes 2026-08-28 08:19:45 +08:00
Jason 9485cf2f3d chore(release): v3.20.1 2026-08-28 08:19:45 +08:00
Jason 5ff199b5a9 fix(usage): align session sync card spacing and gate Sync Now to manual mode
The session sync card sat in the page-level space-y-8 stack while the
pricing/maintenance cards below use space-y-4 inside the Accordion,
leaving an inconsistent 32px vs 16px gap. Group the sync card with the
Accordion under a shared space-y-4 wrapper so all three cards align.

Also show the Sync Now button only when auto-scan is off: in auto mode
the background timer already covers scanning, so the manual entry point
is redundant there.
2026-08-27 23:35:02 +08:00
Jason f05e20336e fix(usage): report pinned-rewrite skips in session sync results
When an external truncation or rewrite pins the cursor to EOF, the
skipped range is permanently dropped (replaying it would double-count
entries whose detail rows were already pruned into the rollups). That
was only logged; manual sync showed plain success. Surface it through
the result's errors instead — deliberately not deferred_files, whose
semantics promise a retry next round that will never happen.
2026-08-27 23:35:02 +08:00
Jason f8d97348cb fix(usage): detect non-append rewrites and surface read errors in session scan
Follow-up hardening on the byte-cursor scan:

- session_log_sync gains last_tail_fingerprint (added to the still
  unreleased v17->v18 migration): a hash of the bytes just before the
  cursor boundary. On each incremental read the stored fingerprint is
  verified against the file, catching same-size or larger rewrites
  that a size check cannot see. The verification read doubles as the
  seek to the cursor and seeds the rolling tail buffer, so the append
  fast path costs no extra IO.
- truncation and fingerprint-mismatch rewrites now pin the cursor to
  the current EOF instead of rescanning from byte 0. A rescan would
  re-import entries whose detail rows were already pruned into the
  daily rollups, double-counting them forever; skipping the rewritten
  range loses at most externally-modified lines, which is the lesser
  harm. Appends after the pin resume normal incremental reads.
- mid-file IO errors are now reported through the sync result
  (errors + deferred count) instead of silently returning success;
  manual sync has no next round to quietly retry in.
2026-08-27 23:35:02 +08:00
Jason bcee61be43 perf(usage): incremental byte-cursor scan for Claude session logs
Replace the line-number cursor with a byte offset so unchanged file
prefixes are skipped via seek instead of re-reading whole files each
round (12MB active file: 6.04s full scan -> 9.3ms incremental), and
prefetch all sync cursors in one query per round across the five
file-based importers instead of one row lookup per file.

Details:
- session_log_sync gains last_byte_offset via a standalone v17->v18
  migration (v17 already ran on live databases; appending the column
  to it would leave them permanently missing the column)
- cursor only advances past complete (newline-terminated) lines; a
  parseable unterminated tail is imported but deferred, fixing the
  legacy bug where a half-written line was counted and then skipped
  forever once completed
- legacy line cursors are converted by skipping N lines byte-wise,
  never by full rescan: rollup_and_prune deletes detail rows that
  request_id dedup relies on, so a rescan would double-count pruned
  history into the rollups
- load_sync_cursors now returns Result and aborts the round on
  failure instead of falling back to an empty map (same rescan
  double-count hazard); Pi reports the error through its result
- on mid-file read errors the cursor keeps the old mtime so the next
  round actually resumes from the committed offset
- imports and cursor updates commit atomically in one transaction

Verified with a frozen snapshot replay (1017 files / 409MB): old and
new code produce identical aggregates.
2026-08-27 23:35:02 +08:00
Jason 092ea1f362 feat(usage): add auto/manual session scan mode toggle
Users reported that the constant background scanning of client session
logs (every 60s) was intrusive. Add a sessionAutoSyncEnabled setting
(default on, so existing behavior is unchanged) that switches session
usage collection between two modes:

- Auto (default): background timer scans client session logs as before
- Manual: background scanning stops entirely (including the startup
  scan pass); logs are only scanned when the user clicks the new
  "Sync Now" button on the usage dashboard

The toggle only governs scan timing. Proxy request accounting is
real-time and never reads session files, so it is unaffected, and the
startup cost backfill (which only patches existing DB rows) still runs.

Also wires up the previously unused sync_session_usage command as the
manual sync entry point, and adds usage.sessionSync.* strings in all
four locales.
2026-08-27 23:35:02 +08:00
Jason 270a4ff372 feat(usage): add OpenCode Go subscription usage query via token plan path
Query the official first-party (undocumented) endpoint
GET https://opencode.ai/zen/go/v1/usage — Bearer-only auth, the opposite
of the inference-side /messages which only accepts x-api-key. The 200
response shape was verified against a real Go subscription key.

- Map rolling/weekly/monthly windows onto the existing five_hour /
  weekly_limit / monthly tiers: no new i18n keys or tray label groups
- Parse each window defensively (upstream reshaped the response within
  an hour of launching it); report an explicit error when no window
  parses instead of rendering an empty card
- Drop resetsAt when percent is 0: the upstream value is a now+window
  placeholder, not a real reset time
- Treat 403 as "valid key but no Go subscription" (Zen and Go share
  the same workspace key), distinct from 401 auth failures
- Auto-enable the usage script on newly added providers across all five
  apps shipping the OpenCode Go preset; claude keeps its existing
  all-coding-plans behavior while claude-desktop/codex/opencode/pi only
  opt in for OpenCode Go
2026-08-27 08:37:56 +08:00
Jason c5e4f70545 fix(codex): stamp requires_openai_auth to match login preservation on third-party switches
Presets and the custom template carried requires_openai_auth = true from
the pre-0.149 era when auth.json held the third-party key. On 0.149 the
injected bearer decides request auth either way, but the stale flag traps
the TUI in the login screen once preservation-off deletes auth.json, while
a preserved login stays hidden and unrefreshed without it.

The third-party write plan now overrides the stored flag with the
preservation setting (off -> false, on -> true), but only on the active
custom table that short-circuits request auth via env_key or the injected
experimental_bearer_token - stamping true on a keyless header-auth table
would route the preserved OAuth login to the third-party endpoint. The
takeover subsystem keeps its existing shapes. The preservation setting is
now an explicit plan parameter so plan output is deterministic in tests.
2026-08-27 08:37:56 +08:00
Jason 58687bd6bd docs(codex): reunite the legacy-reroute doc comment with its function
Splitting first_free_cc_switch_provider_id out of the normalizer left the
normalizer's doc comment stranded on the helper, with one sentence
truncated mid-clause. Move it back above
normalize_codex_legacy_openai_reroute; the helper keeps only its own
description. Comment-only change.
2026-08-27 08:37:56 +08:00
Jason 877df74f98 fix(codex): backfill names on official writes, preflight table conflicts 0.149 rejects
The official branch of plan_codex_live_write never goes through
prepare_codex_provider_live_config, so idle name-less custom tables left
by older versions survived official writes and made Codex 0.149 reject
the whole config — the branch now backfills names itself.

Add an independent semantic preflight over every provider table (idle
ones included, official and third-party alike): aws outside the Bedrock
built-ins and auth combined with requires_openai_auth / env_key /
experimental_bearer_token are combinations 0.149 refuses to load and
cannot be normalized away, so the switch now fails up front with an
actionable error instead of writing a config Codex won't start on. The
auth-safety gates only judge the active route and are skipped when a key
is carried, so they never covered these shapes. Gate-less paths (proxy
backup/restore) intentionally stay untouched.
2026-08-27 08:37:56 +08:00
Jason 798602c359 fix(codex): backfill names for all custom tables, never for Bedrock ones
Codex 0.149 rejects the whole config when any non-Bedrock provider table
has an empty/missing name, active or not — plain config-only switches
never went through the update path's backfill, so prepare now normalizes
every custom table (keyless and official writes included).

The Bedrock built-ins are the mirror image: they only allow
base_url/auth/http_headers/aws.* overrides, and writing name there makes
the built-in merge reject the whole config — the proxy takeover rewrites
base_url through update_codex_toml_field, so its backfill now skips both
reserved ids.

Also drop auth/aws as own-credential short-circuits from the official-auth
fallback predicate: 0.149 validates both as mutually exclusive with
requires_openai_auth, so tables combining them are dead configs the whole
file fails to load with — flagging them keeps them from being written.
2026-08-27 08:37:56 +08:00
Jason 93bb91aad3 fix(codex): make migrated tables loadable, follow non-fallback routes, unreserve oss/ollama-chat
- Normalize migrated stale reserved tables into a shape Codex 0.149
  loads: wire_api forced to "responses" (the chat wire API was removed
  upstream; any other value fails deserialization of the whole config)
  and empty/missing name backfilled. update_codex_toml_field backfills
  name too - it historically created name-less tables that 0.149
  rejects at load.
- Key the migration route-follow on the 0.149 auth.json fallback
  boundary (requires_openai_auth=true without env_key / bearer token /
  auth / aws) instead of "table carries credentials": scoped-token,
  header/query-param auth and unauthenticated local tables keep their
  legitimate third-party route; only credential-less
  requires_openai_auth tables still snap back to the built-in provider.
  The safety-gate predicate shares the same table-level check and now
  honors an in-table experimental_bearer_token short-circuit.
- Remove oss/ollama-chat from both reserved-id lists (Rust + TS
  mirror): neither is reserved on Codex 0.148/0.149, both load as
  ordinary custom tables, so the bearer token must land inside them
  instead of a dead top-level field.
- Match model_provider case-sensitively in official-identity detection
  (proxy + frontend), mirroring upstream: "OpenAI" routes to a custom
  table, not the official provider.
2026-08-27 08:37:56 +08:00
Jason bb54e87a78 fix(codex): credential-aware routing for migrated tables, exact-match shared predicate, migrate all reserved ids
Fifth hardening round on the config-only unification:

- The stale-table migrator now covers all three reserved ids Codex
  refuses at load (openai, ollama, lmstudio — exact lowercase, bedrock is
  exempt upstream) and routes credential-aware: when the renamed table
  was the active route it is followed only on a third-party write that
  can authenticate against it (an injectable key exists, or the table
  carries its own env_key / auth / aws / plain Authorization headers).
  The previous round always snapped back to the built-in provider, which
  silently billed the preserved official account for third-party cards
  that DID have a key. Credential-less tables still snap back so a
  requires_openai_auth fallback can never resolve auth.json against a
  stale address; official writes never follow.
- is_custom_codex_model_provider_id (Rust and its TS mirror) now matches
  reserved ids exactly, aligning with upstream's case-sensitive built-in
  lookup: [model_providers.OpenAI] is a legitimate custom provider whose
  table receives the bearer token, instead of the token dying in a
  top-level field while the safety gates skipped the custom table.
- The migrator runs before the safety gates in the switch plan so the
  gates judge the text that will actually be written.
- The auth-cleanup warning no longer hardcodes ~/.codex/auth.json (wrong
  on Windows and with a custom Codex dir); all four locales updated.
2026-08-27 08:37:56 +08:00
Jason 43818101e8 fix(codex): rename stale reserved tables losslessly, match reserved ids exactly, cover inline tables
Fourth hardening round on the config-only unification:

- Replace the lift-base_url strip with a lossless rename: a stale
  [model_providers.openai] table (rejected at load since Codex 0.148) is
  renamed to the first free cc-switch id keeping every user key, with
  wire_api = "responses" defaulted in. The active route deliberately
  snaps back to the built-in provider and no base_url is promoted
  anywhere — the previous strip lifted the stale address into a live
  top-level reroute AFTER the safety gates had run (and on the official
  branch, which has no gates), sending whatever auth.json holds to an
  unvalidated stale address. The shape never loaded since 0.148, so there
  is no working behavior to preserve.
- Match reserved ids exactly, mirroring upstream's case-sensitive
  validate_reserved_model_provider_ids: `OpenAI` and other case variants
  are legitimate custom ids and are no longer touched by the migration,
  the reroute normalization, or update_codex_toml_field.
- Handle inline `model_providers = { … }` containers in the legacy
  reroute normalization (insert an inline member matching the container
  style) — proxy backup/restore call prepare without the safety gates,
  so skipping inline shapes left the key in a dead top-level field next
  to live auth.json credentials.
- Refuse update_codex_toml_field for the built-in ollama/lmstudio ids:
  they have no top-level reroute knob and any table for them makes Codex
  refuse the whole config, so a takeover of such a card now fails with
  an actionable error instead of writing a config Codex cannot start
  with. Bedrock ids (exempt upstream) and case variants keep the normal
  custom-table path.
- Route switch warnings by code in the frontend: codex_auth_cleanup_failed
  now shows its own localized message (zh/en/ja/zh-TW) instead of the
  unrelated "backfill failed" text.
2026-08-27 08:37:56 +08:00
Jason 9a1a6b83ab fix(codex): migrate stale reserved openai tables, suffix conflicting migration ids, surface auth cleanup failures
Three follow-up hardenings on the config-only unification:

- Strip a stale [model_providers.openai] table on every write path
  (official included) and in prepare_codex_provider_live_config: older
  cc-switch takeover projections created that table, and Codex 0.149
  rejects the whole config at load when a reserved built-in id is
  overridden — the switch reported success while Codex could no longer
  start. The table's base_url (the only key that ever had an effect) is
  lifted to the top-level openai_base_url knob when the built-in provider
  is the active route, feeding the existing reroute normalization.
- When a user-authored [model_providers.cc-switch] table already exists,
  the legacy-reroute migration now continues under the first free
  suffixed id (cc-switch-2, …) instead of giving up: refusing left proxy
  backup/restore — which call prepare without the safety gates — writing
  an unmigrated reroute with live auth.json credentials next to it.
- With preservation off, a failed auth.json deletion after a third-party
  switch now surfaces as a SwitchResult warning
  (codex_auth_cleanup_failed) instead of only a log line, so the user can
  see the official login is still on disk. The switch itself still
  succeeds — config and current are already committed.
2026-08-27 08:37:56 +08:00
Jason 97a7425f7f fix(codex): preflight switches before committing current, share normalization with proxy paths
Four hardening fixes on top of the config-only unification:

- Split write_codex_live_for_provider into a pure plan builder
  (normalization, safety gates, token injection, TOML parsing) and an
  executor, and preflight the plan in switch_normal BEFORE current is
  committed. Previously a write-layer refusal landed after local/DB
  current had moved, so the next switch would backfill the old live
  config into the refused provider's DB row.
- Sink the legacy openai_base_url normalization into
  prepare_codex_provider_live_config, making it the single
  normalize→inject entry point. Takeover backup rebuilds and restore call
  prepare directly; without the migration a restored legacy backup left
  the key in a top-level field Codex ignores while auth.json credentials
  stayed live.
- Stop update_codex_toml_field from creating [model_providers.openai]:
  Codex 0.149 rejects the whole config when a reserved built-in id is
  overridden (validate_reserved_model_provider_ids). The built-in openai
  provider is rerouted via the top-level openai_base_url knob instead,
  and wire_api writes become a no-op for it.
- Never overwrite a user-authored [model_providers.cc-switch] table
  during normalization — nothing proves it is ours, and their headers or
  query params would be lost and backfilled into the DB. The shape falls
  through to the safety gates instead.

The keyless-fallback rejection test now also locks that a refused switch
leaves current on the previous provider.
2026-08-27 08:37:56 +08:00
Jason cbb7912776 refactor(codex): unify provider switching on config-only auth writes
Since Codex 0.149 (openai/codex#39214) custom providers no longer inherit
ambient auth from auth.json, so the API key must travel as a
provider-scoped experimental_bearer_token in config.toml (honored since
Codex 0.48; verified against rust-v0.148 sources). Instead of dual-writing
auth.json + config.toml depending on the preservation setting, make every
third-party switch config-only:

- auth.json is reserved for the official ChatGPT login: preserved when
  the preservation setting is on, deleted otherwise. It never carries
  third-party keys, so official OAuth cannot leak to a third-party
  endpoint through a requires_openai_auth fallback or a lingering login.
- The legacy reroute shape (built-in openai provider + top-level
  openai_base_url) is normalized into a cc-switch-owned custom provider
  table pinning wire_api = "responses", giving the key a provider-scoped
  slot instead of a top-level field Codex ignores.
- Two safety gates guard every third-party write (previously only the
  preservation path): a carried key with no provider table to land in is
  refused, and a keyless config that would resolve auth from auth.json
  (requires_openai_auth without provider-own credentials, or an
  openai_base_url reroute) is refused.
- Token injection skips provider tables carrying their own credentials
  (env_key / auth / aws / Authorization headers); requires_openai_auth
  overrides the header guard so the injected token short-circuits the
  auth.json fallback. Inline provider tables are handled in
  set/extract/remove, and amazon-bedrock-runtime joins the reserved
  provider id lists (Rust + TS).

Official cards are unchanged. Takeover placeholder writes ride in
config.toml the same way, which is what Codex >= 0.149 actually sends to
the local proxy.

Known trade-offs: pre-0.48 Codex releases (which never read the token
field) lose third-party auth, and an empty third-party config with a key
is now refused on every path instead of riding in auth.json.

Refs #6746, #6744
2026-08-27 08:37:56 +08:00
Jason 9a596158ca chore(presets): move TeamoRouter to teamorouter.cn, keep .com as fallback
Switch the TeamoRouter domain and API base URL to teamorouter.cn across all
eight managed apps (Claude Code, Claude Desktop, Codex, Grok Build, OpenCode,
OpenClaw, Hermes, Pi), and update the sponsor links in all four READMEs.

The previous api.teamorouter.com addresses are kept as fallback endpoints in
the four presets whose interface supports endpointCandidates (claude,
claudeDesktop, codex, grokBuild), each matching its file's path tier - bare
host for the Anthropic-native presets, /v1 for Codex and Grok Build.
2026-08-24 16:49:56 +08:00
Jason 4549d2907d fix(capabilities): grant process:allow-exit so exit buttons can quit the app
The db-version-too-new recovery screen and the config-load-failure path
both call exit() from @tauri-apps/plugin-process, but the default
capability only granted process:allow-restart. The IPC call was denied
and silently swallowed by void/await, leaving the app impossible to
quit from those screens.
2026-08-18 17:27:53 +08:00
Jason 0b5da51016 docs(release): add v3.20.0 release notes 2026-08-18 16:48:03 +08:00
Jason 18ca2da0b5 chore(release): v3.20.0 2026-08-18 16:48:02 +08:00
Jason af31a87bdb docs(changelog): draft unreleased section covering v3.19.2..HEAD
69 commits summarized into Added/Changed/Fixed/Security/Internal plus
upgrade notes; section stays [Unreleased] until the version is picked.
2026-08-18 15:43:03 +08:00
Jason 0cd922c527 feat(sponsors): list PPIO as a project sponsor
Move the PPIO preset from the non-sponsor block into the sponsor block
across all six apps that carry it (Claude Code, Claude Desktop, Codex,
OpenCode, OpenClaw, Hermes), keeping file order aligned with the README
sponsor table: right after TeamoRouter, since new-api holds a README slot
but ships no preset.

Each entry gains isPartner/partnerPromotionKey and points apiKeyUrl at the
CC Switch referral URL. Promotion copy added to all four locales, and the
banner row lands just before new-api in all four READMEs.
2026-08-18 12:47:30 +08:00
Jason bad9c15104 fix(pricing): record DeepSeek V4 peak-tier rates and seed Gemini 3.7 Flash
DeepSeek moved the entire V4 line to peak/off-peak billing on 2026-08-16
16:00 UTC, a large increase over the rates the table still carried:

  v4-flash  0.14/0.28/0.0028    ->  0.44/1.32/0.014
  v4-pro    0.435/0.87/0.003625 ->  1.32/3.96/0.044

The table has one row per model and no time-of-day dimension, so it has to
commit to a single tier. Record the peak tier: the vendor states off-peak
as "half of peak", making peak the base list price, and peak hours
(09:00-12:00 and 14:00-18:00 Beijing time) are exactly the working hours
when most users route traffic. The trade-off is that overnight usage now
reads about twice its real cost.

deepseek-chat and deepseek-reasoner follow as V4 Flash legacy aliases, and
deepseek-v4-flash-0731 as the MMDD alias row.

The five repair entries must stay at the end of pricing_fixes. The earlier
2026-07 entries first collapse the older shapes -- chat 0.27/1.10,
reasoner 0.55/2.19, v4-flash cache_read 0.028, v4-pro 1.68/3.36 -- onto a
single value, which these then match with a single guard; moving them
ahead would strand old databases at the intermediate price. The seed/repair
test now walks that two-hop chain and pins the ordering.

Also seed gemini-3.7-flash at 0.75/3.75/0.075, agreed by the launch
announcement, ai.google.dev and models.dev. That is introductory pricing
expiring 2026-12-31, after which 1.50/7.50/0.15 applies; the row is left
out of the audit ignore list on purpose, so the audit reports it once
models.dev switches to the list price.

Both prices were confirmed against the vendors' own pricing pages.
models.dev still carries DeepSeek's pre-change rates, so the seed table
and the reference agreed and the audit could not have surfaced this.
2026-08-18 12:29:15 +08:00
Jason fd14f9c4fe fix(env-check): stop upgrade/conflict preflight from hanging forever
The install-conflict precheck spawned interactive shells (`zsh -lic`) in
an isolated background process group with an inherited terminal, so the
shell stopped itself on SIGTTIN/SIGTTOU and `child.wait()` never
returned (regression from the resolve_path_default refactor in #5522,
which passed no deadline on this path).

- Replace process_group(0) with setsid() in isolate_child_process_group:
  a new session still gives kill(-pid) whole-group termination, and also
  detaches the controlling terminal so interactive shells no longer
  self-stop under job control
- Null out stdin on the resolve_path_default spawn, restoring the
  pre-refactor Command::output() behavior
- Add a 10s deadline to the whole preflight chain: resolve_path_default
  lookups plus a new run_probe_version_command helper for the
  `--version` enumeration on both Unix and Windows (the Windows branch
  previously used untimed .output(); the command builder is split out
  and reused so a hung .cmd shim is now killed via taskkill /T)
- Show preflight progress in the UI: batch button spinner is driven by
  an entry-origin flag (fromBatchEntry) so "Update all (1)" spins too,
  across preflight, confirm dialog, and execution; per-card buttons spin
  while their probe is in flight
- Add regression tests for the probe happy path and for deadline
  termination of a hung child in the isolated group
2026-08-18 11:18:11 +08:00
Jason d1c550bac3 feat(codex): drop Goal mode toggle now that upstream enables goals by default
codex-cli 0.147.0 reports `goals` as a stable feature that defaults to
true, verified against a clean CODEX_HOME with no config.toml: the flag
is on whether or not `[features].goals` is present. The toggle was added
in #3089 against codex 0.134.0, when the flag still had to be opted into.

The toggle had also become actively misleading. Unchecking it deleted the
`goals` line rather than writing `goals = false`, which used to fall back
to the old default of off but now falls back to on -- so the box read
unchecked while the feature stayed enabled.

Removes the two helpers, their TOML patterns, the now-unused
hasTomlSectionBodyContent, the checkbox, the enableGoalMode key in all
four locales, the covering tests, and the user-manual entries. Release
notes for v3.16.0 keep their original text as a historical record.
2026-08-18 11:13:52 +08:00
Jason 6e424fd3df feat(codex): restore 1M context window toggle in provider edit form
Upstream Codex accepts model_context_window=1000000 again, so the
toggle and its paired auto-compact-limit input are un-commented and
visible in the provider edit form once more. This reverts the UI half
of 5b6339d7.

Because the state hooks, helpers and i18n keys were preserved when the
UI was hidden, no behaviour was rewritten: the diff only removes comment
markers. Checking the box still writes model_context_window = 1000000
and back-fills model_auto_compact_token_limit = 900000 when absent;
unchecking removes both fields together.

The restore note left by 5b6339d7 was partly stale. useMemo had already
returned to the React import via the goal-mode and remote-compaction
toggles, and those toggles introduced a live providerConfigUtils import,
so the three int helpers are merged into that existing import instead of
adding a second import of the same module.
2026-08-18 11:13:52 +08:00
52745efe40 test(presets): lock OpenCode Go direct Anthropic access regression
Port the regression coverage from #6196: the Claude Code preset must keep
ANTHROPIC_API_KEY (the Go gateway's /messages endpoint only accepts
x-api-key and silently ignores Bearer), must not reintroduce
ANTHROPIC_AUTH_TOKEN, and must not require local routing. The apiFormat
assertion is adapted to the shape 16cc0d7f landed on main: native
Anthropic presets omit the field entirely.

Co-authored-by: YUZHEthefool <2804776511@qq.com>
Co-authored-by: allenxu09 <s23372.xu@stu.scie.com.cn>
2026-08-18 11:13:52 +08:00
Jason 4080a8e95c feat(presets): add Baidu Qianfan Token Plan preset across six apps
Token Plan (personal) replaced Coding Plan for new purchases on
2026-07-13; existing Coding Plan presets stay untouched since active
subscriptions remain valid until expiry.

- claude/claude-desktop: /anthropic/tokenplan/personal, all roles on
  deepseek-v4-pro per the official Claude Code integration guide
- codex: /v2/tokenplan/personal via openai_chat local routing with a
  six-model catalog; first Qianfan preset with supportsEffort enabled
  (no alias indirection anymore, and the default model is on the
  official reasoning_effort support list) using effortValueMode
  "deepseek", which matches Qianfan's own low/medium->high,
  xhigh->max compatibility mapping verbatim; deepseek models omit
  defaultReasoningLevel so the fallback (max) matches the platform's
  automatic max for agent requests
- opencode/hermes: full six-model lineup on the OpenAI-compatible
  endpoint per the official integration guides
- openclaw: model entry mirrors the official OpenClaw guide verbatim
  (context 98304 is the vendor's OpenClaw-specific figure, not the
  platform model list's 1M)

ernie-5.1 is excluded (official retirement on 8/20). New test locks
endpoints, lineups and OpenClaw figures across five apps; codex is
covered by the existing chat preset and reasoning level tests.
2026-08-16 10:51:29 +08:00
Jason af06356d53 feat(presets): enable reasoning effort for Kimi per vendor's own guide
2026-08-15 audit, Kimi group. Both Kimi presets declared
supportsEffort:false, yet Moonshot's two official Codex integration
guides - which use CC Switch as their worked example - explicitly
instruct users to enable both "支持思考模式" and "支持推理强度/思考
等级". The missing piece was the top-level reasoning_effort string
(low/high/max), the only reasoning control the K3 family supports.

Open Platform preset (api.moonshot.cn/v1):
- supportsEffort:true + effortParam reasoning_effort; effortValueMode
  stays unset (passthrough - the deepseek map would crush low to high)
- kimi-k3: low/high/max. No explicit default: the template default
  medium is outside the subset so the backend falls back to the highest
  tier, max, which is exactly the vendor default
- kimi-k2.7-code: single-tier ["high"] (always-thinking, effort
  documented as unsupported - single tier avoids the template's fake
  differentiated tiers, LongCat precedent)

Kimi For Coding preset (api.kimi.com/coding/v1):
- same declaration change; catalog extended with the full official
  model table: kimi-for-coding-highspeed (262144), k3 (1048576),
  k3-256k (262144)
- k3/k3-256k: low/high/max with explicit defaultReasoningLevel high -
  first use of an explicit default: the vendor default (high) differs
  from the fallback (max), and max would silently burn more tokens
- no none tier anywhere: k2.7/k3 cannot disable thinking, and on the
  Kimi Code gateway disabling reroutes requests to K2.6 (different
  model, different billing). Gateway effort whitelist rejects unknown
  values with 400 (minimal is not in it); the declared subsets keep the
  picker inside the whitelist

Known doc conflict noted inline: the parameter reference says K3 should
not receive the thinking param while the integration guide enables
thinking mode; production shows no failures, so we follow the guide -
first thing to check if users report Kimi 400s.

Test tuple gains an optional expected-default column.
2026-08-16 10:51:29 +08:00
Jason 9dcd3486f7 feat(presets): real thinking switches for Qianfan and BytePlus Codex
2026-08-15 vendor audit, official-switch group:

- Baidu Qianfan Coding Plan: the v2 Chat API documents top-level
  thinking:{type:enabled/disabled} (same shape as Zhipu) across the
  plan's six main models, and Baidu's own OpenCode integration guide
  sends it on the /v2/coding endpoint. Declaration added (with the
  supportsThinking:true flag that gates the whole injection path) plus
  none/high levels. Known alias caveat noted inline: if the console
  resolves qianfan-code-latest to a non-thinking model the field is
  ignored, not rejected (platform documents unsupported params as
  ignore-not-error).
- BytePlus (ModelArk intl): the "not verified for the intl site"
  comment is obsolete - BytePlus' own Codex guide configures this exact
  coding/v3 endpoint with wire_api="responses". Switched apiFormat to
  openai_responses (aligned with the domestic Volcengine Plan presets,
  one less conversion layer) and pre-filled low/medium/high per the
  official Codex doc, with the glm-5-2 tier-collapse caveat noted.

Test moves BytePlus from the Chat-route list to the native Responses
list; reasoning-level expectations extended for both presets.
2026-08-16 10:51:29 +08:00
Jason e12fc62366 fix(presets): correct thinking-switch dialects for aggregator platforms
2026-08-15 vendor audit, aggregator group. All three presets declared
the Zhipu-style top-level thinking:{type} object, which is a model
vendor dialect their platforms never documented:

- ModelScope: platform switch is the top-level enable_thinking bool
  (official model-page extra_body samples + Bailian first-party GLM
  docs). thinkingParam fixed; levels stay unset until field passthrough
  to the upstream is verified.
- Novita: same fix - the Create-chat-completion parameter enum has no
  thinking object; enable_thinking is documented platform-wide. GLM-5.1
  catalog row gains none/high (two-hop inference, noted inline).
- Nvidia NIM: fake switch withdrawn. The per-model official OpenAPI
  (additionalProperties:false) has no top-level thinking field; the
  real control chat_template_kwargs:{thinking:bool} is outside our
  thinkingParam value range, so the declaration becomes an explicit
  no-injection block (supportsThinking:false, thinkingParam:none).

All three keep their codexChatReasoning block on purpose: deleting it
would fall through to the backend name/model inference, which matches
"glm"/"kimi" in the model id and would resurrect the wrong dialect.
A new modelscope branch in infer_aggregator_platform_config covers
hand-created ModelScope providers the same way the siliconflow branch
does (platform identity beats model-vendor hints), with a unit test.
2026-08-16 10:51:29 +08:00
Jason 3f75bbdf37 feat(proxy): extend StepFun effort inference to step-3.7-flash
The StepFun inference block predated step-3.7-flash and only granted
reasoning_effort to models containing "2603". Per the official
reasoning guide and model pages (both .com and .ai sites, 2026-08-15
audit), step-3.7-flash supports low/medium/high (default medium) while
step-3.5-flash-2603 supports low/high; the suffix-less step-3.5-flash
exposes no effort control.

- supports_effort now also matches step-3.7-flash
- effort_value_mode is split per model: 2603 keeps the low_high
  collapsing map; 3.7-flash uses passthrough - reusing low_high would
  collapse medium into high, creating tiers with no wire difference
- the fix stays in the inference table rather than a preset-level
  codexChatReasoning declaration, which is provider-wide and would lose
  the per-model gating (suffix-less step-3.5-flash must not receive
  reasoning_effort - its behavior is undocumented)
- StepFun preset catalogs pre-fill the vendor-documented tiers
  (3.7-flash low/medium/high, 2603 low/high, suffix-less unset); no
  none tier anywhere - StepFun documents no way to disable thinking
- new unit test locks the three per-model inference outcomes
2026-08-16 10:51:29 +08:00
Jason 1435223bd8 fix(presets): replace unavailable SiliconFlow/ModelScope catalog models
2026-08-15 vendor audit (official catalogs, cross-checked live):

- SiliconFlow: MiniMax-M2.7 was never listed on either site - users got
  400 "Model does not exist" out of the box. The two sites carry
  different MiniMax lineups and must not mirror each other:
  - .cn -> Pro/MiniMaxAI/MiniMax-M2.5 (catalog contextLen 196608);
    reasoning levels left unset (no official evidence M2.5 thinking can
    be disabled via enable_thinking)
  - .com -> MiniMaxAI/MiniMax-M3 (official 1M window = 1048576);
    reasoningLevels none/high (platform enable_thinking switch is real
    and M3 thinking is documented as disableable)
- ModelScope: bare ZhipuAI/GLM-5.1 id is not in the free API-Inference
  service list (only the BYOK :DashScope variant exists) - replaced
  with ZhipuAI/GLM-5.2 which is on the free community route. Levels
  left unset until field passthrough to the upstream is verified.

Applied across all seven apps carrying these presets (claude,
claude-desktop, codex, hermes, openclaw, opencode, pi), plus the
hardcoded model id in the SiliconFlow platform-inference Rust test and
the context-window test assertions. Bare MiniMax-M2.7 ids (MiniMax's
own official endpoint) are a different platform and intentionally
untouched. Pi catalog key moved to the existing zai/glm-5.2 entry.

Known follow-up: openclaw SiliconFlow cost figures still reflect the
old M2.7 pricing and need a separate re-audit.
2026-08-16 10:51:29 +08:00
Jason c6247d1358 feat(presets): fill remaining Codex reasoning levels for form visibility
Second round of the #6228 preset pre-fill, reversing the round-1
"deliberately unfilled" list per product ruling: an "unset" reasoning
levels field in the provider form is more misleading than a redundant
or snapshot-style declaration.

- DeepSeek direct (flash + pro): low/high/max, copied from the official
  catalog mirror. Per-row values override the mirror, so the preset must
  be kept in sync if DeepSeek's catalog changes (noted inline).
- MiniMax x2 and Xiaomi MiMo x2 (both models): none/high, equal to the
  template default - zero behavior change, declared only so the form
  shows the real tiers.
- Zhipu GLM x2 (Chat route): none/high two-state. Also fixes a real
  gap: the gpt5_5 template default tiers (low/medium/high/xhigh) had no
  none, so users could not disable thinking from Codex's picker even
  though GLM's thinking:{type:"disabled"} switch is real; the other
  template tiers were fake differentiation (supportsEffort: false).

Still unfilled: Bailian qwen3-coder-plus (no per-model evidence) and
OpenCode Go (multi-vendor Chat gateway without a codexChatReasoning
declaration - the thinking toggle is inert there, so a none tier would
be a fake switch).

Test file extended to 20 assertions covering the new fills and the
updated unfilled list.
2026-08-16 10:51:29 +08:00
Jason e163a671ed feat(presets): pre-fill vendor-documented Codex reasoning levels
Follow-up to #6228 per-model reasoning levels: pass reasoningLevels /
defaultReasoningLevel through the modelCatalog() preset helper and
pre-fill the enums documented by each vendor for native Responses
presets, so known models ship with their real effort tiers out of the
box while remaining user-editable per provider.

Filled (official-doc evidence cited inline):
- Volcengine Agent/Coding Plan ark-code-latest: low/medium/high
- DouBaoSeed doubao-seed-2-1-pro-260628: minimal/low/medium/high
- Tencent Hunyuan hy3 & hy3-preview: low/high
- Longcat LongCat-2.0: high (single tier)
- xAI Grok (API & OAuth) grok-4.5: low/medium/high

Deliberately left unfilled (comments guard against "completing" them):
DeepSeek direct (official catalog mirror already declares low/high/max),
MiniMax & Xiaomi MiMo (official catalog equals template default,
zero-effect fill), Bailian qwen3-coder-plus (no per-model evidence).

defaultReasoningLevel is never declared: the backend fallback lands on
high for every filled entry. Chat-route presets are out of scope since
effort only toggles thinking there (supportsEffort: false).

Adds tests/config/codexReasoningLevelPresets.test.ts locking the
expected enums, the unfilled list, and a canonical-effort whitelist
over all presets (unknown values are silently dropped downstream).
2026-08-16 10:51:29 +08:00
Jason eb69e4922e feat(presets): add XycAi partner preset across all seven apps
- Gateway: apicdn.xycai.us primary, apicdn.xyc.ai fallback
- Claude auth uses ANTHROPIC_API_KEY per official docs
- Claude Desktop connects direct with native Anthropic format
- In-app partner promotion only (no README entry), four locales
- Register xycai icon (PNG via iconUrls) with metadata
2026-08-13 17:53:33 +08:00
Jason 5b8bf1fe4f feat(presets): split Volcengine into Agent Plan and Coding Plan presets
The former 火山Agentplan preset actually pointed at Coding Plan
endpoints (/api/coding[/v3]). Split it into two presets across all six
app preset files:

- 火山 Agent Plan: /api/plan (Anthropic) and /api/plan/v3 (OpenAI),
  with its own activity/agentplan invite link
- 火山 Coding Plan: keeps /api/coding[/v3] and the original
  activity/codingplan invite link

Both plans' OpenAI endpoints are covered by the official Codex docs
(wire_api=responses), so the Coding Plan Codex preset also uses native
Responses. Widen the Volcengine usage detection to
volces.com/api/(plan|coding) in both the frontend pattern and the
backend detect_provider mirror; the quota query already auto-probes
both plans (GetAFPUsage then GetCodingPlanUsage). Split the partner
promo copy into volcengine_agentplan / volcengine_codingplan i18n keys
in all four locales, and pin both presets in the codex native
Responses preset test.
2026-08-13 17:53:01 +08:00
Jason a7f073e95d docs(partner): update FennoAI offer to $1.99 trial plan with $50 credits
FennoAI changed its CC Switch benefit: the previous ¥9.9/month Coding
Plan worth $150 in credits is replaced by a $1.99 Trial Plan worth
$50 in credits, valid for 7 days. Update the in-app promo line in all
four locales and the partner sections of the four READMEs. The 20%
referral reward and the registration link stay unchanged.
2026-08-13 17:53:01 +08:00
Jason c99550e087 chore(presets): migrate RunAPI domain to runapi.host, keep runapi.co as fallback endpoint
Switch RunAPI website, registration and request URLs to runapi.host
across all seven app presets and the four README partner sections.
The previous runapi.co endpoints are retained as endpointCandidates
fallbacks where the preset type supports the field (claude,
claude-desktop, codex, grok-build).
2026-08-13 17:53:01 +08:00
Jason 5f6072ce46 fix(ui): remove partner star badge from main panel provider cards
The star was legacy display logic gated on meta.isPartner, which the
generic provider form almost never persisted. The standalone Grok Build
form unconditionally saves meta.isPartner, resurrecting the dead code
path. Partner stars belong only in the preset selector, so drop the
card-level badge and the now-unused officialPartner i18n key from all
four locales.
2026-08-13 17:53:01 +08:00
Jason 5602324b28 fix(codex): restore angle-bracket text lost in DeepSeek catalog mirror extraction
The official catalog mirror template was extracted from DeepSeek's docs
page with HTML unescaping applied before tag stripping, so literal
angle-bracket text inside the JSON (e.g. "<this good thing>" and the
spaced-path markdown link example) was swallowed as if it were HTML
tags — 8 spots across base_instructions and model_messages for both
models, 86 bytes missing per harness copy. Restore them; the template
is now structurally identical to the current official models.json.

Also refresh the preset comment: deepseek-v4-pro's Codex integration
is live as of 2026-08 (its catalog entry differs from flash only by
priority), so the "switching early errors upstream" warning is gone.
2026-08-13 17:53:01 +08:00
Jason 7dc0a725ee fix(pricing): correct Grok 4.5 cached rate, seed Grok 4.6 and a DeepSeek alias
Findings from a models.dev audit of the seeded pricing table:

- Correct grok-4.5 cached input 0.50 -> 0.30 (seed + repair with an
  old-value guard). The current xAI list table, models.dev and the billing
  observed for grok-4.5-build all state 0.30; 0.50 is grok-4.6's rate.
- Seed grok-4.6 (2 / 6 / 0.50, released 2026-08-12). Both 4.5 and 4.6 double
  their rates above a 200K prompt, but the table has no tier column, so the
  base tier is stored, consistent with other tiered vendors.
- Seed a deepseek-v4-flash-0731 alias at DeepSeek's official price.
  strip_model_date_suffix only strips ISO, 8-digit YYYYMMDD and 6-digit
  YYMMDD suffixes, and the prefix fallback only matches longer rows, so this
  id resolved to no pricing row and was silently billed at zero.

The GrokBuild backfill test expectations follow the corrected cache-read
price; its cache-inclusive input assertion (450 fresh tokens) is unchanged.
2026-08-13 17:53:00 +08:00
Jason c82624761c fix(proxy): stop thinking/reasoning_content injection for Kimi upstreams
Moonshot/Kimi asked to exit the reasoning-vendor workaround: their
endpoints no longer require thinking replay on tool-call turns, and the
injected placeholders disrupt the model's chain of thought.

- Remove moonshot/kimi from REASONING_VENDOR_HINTS; Kimi now gets clean
  passthrough on the anthropic path (no placeholder thinking blocks, no
  signature stripping) and no reasoning_content on the openai_chat path.
- DeepSeek/MiMo keep the behavior (documented server-side requirement).
- Flip the two Kimi gating tests into negative regressions and update
  stale comments/test model names that still referenced Kimi.
2026-08-13 17:53:00 +08:00
Jason d2b070c965 fix(proxy): never clobber Codex official ChatGPT login on takeover restore
The restore backup is a snapshot taken when takeover starts and is never
refreshed from live afterwards. If the user logged into official ChatGPT
while takeover was active, exit/crash-recovery restored the stale
third-party API-key snapshot verbatim over auth.json, wiping the login on
every restart (and the auto re-takeover on startup looped the clobber).

Restore now arbitrates before writing: whenever live auth.json holds real
login credentials (codex_auth_has_credential_login_material, and not the
proxy placeholder), live wins — the backup's API key is demoted into
config.toml as experimental_bearer_token and the backup auth slot is
dropped, so auth.json is left untouched. Live credentials are always newer
than the snapshot because only Codex itself (login / token self-refresh)
advances them, so even an official-shape backup must not roll live tokens
back. Metadata residue (last_refresh / tokens.account_id) counts neither
as a live login to protect nor as backup material to restore. Both restore
entry points — the with-fallback path (exit / crash recovery / takeover
disable) and the simple path (takeover rebuild and failure rollback) —
share the same protection.

Fixes #6277
2026-08-13 17:53:00 +08:00
Jason 967daa1a06 fix(skills): report missing SSOT directory as update in check_updates
check_updates trusted the DB-cached content_hash before ever looking at
disk, so a skill whose SSOT directory is gone (e.g. after restoring a DB
backup on another machine) showed "no update" and the missing files were
permanently masked; the only recovery was uninstall + reinstall.

Extract the local-hash decision into local_hash_for_update_check with
the order inverted: validate the untrusted directory name, check that
the SSOT directory actually exists, and only then trust the cached
hash (computing and backfilling it when absent). A missing directory
now yields None, which never equals the remote hash, so the skill
surfaces as "update available" and the existing update_skill flow
rebuilds it — no new download logic.

Invalid directory values keep the pre-fix behavior (fall back to the
cached hash) instead of being reported as updatable, because
update_skill would hard-fail on the same validation gate.

Covered by four unit tests on the extracted helper.
2026-08-10 23:55:00 +08:00
Jason 3c592d93e6 fix(windows): escape backslashes before Handlebars vars in WiX registry key
The single backslash in Software\{{manufacturer}}\{{product_name}} was
consumed by Handlebars as a mustache escape, so the MSI wrote a literal
key named Software{{manufacturer}}{{product_name}} directly under HKCU.
Double the backslashes to match every other registry key in the template.

Fixes #6283
2026-08-10 23:07:51 +08:00
Jason bef46cd5de chore(grokBuild): correct OpenCode Go exclusion rationale
Upstream has had grok-4.5 since 2026-08, so 'no Grok models upstream' is no longer accurate.
Exclusion is now a deliberate product decision; enrollment requires separate review.
2026-08-10 22:56:51 +08:00
Jason 16cc0d7fea feat(claude): route OpenCode Go preset directly to /messages
/v1/messages only accepts x-api-key: switch env from ANTHROPIC_AUTH_TOKEN to ANTHROPIC_API_KEY and add top-level apiKeyField.
Drop apiFormat so Claude Code connects directly (providerNeedsRouting no longer triggers) instead of through the proxy.
Desktop keeps mode: proxy (it cannot rename models itself) but switches to anthropic passthrough.
2026-08-10 22:56:51 +08:00
Jason 390102a23b fix(codex): fill DeepSeek contextWindow in OpenCode Go catalog
Both deepseek-v4-pro/flash lacked contextWindow; codex_config.rs falls back to 128k for catalog entries without it, forcing premature compaction.
2026-08-10 22:56:51 +08:00
Jason c98cc3a9e9 ci: skip frontend/backend checks when a PR touches neither area
Add a changes-detection job (dorny/paths-filter, SHA-pinned) and gate
the frontend job, the 3-OS backend matrix, and the WSL2 contract gate
on whether the PR actually touches their inputs. About two thirds of
merged PRs only touch one side, and docs-only PRs now skip CI entirely.

Pushes to main still run everything unconditionally so the caches that
PRs restore from stay warm. Path groups mirror .github/labeler.yml,
plus rust-toolchain.toml which pins the compiler used by the backend
jobs.
2026-08-10 17:06:52 +08:00
Jason 36ed280d32 ci: fix i18n labeler glob to match actual locales path
The i18n label never fired because the glob pointed at src/locales/**
while the locale files live under src/i18n/locales/.
2026-08-10 17:06:52 +08:00
Jason 43eaf07355 docs(release): add v3.19.2 release notes 2026-08-06 12:21:00 +08:00
Jason 425e932b7f chore(release): v3.19.2 2026-08-06 12:21:00 +08:00
Jason baf07a2701 perf(usage): batch Codex session inserts and preload sync cursors
Full Codex usage reimport (triggered by SQL backup import of pre-v16
backups, cursor mismatch after cross-machine restore, or manual rebuild)
pegged one CPU core for minutes on large corpora. Measured on a real
corpus (1920 rollout files / ~1.7GB / 82k billable events, macOS,
release build):

- full reimport on a disk-backed database: 36.3s -> 11.1s (~3.3x)
- the write path accounts for most of the win: per-event autocommit
  under journal_mode=delete paid one full journal create/fsync/delete
  cycle per row; Windows pays ~4.6ms per fsync (per #6122
  measurements), so the absolute win there is an order of magnitude
  larger

Changes:

1. sync_single_codex_file writes token events in batched transactions
   (1000 events per commit), releasing the connection lock between
   batches so UI queries can interleave. The sync cursor advances
   inside the same transaction as the final batch, so a crash can
   never leave the cursor ahead of the data; batches committed before
   a crash are covered on rescan by the existing request_id PK +
   fingerprint dedup.
2. CodexSyncPass preloads all session_log_sync cursors once per pass
   (the archived-sessions suffix inheritance query used substr, could
   not use an index, and ran a full table scan per archived file) and
   caches model pricing lookups per pass instead of one SELECT per
   event.
3. The three dedup queries and the insert statement use
   prepare_cached; the two dynamically assembled dedup SQL strings are
   built once via LazyLock.

Behavioral equivalence verified by replaying the real corpus before
and after the change: all 82k imported rows byte-identical across all
exported columns, with identical import/skip/suspected-duplicate
counts. The replay harness is kept as an ignored test
(replay_real_codex_corpus) for future changes on this path.
2026-08-06 09:50:14 +08:00
Jason 290b65c03b chore(sponsors): point Sponsor button to GitHub Sponsors profile 2026-08-04 23:32:39 +08:00
Jason 0cb6e01410 fix(ui): keep header actions visible when all apps are enabled
With every app tab, the profile switcher and the takeover toggles shown,
the header overflowed and clipped the add-provider button. Restructure
the header right side so primary actions live in a fixed shrink-0
cluster, and make AppSwitcher width-aware: apps that no longer fit
collapse into a "more" popover, with the active app always kept visible.
2026-08-04 23:32:39 +08:00
Jason 5b697abc03 chore(sponsors): move Qiniu right after AIGoCode in preset order
Reorders the Qiniu preset entry to sit directly after AIGoCode across all
eight in-app preset files. Pure block move, no field changes; README sponsor
tables are unaffected since Qiniu is not listed there.
2026-08-04 23:30:45 +08:00
Jason 0e604b75bd feat(sponsors): surface Kimi first top-up bonus in README and presets
Kimi is running an API top-up promotion (distinct from the Kimi Code
subscription): new users who complete their first successful top-up get 10%
of the amount back as API credit, capped at CNY 1,000.

In the four READMEs, add a dedicated bonus paragraph between the K3 intro and
the Kimi Code subscription line, so the API and subscription paths each keep
their own call to action. All platform links in the Kimi header block now
carry the campaign track_id: README_ZH uses the mainland platform.kimi.com
tracker, while the en/ja/de READMEs use the global platform.kimi.ai one. The
kimi.com/code subscription links are unchanged.

In the app, give the six Kimi presets (claude, claudeDesktop, codex, opencode,
openclaw, hermes) a partnerPromotionKey so the offer shows under the API key
link, and add the matching string in the zh/en/ja/zh-TW locales. The Kimi For
Coding presets are deliberately left alone — the promotion does not apply to
the subscription. Promotion display is decoupled from isPartner, so this adds
no gold partner star to Kimi.

The English and Japanese strings say "CNY ¥1,000" rather than "¥1,000", since
a bare yen sign reads as JPY in those locales and would understate the offer
by roughly 20x.
2026-08-03 21:34:28 +08:00
Jason 4d3e2c3524 chore(sponsors): update RunAPI in-app promo to first top-up discount
The in-app partnerPromotion.runapi string still advertised the retired
"register and contact support for CNY 14 free credit" offer, while all four
READMEs had already moved to the first-top-up discount. Align the zh, zh-TW,
en and ja strings with the README wording: 9 折 in Chinese, 10% off in
English and Japanese.
2026-08-03 21:34:28 +08:00
Jason 996d512f6e chore(sponsors): drop NekoCode partner from README, presets and i18n
Remove the NekoCode sponsor row from the four README files (en, zh, ja, de)
and the provider preset across all seven preset files that carried it
(claude, claudeDesktop, codex, opencode, openclaw, hermes, grokBuild; there
was never a gemini preset). Also drop the matching partnerPromotion.nekocode
string in the zh/en/ja/zh-TW locales.

The nekocode icon stays registered in src/icons/extracted, since icon names
are persisted on existing provider records and removing it would blank out
the icon for users who already imported the preset. Historical CHANGELOG and
release-notes entries are left untouched.
2026-08-03 21:34:28 +08:00
Jason 9db9c56fda fix(proxy): report dropped Chat tool calls instead of faking completion
When a third-party gateway returns tool calls without a function name,
the Chat -> Responses transform silently discarded them and still marked
the turn as `completed`. Codex then saw a successful turn with nothing
left to do and ended its agent loop without any error, turning a
diagnosable upstream failure into a silent stall.

- Emit `response.failed` (streaming) or a transform error (non-streaming)
  when every tool call in a turn was dropped and none remains usable.
  Gated on `status == "completed"` so that `finish_reason: length`
  truncation keeps its own `incomplete` semantics, matching the existing
  Anthropic streaming path.
- Log all three drop sites with structured, content-free fields (call_id
  presence, argument byte counts, finish reason) so the upstream defect
  can finally be diagnosed from real traffic.
- Treat whitespace-only function names as missing, and resolve tool keys
  conservatively when upstream omits the required `index` field.

Turns that still contain a valid tool call, text-only turns and truncated
turns are unaffected. Adds 13 tests.

Refs #4341
2026-08-03 21:34:28 +08:00
Jason ebbf141fc7 chore(sponsors): drop Unity2.ai partner from README, presets and i18n
Remove the Unity2.ai sponsor entry from the four README files (en, zh, ja,
de) and the provider preset across all eight preset files (claude, codex,
gemini, opencode, openclaw, hermes, claudeDesktop, grokBuild). Also drop
the matching partnerPromotion.unity2 string in the zh/en/ja/zh-TW locales.

Historical CHANGELOG and release-notes entries are left untouched.
2026-08-01 11:39:58 +08:00
Jason fbf52cff73 docs(release): correct the v3.19.1 pointer to the DeepSeek routing guide
The Usage Guides entry told readers the guide's DeepSeek sections no
longer applied to this release. That was true when v3.19.1 shipped, but
the guide has since been rewritten for it, so the warning now steers
people away from an accurate document.

Replace it with what the guide actually says: presets created after
3.19.1 connect directly, while providers saved earlier and
deepseek-v4-pro still need routing. Also drop MiniMax from the
Chat-format list — it moved to native Responses too — and name Zhipu GLM
instead.

The published release body on GitHub was updated to match.
2026-08-01 10:29:39 +08:00
Jason a4bba43f73 docs(guides): update Codex DeepSeek routing guide for v3.19.1
The guide used DeepSeek as its Chat-format example, which stopped being
accurate once the preset moved to native Responses. It is not obsolete,
though: a provider saved before 3.19.1 keeps its stored apiFormat and
still carries the "needs routing" badge, and deepseek-v4-pro has no
official Codex integration yet, so Chat + routing remains its only path.

Rather than swap in a different provider, open with a check for which
case the reader is in (badge present/absent/no-routing-support) plus a
three-row table for DeepSeek specifically. The title and filename stay
put — six published release notes and three sibling guides link here.

Also in this pass:

- Drop the screenshot of the old boolean "needs local routing mapping"
  toggle; that control is now Advanced Options -> Upstream Format, a
  three-way select. The image file stays, since the official-auth
  preservation guide still references it.
- Document the Anthropic Messages format, previously unmentioned.
- Fix the Chat-provider list: DeepSeek and MiniMax both moved to
  Responses, so name Kimi, Zhipu GLM, SiliconFlow and ModelScope.
- Note that converting an existing provider keeps the official catalog's
  capabilities (freeform apply_patch, GPT-5 harness, low/high/max,
  web_search) but that its stored contextWindow of 1000000 overrides the
  official 1048576, with two ways to fix it.
- Record the direct connection's prerequisites: Codex CLI 0.144.0+ and a
  ~75 KB catalog file.
- Add a usage-attribution section: the provider dimension collapses into
  Codex (Session), while the model dimension still separates rows.
- Reference DeepSeek's official Codex integration and Responses API docs.

All UI terms are taken from the locale files so they match what the app
actually renders in each language.
2026-08-01 10:29:32 +08:00
Jason 28529620f4 docs(release): add v3.19.1 release notes 2026-07-31 22:30:36 +08:00
Jason b3a20e58b0 chore(release): v3.19.1 2026-07-31 22:30:28 +08:00
Jason 58dd376dc4 feat(codex): add Tencent Hunyuan TokenHub preset with native Responses
Official TokenHub Codex docs (cloud.tencent.com/document/product/1823/133532)
confirm hy3 speaks the Responses API natively; the mandatory
disable_response_storage=true is already emitted by the config
generator. Models hy3/hy3-preview are text-only with a 256k context
window. Endpoint candidates include the official backup domain, while
the intl site is excluded because API keys are region-scoped.
2026-07-31 19:12:58 +08:00
Jason 56a66eea36 feat(codex): switch Volcengine Agentplan preset to native Responses
Official Codex docs (volcengine.com/docs/82379/2556056, updated
2026-07) confirm the Coding Plan endpoint /api/coding/v3 supports the
Responses API, so the preset no longer needs local route conversion.
BytePlus stays on Chat routing until the international-site docs are
verified. Also document the billing pitfall: the pay-as-you-go /api/v3
endpoint must never appear in plan-subscription endpoint candidates.
2026-07-31 19:12:58 +08:00
Jason 8ae1ce8558 feat(codex): add DeepSeek native Responses support with official catalog mirror
- Switch the DeepSeek preset to openai_responses and align context
  windows with the official catalog (1048576)
- Mirror DeepSeek's official models.json verbatim for native /responses
  providers on deepseek.com hosts, keeping the official GPT-5 harness
  and freeform apply_patch registration self-consistent
- Make catalog spec displayName/contextWindow explicit-only (Option) so
  local defaults no longer clobber official vendor values
2026-07-31 19:12:58 +08:00
Jason f42534ed26 fix(pricing): sync seeded model prices with vendor list prices
- deepseek-chat / deepseek-reasoner: now legacy aliases of V4 Flash,
  0.27/1.10 & 0.55/2.19 -> 0.14/0.28 (cache read 0.0028)
- minimax-m3: 0.60/2.40 -> 0.30/1.20 (official standard tier)
- gpt-5.6-luna -80% (1/6 -> 0.20/1.20) and gpt-5.6-terra -20%
  (2.50/15 -> 2/12) per OpenAI's 2026-07-30 price cut; sol unchanged
- seed 8 new models: bare claude-{opus,sonnet}-4-6, gemini-3.5-flash-lite,
  kimi-k2.7-code-highspeed, glm-5-turbo, glm-5v-turbo, gpt-5.3-codex-spark,
  qwen3.6-flash
- repair guards for existing installs, dual guards on GPT-5.6 rows to
  cover pre-v3.19 DBs that missed the cache-write backfill
2026-07-31 19:12:58 +08:00
Jason e3f80a98f3 fix(codex): clear stale auth on official switch 2026-07-31 19:12:58 +08:00
Jason f07edc7680 fix(settings): make Grok Build upgrade work from the GUI
`grok update` discovers and installs releases by spawning `npm view` and
`npm i -g`, even for xAI's native install — 0.2.112 moved the self-update
path onto npm distribution, so the binary now needs node on PATH.
Lifecycle scripts run under a non-login `bash -c` inheriting launchd's
narrow PATH, where npm and node are invisible, so upgrading grok failed
with a bare `Error: No such file or directory (os error 2)`.

Inject the login shell's real PATH into run_tool_lifecycle_silently,
closing the asymmetry between probing (`$SHELL -lic`, which reads .zshrc)
and execution (non-login bash). Read it through `/usr/bin/env` rather
than `echo $PATH`: fish stores PATH as a list and would emit
space-separated segments, while env always prints the child's real
environment. This also revives the install chain's bare `npm i -g`
fallback, which could only ever exit 127 under the narrow PATH.

Chain the official installer after native Grok's self-update. An npm
fallback would share both of the primary's failure modes — no node, or a
registry mirror missing the tarball — and fail alongside it; the
installer is the only node-free path and lands in the same ~/.grok/bin.
It also rewrites `[cli] installer` back to `internal`, healing users whom
the install-time npm fallback had switched onto npm distribution.
2026-07-31 19:12:58 +08:00
Jason c0ff89b9b2 docs(changelog): sync 3.19.0 security section with the release notes 2026-07-30 12:02:34 +08:00
Jason 3b9d059343 docs(release): add v3.19.0 release notes 2026-07-30 12:01:38 +08:00
Jason 6b13d01825 chore(release): v3.19.0 2026-07-30 09:29:54 +08:00
Jason f5f4281d06 refactor(ui): always show icon-only app switcher, drop auto-compact
The toolbar app switcher used a ResizeObserver-based overflow detection
(useAutoCompact) to collapse app labels when space ran out. With the
number of managed apps growing, the labels are collapsed in practice
anyway, so remove the mechanism and render icons only. Buttons now carry
title/aria-label so app names remain discoverable via tooltip and
accessible to screen readers.
2026-07-30 08:52:48 +08:00
Jason 30409878bd chore(presets): add PackyCode backup endpoints
Add three fallback endpoints alongside the primary www.packyapi.ai across
the five preset files that support endpointCandidates:

  https://cf.api.fan
  https://slb-v1.api.fan
  https://www.packyapi.com

The /v1 suffix follows each file's existing convention rather than the
literal values supplied: bare domains for the Anthropic-native presets
(Claude Code, Claude Desktop, Gemini), /v1 for Codex and Grok Build.
Candidates are consumed as complete base URLs by the endpoint picker and
the speed test, so they must sit at the same path level as the primary.

www.packyapi.com is the pre-b0482320 domain, kept here deliberately as a
fallback -- not a leftover of that migration.

OpenCode, OpenClaw and Hermes have no endpointCandidates field in their
interfaces and are untouched.
2026-07-29 17:09:29 +08:00
Jason bfb767ae17 chore(presets): align Code0 and Qiniu Gemini defaults to 3.6 Flash
Both presets pinned gemini-3.1-pro-preview while the other Gemini presets
had moved to gemini-3.6-flash. Note this is a tier change rather than a
version bump: there is no 3.6 Pro release and 3.5 Pro is still limited to
partner testing, so the current baseline is a flash-tier model.

The gemini-3.1-pro-preview row in the built-in pricing seed is kept so
historical usage keeps its cost.
2026-07-29 16:40:31 +08:00
Jason dbb265956e feat(presets): add A6API sponsor preset across all supported apps
Add the A6API aggregator preset to Claude Code, Claude Desktop, Codex,
Gemini CLI, OpenCode, OpenClaw, Hermes and Grok Build, placed after
NekoCode in the sponsor ordering. Base URLs follow the per-client
convention: no /v1 suffix for the Anthropic-native and Gemini endpoints,
/v1 for the OpenAI-compatible ones. Model defaults mirror NekoCode.

Also add the four-locale promotion copy, the sponsor row in all four
READMEs and the icon index entry.

The supplied artwork was resized before landing: the icon was a 1024x1024
PNG base64-wrapped in an SVG shell (652K, the largest entry in iconUrls
and shipped in every build), now a 256x256 PNG at 60K; the banners were
16:9, the only ones deviating from the 2.406 project standard, now
cropped to 1280x532.
2026-07-29 16:40:23 +08:00
Jason 87b0e3fb85 fix(test): pin zip extraction temp dir instead of hijacking TMPDIR
The two cleanup-guard tests introduced in ff3bc242 set the process-global
TMPDIR to a scratch dir and asserted it ended up empty. serial_test only
serializes marked tests, so any concurrent test creating a tempdir inside
the hijacked window landed in scratch and randomly failed the emptiness
assertion on Ubuntu/macOS CI (Windows ignores TMPDIR).

Add an extract_local_zip_in(zip_path, base_dir) seam that takes the temp
base explicitly; the public function delegates with std::env::temp_dir().
Tests now pass their private scratch dir directly, dropping the TMPDIR
mutation and the serial markers — the race is impossible by construction.
2026-07-29 10:22:30 +08:00