refactor: 0.1.0-beta

This commit is contained in:
leokun
2026-08-13 22:01:11 +08:00
parent a3ec2a0dfc
commit 3e7a15017d
401 changed files with 8696 additions and 182189 deletions
@@ -0,0 +1,553 @@
# 前后端 ConnectRPC 重构完整方案
## 1. 目标
本方案重构桌面端、前端、本机控制面、代理层和具体服务端实现之间的边界。
最终目标如下:
1. 前端业务通信全部使用 ConnectRPC,不再使用任何 Wails 业务 IPC。
2. `internal/startup` 只负责依赖组装、启动顺序、运行时注册和优雅退出。
3. 操作系统与 Wails 能力统一收敛到 `internal/platform`
4. `internal/backend/app` 只负责产品级本机控制面。
5. Cursor 协议、Agent 和 Prompt 全部归 `internal/backend/cursor`
6. Runtime 是通用服务运行时,不绑定 Cursor,也不使用“Cursor backend”作为领域名称。
7. 当前 Cursor Host 与 MITM 只是一个 Runtime 实现,未来可以并列注册 Devin 等实现。
8. Cursor Host 未处理的接口返回 `404`;代理层未命中的请求原样转发到原始上游。
## 2. 强制边界
### 2.1 禁止业务 IPC
前端禁止继续使用以下能力:
```text
@bindings
Call.ByName
Events.On
Events.Emit
application.NewService
```
Wails 只负责桌面应用生命周期和 WebView,不再承载配置、运行时、模型或事件等业务接口。
### 2.2 Runtime 不绑定 Cursor
`backend/app/runtime.go` 表达的是通用运行时用例:
- 列出可用运行时;
- 启动、停止和重启指定运行时;
- 查询状态和最近一次错误;
- 向前端发布运行时状态变化。
它不能出现以下设计:
```text
CursorBackend
StartCursor
StopCursor
CursorMITMStatus
```
Cursor Host、MITM 和系统代理的组合只存在于启动装配阶段,不进入 App 的通用 DTO。
## 3. 总体架构
```mermaid
flowchart LR
UI["Frontend"] -->|"ConnectRPC"| APP["app.v1.AppService"]
APP --> APPDOMAIN["backend/app"]
APPDOMAIN -->|"RuntimeController"| SUPERVISOR["startup.Supervisor"]
SUPERVISOR --> CURSORRT["Cursor Runtime"]
SUPERVISOR --> DEVINRT["Devin Runtime"]
SUPERVISOR --> FUTURERT["Future Runtime"]
CURSORRT --> CURSORHOST["Cursor Host"]
CURSORRT --> MITM["MITM"]
CURSORRT --> SYSPROXY["platform/network"]
CURSORIDE["Cursor IDE"] --> MITM
MITM -->|"模型和 Agent 路由"| CURSORHOST
MITM -->|"其他请求原样转发"| UPSTREAM["原始上游"]
```
## 4. 目标目录
```text
internal/
├── startup/
│ ├── bootstrap.go
│ ├── wiring.go
│ └── supervisor.go
├── platform/
│ ├── desktop/
│ │ ├── app.go
│ │ ├── window.go
│ │ ├── tray.go
│ │ └── browser.go
│ ├── filesystem/
│ │ ├── paths.go
│ │ └── migrate.go
│ ├── network/
│ │ └── system_proxy.go
│ └── update/
│ └── installer.go
├── backend/
│ ├── app/
│ │ ├── host.go
│ │ ├── module.go
│ │ ├── service.go
│ │ ├── snapshot.go
│ │ ├── events.go
│ │ ├── config.go
│ │ ├── runtime.go
│ │ ├── model.go
│ │ ├── update.go
│ │ ├── desktop.go
│ │ ├── repository.go
│ │ ├── proto/
│ │ │ ├── app_v1.proto
│ │ │ └── types_v1.proto
│ │ └── gen/appv1/
│ │
│ ├── cursor/
│ │ ├── module.go
│ │ ├── host.go
│ │ ├── prompt/
│ │ ├── llm/
│ │ ├── loop/
│ │ ├── store/
│ │ ├── transport/
│ │ ├── proto/
│ │ │ ├── agent_v1.proto
│ │ │ ├── aiserver_v1.proto
│ │ │ ├── from_extensions/
│ │ │ ├── extractor/
│ │ │ └── scripts/
│ │ └── gen/
│ │ ├── agentv1/
│ │ └── aiserverv1/
│ │
│ └── devin/
│ └── .gitkeep
└── proxy/
├── server.go
├── router.go
├── passthrough.go
└── certificate.go
```
前端目标目录如下:
```text
frontend/src/rpc/
├── transport.js
├── appClient.js
├── watch.js
└── gen/
└── appv1/
```
`backend/app` 保持单一扁平 Go package,不按配置、模型等功能继续拆子目录。只有 protobuf 源文件和生成代码保留独立目录。
## 5. 模块职责
### 5.1 `internal/startup`
`startup` 是唯一组合根,负责:
- 创建数据库连接;
- 执行各模块声明的迁移;
- 创建 App、Cursor、Proxy 和 Platform 实例;
- 注入模块依赖;
- 注册所有 Runtime 实现;
- 确定启动和停止顺序;
- 捕获退出信号并等待资源释放。
`startup` 不负责窗口、托盘、浏览器、系统代理命令等具体平台操作,这些能力必须通过 `platform` 注入。
### 5.2 `internal/platform`
`platform` 只包装本机和操作系统能力:
- `desktop`Wails、窗口、托盘和浏览器;
- `filesystem`:数据目录、配置目录、日志目录和文件迁移;
- `network`:系统代理读取、设置和恢复;
- `update`:安装包验证与执行。
只有 `platform/desktop` 可以直接导入 Wails application API。`platform` 不依赖 protobuf、AppService 或 Cursor 协议。
### 5.3 `internal/backend/app`
App 是产品级本机控制面,负责:
- 产品配置;
- 通用 Runtime 控制;
- BYOK 模型配置和连通性测试;
- 应用更新状态;
- 受控桌面动作;
- App 快照和 App 事件流。
App 不负责:
- Cursor IDE 协议;
- MITM 实现;
- 具体 Runtime 的启停细节;
- 操作系统命令。
### 5.4 `internal/backend/cursor`
Cursor 模块拥有所有 Cursor 专属语义:
- Cursor Host 路由;
- Cursor Connect、Bidi 和 RunSSE 协议;
- Agent Loop、Prompt、LLM 和会话存储;
- Cursor 提取的 protobuf 和生成代码。
Cursor 不导入 `backend/app`。需要模型目录等通用数据时,由 Cursor 自己声明小接口,再由 `startup` 注入实现。
### 5.5 `internal/proxy`
Proxy 是通用代理基础设施,不导入 Cursor package。
Cursor 模块提供它能处理的路由集合,`startup` 将路由匹配器和目标地址注入 Proxy。未命中的请求必须保留原请求的 method、path、query、header、body 和流式响应语义,并发送到原始上游。
## 6. Runtime 设计
### 6.1 App 侧端口
`backend/app/runtime.go` 定义通用端口:
```go
// RuntimeController 管理已注册的服务运行时。
type RuntimeController interface {
List(context.Context) ([]RuntimeDescriptor, error)
Start(context.Context, string) error
Stop(context.Context, string) error
Restart(context.Context, string) error
Status(context.Context, string) (RuntimeStatus, error)
}
```
Runtime DTO 只包含通用字段:
```text
RuntimeDescriptor {
id
kind
state
capabilities
endpoint
last_error
revision
}
```
其中 `id` 标识一个配置实例,`kind` 标识实现类型,例如 `cursor``devin`。App 和前端不能根据 Cursor 专属字段决定运行时流程。
### 6.2 Supervisor
`startup/supervisor.go` 实现 `RuntimeController`,维护 Runtime 注册表和状态机:
```text
Stopped -> Starting -> Running -> Stopping -> Stopped
-> Failed
```
必须满足:
- 同一个 Runtime 的启停操作串行执行;
- 重复 Start 和 Stop 具有幂等语义;
- 启动中途失败时回滚已经启动的组件;
- Stop 按 Start 的逆序执行;
- 状态变化携带递增 revision;
- 应用退出时统一停止所有已启动 Runtime。
### 6.3 当前 Cursor Runtime
当前在 `startup/wiring.go` 注册一个 `kind=cursor` 的 Runtime。它的启动顺序为:
1. 校验 Cursor 和模型配置;
2. 启动 Cursor Host
3. 启动 MITM
4. 根据配置启用系统代理;
5. 发布 Running 状态。
停止时按相反顺序恢复系统代理、停止 MITM、停止 Cursor Host。
Cursor Host 和 MITM 是当前实现的内部组件,不应被命名为整个 Runtime。未来接入 Devin 时,只需注册新的 Runtime 实现,不修改 AppService 协议。
## 7. ConnectRPC 服务面
### 7.1 `app.v1.AppService`
第一阶段使用明确方法,不提供通用 JSON Invoke
```text
Bootstrap
Watch
GetConfig
UpdateConfig
ListRuntimes
GetRuntime
StartRuntime
StopRuntime
RestartRuntime
ListModels
SaveModel
DeleteModel
TestModel
GetAds
GetUpdate
CheckUpdate
InstallUpdate
OpenWindow
OpenExternal
```
App 的 `Watch` 第一条消息是完整 App 快照,后续发送带 revision 的增量事件:
```text
snapshot
config_changed
runtime_changed
model_changed
ads_changed
update_changed
```
### 7.2 Cursor IDE 协议服务
Cursor IDE 使用独立 Host。该 Host 只注册:
- 模型列表接口;
- Agent BidiAppend 接口;
- Agent RunSSE 接口。
其他路径全部返回 `404`,不能做代理兜底。代理兜底只能发生在 Proxy 层。
## 8. 两个本地 Host
### 8.1 App Host
App Host 绑定随机回环地址 `127.0.0.1:0`,负责:
- 提供前端静态资源;
- 注册 AppService
- 校验 Origin 和本地会话;
- 提供 ConnectRPC 流式响应。
桌面启动时生成一次性 bootstrap token。WebView 首次访问 bootstrap 地址后,Host 写入 `HttpOnly``SameSite=Strict` Cookie,并重定向到普通首页。前端代码不长期保存 token。
### 8.2 Cursor Host
Cursor Host 绑定 Cursor 配置要求的本机地址,只服务 Cursor IDE 协议。它与 App Host 使用不同的路由表和认证规则。
## 9. 前端架构
前端只保留 `appClient`,负责配置、Runtime、模型、更新和桌面动作。
启动流程如下:
1. 创建同源 Connect-Web transport
2. 调用 AppService `Bootstrap`
3. 启动 `Watch`
4. 按 revision 丢弃重复或乱序事件;
5. 流断开后退避重连,并重新取得完整快照。
前端不导入 Wails runtime,也不通过全局事件总线传递后端状态。
## 10. 数据库与依赖注入
启动过程固定为:
1. `platform/filesystem` 解析数据路径;
2. `startup` 打开数据库连接;
3. App 和 Cursor 分别提供自己的迁移集合;
4. `startup` 按版本执行迁移;
5. 创建 App Repository 和 Cursor Repository
6. 将接口注入对应 Service
7. 注册 ConnectRPC Handler
8. 启动 App Host 和桌面窗口。
模块只能访问自己拥有的表。跨模块调用使用接口,不共享数据库 DTO。
## 11. Proto 和生成代码
新建的产品控制协议位于:
```text
internal/backend/app/proto
internal/backend/app/gen
```
所有 Cursor 专属协议位于:
```text
internal/backend/cursor/proto
internal/backend/cursor/gen
```
当前根目录的 `proto``gen` 以及协议提取器都要迁入 Cursor 模块。提取器必须按 parser、symbols、renderer 等职责拆分,单文件禁止超过 500 行。
前端只生成 AppService 所需的 Web 客户端,不把 Cursor IDE 上游协议暴露给 UI。
## 12. 依赖方向
允许的依赖方向如下:
```text
main -> startup
startup -> platform
startup -> backend/app
startup -> backend/cursor
startup -> proxy
frontend -> app.v1
backend/app -> 自己声明的端口
backend/cursor -> 自己声明的端口
proxy -> 注入的路由和目标接口
```
禁止以下依赖:
```text
backend/app -> backend/cursor
backend/cursor -> backend/app
platform -> backend
platform -> protobuf
proxy -> backend/cursor
任何业务包 -> startup
```
## 13. 现有代码迁移映射
```text
internal/app/runner.go
-> startup/bootstrap.go
-> startup/wiring.go
-> platform/desktop/*
internal/bridge/*
-> 删除
internal/client 中的产品配置、模型、更新
-> backend/app 对应文件
internal/appdata
-> platform/filesystem
系统代理操作
-> platform/network
更新状态与检查
-> backend/app/update.go
安装命令
-> platform/update/installer.go
根 proto、gen 和提取器
-> backend/cursor/proto
-> backend/cursor/gen
```
## 14. TDD 实施顺序
### 阶段一:建立架构守卫
先写失败测试,检查:
- 前端禁止的 Wails IPC 标识;
- App 与 Cursor 禁止互相导入;
- Wails application API 只能出现在 `platform/desktop`
- 根目录不再存在 Cursor `proto``gen`
- 所有手写源码不超过 500 行。
### 阶段二:建立 App ConnectRPC Host
先测试再实现:
- loopback 随机端口;
- bootstrap token 换取 Cookie
- AppService unary 调用;
- Watch 首包快照和 revision
- 非法 Origin 和无会话请求拒绝。
### 阶段三:实现通用 Runtime
使用两个 Fake Runtime 先验证:
- 注册和列出多个 kind
- 幂等 Start 和 Stop
- 并发操作串行化;
- 部分启动失败回滚;
- 逆序停止;
- 状态 revision
- Cursor Runtime 和 Devin Runtime 不需要修改 AppService。
然后再把 Cursor Host、MITM 和系统代理接入 Cursor Runtime。
### 阶段四:切换前端
先为 RPC 状态层编写测试,再替换现有 bindings 和 Events。切换完成后删除 `internal/bridge` 与所有 Wails 业务服务注册。
### 阶段五:迁移 Cursor Proto
迁移 Cursor IDE 协议源文件、生成代码和提取器,并使用协议 fixture 验证迁移前后字节结果一致。
### 阶段六:清理和集成验证
删除旧接口、旧事件、旧生成代码和空目录,运行完整单元测试、集成测试、静态检查与编码风格检查。
## 15. 必须覆盖的测试
### App Host
- 首次 bootstrap 成功且 token 只能使用一次;
- Connect unary 和 server stream 可用;
- 重连后重新获得完整快照;
- Host 停止后连接和 goroutine 全部退出。
### Runtime
- 多种 Runtime 并存;
- 状态转换合法;
- Cursor 启动顺序正确;
- Cursor 停止顺序与启动相反;
- MITM 启动失败时 Cursor Host 被回滚;
- 应用退出时所有 Runtime 被停止。
### Cursor Host 与 Proxy
- 模型列表和 Agent 接口可访问;
- Cursor Host 的其他路径返回 `404`
- Proxy 只拦截 Cursor Host 明确支持的路由;
- 其他请求的 method、path、query、header、body、status 和响应流保持透传语义。
### 前端
- 不存在 Wails bindings 和业务 Events
- App 状态订阅只通过 AppService
- 重复 revision 不会重复更新状态;
- 断流后可以恢复快照和订阅。
## 16. 完成标准
满足以下条件才算重构完成:
1. 前端业务链路全部经过 ConnectRPC。
2. `internal/bridge` 已删除。
3. `backend/app` 只包含产品级配置、Runtime 和桌面控制能力。
4. Runtime API、DTO、状态和测试均不绑定 Cursor。
5. Cursor Host 与 MITM 只作为已注册 Runtime 的当前实现。
6. Cursor 专属 proto、gen 和提取器全部位于 `backend/cursor`
7. Cursor Host 未注册路径稳定返回 `404`
8. Proxy 未命中请求稳定透传到原始上游。
9. 只有 `platform/desktop` 直接使用 Wails application API。
10. 所有新增和调整的源码、测试均使用简洁中文注释,单文件不超过 500 行。