Files
cursor-byok/docs/前后端ConnectRPC重构完整方案.md
T
2026-08-13 22:01:11 +08:00

15 KiB
Raw Blame History

前后端 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

前端禁止继续使用以下能力:

@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 只包装本机和操作系统能力:

  • desktopWails、窗口、托盘和浏览器;
  • 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 标识实现类型,例如 cursordevin。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。它的启动顺序为:

  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

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 写入 HttpOnlySameSite=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 和生成代码

新建的产品控制协议位于:

internal/backend/app/proto
internal/backend/app/gen

所有 Cursor 专属协议位于:

internal/backend/cursor/proto
internal/backend/cursor/gen

当前根目录的 protogen 以及协议提取器都要迁入 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 protogen
  • 所有手写源码不超过 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 行。