第 8 章 · 02 三前端实现与 ACP 编辑器集成 本节摘要:本节走进三个前端的真实实现——同一个 ,四种截然不同的前端形态。TUI 前端用 Charm 全家桶(bubbletea/lipgloss/bubbles)画终端,有 CLI 主题和图片折叠效果;HTTP/SSE serve 前端是 OpenAI 兼容的 HTTP 网关,远程客户端按 OpenAI 协议接入;Wails 桌面前端用 Go 后端 + React 19 前端,富交互桌面应用;此外还有 ACP(Agent Client Protocol)编辑器集成,internal/acp(13066 行)把 Controller 适配成 stdio JSON-RPC,VS Code 扩展通过 ACP 协议驱动 Reasonix。
本节摘要:本节走进三个前端的真实实现——同一个
control.Controller,四种截然不同的前端形态。TUI 前端用 Charm 全家桶(bubbletea/lipgloss/bubbles)画终端,有 CLI 主题和图片折叠效果;HTTP/SSE serve 前端是 OpenAI 兼容的 HTTP 网关,远程客户端按 OpenAI 协议接入;Wails 桌面前端用 Go 后端 + React 19 前端,富交互桌面应用;此外还有 ACP(Agent Client Protocol)编辑器集成,internal/acp(13066 行)把 Controller 适配成 stdio JSON-RPC,VS Code 扩展通过 ACP 协议驱动 Reasonix。三前端各有所长——TUI 轻量、SSE 远程、桌面富交互——但共享同一个 Controller,行为完全一致。本节末尾给出三前端对比表。
内容来源:原项目源码
internal/serve、desktop/、internal/acp/protocol.go,配图images/cli-theme-effect.svg、images/paste-folding-effect.svg、images/desktop-v2-layout-preview.png、images/desktop-v2-skills-preview.png、images/desktop-theme-dark.jpg、images/slash-command-views.svg、images/project-topic-tabs-context.png,对照docs/serve_protocol.md。
⚠️ 注意:本节聚焦"前端如何适配 Controller",不深入 Charm/Wails/React 各自的 API 细节。读者可结合官方文档学习具体前端框架。
阅读完本节,你应当能够:
TUI(Terminal User Interface)是 Reasonix 的默认前端——开箱即用、零依赖、SSH 友好。它用 Charm 生态的三个核心库搭建:
| 库 | 作用 |
|---|---|
bubbletea |
Elm 架构(Model-Update-View)的 TUI 框架,管理状态和事件循环 |
lipgloss |
终端样式(颜色、边框、布局),声明式写终端 UI |
bubbles |
常用组件(文本框、列表、分页、spinner 等) |
TUI 前端对 Controller 的适配是教科书式的"命令/事件双流"消费:
Send/Cancel/Approve 等。event.Sink 把事件渲染成终端字符 → bubbletea 触发重绘。TUI 不是丑陋的纯文本,Reasonix 用 lipgloss 做了完整的 CLI 主题系统,效果见 images/cli-theme-effect.svg——配色、边框、状态栏、工具调用块都有视觉设计。主题让终端里的 agent 输出有结构感:推理段落一种颜色,工具调用一种颜色,审批请求一种颜色,用户一眼能分辨当前 agent 在干什么。
主题系统写在 Controller 之外(它是 TUI 专属的表现层,第 1 节讲过判断标准——UI 表现属于前端专属)。但这不影响行为一致性——主题只改颜色不改语义,TUI 用户和桌面用户看到的 agent 行为是同一套。
images/paste-folding-effect.svg 展示了 TUI 的图片折叠技巧——当 agent 输出大段图片(比如粘贴截图、生成图表)时,TUI 默认折叠只显示一行摘要,用户按键展开看全图。这是终端场景的工程优化:终端宽高有限,大图片会刷屏,折叠让会话流保持紧凑。
这个折叠行为是 TUI 专属的(桌面对话框直接显示,SSE 用 URL 引用),所以写在 TUI 前端里而不是 Controller 里。Controller 只负责吐出"这里有一张图"的事件,怎么显示由各前端决定。
💡 契约要点:TUI 前端是"命令/事件双流"最纯粹的消费者。bubbletea 管状态机和重绘,lipgloss 管样式,bubbles 提供组件。主题和图片折叠是 TUI 专属表现层,不进 Controller——这是"判断行为是否跨前端共享"的实操案例。
internal/serve 实现了 HTTP/SSE serve 前端。它的定位很特别——它是一个 OpenAI 兼容的 HTTP 网关。
OpenAI 的 Chat Completions API 是事实上的行业标准(/v1/chat/completions、SSE 流式响应、tool calling)。Reasonix 把 serve 前端做成 OpenAI 兼容,意味着:
api.openai.com 改成本地 reasonix serve 的地址即可。这是"传输无关 Controller"的远程延伸——serve 前端把 Controller 的事件流翻译成 OpenAI 风格的 SSE 数据帧(data: {...}\n\n),把 HTTP POST 的 chat completion 请求翻译成 Controller 的 Send 命令。协议层是 OpenAI 兼容的,但底层执行是完整的 Reasonix agent(工具、权限、沙箱、缓存全都在)。
docs/serve_protocol.md 详细描述了这个协议——支持的端点、SSE 事件格式、鉴权、会话管理。serve 还提供了 index.html/login.html/provider_setup.html 等内置网页,用户开浏览器就能用。
internal/serve/ serve.go 主入口 auth.go 鉴权 broadcaster.go SSE 广播(把一个事件流扇出给多个订阅者) provider_setup.go 模型配置页面 index.html 内置聊天网页 login.html 登录页 ...
broadcaster.go 是个有意思的组件——一个 Controller 的事件流,可能要同时推给网页 UI、日志、计费等多个订阅者。broadcaster 做"一进多出"的扇出,让多个订阅者各自收到完整事件流。
serve 前端的存在证明了 transport-agnostic 的威力——HTTP/SSE 是和终端完全不同的传输层,但接的是同一个 Controller,跑的是同一套 agent 行为。远程用户和本地 TUI 用户得到的体验在语义上完全一致。
💡 契约要点:serve 前端是 transport-agnostic 的远程证明。OpenAI 兼容协议让 Reasonix 能被任何标准客户端调用,把"本地 coding agent"延伸成"远程 agent 服务"。底层 Controller 不变,只换了传输层和协议适配层。
desktop/ 是 Reasonix 的桌面前端,技术栈是 Wails + React 19。
Wails 是 Go 的桌面应用框架,定位类似 Electron 但用 Go 代替 Node.js 做后端——Go 编译成原生二进制,前端用 Web 技术(React/Vue 等)渲染。相比 Electron,Wails 的优势是:
这和 Reasonix 的"薄 harness + 单静态二进制"哲学(第 1 章)完全契合——Wails 让桌面应用也是一个相对轻量的二进制,而不是动辄几百 MB 的 Electron 包。
桌面前端的分层:
React 19 前端(UI 层) - 状态管理:Zustand - 终端组件:xterm.js - 数学公式:KaTeX - 图表:Mermaid ↓ Wails 桥(双向调用) Go 后端(Wails 绑定) - 调 control.Controller(命令) - 实现 event.Sink(事件) - 持有桌面专属逻辑(主题、窗口、文件拖拽)
React 19 前端用了几个值得注意的库:
这些都是桌面专属的富交互能力——TUI 画不了数学公式和流程图,SSE 客户端通常也不渲染 Mermaid。桌面用 React 把这些做好,但底层的 agent 推理、工具调用、审批流还是走 Controller。
images/desktop-v2-layout-preview.png 展示了桌面 v2 的整体布局——侧边栏(会话列表/Bots)、主区域(对话)、底部(输入框)、可能还有 Skills 面板(images/desktop-v2-skills-preview.png)。这是富交互桌面应用的标配。
images/desktop-theme-dark.jpg 是暗色主题的截图。Reasonix 桌面自带 8 套官方主题(第 10 章第 2 节详述 desktop/themes/official/),用户可以切换。主题是桌面专属的(终端有 CLI 主题、桌面有桌面主题),但都只是表现层,不影响 agent 行为。
桌面 Go 后端还做了一件重要的事——它同时承载 bot 运行时(bot_bridge.go/bot_runtime_app.go 等)。也就是说,桌面应用除了是 Reasonix 的 UI,还是 IM 机器人(第 10 章第 1 节)的运行宿主。这是桌面相对 TUI/SSE 的独特定位——它不只是前端,还是集成枢纽。
⚠️ 注意:桌面是体量最大的前端(Go 后端 11.1 万行 + React 前端 12.6 万行),但它依然只是 Controller 的一个消费者。bot_bridge 这些桥接代码把 IM 网关接进来,但不重新实现 agent 逻辑。判断一个功能该不该写进桌面后端的标准还是那条——是否跨前端共享。
第四个"前端"严格说是编辑器集成——internal/acp(13066 行)实现了 ACP(Agent Client Protocol)。
ACP 是一个开放协议(agentclientprotocol.com),让编辑器(VS Code、Neovim、Zed 等)能标准化地驱动 AI coding agent。可以理解为"AI agent 的 LSP"——LSP 让编辑器和语言服务器对话,ACP 让编辑器和 agent 对话。
internal/acp/protocol.go 的包注释说得很清楚:
// Package acp implements the Agent Client Protocol // (https://agentclientprotocol.com) transport: a stdio JSON-RPC 2.0 agent // that editors and other host clients speak to drive Reasonix. ... // The package is an adapter layer over the v2 kernel and depends only on // stable contracts: it maps the agent's typed event.Event stream onto // session/update notifications, bridges permission.Approver onto // session/request_permission round-trips, and exposes the whole thing over // NDJSON JSON-RPC.
关键信息:
session/update 通知。session/request_permission 往返请求。// ProtocolVersion is the ACP version this agent implements. Matches main. const ProtocolVersion = 1
ACP 包刻意保持协议版本号和主线分支一致——因为很多工具是基于 v1 ACP 协议集成的,Reasonix v2 必须保持 wire contract(线路协议)完全一致,否则这些工具就废了。这是协议适配层的核心契约——协议是公共承诺,实现可以变,协议不能变。
通过 ACP,VS Code 扩展(以及任何支持 ACP 的编辑器)可以把 Reasonix 当成一个后端 agent 来驱动。images/slash-command-views.svg 展示了 slash 命令在编辑器里的视图——用户在 VS Code 里输入 /reviewer、/doc-rewriter 这些 slash 命令(第 9 章 SUBAGENT_PROFILES),命令通过 ACP 传给 Reasonix,Reasonix 启动子代理执行,结果通过 ACP 事件流回传给 VS Code 渲染。
ACP 包的结构印证了它是纯适配层:
internal/acp/ protocol.go ACP 协议类型(wire types) server.go stdio JSON-RPC 服务器 dispatch.go 事件 → session/update 通知 service.go 会话工厂(组合 root 提供) status.go 状态报告 ...
它 import 了 agent/event/permission 这些内核包,但不 import 任何前端包。它自己就是"前端"(对编辑器来说),但实现方式是协议适配而不是 UI 渲染。
💡 契约要点:ACP 是"新前端零成本接入"的最佳实证(第 1 节讲的第三个收益)。Reasonix 没有 为 VS Code 重写 agent,只是写了一个 13066 行的协议适配层,把 Controller 的命令/事件映射到 ACP 的 JSON-RPC。任何支持 ACP 的编辑器都能零成本接入 Reasonix,这正是 transport-agnostic Controller 的长期红利。
最后用一张表把三前端(加 ACP)的定位对比清楚。配图 images/project-topic-tabs-context.png 展示了多前端共享同一会话上下文的效果。
| 维度 | TUI | HTTP/SSE serve | Wails 桌面 | ACP 编辑器 |
|---|---|---|---|---|
| 传输 | 终端 stdin/stdout | HTTP + SSE | Wails IPC + webview | stdio JSON-RPC |
| 技术 | Charm(bubbletea/lipgloss/bubbles) | Go net/http + broadcaster | Go Wails + React 19 | internal/acp 协议适配 |
| 定位 | 轻量本地、SSH 友好 | 远程访问、OpenAI 兼容客户端接入 | 富交互、集成枢纽(IM bot 宿主) | 编辑器内嵌(VS Code 等) |
| 富渲染 | CLI 主题、图片折叠 | 网页 + URL 引用 | KaTeX/Mermaid/xterm、8 套主题 | 编辑器原生 UI |
| 共享 | 同一个 control.Controller,同一套 agent 行为 | 同上 | 同上 | 同上 |
最后一行是这张表的灵魂——不管前端形态差多大,底层 Controller 和 agent 行为完全一致。TUI 用户的 /approve、桌面用户的按钮点击、SSE 客户端的 HTTP 调用、VS Code 用户的 ACP 请求,最终都走同一段 Controller 审批逻辑,跑同一个 agent,用同一套工具和权限规则。这就是 REASONIX.md 那句"One transport-agnostic control.Controller sits behind every frontend"在工程上的兑现。
images/cli-theme-effect.svg)和图片折叠(images/paste-folding-effect.svg)是 TUI 专属表现层不进 Controller。broadcaster.go 做事件流一进多出;docs/serve_protocol.md 定义协议;内置 index.html 等网页;是 transport-agnostic 的远程证明。images/desktop-v2-layout-preview.png 展示布局、images/desktop-v2-skills-preview.png 展示 Skills;桌面同时是 IM bot 运行宿主(bot_bridge.go)。session/update 通知、把权限审批桥接成 request_permission 往返;ProtocolVersion = 1 保持 wire contract 稳定;VS Code 扩展通过 ACP 接入(images/slash-command-views.svg)。下一章我们钻进 Controller 编排的一个特殊场景——多 agent 协作。internal/agent 的 fleet/coordinator 实现子代理编排(主代理委派子代理并行处理),SUBAGENT_PROFILES.md 定义子代理配置,把"一个 agent 干所有事"升级成"多个子代理分工协作"。