mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-04 19:45:34 +08:00
1094 lines
50 KiB
Markdown
1094 lines
50 KiB
Markdown
# 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 <path-or-id>`。
|
||
|
||
全局 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<Provider>` 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 的请求数据流在失败与并发条件下仍然只有一个真相。
|