Common config snippets with array fields (e.g. permissions.deny) were
silently overwriting provider-specific entries instead of merging them.
This changes deepMerge to append only new elements (deduplicated by
bidirectional subset equality), deepRemove to strip one element per
source item, and isSubset to use bipartite matching so each source
element claims a distinct target element.
On the Rust side, the live backfill path now uses a new
json_deep_remove_preserving_original_arrays that receives the original
provider settings and only removes entries injected by the snippet,
preserving entries the provider already had. Type-mismatch guards in
both the object and array branches restore the original value when a
snippet changes a key's type.
Fixes#6141
The Some arm of set_codex_model_catalog_json_field unconditionally overwrote model_catalog_json with the cc-switch-owned filename, discarding any user-provided custom catalog path or filename. This mirrors the ownership check already present in the None arm: only claim the pointer when it is absent or already cc-switch-owned, leaving user-managed external catalog files untouched.
Add tests covering both full-path and relative-filename user-owned catalogs in the Some arm.
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.
* fix(opencode): support unified OMO config
* fix(opencode): couple rollback snapshots to writes
* fix(ci): simplify rollback version type
* fix OMO unified config review issues
* validate OMO round-trip semantics
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.
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.
* fix(skills): build readme_url from the resolved source directory
For skills.sh installs, `skill.directory` is just the skillId (the
innermost directory name) and the search-result readme_url is the bare
repo URL, so the persisted doc link lost the real nested path
(e.g. skills/developertools/solutions/<name>) and 404'd (#6111).
- install: derive the doc path from the resolved source directory
(repo-relative, SKILL.md-anchored) ahead of the stale readme_url
extraction and the directory-name fallback.
- update_skill: same derivation, so records written by older versions
self-heal on the next update instead of re-assembling the broken
path from the stale readme_url. repo_branch already follows
used_branch.
- New choose_doc_path / doc_path_for_source helpers with regression
tests; dropping the resolved-path priority fails the tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(skills): harden nested source resolution
* fix(skills): refresh repaired source metadata
* refactor(skills): keep nested readme fix install-only
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: YUZHEthefool <2804776511@qq.com>
* perf(backup): batch dump INSERTs and restore preserved tables in one transaction
Backup import was pathologically slow for databases with large log
tables. Measured on a database with 26k log rows (Windows, debug build):
- local file import (import_sql_string): 28.0s -> 0.39s (~71x)
- WebDAV/S3 sync import (import_sql_string_for_sync): 118.9s -> 0.33s
(~360x)
- export: 367ms/15.4MB -> 193ms/3.9MB (column list no longer repeated
per row)
Root causes, isolated with a phase-level benchmark (kept in the test
suite as ignored perf harnesses):
1. dump_sql emitted one INSERT statement per row. The restore side
parses/prepares/finalizes every statement individually, so import
cost scaled with statement count, not data volume. A control run on
an in-memory database was equally slow, proving this is CPU-bound
parsing, not disk I/O; journal/synchronous pragmas made no
difference. dump_sql now emits multi-row VALUES batches of 200 rows
(~200x fewer statements; SQLite has supported multi-row VALUES since
3.7.11, and both old and new formats import through the same generic
execute_batch, so dumps remain cross-version compatible).
2. restore_tables re-inserted preserved local tables (proxy logs,
stream check logs, usage rollups) with one implicit autocommit
transaction per row against the disk-backed staging database - one
fsync per row, ~4.6ms each on Windows. All restores now run in a
single transaction with the INSERT prepared once per table, which
also makes a mid-restore failure roll back atomically instead of
leaving a half-restored table.
Regression tests:
- dump_sql_batches_rows_into_multi_row_inserts pins the batch shape
(450 rows -> exactly 3 INSERT statements; fails if reverted to
per-row)
- multi_row_dump_round_trips_special_values round-trips quotes,
newlines, commas, CJK, emoji, BLOB storage class, and NULL through
export+import
- existing authorizer / genuine-export / sync-preserve tests still pass
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(backup): preserve SQL dump compatibility
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: YUZHEthefool <2804776511@qq.com>
* fix(security): harden usage script execution and Grok session log collection
Three hardening fixes in v3.19.0 code:
1. **usage_script: cap JS runtime resources and execution time**
`execute_usage_script` used a vanilla `Runtime::new()` with no limits,
so a malicious or buggy script delivered via deeplink or synced DB could
hang the backend thread forever with `while(true)`. Add
`create_script_runtime()` enforcing a 5-second interrupt handler, 16 MiB
memory limit, and 256 KiB stack limit. Regression test confirms an
infinite-loop script is rejected within seconds instead of blocking.
2. **Grok session log: skip oversized files**
`sync_single_grok_file` loaded the entire `updates.jsonl` into memory
with no size cap. Add a 50 MiB limit and skip files that exceed it.
3. **Grok session log: bound directory traversal**
`collect_files_named` recursed without depth limits and followed
symlinks, so a symlink cycle under `~/.grok/sessions` caused a stack
overflow. Add a 16-level depth limit and skip symlinks entirely.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ui): show hidden usage access token and user id in deeplink provider import dialog
The provider import confirmation dialog parsed usageAccessToken and
usageUserId from ccswitch:// URLs and persisted them in
ProviderMeta.usage_script, but never rendered them. Users could not see
these credentials before approving the import.
- Add i18n keys for usageAccessToken/usageUserId in all locales.
- Render both fields in the usage script section; mask the access token
like usageApiKey.
- Add a regression test verifying the dialog displays the masked token
and the user id.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(security): bound Codex model catalog and proxy response bodies
Two issues were found while auditing security boundaries:
1. **Codex model_catalog_json arbitrary file read**
only checked the filename and
accepted any absolute path. A malicious could
point CC Switch at an external file (e.g. ) or a huge
file to exhaust memory.
- Require the resolved path to stay inside
using a new helper (lexical, no filesystem calls).
- Treat Unix-style absolute paths as absolute on Windows.
- Cap catalog reads at 32 MiB.
2. **Unbounded proxy response bodies / compression bombs**
Non-streaming responses and error responses were read with
and fully decompressed without any byte
limit, allowing a malicious upstream to OOM the backend.
- Add with a 128 MiB ceiling.
- Reject oversized bodies and oversized decompressed payloads with
.
- Map the new error variant into the Codex proxy error response.
Regression tests cover: absolute-path containment, traversal rejection,
oversized catalog file, oversized buffered/streamed proxy responses.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ci): set up pnpm via Corepack instead of pnpm/action-setup v6
pnpm/action-setup v6 installs pnpm through a two-step self-installer:
it bootstraps the latest pnpm (11.7.0) via npm, then downloads the
pinned version (10.12.3) from the npm registry to switch. When that
second tarball fetch failed (error 23), the self-installer still
reported "done" and crashed with ENOENT on @pnpm/linux-x64's
package.json, failing the CI setup step.
Replace the action with Node 20's bundled Corepack and pin the version
once via package.json's packageManager field, so setup performs a
single exact-version download with no floating bootstrap. This also
lets release.yml drop its Windows ARM64 special case, since Corepack
has no arch restrictions, and pins the same version for local dev.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(security): close review gaps in proxy body caps, deeplink dialog, catalog path
Address maintainer review on the security PR:
- Proxy: bound the four remaining unbounded forwarder read paths
(non-2xx error body, failover pre-read, both 2xx error-envelope
validators) with bytes_with_limit + bounded decompression.
- Proxy: bytes_with_limit now accumulates Hyper/Reqwest/Streamed bodies
chunk-by-chunk and aborts mid-stream instead of collecting the full
body before comparing; Buffered keeps the post-hoc check.
- Proxy: decompression is budgeted at the decoder (Read::take on
gzip/deflate/zstd, bounded brotli reader) via decompress_body_with_limit,
so bombs are cut off at the byte budget instead of after full expansion,
including stacked-encoding intermediates.
- Deeplink: show the usage section whenever any usage field is present,
matching build_provider_meta persistence — a deeplink carrying only
usageAccessToken/usageUserId no longer imports credentials invisibly.
- Codex catalog: after the lexical containment check, canonicalize the
existing file and re-verify containment so symlinks inside ~/.codex
cannot escape the config dir; the path_is_within doc comment no longer
oversells the lexical check as symlink-safe.
- Grok sync: comment now matches the skip-all-symlinks behavior and each
skip is logged, so sessions behind a symlink are diagnosable.
- Docs: CONTRIBUTING notes the packageManager pnpm pin and Corepack flow.
Regression tests follow the same methodology as round 1: reverting a
guard fails the corresponding test (server write-count assertions prove
the mid-stream abort; a truncated gzip stream distinguishes TooLarge
from read-to-end errors; symlink escape is rejected only after the
canonicalize re-check).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(opencode): load OMO models from runtime opencode models
Surface OAuth/Zen free models in OMO/OMO Slim pickers by running the
installed OpenCode CLI, with a 20s timeout and a toast when discovery fails.
* style: fix rustfmt and prettier for OMO runtime models
* fix(opencode): pass OPENCODE_CONFIG_DIR for runtime model discovery
Honor the configured OpenCode config directory when running
`opencode models`, including WSL UNC path translation. Also drop the
unused run_detected_tool_command wrapper that failed clippy.
* fix(opencode): satisfy Windows Clippy
* fix(opencode): bound runtime model discovery
* fix(opencode): address runtime model review feedback
* fix(ci): parenthesize unsafe kill expression
* feat(codex-oauth): show per-account usage in Auth Center
Each ChatGPT (Codex OAuth) account under Settings → 认证 now displays its
own subscription usage — reset countdowns and per-window progress bars —
directly in the account list, instead of usage only being visible on the
active provider card.
- Add useCodexOauthQuotaByAccountId(accountId) and refactor
useCodexOauthQuota to delegate to it (shared query key → cache reuse)
- Add CodexOauthAccountQuota, a thin per-account wrapper that reuses the
existing SubscriptionQuotaView expanded layout (same look and 5-state
handling as provider cards), with a light spinner on first load
- Render it under each account row in CodexOAuthSection; fetch once when
the Auth Center opens, manual refresh available (no polling)
Copilot is intentionally left out — same as before, this is Codex-only.
* refactor(codex-oauth): stable async loading placeholder for account usage
The account header (login + badges + actions) already renders independently
of the usage query — the quota is fetched async via Tauri invoke + React
Query, so the account never waits on it. Make that visually obvious and
jump-free: while the usage loads, show a spinner inside a placeholder shaped
like the final quota card (same rounded-xl / border / bg-card), so the card
morphs smoothly into the data instead of popping in from an empty gap.
* fix(codex-oauth): scope account quota to auth center
---------
Co-authored-by: Jason <farion1231@gmail.com>
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.
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.
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.
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
* fix(skills): resolve source dir by SKILL.md anchor instead of name
resolve_skill_source_dir previously guessed the source dir via root.join(name).is_dir() without verifying SKILL.md, misjudging same-name non-skill dirs (e.g. the ast-grep plugin wrapper dir in ast-grep/agent-skill) and causing install failure #4141.
Now anchors on SKILL.md: direct + SKILL.md check -> root manifest explicit skills[] -> fallback by name -> root fallback. Adds 5 layout tests.
Closes#4141
* fix(skills): drop speculative manifest resolver path
resolve_via_manifest (parsing root .claude-plugin/marketplace.json &
plugin.json explicit skills[]) is inert for the actual #4141 case: the
real ast-grep/agent-skill marketplace.json declares no skills[] array,
so the manifest branch never produces a candidate. The #4141 fix is
delivered entirely by resolve_skill_source_dir step 1's SKILL.md anchor
plus the pre-existing find_skill_dir_by_name DFS.
Keeping the manifest path would pull npx-skills package-parity semantics
(pluginRoot / source / remote-object source / skills[] / "./"-validation
/ ...) into a bug hotfix, with no real manifest proving it is not dead
code. Drop it to keep this PR a focused #4141 hotfix.
- remove SkillMarketplaceMetadata / SkillManifestPlugin /
SkillMarketplaceManifest, resolve_via_manifest, sanitize_manifest_path
- narrow resolve_skill_source_dir to 3 steps
(direct+SKILL.md -> by-name DFS+SKILL.md -> root+SKILL.md -> None)
- replace the two synthetic manifest tests with a negative case:
same-name wrapper dir without SKILL.md and no inner skill -> None
cargo test --lib resolve_skill_source_dir: 7 passed
cargo clippy --lib: clean
* feat(pricing): seed Qwen3.8 Max built-in model pricing
Add insert-if-absent row for qwen3.8-max at 2/6 USD per Mtok input/output with 0.20 cache read.
* fix(pricing): set qwen3.8-max cache write to 2.50
Align cache_write with official explicit context-cache rate (125 percent of input). cache_read stays 0.20 (10 percent hit).
* fix(pricing): correct qwen3.8-max cache read price
---------
Co-authored-by: Jason <farion1231@gmail.com>
* 修复 Copilot 与新版 Claude Code 的兼容问题
* docs(proxy): correct Copilot placeholder rationale to the real mechanism
Claude Code (verified on 2.1.220) does not format-validate ANTHROPIC_API_KEY
against sk-ant-*: in headless mode the placeholder is sent upstream as-is.
The actual failure mode is the interactive custom-API-key approval prompt,
which defaults to "No (recommended)" — following the default ignores the
key and lands users in "Not logged in". Also drop the #3289 citation,
which describes a missing-placeholder scenario, not key validation.
---------
Co-authored-by: Jason <farion1231@gmail.com>
* fix(hermes): use SOUL.md instead of AGENTS.md for Hermes prompt filename
* test(hermes): add regression test for SOUL.md prompt filename
---------
Co-authored-by: mmm-05610 <maoqh@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
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.
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.
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.
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.
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.
- 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
`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.
* fix(i18n): add missing grokBuild translation keys to all locales
providerForm.requiredFields and failover.tooltip.takeoverRequired were
missing from all four locale files (en, zh, zh-TW, ja). The Grok Build
provider form validation toast and the failover tooltip fell back to
hardcoded Chinese defaultValue, which leaked simplified Chinese into
zh-TW and zh-Hant UI even though fallbackLng is set to en.
Add the two keys to every locale so each language shows its own
translation.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(i18n): add 6 more missing translation keys to all locales
A broader scan found six more keys referenced in code with hardcoded
Chinese defaultValue but missing from all four locale files (en, zh,
zh-TW, ja):
- provider.duplicateLiveIdsLoadFailed (App.tsx provider duplicate toast)
- codexConfig.noCommonConfigToApply (useCodexCommonConfig snippet error)
- claudeDesktop.route.stopBlockedByTakeover (ClaudeDesktopRouteToggle warning)
- notifications.proxyReasonClaudeDesktop (useProviderActions proxy reason)
- proxy.server.stopped / proxy.server.stopFailed (useProxyStatus toasts)
Add proper per-language translations to all locales, matching the
existing sibling-key style (e.g. proxy.server.started/startFailed uses
the same {{detail}} interpolation).
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(i18n): add missing unpriced translations
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jason <farion1231@gmail.com>
Complete the Traditional Chinese strings for the About-page tool manager and align the install/update hint with the current supported tools. Add locale coverage that requires every tool-management label and preserves interpolation variables across all four translations.
Constraint: The About tool manager was extended across three commits without matching zh-TW entries.
Rejected: Rely on i18next fallback text | leaves the Traditional Chinese UI partially English and hides future locale drift.
Confidence: high
Scope-risk: narrow
Directive: Update toolManagementLocales.test.ts whenever the About tool manager adds a translatable label.
Tested: pnpm typecheck; pnpm format:check; 7 locale tests; 575 Vitest tests; zero missing zh-TW settings keys
Not-tested: Manual visual inspection of the About page
Related: e3df86587, ea604a182, 014c82d28
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.
Add three fallback endpoints alongside the primary www.packyapi.ai across
the five preset files that support endpointCandidates:
https://cf.api.fanhttps://slb-v1.api.fanhttps://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.
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.
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.