50 KiB
Pi 支持重设计 Handoff
文档状态:设计与实现交接,不是发布说明,也不表示当前实现可合并。 快照日期:2026-07-31(UTC)。 仓库:
farion1231/cc-switch。 当前工作分支:feat/pi-switch-redesign。 实现基线:b884595a237931808d4775fb709461141a26f508。 待重设计实现快照:041ff113e1e0c6b03c0999a1659121fc5979b62a。 当前结论:第 7 轮双盲审后仍有两个已验证 High 和一个持久化数据一致性问题;本次 handoff 审计又确认了一个 device-local gateway token 外泄到 portable sync 的安全契约缺陷。不得把该快照直接合并、继续叠补丁或称为完成。
1. 一页摘要
这项工作的目标不是给 Pi 再造一个扩展系统,而是让 Pi 成为 CC Switch 的一等应用入口,并遵循一个简单边界:
Pi 核心原生提供、且与 CC Switch 现有管理域相符的能力,CC Switch 才直接支持;需要另造协议、扩展包、信任系统或兼容层的能力,不进入本次范围。
冻结后的目标范围包括:
- Provider / Model 管理、默认 Provider / Model、预设和模型拉取;
- Pi 原生全局 Skills;
- Pi 原生全局
AGENTS.md上下文; - Pi 原生全局 prompt templates;
- Pi 原生 sessions 的发现、查看、恢复和删除;
- 同协议、请求级的本地 gateway、failover 和 circuit breaker;
- 与 CC Switch 既有界面一致的 UI、i18n、备份恢复和代理生命周期。
明确不做:
- MCP:Pi 核心明确没有内置 MCP;
- OpenAI ↔ Anthropic 等跨协议转换;
- User-Agent 伪装或冒充官方客户端;
- Pi extension/package 的安装、启停和升级;
- project-local resource、package 和 trust 管理;
- Pi OAuth /
auth.json凭据生命周期管理; - 为了“功能齐全”而继续加入 themes、keybindings、agent switcher 等可选域。
041ff113 已经覆盖上述大部分 UI 和后端入口,但其状态模型仍有根本缺陷:
app_type="pi"同时被当作产品归属和计费协议语义,导致 OpenAI/Gemini Pi 路由的缓存 token 与费用写错;- Skill 部署只有路径,没有所有权凭据,可能覆盖并删除用户原生 Pi Skill;
- Provider 删除补偿只快照主表,投影失败时会永久丢失级联删除的自定义 endpoint;
- 本机 gateway bearer token 存在通用
settings表中,当前会被明文写入 portable SQL 和 WebDAV/S3 同步产物。
下一位开发者应把当前提交当作需求原型和测试素材,而不是待补齐的实现。先重审协议边界、资源所有权和 Provider aggregate,再以净减代码量为目标重构。
2. 用户要求与不可变约束
2.1 产品要求
- Pi 在 CC Switch 中必须是一等应用,不是“通过 Claude/Codex 假装支持 Pi”。
- 所有实际入口都要一致工作:应用切换器、Provider 列表、添加/编辑、默认模型、Skills、Prompts、Sessions、Proxy、Failover、备份恢复、同步和 i18n。
- 前端 UI/UX 复用 CC Switch 现有组件、视觉语言和交互,不单独做一套 Pi 风格。
- Provider 需要 CRUD、预设、模型拉取和模型级配置。
- 如果成熟 Pi switch 工具有本地 proxy/failover/circuit breaker,则 CC Switch 也要有,但不能因此引入协议伪装或跨协议转换。
- 应频繁对照 Pi 官方文档和官方源码,并参考成熟社区实现,避免根据记忆闭门设计。
2.2 工程要求
- 优先一个 PR,最多两个 PR;双盲审数量不等于 PR 数量。
- 不允许为了拆 PR 而破坏一个完整的数据不变量。
- 当前 113 个文件、
+12884/-816的实现快照已经偏大;下一轮要主动删掉临时兼容层、重复分支和 app-specific glue。 - 功能范围已经冻结。除设计重构自然消除的问题外,只处理 blocker/high 和明确的数据一致性问题。
- 如果继续出现“Anthropic 统一入口 → 多协议上游”的兼容性压力,停止局部修补,回到协议边界设计。
- 不要修改或删除
stash@{0}: On pi-support: pi-safety-layer-draft。 - 仓库交付物中不得保留
dist/或dist-web/。 - 开发 renderer 使用 5181;handoff 时
http://127.0.0.1:5181返回 200,但这是临时运行状态,不是持久契约。
2.3 审查要求
当前工作区的 AGENTS.md 要求每次非平凡实现完成后执行独立盲审。该文件由开发环境注入并被 .gitignore 排除,不属于 Git tree;为避免 clean clone 或 GitHub 阅读者依赖断链,下面完整记录本次交接必须延续的有效契约:
- 固定完整审查范围,通常为
main...HEAD,并确保所有范围内修改都对 reviewer 可见。 - 启动两位 fresh、无对话继承、互相独立、只读的 blind reviewers,让两人都检查完整范围。
- 给 reviewer 详细但中立的仓库布局、架构边界、权威契约、精确 range、验证命令和审查维度。
- 不透露实现内容、目标 bug、设计理由、已知问题、历史 finding 或另一位 reviewer 的结论。
- finding 必须给出 severity、confidence、精确文件与行、具体故障场景、推理和建议方向;没有 finding 时必须明确写出。
- 主审必须独立对照源码与测试验证每条 finding,不能机械套用建议。
- 确认问题并完成 material fix 后重新双审;明显收敛到小型局部 patch 后,后续轮次可改用一位新的 blind reviewer。
- 按底层 invariant/故障场景跟踪问题;出现 review/repair 循环、补丁互相矛盾或同一 invariant 重复失败时,停止补丁并重审 ownership、boundary、state model、data flow、contract 和 test strategy。
- 设计级调整仍无法解决循环时,停止并报告未解决条件、历史、冲突约束和可行选项。
- 完成条件是无已验证 blocker/high、相关检查全部通过,并由主审审计完整 diff。
- 同一 implementation 最多七轮双 reviewer 审查;第七轮后仍有 blocker/high 或数据完整性问题时,禁止第八轮和继续打补丁,必须停止并报告。
当前 Pi 实现已经耗尽这七轮额度。后续不能把一次新的 review 称为“第八轮补丁审查”。如果重启开发,必须明确作为一次新的、设计已改变的 implementation,而不是延续现有 patch loop。
3. 功能边界的判定规则
3.1 契约优先级
每次开始编码前,按以下顺序确认行为:
- Pi 官方
latest文档; - Pi 官方仓库当前源码和测试,并记录审阅的 commit;
- Pi 实际配置 schema、资源加载顺序和 CLI 行为;
- CC Switch 自己的 Claude/Codex/Gemini 既有不变量;
- 成熟 Pi switch 项目,只作为产品、交互和容灾设计参考;
- 其他 bridge/extension,只作为互操作性参考。
社区工具不是 Pi 原生契约。Pi 文档与源码不一致时,不要自行选一个“更方便”的解释:记录差异,先定义 CC Switch 的 managed/read-only 边界。
本 handoff 与本实现统一对照的 Pi 官方源码固定点为:
earendil-works/pi
implementation pin = ab366ebe94cacd419d986be454f12b1b9913aaca
snapshot date = 2026-07-31 UTC
Phase 0 在同日重新执行 §10.5 的 git ls-remote;刷新时远端 HEAD 已前进到
977ec833bbb86e245057e9162dbc1443c7b6e707。该值只记录远端刷新事实,不是第二个
实现 pin;schema、oracle、源码注释和审查 authority 均继续使用上面的
ab366ebe94cacd419d986be454f12b1b9913aaca 干净源码快照。
官方入口:
- Custom Models
- Skills
- Prompt Templates
- Sessions
- Using Pi / Context Files / Design Principles
- Pi source
3.2 “Pi 原生”不等于“本 PR 全做”
Pi 核心包含 extensions、packages、themes、keybindings、project resources 等能力,但它们会引入 CC Switch 当前没有的 package manager、project trust 或新资源域。
本次冻结边界是:
- 原生能力必须存在,才允许接入;
- 同时还必须落在 CC Switch 已有管理域或用户明确要求的 proxy/failover 域内;
- 不因为 Pi 原生存在某能力,就自动扩大本 PR。
因此:
- Skills、全局 context、prompt templates、sessions:在现有 CC Switch 管理域中,纳入;
- MCP:Pi 核心没有,排除;
- packages/extensions/project trust/themes/keybindings:即使 Pi 原生存在,也不在冻结范围;
- proxy/failover:不是 Pi 核心配置管理能力,但成熟 switch 工具已有,且用户明确要求,作为唯一产品层例外纳入;
- proxy 例外仍受“同协议透明转发”限制。
4. 功能矩阵
| 能力 | Pi 核心状态 | 本次目标 | 当前快照 | 边界说明 |
|---|---|---|---|---|
| Custom Provider / Model | models.json 原生 |
必须 | 已实现子集 | 必须兼容 Pi 当前 schema,不能静默丢 overlay |
| 默认 Provider / Model | settings.json 原生 |
必须 | 已实现 | 多模型 Provider 必须显式选择 model |
| Provider 预设 | CC Switch 产品层 | 必须 | 已实现 | 只生成 Pi 原生配置,不创造私有格式 |
| 拉取可用模型 | CC Switch 产品层 | 必须 | 已实现 | 结果落为原生 model entries |
| 全局 Skills | 原生 | 必须 | 已实现但有 High | 只管理全局;必须有部署所有权 |
~/.agents/skills |
原生共享位置 | 必须兼容 | 已实现 | 注意 Pi 的优先级、frontmatter、ignore 规则 |
| Project Skills | 原生、受 trust 约束 | 不做 | 未做 | 不接管 .pi/.agents project resources |
全局 AGENTS.md |
原生 context file | 必须 | 已实现 | 它是 context,不等同于 SYSTEM.md |
SYSTEM.md / APPEND_SYSTEM.md |
原生 | 冻结范围外 | 未做 | UI 不得把 AGENTS.md 误称为真正 system prompt |
| Prompt Templates | 原生 | 必须 | 已实现 | 全局、非递归、slash-command filename 语义 |
| Sessions | 原生 | 必须 | 已实现但有 Medium | 解析 session tree,而非把 JSONL 当线性日志 |
| 本地 gateway | 产品层 | 必须 | 已实现 | loopback、请求级路由、凭据不可进入 route token |
| Failover / Circuit Breaker | 产品层 | 必须 | 已实现 | 同 model、同 wire family、请求级,不改全局 current |
| SSE streaming | API 原生 | 必须 | 已实现 | 同协议逐段转发 |
| 跨协议转换 | 非必要 | 不做 | 未做 | 禁止 OpenAI ↔ Anthropic 转换 |
| User-Agent disguise | 非必要 | 不做 | 未做 | 禁止伪装 Claude Code/Codex/Gemini |
| MCP | Pi 明确无内置 | 不做 | 未做 | 不通过私有 extension 绕过边界 |
OAuth / auth.json |
Pi 原生凭据域 | 不做 | 未做 | 保持 Pi 所有权,CC Switch 不复制凭据 |
| Pi package/extension 管理 | 原生 | 不做 | 未做 | 不建立第二套 package manager |
| Agent 内自动 model switch | 社区 extension | 不做 | 未做 | Pi 已有 /model,不是 CC Switch 配置职责 |
5. 当前实现快照包含什么
这一节只描述 041ff113 中存在的代码,不表示它已经满足发布质量。
5.1 前端入口
- 应用注册与图标
- 应用切换器
- Pi Provider 预设
- 当前包含 OpenAI、OpenRouter、Anthropic、DeepSeek、SiliconFlow;
- 预设只提供 endpoint/API family 起点,模型仍需拉取或显式配置。
- Pi Provider 表单
- 默认模型选择
- Provider 卡片操作
- Pi prompt templates 面板
- 共享 Prompt 面板
- 共享 Skills 面板
- 共享 Sessions 页面
- 共享 Proxy / Failover UI
- Pi API 封装
- en / zh / zh-TW / ja 四语 i18n
UI 采用现有 Card、Dialog、Form、Toggle、Tabs、Toast 和 app icon 体系,没有另造 TUI 或 Pi 专用设计系统。
5.2 后端入口
- Pi 配置投影
models.json/settings.json;- exact-key projection manifest;
- direct / proxy 两种投影;
- default provider/model;
- 导入已有 Provider;
- 环境变量和
!command值解析。
- 保留未知 JSON 的文档写入
- 运行时配置值解析
- Pi API family 与 failover wire profile
- 不可变 runtime snapshot 与 epoch/generation fencing
- 请求入口
- 路由与熔断选择
- 共享 forwarder
- Proxy 生命周期和投影协调
- Provider CRUD / switch / compensation
- Pi projection DAO
- Schema / migration / backup restore
- 全局
AGENTS.md - Prompt templates service
- Skills 集成
- Pi session adapter
5.3 测试入口
- Pi config Rust tests
proxy/pi_route.rs、proxy/pi_runtime.rs、services/proxy.rs内联 Rust testsservices/provider/mod.rs、services/skill.rs、database/backup.rs内联回归 tests- Pi Provider 表单 tests
- 默认模型 Dialog tests
- Prompt Templates tests
- Skills tests
- 前端 Pi API tests
- Pi app config tests
6. 期望的数据所有权与数据流
6.1 所有权表
| 状态/文件 | 权威所有者 | CC Switch 可以做什么 | 禁止做什么 |
|---|---|---|---|
SQLite providers(app_type='pi') |
CC Switch | 管理 CC Switch Provider aggregate | 假设它等同于 Pi 的完整 provider universe |
pi_provider_projections |
本设备 CC Switch | 记录 exact provider key ownership | 通过前缀或内容猜测所有权 |
~/.pi/agent/models.json |
Pi + 用户 + 第三方共享 | 只 patch manifest-owned exact keys | 全文件覆盖、删除未拥有 key |
~/.pi/agent/settings.json |
Pi + 用户共享 | 只 patch必要默认字段 | 重写未知设置 |
Pi auth.json |
Pi | 保留、引用 Pi 的认证存在性 | 导入、导出、覆盖 OAuth/token |
| gateway token | 本设备 CC Switch | 本地生成、只用于 loopback gateway | 进入 portable sync、URL 或日志 |
| proxy runtime snapshot | 当前进程 | 原子发布、request lease | 请求中读取可变全局 Provider |
~/.pi/agent/skills |
Pi + 用户共享 | 只删除/替换有 ownership 证据的 deployment | 仅凭目录同名覆盖或删除 |
~/.agents/skills |
用户共享 SSOT | 在 unified 模式复用 Pi 原生 discovery | 制造第二份伪“启用状态” |
~/.pi/agent/AGENTS.md |
用户 + CC Switch 共享 | 有明确 prompt ownership 时合成 | 把任意原生文件当 CC Switch owned |
~/.pi/agent/prompts/*.md |
用户 + CC Switch 共享 | 对用户明确选择的单文件 CRUD | 递归扫描、删未知文件 |
| Pi sessions | Pi | 只读发现/查看;用户显式删除 | 修改 transcript 内容或错误解析 branch |
所有 ~/.pi/agent 默认路径都必须经过
get_pi_agent_dir,尊重
PI_CODING_AGENT_DIR。Sessions 还要处理
PI_CODING_AGENT_SESSION_DIR 和 settings.json.sessionDir,不能在其他
模块重新硬编码 home path。
6.2 Provider direct mode
CC Switch SQLite Provider aggregate
│
├─ validate against supported Pi managed shape
├─ claim/reuse exact models.json provider key
├─ atomic patch of only that key
└─ patch defaultProvider/defaultModel when explicitly selected
重要点:
- SQLite 是 CC Switch managed Provider 的 SSOT;
models.json是共享投影,不是数据库镜像;- unowned Pi entries 必须保留;
- built-in overlay、extension provider 和完整 custom catalog 不是同一种对象,不能都塞进一个必填
models[]的结构。
6.3 Proxy mode
flowchart LR
DB["SQLite Provider aggregate"] --> SNAP["Immutable PiRuntimeSnapshot"]
DB --> PROJ["Exact-key models.json projection"]
CFG["Pi proxy/failover config"] --> SNAP
SNAP --> ROUTE["route token + wire family binding"]
PROJ --> PI["Pi native client"]
PI --> GATE["Loopback Pi gateway"]
GATE --> LEASE["Request-local snapshot lease"]
ROUTE --> LEASE
LEASE --> TRY["Compatible provider attempts"]
TRY --> UP["Same-protocol upstream"]
UP --> STREAM["Same-format response/SSE"]
STREAM --> PI
STREAM --> USAGE["Usage attribution + explicit wire token semantics"]
必须保持的边界:
- Pi 仍通过原生
models.json看到模型; - proxy base URL 只指向 loopback listener;
- route token 必须 credential-blind;
- 每个请求绑定一个 immutable catalog epoch;
- failover 只在与请求 wire contract 兼容的候选中进行;
- failover 不改变“当前 Provider”、不写全局 live config;
- 停止 takeover 时先关闭 runtime admission,再恢复 direct projection;
- usage 的产品归属仍是 Pi,但 token 语义由 wire family 决定。
6.4 Skills
Pi 当前 discovery 不是“目录存在即启用”:
- 有明确加载位置和优先级;
- 解析
SKILL.mdfrontmatter; description必填;- effective name 来自 frontmatter
name,可与目录名不同; - 重名 first-wins;
- root 直接
.md与递归SKILL.md的规则不同; - 遵循
.gitignore、.ignore、.fdignore; ~/.pi/agent/skills和~/.agents/skills可能产生 shadow。
当前实现修正了 name、description、root file、递归与 first-wins,但仍缺:
- ignore-file 语义;
- 非 unified 模式的 deployment ownership。
6.5 Prompts 与 context
必须区分三个概念:
~/.pi/agent/AGENTS.md:全局 context file;~/.pi/agent/SYSTEM.md/APPEND_SYSTEM.md:真正替换/追加 system prompt;~/.pi/agent/prompts/*.md:slash-command prompt templates。
当前共享 Prompt UI 把 Pi 的 AGENTS.md 放在名为 “system prompt” 的 tab 下。这是术语陷阱;下一轮要么改成“全局上下文”,要么明确扩展范围支持真正的 SYSTEM.md,不能继续把两者混称。
6.6 Sessions
Pi session JSONL 是树,不是简单线性消息数组:
- header 包含 session id、cwd、timestamp、version;
- entries 通过 id / parentId 构成分支;
- UI 应展示当前 active branch;
/tree、/fork、/clone会产生非线性历史;- 恢复使用
pi --session <path-or-id>。
全局 Session Manager 无法仅凭一个相对 sessionDir 推导所有启动 cwd。禁止在无法解析时悄悄扫描默认目录并表现为“已支持”;应显式提示 project-relative scope,或让用户提供可枚举的绝对 root。
7. 必须先写成测试的设计不变量
7.1 协议与计费
logical_app_type只用于归属、UI 和筛选。wire_api_family决定 adapter、endpoint、缓存 token 语义和 compatibility。input_token_semantics必须在写入时显式确定,不能事后从app_type猜。- Anthropic
input_tokens是 fresh input;OpenAI/Gemini 的输入字段包含 cached subset。 - Pi 的四个 API family 都需要 cache-heavy 计费和 rollup tests。
7.2 资源所有权
- 只删除有 durable ownership 证据的路径或 JSON key。
- 同名目录、相同内容、指向 SSOT 的 symlink 都不能自动等同于 ownership。
- 发现 unowned collision 时,只能拒绝、显式 adopt 或先备份再接管。
- disable/uninstall/sync-all/migration 都必须走同一个 ownership 判定。
- portable backup/sync 不得转移本设备 projection/gateway ownership。
7.3 Provider aggregate 与投影
- Provider mutation 的 rollback snapshot 必须覆盖同一 cascade boundary:
- provider row;
provider_endpoints;- projection claim;
- 其他持久化 child rows。
- runtime snapshot、SQLite 和
models.json之间只能发布一个完整 catalog generation。 - 文件投影失败后,返回错误时数据库和 runtime 必须收敛到明确的 authoritative state。
- 不允许“主行恢复了,所以回滚完成”的假成功。
- 导入已有配置必须 side-effect free,直到用户明确采用。
7.4 并发与生命周期
- Provider CRUD、default switch、failover queue、proxy enable/disable、restore 必须共用同一 Pi mutation boundary。
- mutation 开始先把 catalog epoch 标为不可 admission。
- 请求只使用 lease 时看到的 immutable Provider clone。
- 旧 epoch 的 health/usage writeback 不得污染新 catalog。
- listener generation 与投影使用的 host/port 必须来自同一已绑定实例。
7.5 共享文件
- JSON patch 保留未知字段与未拥有 entries。
- 写入使用同目录临时文件和 atomic replace。
- symlink、非普通文件、超大文件和 path traversal fail closed。
- command-valued secrets 需要完整进程树 timeout,stdout 读取也必须有界。
- 错误日志不得包含 resolved secret。
8. 已踩过的坑与当前未解决问题
8.1 阻断合并的问题
H1:产品归属与 wire token 语义混用
涉及:
- RequestContext::new_for_pi
- CostCalculator::calculate_for_app
- UsageLogger::log_request
- SQL cache semantics helpers
故障场景:
Pi -> OpenAI/Gemini route
input total = 1000
cached = 800
fresh should be 200
当前写入仍以 app_type="pi" 选择 fresh semantics,因此按 1000 fresh + 800 cached 计费,并把错误 semantics 持久化到日志和 rollup。
为什么是设计问题:
RequestContext已知道PiApiFamily;- forwarder 使用
wire_app_type选择 adapter; - usage 路径却丢失 wire family,只留下逻辑 app label;
- 给 cache-inclusive whitelist 简单加
"pi"会反过来破坏 Anthropic Pi route。
推荐方向:
- 在 usage event 中加入显式
WireUsageSemantics或PiApiFamily; app_type="pi"保留用于归属;- calculator、logger、backfill、rollup SQL、frontend normalization 共用同一显式 semantics。
H2:Pi Skill 部署没有 ownership
涉及:
- SkillService::sync_to_app_dir
replace_dest_with_copyremove_from_app- storage migration / sync-all / toggle paths
故障场景:
- CC Switch 在自己的 SSOT 管理
review; - 用户独立拥有
~/.pi/agent/skills/review; - 在默认
CcSwitchstorage mode 为 Pi 启用 managedreview; - Auto/Copy/Symlink 路径会删除原生目录并替换;
- 后续 disable 又会删除当前目标。
已有 shadow detection 只覆盖 unified mode 的 effective discovery,不等于 deployment ownership。
推荐方向:
- 引入 deployment manifest,最少记录 app、managed skill id、destination、method、source identity;
- symlink 必须核对 canonical target;
- copy deployment 可记录内容 digest/generation,但 digest 不能单独证明 ownership;
- collision 默认拒绝,adopt 必须显式并有备份;
- remove 只处理 manifest-owned destination。
D1:Provider 删除补偿丢失 child rows
涉及:
- ProviderService::delete
- Database::get_provider_by_id
provider_endpointscascade
故障场景:
- 删除一个非当前 Pi Provider;
- SQLite delete 成功并 cascade 删除
provider_endpoints; models.json投影因权限或文件错误失败;- rollback 调用
save_provider; - snapshot 没有 hydrate 独立 endpoint 表,主行恢复但 endpoint 永久丢失。
provider_health 也会丢失,但它是可重建 runtime state;provider_endpoints 是持久化用户配置,所以这是明确数据一致性问题。
推荐方向:
- 提供
get_provider_aggregate/restore_provider_aggregate; - 或把 delete + child snapshot + compensation 放入专门的 Pi mutation transaction object;
- 不要继续在调用点手工拼
Option<Provider>rollback。
S1:device-local gateway token 被导出到 portable/sync 产物
涉及:
- database/backup.rs
- services/sync_protocol.rs
settings.pi_gateway_token
当前 token 被存为 settings 表中的普通 row。portable export 和
WebDAV/S3 sync 只支持按整张表跳过数据,没有排除单个 setting key,因此
dump_sql() 会把 pi_gateway_token 以明文写入 db.sql。导入阶段恢复本机
token 只能避免目标设备采用远端 token,不能撤回已经写入本地备份或上传到远端的
副本。
这违反第 6.1 节的 device-local ownership 和第 14.2 节的凭据验收约束。即使 gateway 仅监听 loopback,也不能把该 token 当作可迁移配置。
推荐方向:
- 优先把 gateway token 移到既有 device-local settings 存储,或使用明确的 local-only 表;
- 如果仍放在通用
settings表,导出层必须按 key 过滤,而不是事后在 import 覆盖; - 为
export_sql_string()和export_sql_string_for_sync()分别增加断言: 产物不含 token key,也不含 token value; - 同时检查历史 binary backup/SQL backup 的披露和 token rotation 策略。
8.2 已验证但因冻结范围未修的 Medium
M1:Pi 原生 provider overlay 被静默跳过
当前 PiProviderShape 强制:
- provider-level
baseUrl; - 非空
models[]; - 拒绝 model-level
baseUrl。
Pi 当前官方 schema 支持:
models可选;- built-in provider 仅覆盖
baseUrl; modelOverrides-only;- model-level
baseUrl; - provider/model 两级
api。
导入逻辑对不支持 shape 只写 debug log 并跳过。原文件仍被保留,所以不是数据丢失,但“原生 Pi provider 支持”声明不完整。
设计选项:
- 完整支持 effective model composition;
- 把 native overlay 作为 read-only/unmanaged entry 显示;
- 明确 UI 只管理 full custom catalogs,并对跳过项给出诊断。
不要继续让用户看到“nothing imported”却不知道原因。
M2:!command timeout 不覆盖后代进程
runtime.rs 只 kill 直接 shell,然后无界 reader.join()。
例如:
{ "apiKey": "!printf token; sleep 30 &" }
后台进程继承 stdout 后,shell 已退出但 pipe 不 EOF,读取线程可以超过 10 秒无限等待。
需要 process group/job object、完整 tree termination 和有界 stdout collection。Pi 官方本身允许 command-valued config,但 CC Switch 一旦代执行,就必须对自己的 GUI/proxy 线程负责。
M3:不同 host 的同协议 failover 被阻止
pi_route.rs 把 endpoint 放进 wire profile equality。两个 API family、model id、request-shaping 都相同但 host 不同的 Provider 无法 failover。
这可能是保守安全边界,也可能违背“多 Provider 容灾”的产品承诺。编码前先决定:
- endpoint 是 transport destination,还是 wire compatibility 的一部分;
- 哪些 headers/compat 字段会使 host 相关;
- 是否需要显式 failover compatibility group。
M4:relative sessionDir 被回退为默认 root
Pi session adapter 无法解析相对 cwd 时扫描默认 ~/.pi/agent/sessions。这会产生“扫描成功但展示错误集合”的假象。
建议 fail explicit,或在 UI 让用户选择绝对 root;不要猜 cwd。
M5:Usage Dashboard 无 Pi filter
Pi 日志写为 app_type="pi",但 frontend usage types 的 AppType / KNOWN_APP_TYPES 没有 Pi。Pi 数据会进入 All,却无法单独筛选。
修复时必须先解决 H1,避免把显示 app type 又拿去决定 token semantics。
8.3 已验证 Low / UX 问题
- 多模型 Pi Provider 同时显示“选择默认模型”和通用“启用”;后者调用无 model id 的 switch,必然报错。
- Skill effective status 不遵循 Pi 的
.gitignore、.ignore、.fdignore。 - Prompt template slug 允许空白;Pi slash command 只解析第一个 token,生成的模板无法按 UI 名称调用。
- Pi Prompt UI 把
AGENTS.md标成 system prompt,概念不准确。
8.4 已经修过、不要回归的坑
Shared JSON ownership
- 不能整体覆盖
models.json/settings.json; - exact provider key ownership 必须 durable;
- portable sync 不得携带本机 projection manifest;
- stale claim cleanup 失败不能被忽略。
Restore race
数据库 restore 在 staging 期间不能提前读取“本地保留表”。当前修复在 commit boundary 持 live DB lock,再把 projection/runtime-local state 复制进 staged DB,最后提交。
Mutable runtime race
请求不能在路由后继续读取 mutable DB/global Provider。当前 immutable snapshot、catalog epoch 和 generation fencing 是正确方向,应保留概念但可压缩实现。
Failover 不能改全局 current
Pi failover 是单请求行为。不得复用会把 Provider 设为 current、改 live config 或广播 UI state 的旧 switch path。
Skill shadow 不等于目录同名
Pi 通过 frontmatter effective name 判重。目录名、文件名和 Skill name 可能不同;同目录也可能包含不同 effective name。
Pi session 是 tree
只读取最后一行或按文件顺序展示会把 abandoned branch 混入 active transcript。
Restore / proxy 生命周期
数据库 restore、proxy start/stop、listener port 更新和 direct/proxy projection 必须使用同一锁与 generation;不能只修正常 CRUD。
9. 七轮 blind review 历史
下面按底层 invariant 记录,而不是逐条复述 reviewer 文案。
证据限制:下表是根据本次持续开发会话中的 reviewer 回执和主审裁决重建的
handoff 摘要,不是仓库内可独立复放的审计日志。已知最终冻结范围是
b884595a237931808d4775fb709461141a26f508...041ff113e1e0c6b03c0999a1659121fc5979b62a;
前六轮各自的 range SHA、两份原始报告和逐项裁决没有作为 Git artifact 保存,
因此接手者不能仅凭下表复放每轮过程,也不应编造缺失证据。七轮硬停止仍是当前
用户指令与本地审查契约,必须遵守。
| 轮次 | 结果 | 处理 |
|---|---|---|
| 1–2 | 早期实现审查与基础修复 | 已进入后续收敛范围;没有把旧结论当作终审豁免 |
| 3 | 发现 DB restore 与 Pi runtime/projection 生命周期 High | 修复 restore 后的 projection/runtime reconcile |
| 4 | 无 blocker/high;剩余 Medium/Low | 冻结可选能力,不扩 protocol compat |
| 5 | immutable runtime snapshot / request-local routing 后无 blocker/high | 保留 epoch/generation fencing |
| 6 | restore preserve race、Skill effective discovery、compensation failure reporting 等数据一致性问题 | 修复并补测试;overlay、cross-host failover、relative sessionDir、slug 等冻结 |
| 7 | Reviewer B:无 blocker/high,报告 Medium/Low;Reviewer A:2 High + 2 Medium | 主审确认 H1、H2 和 D1 成立,触发硬停止 |
有一次 review 因上游 main 移动而取消,审查的是旧基线,不计入正式轮次。
第 7 轮后已执行:
- 停止代码修改;
- 未启动第 8 轮;
- 未 push;
- 未创建 PR;
- 删除一次性验证容器
cc-switch-pi-validate-20260730; - 保留 renderer 5181、stash 和干净工作树。
10. 参考实现与应该借鉴什么
10.1 @cokefenta/pi-switch
- GitHub:CallmeLins/pi-switch
- Pi package:package page
- 安装:
pi install npm:@cokefenta/pi-switch - 本次审阅 commit:
5cfa2e8d3f6657b0508ee66ea63acdedfb53388d - 2026-07-31 快照:34 stars / 2 forks
高价值参考:
- CLI / TUI / WebUI 复用同一个 core;
- Provider CRUD、预设、模型 expose/fetch;
- model-name request routing;
- failover chain、circuit breaker、half-open recovery;
- same-format SSE;
- stats、backup、doctor;
- 把“配置管理”和“runtime gateway”拆成不同模块。
不要复制:
- OpenAI ↔ Anthropic conversion;
- User-Agent disguise;
- package management;
- 它自己的私有 profile 配置作为 CC Switch 新 SSOT;
- cross-format 非流式降级。
它证明了用户确实需要 gateway/failover,不证明它的协议转换边界适合 CC Switch。
10.2 Wing900/Pi-switch
- GitHub:Wing900/Pi-switch
- 本次审阅 commit:
e2cc7f7837136277d317b742b650f402048ca80d - 2026-07-31 快照:47 stars / 4 forks
可参考:
- 桌面用户对 Provider/Model 拉取、默认模型和一键启动 Pi 的信息架构;
- 小型 GUI 的操作路径。
不要复制:
- Wails 技术栈;
- 仍在快速变化的内部数据模型;
- 仅凭 star 数判断实现正确。
10.3 pi-cc-switch-provider
- GitHub:Ginkgoooo/pi-cc-switch-provider
- 安装:
pi install git:github.com/Ginkgoooo/pi-cc-switch-provider - 本次审阅 commit:
740c06692b15ad891e6d3ac3ee24c053d2626a9a - 2026-07-31 快照:8 stars / 1 fork
高价值参考:
- 读取 CC Switch Claude/Codex live 配置的路径解析;
- live vs fixed routing;
- Pi provider registration;
- 模型选择、reasoning、auth 和 config-dir override 的一致快照;
- relative path fail-closed;
- 凭据不写入 extension local config。
不要把它作为主架构:
- 它依赖 Pi extension;
- 它桥接 Claude/Codex,不等于 Pi 原生
models.json管理; - 它的价值是兼容已有重度 cc-switch 用户,而不是替代一等 Pi support。
10.4 pi-model-switch
- GitHub:nicobailon/pi-model-switch
- 本次审阅 commit:
e2798ea5dd3daf61ced4bd18a2cd14435cf896cd - 2026-07-31 快照:93 stars / 9 forks
它解决“agent 在 Pi 内自己切 model”,不属于 CC Switch Provider 配置与容灾职责。可以理解用户场景,不应加入冻结范围。
10.5 参考项目的时效性规则
Star、release、README 和默认分支都会变化。开始新设计时重新执行:
git ls-remote https://github.com/earendil-works/pi.git HEAD
git ls-remote https://github.com/CallmeLins/pi-switch.git HEAD
git ls-remote https://github.com/Ginkgoooo/pi-cc-switch-provider.git HEAD
git ls-remote https://github.com/Wing900/Pi-switch.git HEAD
把实际审阅 commit 写进设计说明和测试注释;不要只写“参考最新版本”。
11. CC Switch 内部优质参考
11.1 先看代码,再看 PR
最值得复用的现有边界:
- Provider CRUD / switch:services/provider
- live config ownership:services/provider/live.rs
- proxy 生命周期:services/proxy.rs
- request adapters:proxy/providers
- request-local forwarding:proxy/forwarder.rs
- Codex exact owned artifacts:codex_config.rs
- session provider adapters:session_manager/providers
- shared Prompt / Skill UI:components/prompts、components/skills
- usage semantics:proxy/usage、sql_helpers.rs
Claude 的优点是简单的 provider/live 配置映射;Codex 的优点是模型目录、OAuth 所有权、takeover 与 session 的复杂边界已经被大量真实问题打磨。Pi 需要同时借鉴两者,但不能把 Pi 强行归约为 Claude 或 Codex。
11.2 merged PR 高信号参考集
检索方法:
gh pr list \
--repo farion1231/cc-switch \
--state merged \
--limit 300 \
--json number,title,author,mergedAt,url,additions,deletions,changedFiles
以下计数是 2026-07-31 查询到的最近 300 条 merged PR 中的贡献次数,不是作者终身贡献量。筛选信号依次是:owner/maintainer、重复 merged 贡献、相关领域、review/测试质量;star/follower 只能是弱信号。
| PR | 作者与可信信号 | 为什么值得看 |
|---|---|---|
| #1098 partial key-field merging | farion1231,仓库 owner |
共享配置不能全量覆盖 |
| #4076 takeover residue recovery | farion1231 |
takeover/restore ownership与残留恢复 |
| #1724 provider key lifecycle | yovinchen,最近 300 条中 34 个 merged PR |
Provider key ownership、serializer failure |
| #1714 transparent header forwarding | yovinchen,34 merged |
共享 forwarder 与 header 边界 |
| #1561 full URL endpoint rewriting | yovinchen,34 merged |
endpoint 不是简单字符串拼接 |
| #1918 Gemini Native API proxy | yovinchen,34 merged |
新 native protocol family 如何贯穿 handler/adapter/UI/test |
| #5928 remove redundant proxy query paths | SaladDay,最近 300 条中 9 个 merged PR |
以净减代码量收敛查询边界 |
| #2349 Codex session history | SaladDay,9 merged |
Provider 切换与 session 可见性 |
| #2429 side-effect-free import | xwil1,最近 300 条中 3 个 merged PR |
import 阶段不应改 live state |
| #3360 catalog refresh on switch | Postroggy,最近 300 条中 2 个 merged PR |
Provider 与 model catalog 必须同时收敛 |
| #3689 skip backup/restore for proxy placeholder | YongmaoLuo,最近 300 条中 2 个 merged PR |
不把 takeover placeholder 当真实配置备份 |
| #2791 protect skills during copy fallback | rogerdigital,最近 300 条中 2 个 merged PR |
Skill copy fallback 的数据安全 |
| #5811 Skill/security hardening | zayokami,最近 300 条中 2 个 merged PR,该 PR 已 approved |
zip-slip、凭据泄漏、panic 等威胁模型 |
| #2231 root-level Skill discovery | santugege,merged/approved |
Skill discovery 不能只按常见目录猜 |
| #2774 response model/input token logging | LaoYueHanNi,最近 300 条中 3 个 merged PR |
wire conversion 后 usage 语义易错 |
| #5071 Codex native Anthropic upstream | yeeyzy,最近 300 条中 2 个 merged PR,该 PR 已 approved |
可研究多协议代价;不能据此扩大 Pi 跨协议范围 |
不要机械 cherry-pick 这些 PR。它们提供的是已经在本仓库暴露过的 failure patterns。
12. 推荐的重设计
12.1 先写四份小契约,不先改代码
建议先在同一设计文档中固定:
PiManagedProvider与PiNativeOverlay的边界;LogicalApp、WireApiFamily、UsageSemantics三者关系;ManagedDeploymentownership model;PiMutation的 DB aggregate、file projection、runtime publication 顺序。
每份契约都应有:
- authoritative source;
- owned/unowned state;
- success postcondition;
- failure postcondition;
- rollback boundary;
- concurrency boundary;
- 最少三个故障注入测试。
12.2 Provider 类型不要再用一个松散 JSON shape 承担所有语义
建议概念上拆成:
PiManagedProvider
id
display metadata
auth expression (unresolved)
provider defaults
explicit managed models
model overrides
custom endpoints
projection key
PiNativeEntry
provider key
raw shared JSON
effective kind: built-in overlay | custom catalog | extension overlay
ownership: unowned | adopted | managed
diagnostics
不一定要建立两张新表,但代码层必须能区分,避免:
- 导入时强行要求
models[]; - 为了 proxy 方便而拒绝 Pi 合法配置;
- 把 unproxyable 等同于 invalid;
- 把 read-only native entry 悄悄隐藏。
12.3 usage 事件显式携带 wire semantics
推荐最小结构:
struct UsageAttribution {
logical_app: AppType, // Pi
wire_family: WireApiFamily, // Anthropic/OpenAI Responses/...
input_semantics: InputTokenSemantics,
}
不要让 frontend KNOWN_APP_TYPES、Rust app whitelist 和 SQL app list 各自推导 semantics。数据库已经有 input_token_semantics,应让写入路径成为 SSOT。
12.4 Skill ownership 复用为通用能力
这不是 Pi 特例。当前 Claude/Codex 等 app 也可能存在同名原生目录。
建议做一个小而通用的 deployment ledger:
managed_resource_deployments
resource_type
resource_id
app_type
destination
deployment_kind
source_identity
generation
created_at
先确认仓库是否已有可复用的 ownership 表;没有再加。删除、迁移、copy fallback、symlink refresh 都通过同一个 service。
12.5 Provider mutation 使用 aggregate,而不是继续扩充 compensation 参数
当前调用点逐步增加 previous provider、rollback result、reconcile result,已经显示边界错误。
推荐:
- 一个函数加载完整
ProviderAggregate; - 一个 mutation guard 负责 epoch、DB snapshot、projection、runtime publish;
- 错误返回中明确
authoritative_state; - compensation 是 aggregate restore,不是
save_provider。
如果实现继续在每个 CRUD 分支手工加 rollback,立即停止。
12.6 净减代码量的具体方向
- 把 Pi capability 放入 app capability registry,减少
appId === "pi"分支; - Provider UI 复用通用 model editor,只保留 schema adapter;
- direct/proxy projection 共用一个 pure projection plan;
- default provider/default model 合并为一个明确 command;
- usage 语义使用 typed field,删除跨 Rust/TS 的 app whitelist 推断;
- Skill discovery 与 deployment ownership 分层,状态计算不触碰文件;
- Sessions 复用 provider adapter,不在共享 UI 增加 Pi 条件;
- 删除所有为跨协议可能性预留、但冻结范围永远不会用到的 compatibility glue。
13. 建议执行顺序
Phase 0:冻结与隔离
- 不在
041ff113上继续追加兼容补丁; - 保留该 commit 供 diff、测试和 UI 原型参考;
- 从最新
main建一个明确命名的新设计分支,或在当前分支先 revert/replace 有问题的层; - 开始前再次确认 upstream main 和 Pi official commit。
Phase 1:设计与 contract tests
按顺序完成:
- usage attribution contract tests;
- Skill ownership collision tests;
- Provider aggregate rollback tests;
- native overlay classification tests;
- runtime generation/concurrency tests。
这一步不加新 UI。
Phase 2:后端最小闭环
只实现:
- native config classification;
- exact-key projection;
- default model;
- same-family gateway;
- request-local failover;
- explicit usage semantics;
- ownership-safe Skills;
- 完整 Provider aggregate rollback。
Phase 3:复用现有 UI
- 接回 Provider form/default dialog;
- 接回 Skills/Prompts/Sessions/Proxy 入口;
- 清理重复
pi条件分支; - 补齐四语 i18n parity;
- 修正
AGENTS.md的 UI 术语。
Phase 4:恢复与故障注入
至少覆盖:
- malformed/unwritable
models.json; - DB rollback trigger failure;
- endpoint child rows;
- proxy start/stop 与并发 Provider edit;
- database import/restore;
- stale request writeback;
- native Skill collision;
- background credential command;
- relative session dir;
- cache-heavy four-family usage。
Phase 5:单 PR 收敛
- 首选一个 PR;
- PR 内可以有多个便于 review 的提交;
- 不以多 PR 代替清晰架构;
- 如果完整设计仍远超健康审查体量,只允许拆成最多两个独立可验证的 PR,并先征得用户确认。
14. 验收标准
14.1 功能
- Pi 可在 AppSwitcher 正常显示、隐藏和切换;
- Provider CRUD、duplicate、sort、preset、fetch models 正常;
- 合法 native entry 不被破坏,unsupported/read-only entry 有可见诊断;
- default provider/model 在 direct/proxy 两种模式一致;
- Skills、global
AGENTS.md、prompt templates、sessions 都走 Pi 原生路径; - proxy/failover/circuit breaker 只做 same-family request-local routing;
- Pi usage 可单独筛选,费用和 fresh/cache token 正确;
- backup/restore 后 DB、projection、runtime 一致。
14.2 安全与数据
- 无 unowned JSON key 或文件被覆盖/删除;
- 无 credential 进入 URL、route token、log、portable backup;
- command resolver 不可无限挂起;
- path traversal、symlink、非普通文件 fail closed;
- deletion rollback 恢复完整 aggregate;
- stale epoch 不写回 usage/health。
14.3 UI/UX
- 使用现有 Provider 卡片、Dialog、Form、Tabs、Toast 和颜色;
- Pi 多模型 Provider 不显示必然失败的通用 Enable;
- 所有入口有 loading/error/empty/disabled 状态;
- en、zh、zh-TW、ja key parity;
- 不把
AGENTS.md称为实际SYSTEM.md; - 5181 renderer 上手工覆盖所有入口。
14.4 审查与验证
git diff --check;- Rust fmt、clippy、full lib tests;
- frontend typecheck、unit tests、format、renderer build;
- build 后删除生成的
dist//dist-web/; - 两位 fresh blind reviewers 审完整范围;
- 主审逐条验证;
- 无 validated blocker/high;
- 无明确数据一致性 finding;
- 最终主审检查完整 diff,而不是只看最后一个 patch。
15. 验证命令与环境注意事项
最后一次实现验证通过:
cargo test --lib: 2319 passed, 0 failed, 2 ignored
frontend unit tests: 89 files, 593 tests passed
cargo fmt --check: passed
cargo clippy --lib -- -D warnings: passed
pnpm typecheck: passed
pnpm format:check: passed
pnpm build:renderer: passed
git diff --check: passed
建议命令:
cd src-tauri
cargo fmt --all -- --check
cargo clippy --lib -- -D warnings
cargo test --lib
cd ..
pnpm typecheck
pnpm test:unit -- --run
pnpm format:check
pnpm build:renderer
git diff --check
注意:
- 之前的隔离验证容器已经按要求删除,不要假设它仍存在;
- reviewer 曾遇到 host
src-tauri/target权限和 OpenSSL dev metadata 问题; - 如需容器验证,重新创建一次性容器并保证生成文件所有权不会污染宿主;
- renderer build 会生成
dist/,验证后必须删除; - 不要删除或覆盖用户 stash。
16. 当前交接状态
在新增本文档前确认的 feature implementation 状态:
branch: feat/pi-switch-redesign
implementation snapshot: 041ff113e1e0c6b03c0999a1659121fc5979b62a
tracking before this docs change: origin/main, ahead 1
origin/main: b884595a237931808d4775fb709461141a26f508
feature worktree before this docs change: clean
PR: none
push: none
dist/: absent
dist-web/: absent
stash@{0}: On pi-support: pi-safety-layer-draft
renderer: http://127.0.0.1:5181 -> 200
validation container: removed
本文档应作为 041ff113 之后的独立本地 docs change 交付;不要据此把
041ff113 解释为可合并。上述是 2026-07-31 的运行时事实,下一位接手者必须重新执行:
git fetch origin main
git status --short --branch
git log -1 --oneline --decorate
git stash list
test ! -e dist
test ! -e dist-web
curl -fsS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:5181
17. 不要做的事情
- 不要直接 push 或为
041ff113创建 PR。 - 不要在该实现上启动“第 8 轮双审”。
- 不要通过把
"pi"加进 cache-inclusive app whitelist 修 H1。 - 不要只在 Pi toggle 上加一个
dest.exists()判断修 Skill ownership。 - 不要只给
Providerstruct 塞更多临时字段修 rollback。 - 不要把 endpoint 从 wire profile 删除后就宣称 cross-host failover 安全。
- 不要把所有 Pi native features 自动加入本 PR。
- 不要加入 MCP extension 来绕开“Pi 无内置 MCP”的边界。
- 不要复制 pi-switch 的协议转换或 UA disguise。
- 不要用 star/follower 替代源码、测试和贡献记录审查。
- 不要让导入已有 Pi 配置产生写文件、claim ownership 或切默认值等副作用。
- 不要因为全量 tests 通过就忽略未覆盖的数据流不变量。
18. 接手者的第一天清单
- 阅读本文、当前环境若存在的本地
AGENTS.md和041ff113完整 diff;可分发的审查契约以第 2.3 节为准。 - 更新 Pi 官方源码并记录 commit。
- 重跑第 16 节状态检查。
- 用最小复现确认 H1、H2、D1、S1,不先修。
- 写出第 12.1 节四份契约。
- 决定 native overlay 的 managed/read-only 表达。
- 评估从 main 重建与在当前分支 replace 两种方案的净代码量。
- 向用户汇报设计、预计删改范围和单 PR 边界。
- 获得设计确认后才开始新 implementation。
最终目标不是“让 Pi 在列表里出现”,而是让 Pi 的原生配置、CC Switch 的状态所有权和本地 gateway 的请求数据流在失败与并发条件下仍然只有一个真相。