# 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 和后端入口,但其状态模型仍有根本缺陷: 1. `app_type="pi"` 同时被当作产品归属和计费协议语义,导致 OpenAI/Gemini Pi 路由的缓存 token 与费用写错; 2. Skill 部署只有路径,没有所有权凭据,可能覆盖并删除用户原生 Pi Skill; 3. Provider 删除补偿只快照主表,投影失败时会永久丢失级联删除的自定义 endpoint; 4. 本机 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 阅读者依赖断链,下面完整记录本次交接必须延续的有效契约: 1. 固定完整审查范围,通常为 `main...HEAD`,并确保所有范围内修改都对 reviewer 可见。 2. 启动两位 fresh、无对话继承、互相独立、只读的 blind reviewers,让两人都检查完整范围。 3. 给 reviewer 详细但中立的仓库布局、架构边界、权威契约、精确 range、验证命令和审查维度。 4. 不透露实现内容、目标 bug、设计理由、已知问题、历史 finding 或另一位 reviewer 的结论。 5. finding 必须给出 severity、confidence、精确文件与行、具体故障场景、推理和建议方向;没有 finding 时必须明确写出。 6. 主审必须独立对照源码与测试验证每条 finding,不能机械套用建议。 7. 确认问题并完成 material fix 后重新双审;明显收敛到小型局部 patch 后,后续轮次可改用一位新的 blind reviewer。 8. 按底层 invariant/故障场景跟踪问题;出现 review/repair 循环、补丁互相矛盾或同一 invariant 重复失败时,停止补丁并重审 ownership、boundary、state model、data flow、contract 和 test strategy。 9. 设计级调整仍无法解决循环时,停止并报告未解决条件、历史、冲突约束和可行选项。 10. 完成条件是无已验证 blocker/high、相关检查全部通过,并由主审审计完整 diff。 11. 同一 implementation 最多七轮双 reviewer 审查;第七轮后仍有 blocker/high 或数据完整性问题时,禁止第八轮和继续打补丁,必须停止并报告。 当前 Pi 实现已经耗尽这七轮额度。后续不能把一次新的 review 称为“第八轮补丁审查”。如果重启开发,必须明确作为一次新的、设计已改变的 implementation,而不是延续现有 patch loop。 ## 3. 功能边界的判定规则 ### 3.1 契约优先级 每次开始编码前,按以下顺序确认行为: 1. Pi 官方 `latest` 文档; 2. Pi 官方仓库当前源码和测试,并记录审阅的 commit; 3. Pi 实际配置 schema、资源加载顺序和 CLI 行为; 4. CC Switch 自己的 Claude/Codex/Gemini 既有不变量; 5. 成熟 Pi switch 项目,只作为产品、交互和容灾设计参考; 6. 其他 bridge/extension,只作为互操作性参考。 社区工具不是 Pi 原生契约。Pi 文档与源码不一致时,不要自行选一个“更方便”的解释:记录差异,先定义 CC Switch 的 managed/read-only 边界。 本 handoff 与本实现统一对照的 Pi 官方源码固定点为: ```text 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](https://pi.dev/docs/latest/models) - [Skills](https://pi.dev/docs/latest/skills) - [Prompt Templates](https://pi.dev/docs/latest/prompt-templates) - [Sessions](https://pi.dev/docs/latest/sessions) - [Using Pi / Context Files / Design Principles](https://pi.dev/docs/latest/usage) - [Pi source](https://github.com/earendil-works/pi) ### 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 前端入口 - [应用注册与图标](../src/config/appConfig.tsx) - [应用切换器](../src/components/AppSwitcher.tsx) - [Pi Provider 预设](../src/config/piProviderPresets.ts) - 当前包含 OpenAI、OpenRouter、Anthropic、DeepSeek、SiliconFlow; - 预设只提供 endpoint/API family 起点,模型仍需拉取或显式配置。 - [Pi Provider 表单](../src/components/providers/forms/PiProviderForm.tsx) - [默认模型选择](../src/components/providers/PiDefaultModelDialog.tsx) - [Provider 卡片操作](../src/components/providers/ProviderActions.tsx) - [Pi prompt templates 面板](../src/components/prompts/PiPromptTemplatesPanel.tsx) - [共享 Prompt 面板](../src/components/prompts/PromptPanel.tsx) - [共享 Skills 面板](../src/components/skills/UnifiedSkillsPanel.tsx) - [共享 Sessions 页面](../src/components/sessions/SessionManagerPage.tsx) - [共享 Proxy / Failover UI](../src/components/proxy) - [Pi API 封装](../src/lib/api/pi.ts) - en / zh / zh-TW / ja 四语 i18n UI 采用现有 Card、Dialog、Form、Toggle、Tabs、Toast 和 app icon 体系,没有另造 TUI 或 Pi 专用设计系统。 ### 5.2 后端入口 - [Pi 配置投影](../src-tauri/src/pi_config/mod.rs) - `models.json` / `settings.json`; - exact-key projection manifest; - direct / proxy 两种投影; - default provider/model; - 导入已有 Provider; - 环境变量和 `!command` 值解析。 - [保留未知 JSON 的文档写入](../src-tauri/src/pi_config/document.rs) - [运行时配置值解析](../src-tauri/src/pi_config/runtime.rs) - [Pi API family 与 failover wire profile](../src-tauri/src/proxy/pi_route.rs) - [不可变 runtime snapshot 与 epoch/generation fencing](../src-tauri/src/proxy/pi_runtime.rs) - [请求入口](../src-tauri/src/proxy/handlers.rs) - [路由与熔断选择](../src-tauri/src/proxy/provider_router.rs) - [共享 forwarder](../src-tauri/src/proxy/forwarder.rs) - [Proxy 生命周期和投影协调](../src-tauri/src/services/proxy.rs) - [Provider CRUD / switch / compensation](../src-tauri/src/services/provider/mod.rs) - [Pi projection DAO](../src-tauri/src/database/dao/pi.rs) - [Schema / migration / backup restore](../src-tauri/src/database) - [全局 `AGENTS.md`](../src-tauri/src/prompt_files.rs) - [Prompt templates service](../src-tauri/src/services/pi_prompt_templates.rs) - [Skills 集成](../src-tauri/src/services/skill.rs) - [Pi session adapter](../src-tauri/src/session_manager/providers/pi.rs) ### 5.3 测试入口 - [Pi config Rust tests](../src-tauri/src/pi_config/tests.rs) - `proxy/pi_route.rs`、`proxy/pi_runtime.rs`、`services/proxy.rs` 内联 Rust tests - `services/provider/mod.rs`、`services/skill.rs`、`database/backup.rs` 内联回归 tests - [Pi Provider 表单 tests](../tests/components/PiProviderForm.test.tsx) - [默认模型 Dialog tests](../tests/components/PiDefaultModelDialog.test.tsx) - [Prompt Templates tests](../tests/components/PiPromptTemplatesPanel.test.tsx) - [Skills tests](../tests/components/UnifiedSkillsPanel.test.tsx) - [前端 Pi API tests](../tests/lib/piApi.test.ts) - [Pi app config tests](../tests/config/piAppConfig.test.ts) ## 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`](../src-tauri/src/pi_config/mod.rs),尊重 `PI_CODING_AGENT_DIR`。Sessions 还要处理 `PI_CODING_AGENT_SESSION_DIR` 和 `settings.json.sessionDir`,不能在其他 模块重新硬编码 home path。 ### 6.2 Provider direct mode ```text 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 ```mermaid 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.md` frontmatter; - `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 `。 全局 Session Manager 无法仅凭一个相对 `sessionDir` 推导所有启动 cwd。禁止在无法解析时悄悄扫描默认目录并表现为“已支持”;应显式提示 project-relative scope,或让用户提供可枚举的绝对 root。 ## 7. 必须先写成测试的设计不变量 ### 7.1 协议与计费 1. `logical_app_type` 只用于归属、UI 和筛选。 2. `wire_api_family` 决定 adapter、endpoint、缓存 token 语义和 compatibility。 3. `input_token_semantics` 必须在写入时显式确定,不能事后从 `app_type` 猜。 4. Anthropic `input_tokens` 是 fresh input;OpenAI/Gemini 的输入字段包含 cached subset。 5. Pi 的四个 API family 都需要 cache-heavy 计费和 rollup tests。 ### 7.2 资源所有权 1. 只删除有 durable ownership 证据的路径或 JSON key。 2. 同名目录、相同内容、指向 SSOT 的 symlink 都不能自动等同于 ownership。 3. 发现 unowned collision 时,只能拒绝、显式 adopt 或先备份再接管。 4. disable/uninstall/sync-all/migration 都必须走同一个 ownership 判定。 5. portable backup/sync 不得转移本设备 projection/gateway ownership。 ### 7.3 Provider aggregate 与投影 1. Provider mutation 的 rollback snapshot 必须覆盖同一 cascade boundary: - provider row; - `provider_endpoints`; - projection claim; - 其他持久化 child rows。 2. runtime snapshot、SQLite 和 `models.json` 之间只能发布一个完整 catalog generation。 3. 文件投影失败后,返回错误时数据库和 runtime 必须收敛到明确的 authoritative state。 4. 不允许“主行恢复了,所以回滚完成”的假成功。 5. 导入已有配置必须 side-effect free,直到用户明确采用。 ### 7.4 并发与生命周期 1. Provider CRUD、default switch、failover queue、proxy enable/disable、restore 必须共用同一 Pi mutation boundary。 2. mutation 开始先把 catalog epoch 标为不可 admission。 3. 请求只使用 lease 时看到的 immutable Provider clone。 4. 旧 epoch 的 health/usage writeback 不得污染新 catalog。 5. listener generation 与投影使用的 host/port 必须来自同一已绑定实例。 ### 7.5 共享文件 1. JSON patch 保留未知字段与未拥有 entries。 2. 写入使用同目录临时文件和 atomic replace。 3. symlink、非普通文件、超大文件和 path traversal fail closed。 4. command-valued secrets 需要完整进程树 timeout,stdout 读取也必须有界。 5. 错误日志不得包含 resolved secret。 ## 8. 已踩过的坑与当前未解决问题 ### 8.1 阻断合并的问题 #### H1:产品归属与 wire token 语义混用 涉及: - [RequestContext::new_for_pi](../src-tauri/src/proxy/handler_context.rs) - [CostCalculator::calculate_for_app](../src-tauri/src/proxy/usage/calculator.rs) - [UsageLogger::log_request](../src-tauri/src/proxy/usage/logger.rs) - [SQL cache semantics helpers](../src-tauri/src/services/sql_helpers.rs) 故障场景: ```text 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](../src-tauri/src/services/skill.rs) - `replace_dest_with_copy` - `remove_from_app` - storage migration / sync-all / toggle paths 故障场景: 1. CC Switch 在自己的 SSOT 管理 `review`; 2. 用户独立拥有 `~/.pi/agent/skills/review`; 3. 在默认 `CcSwitch` storage mode 为 Pi 启用 managed `review`; 4. Auto/Copy/Symlink 路径会删除原生目录并替换; 5. 后续 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](../src-tauri/src/services/provider/mod.rs) - [Database::get_provider_by_id](../src-tauri/src/database/dao/providers.rs) - `provider_endpoints` cascade 故障场景: 1. 删除一个非当前 Pi Provider; 2. SQLite delete 成功并 cascade 删除 `provider_endpoints`; 3. `models.json` 投影因权限或文件错误失败; 4. rollback 调用 `save_provider`; 5. 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` rollback。 #### S1:device-local gateway token 被导出到 portable/sync 产物 涉及: - [database/backup.rs](../src-tauri/src/database/backup.rs) - [services/sync_protocol.rs](../src-tauri/src/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 支持”声明不完整。 设计选项: 1. 完整支持 effective model composition; 2. 把 native overlay 作为 read-only/unmanaged entry 显示; 3. 明确 UI 只管理 full custom catalogs,并对跳过项给出诊断。 不要继续让用户看到“nothing imported”却不知道原因。 #### M2:`!command` timeout 不覆盖后代进程 [runtime.rs](../src-tauri/src/pi_config/runtime.rs) 只 kill 直接 shell,然后无界 `reader.join()`。 例如: ```json { "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](../src-tauri/src/proxy/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](../src-tauri/src/session_manager/providers/pi.rs) 无法解析相对 cwd 时扫描默认 `~/.pi/agent/sessions`。这会产生“扫描成功但展示错误集合”的假象。 建议 fail explicit,或在 UI 让用户选择绝对 root;不要猜 cwd。 #### M5:Usage Dashboard 无 Pi filter Pi 日志写为 `app_type="pi"`,但 [frontend usage types](../src/types/usage.ts) 的 `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](https://github.com/CallmeLins/pi-switch) - Pi package:[package page](https://pi.dev/packages/%40cokefenta/pi-switch) - 安装:`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](https://github.com/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](https://github.com/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](https://github.com/nicobailon/pi-model-switch) - 本次审阅 commit:`e2798ea5dd3daf61ced4bd18a2cd14435cf896cd` - 2026-07-31 快照:93 stars / 9 forks 它解决“agent 在 Pi 内自己切 model”,不属于 CC Switch Provider 配置与容灾职责。可以理解用户场景,不应加入冻结范围。 ### 10.5 参考项目的时效性规则 Star、release、README 和默认分支都会变化。开始新设计时重新执行: ```bash 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](../src-tauri/src/services/provider) - live config ownership:[services/provider/live.rs](../src-tauri/src/services/provider/live.rs) - proxy 生命周期:[services/proxy.rs](../src-tauri/src/services/proxy.rs) - request adapters:[proxy/providers](../src-tauri/src/proxy/providers) - request-local forwarding:[proxy/forwarder.rs](../src-tauri/src/proxy/forwarder.rs) - Codex exact owned artifacts:[codex_config.rs](../src-tauri/src/codex_config.rs) - session provider adapters:[session_manager/providers](../src-tauri/src/session_manager/providers) - shared Prompt / Skill UI:[components/prompts](../src/components/prompts)、[components/skills](../src/components/skills) - usage semantics:[proxy/usage](../src-tauri/src/proxy/usage)、[sql_helpers.rs](../src-tauri/src/services/sql_helpers.rs) Claude 的优点是简单的 provider/live 配置映射;Codex 的优点是模型目录、OAuth 所有权、takeover 与 session 的复杂边界已经被大量真实问题打磨。Pi 需要同时借鉴两者,但不能把 Pi 强行归约为 Claude 或 Codex。 ### 11.2 merged PR 高信号参考集 检索方法: ```bash 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](https://github.com/farion1231/cc-switch/pull/1098) partial key-field merging | `farion1231`,仓库 owner | 共享配置不能全量覆盖 | | [#4076](https://github.com/farion1231/cc-switch/pull/4076) takeover residue recovery | `farion1231` | takeover/restore ownership与残留恢复 | | [#1724](https://github.com/farion1231/cc-switch/pull/1724) provider key lifecycle | `yovinchen`,最近 300 条中 34 个 merged PR | Provider key ownership、serializer failure | | [#1714](https://github.com/farion1231/cc-switch/pull/1714) transparent header forwarding | `yovinchen`,34 merged | 共享 forwarder 与 header 边界 | | [#1561](https://github.com/farion1231/cc-switch/pull/1561) full URL endpoint rewriting | `yovinchen`,34 merged | endpoint 不是简单字符串拼接 | | [#1918](https://github.com/farion1231/cc-switch/pull/1918) Gemini Native API proxy | `yovinchen`,34 merged | 新 native protocol family 如何贯穿 handler/adapter/UI/test | | [#5928](https://github.com/farion1231/cc-switch/pull/5928) remove redundant proxy query paths | `SaladDay`,最近 300 条中 9 个 merged PR | 以净减代码量收敛查询边界 | | [#2349](https://github.com/farion1231/cc-switch/pull/2349) Codex session history | `SaladDay`,9 merged | Provider 切换与 session 可见性 | | [#2429](https://github.com/farion1231/cc-switch/pull/2429) side-effect-free import | `xwil1`,最近 300 条中 3 个 merged PR | import 阶段不应改 live state | | [#3360](https://github.com/farion1231/cc-switch/pull/3360) catalog refresh on switch | `Postroggy`,最近 300 条中 2 个 merged PR | Provider 与 model catalog 必须同时收敛 | | [#3689](https://github.com/farion1231/cc-switch/pull/3689) skip backup/restore for proxy placeholder | `YongmaoLuo`,最近 300 条中 2 个 merged PR | 不把 takeover placeholder 当真实配置备份 | | [#2791](https://github.com/farion1231/cc-switch/pull/2791) protect skills during copy fallback | `rogerdigital`,最近 300 条中 2 个 merged PR | Skill copy fallback 的数据安全 | | [#5811](https://github.com/farion1231/cc-switch/pull/5811) Skill/security hardening | `zayokami`,最近 300 条中 2 个 merged PR,该 PR 已 approved | zip-slip、凭据泄漏、panic 等威胁模型 | | [#2231](https://github.com/farion1231/cc-switch/pull/2231) root-level Skill discovery | `santugege`,merged/approved | Skill discovery 不能只按常见目录猜 | | [#2774](https://github.com/farion1231/cc-switch/pull/2774) response model/input token logging | `LaoYueHanNi`,最近 300 条中 3 个 merged PR | wire conversion 后 usage 语义易错 | | [#5071](https://github.com/farion1231/cc-switch/pull/5071) Codex native Anthropic upstream | `yeeyzy`,最近 300 条中 2 个 merged PR,该 PR 已 approved | 可研究多协议代价;不能据此扩大 Pi 跨协议范围 | 不要机械 cherry-pick 这些 PR。它们提供的是已经在本仓库暴露过的 failure patterns。 ## 12. 推荐的重设计 ### 12.1 先写四份小契约,不先改代码 建议先在同一设计文档中固定: 1. `PiManagedProvider` 与 `PiNativeOverlay` 的边界; 2. `LogicalApp`、`WireApiFamily`、`UsageSemantics` 三者关系; 3. `ManagedDeployment` ownership model; 4. `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 承担所有语义 建议概念上拆成: ```text 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 推荐最小结构: ```rust 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: ```text 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 按顺序完成: 1. usage attribution contract tests; 2. Skill ownership collision tests; 3. Provider aggregate rollback tests; 4. native overlay classification tests; 5. 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. 验证命令与环境注意事项 最后一次实现验证通过: ```text 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 ``` 建议命令: ```bash 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 状态: ```text 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 的运行时事实,下一位接手者必须重新执行: ```bash 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。 - 不要只给 `Provider` struct 塞更多临时字段修 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. 接手者的第一天清单 1. 阅读本文、当前环境若存在的本地 `AGENTS.md` 和 `041ff113` 完整 diff;可分发的审查契约以第 2.3 节为准。 2. 更新 Pi 官方源码并记录 commit。 3. 重跑第 16 节状态检查。 4. 用最小复现确认 H1、H2、D1、S1,不先修。 5. 写出第 12.1 节四份契约。 6. 决定 native overlay 的 managed/read-only 表达。 7. 评估从 main 重建与在当前分支 replace 两种方案的净代码量。 8. 向用户汇报设计、预计删改范围和单 PR 边界。 9. 获得设计确认后才开始新 implementation。 最终目标不是“让 Pi 在列表里出现”,而是让 Pi 的原生配置、CC Switch 的状态所有权和本地 gateway 的请求数据流在失败与并发条件下仍然只有一个真相。