CC Switch 接进第九个受管应用,动了一百多个文件

2026-08-31

如果只看发布说明的一句话,「新增支持一个 AI 编程 CLI」听上去像是往某个数组里加一条记录。真去翻这次改动会发现完全不是:接入 Pi 的那个提交一次改了 155 个文件。更值得琢磨的是,这 155 个文件并不集中在某一层,而是从 React 组件一直穿到 SQLite 建表语句。

这篇想把这条纵贯线拆开看:接一个新客户端,成本具体落在哪几层,哪些是必须改的,哪些是因为「新来的这个跟别人不一样」才被逼出来的。

先说清楚这篇是怎么来的

本文只做了一件事:静态读源码。对应的仓库快照是 3217f725(仓库内三处版本号文件写的都是 3.20.1),核对日 2026-08-31,另有一份 v3.19.2 的快照仅用于做 diff 对照。所有结论都来自文件内容、git show --stat 与 grep 统计。

我们没有安装、没有编译、也没有运行过这个桌面应用,因此下面不会出现任何关于界面长什么样、点哪里、切换快不快的描述。凡是引用 README 或发布说明的地方,都会写明是文档的说法而不是代码实况。

三处文档,三个数

先看一个最容易被绊到的地方。同一份 v3.20.1 快照里,「这个工具管几个 CLI」有三个不同答案:

来源位置说的是几个
代码src/config/appConfig.tsx:19-29src/lib/api/types.ts:2-11src-tauri/src/app_config.rs:380-3959
README 中文版README_ZH.md:210「一个应用,八个工具」、:230:2658
用户手册docs/user-manual/zh/1-getting-started/1.1-introduction.md:57

三处都能自己复现:

sed -n '19,30p' src/config/appConfig.tsx
grep -n "八个工具\|8 个支持工具\|支持八个工具" README_ZH.md
sed -n '40,55p' docs/user-manual/zh/1-getting-started/1.1-introduction.md

这里只陈述差异、标出各自的具体位置;至于哪一处该改、为什么没同步,不在能核实的范围内。但有两个可核实的事实值得记下来:v3.19.2 快照里的 README 同样写「八个工具」,在那个版本上它是对的;而用户手册那份 7 个的清单在 v3.19.2 与 v3.20.1 两版逐字相同,也就是说漏掉的那个应用在上一版就已经在代码里了。

所以引用这类数字时必须挂版本:v3.19.2 时受管应用是 8 个,v3.20.1 已改为 9 个,下一版还可能变。写成「这个工具支持 N 个 CLI」这种不带版本限定的句子,过一个月就是错的——README 本身就是活教材。

变更前:一个集合被复用

先看 v3.19.2 的注册表长什么样。那时 src/config/appConfig.tsx 里只有三组常量:APP_IDSSKILLS_APP_IDS,以及直接写成 [...SKILLS_APP_IDS]MCP_APP_IDS(旧快照 appConfig.tsx:40)。

这个写法背后有一个隐含假设:能用 Skills 的应用,就能参与 MCP 同步。 在当时那批应用上,这个假设是成立的,所以复用一份集合既省事又不会错。

变更后:四组显式常量加三个类型守卫

v3.20.1 的同一个文件里,这套隐式复用被拆成了几组各自独立的常量(appConfig.tsx):

常量行号边界
APP_IDS19-29全部受管应用,pi 排在末位
DEFAULT_VISIBLE_APPS31-41默认可见性,v3.19.2 无此常量
SKILLS_APP_IDS44-52参与 Skills 面板的,排除 claude-desktopopenclaw
PROXY_APP_IDS60-65claude / codex / gemini / grokbuild不含 pi
ADDITIVE_APP_IDS76-81opencode / openclaw / hermes / pi
MCP_APP_IDS89-96排除 claude-desktop / openclaw / pi

配套还多了三个类型守卫函数:isProxyAppId:67)、isAdditiveAppId:83)、isMcpAppId:98)。

拆分的理由写在源码注释里,不用猜。PROXY_APP_IDS 上方那行是 Apps with a complete local gateway + failover data plane.appConfig.tsx:59)——只有拥有完整本地网关与故障转移数据面的那几个应用才进这一组。McpAppId 上方那行更直接:Pi has no native MCP registry; do not manufacture a disabled mirror.appConfig.tsx:87)。

「不要给它造一个假的禁用镜像」——这句注释解释了为什么必须拆。如果继续复用一份集合,新应用要么被塞进它根本没有的能力里,要么就得在下游到处写特判。发布说明那侧的口径与源码对得上:明确不接的几项里就列着 MCP 同步(无原生注册表)、代理接管与故障转移(v3.20.0-zh.md:70)。至于后端,AppType 枚举这次只是在末尾多了一个成员(src-tauri/src/app_config.rs:380-395),与前端的 AppId 一一对应——枚举只管谁是成员,不管谁有什么能力;能力上的差别是靠上面那几组子集各切一刀切出来的,四个子集之间不是包含关系,AppId 只是它们的并集。

这是这次接入最有价值的一处结构变化:新增一个成员,逼出了一次能力边界的显式化。 集合复用能省事,前提是所有成员的能力完全同构;来一个不同构的,隐式复用就必须还债。

155 个文件都散在哪

从哪个提交看出来:84e75ad2 feat(pi): add native coding agent support (#6064)

git show --stat 84e75ad2 | tail -1
# 155 files changed, 18765 insertions(+), 912 deletions(-)

把这个提交触及的文件按目录聚合,改动最密的几层依次是前端组件(src/components)、前端数据层(src/libsrc/hooks)、Rust 服务层(src-tauri/src/services)、Tauri 命令层(src-tauri/src/commands)、会话管理器(src-tauri/src/session_manager);此外代理层、深链、数据库、国际化、前端配置目录也各有改动。

换句话说,接一个新客户端不是「加一个适配器」,而是同时穿过界面层、数据层、服务层、命令层、会话层、存储层与文案层。 任何一层漏掉,功能就是半截的。

新增的后端模块也很整齐,两个快照做目录 diff 就能看出来,而且没有任何文件被删除

  • src-tauri/src/pi_config/(新增目录,mod.rs 承担 models.json 的读改写与冲突检测)
  • src-tauri/src/commands/pi.rs(命令入口)
  • src-tauri/src/services/session_usage_pi.rs(用量摄取)
  • src-tauri/src/session_manager/providers/pi.rs(会话浏览)

命令登记这一侧的增量也可以精确对账。Tauri 命令集中登记在 src-tauri/src/lib.rsgenerate_handler! 宏里(新快照起始于 lib.rs:1371),把两版的条目排序后逐行 diff,结果是净增 10 条、删除 0 条:9 条属于新应用(状态、会话发现、用量脚本、提示词文件的读写删、斜杠命令模板的增删查),剩下 1 条 commands::auth_cancel_login 与新应用无关。

这也顺带说明了这类改动的形状:新增一条命令要同时改属性宏与登记表两个地方,两处必须对齐,所以命令总数这种数字每个版本都在动,不适合被当成稳定事实引用。

六套「应用」枚举,它在四套里、不在两套里

真正的适配成本不在共性,在差异。同一个「应用」概念在这个仓库里至少有六套成员不同的枚举,而新接入的这个成员,在其中几套里在、几套里刻意不在:

  • APP_IDSDEFAULT_VISIBLE_APPSSKILLS_APP_IDSADDITIVE_APP_IDS 里;
  • 不在 PROXY_APP_IDS(没有本地网关,也就没有代理接管与故障转移);
  • 不在 MCP_APP_IDS(没有原生 MCP 注册表)。

用量侧还有第七套独立口径:src/types/usage.ts:192AppType 只覆盖会产生用量数据的那一批,与前端的 AppId 并不是同一个集合。

ADDITIVE_APP_IDS 这一组尤其值得单独说。发布说明把它叫做「累加模式」,原话是「供应商的启用与否等于其键是否存在于 ~/.pi/agent/models.json,多个供应商共存」(docs/release-notes/v3.20.0-zh.md:68)。对照另一类应用的「切换」语义——一次只有一个生效的供应商——这是两种根本不同的配置模型:

切换型累加型
启用的判据当前哪一份配置是 live键在不在目标配置文件里
同时生效几个一个多个并存
常量归属不在 ADDITIVE_APP_IDSappConfig.tsx:76-81

同一套界面要同时承载这两种模型,这不是工作量问题,是设计问题。发布说明只举了两个同类应用为例,而 ADDITIVE_APP_IDS 数组里实际是四个成员——原文用的是「同类」而不是「仅此两个」,所以这不算文档说错,但要拿准确成员还是得回去看那个数组。

存储层与数据面上的连带改动

新应用带来的不只是配置读写,还有一条独立的用量来源。proxy_request_logs 表有一列 data_source(DDL 在 src-tauri/src/database/schema.rs:210,默认值 'proxy'),v3.20.1 里多了 pi_session 这个取值,常量写在 src-tauri/src/services/session_usage_pi.rs:24。同文件 :25 还有一个 PROVIDER_PLACEHOLDER,也就是写日志行时用的下划线开头的假 provider_id,用来跟真实供应商行区分开。

会话来源侧同理:后端的分发表是 src-tauri/src/session_manager/mod.rs 里那几处 match provider_id(新快照 :110-117 读消息、:167-180 删会话、:204-211 会话根目录),新版多了一个分支。前端筛选器的联合类型也跟着加了一个成员(src/components/sessions/SessionManagerPage.tsx:85-95)。

数据库这边,两个快照之间 schema 版本连跳了两级,而基线建表只多了一张表——因为其中一级迁移是给已有表加列而不是建新表。这条线索单独展开值得一整篇,这里不重复。

为什么只有它需要一套「会话目录发现」

最能说明「差异才是成本」的是这一处。其余会话来源的根目录都是固定推导出来的,而新接入的这个应用,会话目录是用户可配的,还可能被配成相对路径。

相对路径是相对于项目 cwd 的,而全局扫描没有 cwd 概念——这不是「不支持」,是语义上无法全局解析。源码里的报错文案把这层意思写得很准:Pi sessionDir '{configured_path}' requires a project cwd and cannot be globally enumeratedsrc-tauri/src/session_manager/providers/pi.rs:106:219),另一处是 Relative Pi sessionDir cannot be globally resolvedpi.rs:251)。注意它说的是 requires a project cwd,不是 invalid path

于是就有了一整套只为这一个来源存在的降级状态机:classify_configured_session_dirpi.rs:158)、resolve_global_session_dirpi.rs:196)、session_discoverypi.rs:112),前端也配了对应的查询与两种状态分支(SessionManagerPage.tsx:197-200:811:824-836)。发布说明在升级提醒里把结论写成了一句用户可操作的话:会话浏览与用量导入请把该目录设成绝对路径(v3.20.0-zh.md:242)。

一个连带修复

接入过程里还顺手补了一类沉默失败。发布说明这样描述:代理与故障转移命令现在对所有没有本地网关的应用显式拒绝,而不是写下一堆不会生效的配置(v3.20.0-zh.md:70)。

这一条和前面 PROXY_APP_IDS 那组常量是同一件事的两面:先把「谁有本地网关」显式化,才有可能对「谁没有」给出明确拒绝。 在能力边界还是隐式的时候,这类调用只能悄悄写下去、悄悄不生效。

自己去核这几条

上面每条结论都能在仓库里复现。以下命令未经实测,以官方文档与 --help 的实际输出为准:

sed -n '19,30p' src/config/appConfig.tsx   # 受管应用注册表
git show --stat 84e75ad2 | tail -1         # 这次接入的改动规模
ls src-tauri/src/                          # 与旧快照人眼比对,唯一差异是新增目录 pi_config
grep -rn "const DATA_SOURCE" src-tauri/src/services/

值得记住的不是某个具体数字,而是这条纵贯线本身:一个聚合类工具的真正复杂度,不在它管了几个客户端,而在这几个客户端的能力边界互不相同。 相同的部分可以抽象掉,不同的部分只能一处一处地显式写出来——四组常量、三个类型守卫、一套只为一个来源存在的目录发现降级路径,都是这份账单上的条目。


本文依据 CC Switch 官方仓库(github.com/farion1231/cc-switch)的 README、docs/ 下的用户手册、 路由指南与发布说明,以及 src/src-tauri/tests/ 的源码整理, 核对日 2026-08-31,对应仓库快照 3217f725(仓库内版本号 3.20.1)。 本文内容为仓库源码与文档口径,我们没有安装或运行过这个桌面应用, 因此不涉及界面外观、操作手感与切换速度的任何描述。 文中出现的阈值与默认值均为源码中的默认配置,不构成对实际运行结果的保证。 该项目仍在快速迭代,版本与默认值随时可能变动,请以仓库最新内容为准。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。

留言讨论

评论发布后会被人工复核,违规内容将被删除。

    还没有人评论,来说说你的看法

    如果发表没有反应,可以前往联系我们告诉我们。

    在 CC Switch 里加一个国内直连的供应商

    力达云网关,注册送 ¥5 额度,一期提供 DeepSeek。

    去添加

    这个页面有问题?

    提交时会附带当前页面地址和浏览器信息,帮助我们定位问题。不填联系方式即为匿名。