mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-08-18 03:57:06 +08:00
554 lines
15 KiB
Markdown
554 lines
15 KiB
Markdown
# 前后端 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 行。
|