15 KiB
前后端 ConnectRPC 重构完整方案
1. 目标
本方案重构桌面端、前端、本机控制面、代理层和具体服务端实现之间的边界。
最终目标如下:
- 前端业务通信全部使用 ConnectRPC,不再使用任何 Wails 业务 IPC。
internal/startup只负责依赖组装、启动顺序、运行时注册和优雅退出。- 操作系统与 Wails 能力统一收敛到
internal/platform。 internal/backend/app只负责产品级本机控制面。- Cursor 协议、Agent 和 Prompt 全部归
internal/backend/cursor。 - Runtime 是通用服务运行时,不绑定 Cursor,也不使用“Cursor backend”作为领域名称。
- 当前 Cursor Host 与 MITM 只是一个 Runtime 实现,未来可以并列注册 Devin 等实现。
- Cursor Host 未处理的接口返回
404;代理层未命中的请求原样转发到原始上游。
2. 强制边界
2.1 禁止业务 IPC
前端禁止继续使用以下能力:
@bindings
Call.ByName
Events.On
Events.Emit
application.NewService
Wails 只负责桌面应用生命周期和 WebView,不再承载配置、运行时、模型或事件等业务接口。
2.2 Runtime 不绑定 Cursor
backend/app/runtime.go 表达的是通用运行时用例:
- 列出可用运行时;
- 启动、停止和重启指定运行时;
- 查询状态和最近一次错误;
- 向前端发布运行时状态变化。
它不能出现以下设计:
CursorBackend
StartCursor
StopCursor
CursorMITMStatus
Cursor Host、MITM 和系统代理的组合只存在于启动装配阶段,不进入 App 的通用 DTO。
3. 总体架构
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. 目标目录
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
前端目标目录如下:
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 定义通用端口:
// 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 只包含通用字段:
RuntimeDescriptor {
id
kind
state
capabilities
endpoint
last_error
revision
}
其中 id 标识一个配置实例,kind 标识实现类型,例如 cursor 或 devin。App 和前端不能根据 Cursor 专属字段决定运行时流程。
6.2 Supervisor
startup/supervisor.go 实现 RuntimeController,维护 Runtime 注册表和状态机:
Stopped -> Starting -> Running -> Stopping -> Stopped
-> Failed
必须满足:
- 同一个 Runtime 的启停操作串行执行;
- 重复 Start 和 Stop 具有幂等语义;
- 启动中途失败时回滚已经启动的组件;
- Stop 按 Start 的逆序执行;
- 状态变化携带递增 revision;
- 应用退出时统一停止所有已启动 Runtime。
6.3 当前 Cursor Runtime
当前在 startup/wiring.go 注册一个 kind=cursor 的 Runtime。它的启动顺序为:
- 校验 Cursor 和模型配置;
- 启动 Cursor Host;
- 启动 MITM;
- 根据配置启用系统代理;
- 发布 Running 状态。
停止时按相反顺序恢复系统代理、停止 MITM、停止 Cursor Host。
Cursor Host 和 MITM 是当前实现的内部组件,不应被命名为整个 Runtime。未来接入 Devin 时,只需注册新的 Runtime 实现,不修改 AppService 协议。
7. ConnectRPC 服务面
7.1 app.v1.AppService
第一阶段使用明确方法,不提供通用 JSON Invoke:
Bootstrap
Watch
GetConfig
UpdateConfig
ListRuntimes
GetRuntime
StartRuntime
StopRuntime
RestartRuntime
ListModels
SaveModel
DeleteModel
TestModel
GetAds
GetUpdate
CheckUpdate
InstallUpdate
OpenWindow
OpenExternal
App 的 Watch 第一条消息是完整 App 快照,后续发送带 revision 的增量事件:
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、模型、更新和桌面动作。
启动流程如下:
- 创建同源 Connect-Web transport;
- 调用 AppService
Bootstrap; - 启动
Watch; - 按 revision 丢弃重复或乱序事件;
- 流断开后退避重连,并重新取得完整快照。
前端不导入 Wails runtime,也不通过全局事件总线传递后端状态。
10. 数据库与依赖注入
启动过程固定为:
platform/filesystem解析数据路径;startup打开数据库连接;- App 和 Cursor 分别提供自己的迁移集合;
startup按版本执行迁移;- 创建 App Repository 和 Cursor Repository;
- 将接口注入对应 Service;
- 注册 ConnectRPC Handler;
- 启动 App Host 和桌面窗口。
模块只能访问自己拥有的表。跨模块调用使用接口,不共享数据库 DTO。
11. Proto 和生成代码
新建的产品控制协议位于:
internal/backend/app/proto
internal/backend/app/gen
所有 Cursor 专属协议位于:
internal/backend/cursor/proto
internal/backend/cursor/gen
当前根目录的 proto、gen 以及协议提取器都要迁入 Cursor 模块。提取器必须按 parser、symbols、renderer 等职责拆分,单文件禁止超过 500 行。
前端只生成 AppService 所需的 Web 客户端,不把 Cursor IDE 上游协议暴露给 UI。
12. 依赖方向
允许的依赖方向如下:
main -> startup
startup -> platform
startup -> backend/app
startup -> backend/cursor
startup -> proxy
frontend -> app.v1
backend/app -> 自己声明的端口
backend/cursor -> 自己声明的端口
proxy -> 注入的路由和目标接口
禁止以下依赖:
backend/app -> backend/cursor
backend/cursor -> backend/app
platform -> backend
platform -> protobuf
proxy -> backend/cursor
任何业务包 -> startup
13. 现有代码迁移映射
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. 完成标准
满足以下条件才算重构完成:
- 前端业务链路全部经过 ConnectRPC。
internal/bridge已删除。backend/app只包含产品级配置、Runtime 和桌面控制能力。- Runtime API、DTO、状态和测试均不绑定 Cursor。
- Cursor Host 与 MITM 只作为已注册 Runtime 的当前实现。
- Cursor 专属 proto、gen 和提取器全部位于
backend/cursor。 - Cursor Host 未注册路径稳定返回
404。 - Proxy 未命中请求稳定透传到原始上游。
- 只有
platform/desktop直接使用 Wails application API。 - 所有新增和调整的源码、测试均使用简洁中文注释,单文件不超过 500 行。