摘要
将浏览器自动化和 GUI/Computer-Use 能力统一收入 Workspace Container,取代独立的 Browser Gateway 服务。通过在 Bridge gRPC 协议中新增 Tunnel RPC,让 Memoh Server 能够透明地拨号到容器内部的任意 TCP 服务(CDP、VNC 等),在此基础上分别构建浏览器操控和桌面交互两套能力。
动机
当前架构中 Browser Gateway 是一个独立的 Bun/Elysia 服务,运行 Playwright 驱动浏览器。这带来几个问题:
- 运维复杂度:多一个独立服务需要部署、监控、升级
- 无 bot 隔离:所有 bot 共享同一组浏览器进程,存在安全和资源隔离问题
- 无 GUI 能力:当前架构不支持 Computer-Use(屏幕截图 + 鼠标键盘操控)和可视化桌面
新架构将浏览器和显示栈都放进每个 bot 的 Workspace Container,复用已有的容器隔离机制,消除独立服务,同时引入 GUI 能力。
设计
0. 共享基础设施:Bridge Tunnel RPC
Tunnel 是所有后续能力的基础。它允许 Memoh Server 通过已有的 Bridge gRPC 连接(UDS)拨号到容器内的任意本地 TCP 端口。
Proto 定义
// 新增到 bridge.proto
message TunnelOpen {
string address = 1; // e.g. "127.0.0.1:9222", "127.0.0.1:5999"
}
message TunnelData {
bytes data = 1;
}
message TunnelClose {
string error = 1; // 空字符串表示正常关闭
}
message TunnelFrame {
oneof frame {
TunnelOpen open = 1; // 仅第一帧,client→bridge
TunnelData data = 2; // 双向
TunnelClose close = 3; // 任一端发起
}
}
service ContainerService {
// ... 现有 RPC ...
rpc Tunnel(stream TunnelFrame) returns (stream TunnelFrame);
}
Bridge 侧实现
收到 TunnelOpen 后 net.Dial("tcp", address) 建立到目标的 TCP 连接,之后双向 relay TunnelData,任一端发送 TunnelClose 或 stream 断开时关闭 TCP 连接。
安全约束:只允许 dial 127.0.0.1,拒绝所有其他地址。方向仅 server→container,container 侧不可主动建立反向隧道。
Server 侧封装
bridge.Client 新增方法:
func (c *Client) DialContext(ctx context.Context, network, address string) (net.Conn, error)
返回实现 net.Conn 接口的 tunnelConn,内部持有一个 gRPC stream。一次 Tunnel stream = 一条 TCP 连接,不做多路复用。
调用方可直接注入到 http.Transport、chromedp allocator、或任何接受 Dialer 的 Go 组件中:
transport := &http.Transport{DialContext: bridgeClient.DialContext}
Part 1: VNC / Computer-Use
1.1 Display Bundle
自包含的 X11/VNC 运行时,mount 到容器内 /opt/memoh/display/。
内容:
- Xvnc(TigerVNC)
- xkbcomp(X keyboard compiler,Xvnc 运行时依赖)
- xkb-data(键盘布局数据文件)
- 最小字体集(cursor font)
构建方式:Nix buildEnv 产出自包含 closure(含 glibc 副本,不依赖宿主 libc 版本)。CI 构建后打成 tarball 作为 release artifact。安装脚本 docker/display/install.sh 用 wget + tar 下载解压,与 docker/toolkit/install.sh 模式一致。
平台支持:glibc only。Xvnc 无法静态链接,musl 容器不支持 display 能力。Bridge 启动时检测 /opt/memoh/display/bin/Xvnc 是否存在且可执行,不可用时 display 相关功能静默禁用。
1.2 Xvnc 管理
启停策略:Bot 级别配置开关「启用显示器」。启用后 bridge 在容器启动时拉起 Xvnc:
LD_LIBRARY_PATH=/opt/memoh/display/lib \
/opt/memoh/display/bin/Xvnc :99 \
-geometry 1280x800 -depth 24 \
-SecurityTypes None
监听 RFB 端口 5999(display :99 → port 5900 + 99 = 5999)。Bridge 监控子进程,异常退出自动重启。
VNC 不设密码认证(-SecurityTypes None)—— 容器内部网络不暴露,所有外部访问都经过 Tunnel + Memoh auth。
1.3 Computer-Use(Server 侧 RFB Client)
Server 侧实现轻量的 RFB(VNC 协议,RFC 6143)client,通过 Tunnel dial 127.0.0.1:5999 连接容器内的 Xvnc。
能力:
| 操作 |
RFB 实现 |
| 截屏 |
FramebufferUpdateRequest → 接收像素数据 → Go 侧 encode PNG,全内存,无临时文件 |
| 鼠标操作 |
PointerEvent 消息(6 bytes) |
| 键盘操作 |
KeyEvent 消息(8 bytes) |
| 屏幕尺寸 |
RFB 握手 ServerInit 消息中获取 |
不使用 xdotool 或 ImageMagick —— 所有交互通过 VNC 协议完成,display bundle 内无需额外工具。
1.4 Agent Tools
新增 computer_action / computer_observe 工具,与 browser tools 平级,仅在 display 已启用时暴露给 agent:
computer_observe:
screenshot — 返回 base64 PNG
get_screen_size — 返回宽高
computer_action:
click(x, y) / double_click(x, y) / right_click(x, y)
move(x, y)
type(text)
key(combo) — 如 ctrl+c、Enter
scroll(direction, amount)
drag(from_x, from_y, to_x, to_y)
1.5 前端 noVNC
Memoh web 端内嵌 noVNC 组件(纯 JavaScript VNC 客户端),不进 container。
连接路径:
noVNC (浏览器) → WebSocket → Memoh Server WS endpoint → Tunnel → container:5999
Server 侧实现 WebSocket-to-TCP 代理,一层薄 relay,复用现有 auth 体系鉴权。
1.6 桌面环境(可选,待定)
Memoh 不 bundle 桌面环境,也不假定容器内安装了什么桌面。用户自行选择包含桌面环境的 workspace image(Memoh 可提供预装 XFCE 的镜像变体作为参考),并在 bot 设置中:
- 启用桌面环境开关
- 填写桌面启动命令(如
startxfce4、gnome-session)
Bridge 在 Xvnc 启动后执行用户配置的启动命令(DISPLAY=:99)。
运行时组合:
- 无桌面:Xvnc :99 → 空 X display,headful 浏览器或其他 GUI 应用直接显示
- 有桌面:Xvnc :99 → 用户配置的桌面 session → 完整桌面体验
本节细节待定,将在 Part 1 核心能力(1.1-1.5)实现后再细化。
Part 2: Browser / Browser-Use
2.1 Playwright 与 Chromium
Playwright CLI 及 browser server 脚本 mount 到 /opt/memoh/playwright/,使用 /opt/memoh/toolkit/ 中已有的 Node.js 运行。
Chromium 获取方式(二选一,待定):
方案 A — 预打包挂载:Chromium 二进制打包进 server image,随 /opt/memoh/playwright/ 一起 mount。
- 优势:零下载延迟,无网络依赖,开箱即用
- 代价:server image 增大 ~350MB,所有 bot 不论是否使用都携带浏览器,版本更新需重新发布 image
方案 B — 按需下载:/opt/memoh/playwright/ 只挂载 Playwright CLI,Chromium 由 playwright install chromium 在容器内按需下载,保存在容器可写层(~350MB)。
- 优势:server image 保持精简,只有使用浏览器的 bot 承担存储开销
- 代价:首次使用需下载,需要网络连通
方案 B 的安装触发与进度汇报:
- 用户在前端为 bot 启用浏览器 → 触发后台安装(主路径)
- Agent 在安装未完成时调用 browser tool → tool result 返回
playwright install chromium 的 stdout/stderr 原样输出,agent 自行理解进度并反馈给用户
- Agent 在未启用浏览器时调用 browser tool → 触发开始安装,同样返回 stdout/stderr
- 安装完成后,后续 browser tool call 正常执行
2.2 Chromium 启动与生命周期
Bridge 直接 exec 启动 Chromium,不经过 Playwright server,不需要 Node 运行时参与浏览器操控:
chromium --remote-debugging-port=9222 --no-first-run --disable-default-apps
headless vs headful:
- 默认
--headless=new(Chromium 新版 headless 模式)
- 若 Xvnc 已启动(Part 1),加
DISPLAY=:99 并去掉 headless flag → headful 模式,浏览器窗口在 noVNC 中可见,兼容 Computer-Use
生命周期:由 bridge 管理,首次 browser tool call 时拉起 Chromium 进程。Bridge 监控子进程,异常退出后下次调用重新拉起。Container 停止时 bridge 作为 PID 1 自然回收所有子进程。
2.3 Go 侧 Browser 操控(chromedp + CDP)
Go server 通过 Tunnel dial 127.0.0.1:9222 获取 CDP WebSocket 连接,使用 chromedp 库实现所有浏览器操控。
替代现有 BrowserProvider 中通过 HTTP 调用 Browser Gateway 的全部逻辑。Tool 定义(browser_action / browser_observe)对 agent 保持兼容。
chromedp 原生支持多 BrowserContext 和多 Tab 管理,替代原 Gateway 中基于内存 Map 的 storage + activePage 跟踪逻辑。
2.4 Agent Tools
保持现有 tool 定义兼容:
browser_action:navigate、click、dblclick、focus、type、fill、press、hover、select、check/uncheck、scroll、drag、upload、go_back/go_forward、reload、wait、tab_new/tab_select/tab_close
browser_observe:screenshot、screenshot_annotate、snapshot(accessibility tree)、get_content、get_html、evaluate、get_url、get_title、pdf、tab_list
实现从 HTTP Gateway 调用迁移到 chromedp CDP 调用,行为不变。
删除项
| 删除内容 |
说明 |
apps/browser/ |
整个 Browser Gateway 应用 |
docker/Dockerfile.browser |
Gateway Docker 构建文件 |
devenv/Dockerfile.browser |
开发环境 Gateway 构建文件 |
| Browser Gateway 相关 docker-compose 配置 |
服务定义、端口、profile |
internal/browsercontexts/ |
DB browser_contexts 表及 service(上下文配置迁入 bot settings) |
db/migrations/0027_browser_contexts.* |
对应数据库迁移 |
scripts/install.sh 中 Browser Gateway 相关部分 |
安装脚本中的 browser 选项、BROWSER_TAG 等 |
实现顺序
- Bridge Tunnel RPC — 共享基础设施,所有后续工作的前提
- Browser 迁移(Part 2) — 价值最高,替换现有 Gateway,用户可见功能不变
- Display bundle + Xvnc(Part 1.1-1.2) — VNC 基础能力
- Computer-Use RFB client + tools(Part 1.3-1.4) — agent GUI 操控
- noVNC 前端(Part 1.5) — 用户可视化控制台
- 桌面环境镜像变体(Part 1.6) — 可选增强
摘要
将浏览器自动化和 GUI/Computer-Use 能力统一收入 Workspace Container,取代独立的 Browser Gateway 服务。通过在 Bridge gRPC 协议中新增 Tunnel RPC,让 Memoh Server 能够透明地拨号到容器内部的任意 TCP 服务(CDP、VNC 等),在此基础上分别构建浏览器操控和桌面交互两套能力。
动机
当前架构中 Browser Gateway 是一个独立的 Bun/Elysia 服务,运行 Playwright 驱动浏览器。这带来几个问题:
新架构将浏览器和显示栈都放进每个 bot 的 Workspace Container,复用已有的容器隔离机制,消除独立服务,同时引入 GUI 能力。
设计
0. 共享基础设施:Bridge Tunnel RPC
Tunnel 是所有后续能力的基础。它允许 Memoh Server 通过已有的 Bridge gRPC 连接(UDS)拨号到容器内的任意本地 TCP 端口。
Proto 定义
Bridge 侧实现
收到
TunnelOpen后net.Dial("tcp", address)建立到目标的 TCP 连接,之后双向 relayTunnelData,任一端发送TunnelClose或 stream 断开时关闭 TCP 连接。安全约束:只允许 dial
127.0.0.1,拒绝所有其他地址。方向仅 server→container,container 侧不可主动建立反向隧道。Server 侧封装
bridge.Client新增方法:返回实现
net.Conn接口的tunnelConn,内部持有一个 gRPC stream。一次 Tunnel stream = 一条 TCP 连接,不做多路复用。调用方可直接注入到
http.Transport、chromedp allocator、或任何接受Dialer的 Go 组件中:Part 1: VNC / Computer-Use
1.1 Display Bundle
自包含的 X11/VNC 运行时,mount 到容器内
/opt/memoh/display/。内容:
构建方式:Nix
buildEnv产出自包含 closure(含 glibc 副本,不依赖宿主 libc 版本)。CI 构建后打成 tarball 作为 release artifact。安装脚本docker/display/install.sh用wget + tar下载解压,与docker/toolkit/install.sh模式一致。平台支持:glibc only。Xvnc 无法静态链接,musl 容器不支持 display 能力。Bridge 启动时检测
/opt/memoh/display/bin/Xvnc是否存在且可执行,不可用时 display 相关功能静默禁用。1.2 Xvnc 管理
启停策略:Bot 级别配置开关「启用显示器」。启用后 bridge 在容器启动时拉起 Xvnc:
监听 RFB 端口 5999(display :99 → port 5900 + 99 = 5999)。Bridge 监控子进程,异常退出自动重启。
VNC 不设密码认证(
-SecurityTypes None)—— 容器内部网络不暴露,所有外部访问都经过 Tunnel + Memoh auth。1.3 Computer-Use(Server 侧 RFB Client)
Server 侧实现轻量的 RFB(VNC 协议,RFC 6143)client,通过 Tunnel dial
127.0.0.1:5999连接容器内的 Xvnc。能力:
不使用 xdotool 或 ImageMagick —— 所有交互通过 VNC 协议完成,display bundle 内无需额外工具。
1.4 Agent Tools
新增
computer_action/computer_observe工具,与 browser tools 平级,仅在 display 已启用时暴露给 agent:computer_observe:screenshot— 返回 base64 PNGget_screen_size— 返回宽高computer_action:click(x, y)/double_click(x, y)/right_click(x, y)move(x, y)type(text)key(combo)— 如ctrl+c、Enterscroll(direction, amount)drag(from_x, from_y, to_x, to_y)1.5 前端 noVNC
Memoh web 端内嵌 noVNC 组件(纯 JavaScript VNC 客户端),不进 container。
连接路径:
Server 侧实现 WebSocket-to-TCP 代理,一层薄 relay,复用现有 auth 体系鉴权。
1.6 桌面环境(可选,待定)
Memoh 不 bundle 桌面环境,也不假定容器内安装了什么桌面。用户自行选择包含桌面环境的 workspace image(Memoh 可提供预装 XFCE 的镜像变体作为参考),并在 bot 设置中:
startxfce4、gnome-session)Bridge 在 Xvnc 启动后执行用户配置的启动命令(
DISPLAY=:99)。运行时组合:
Part 2: Browser / Browser-Use
2.1 Playwright 与 Chromium
Playwright CLI 及 browser server 脚本 mount 到
/opt/memoh/playwright/,使用/opt/memoh/toolkit/中已有的 Node.js 运行。Chromium 获取方式(二选一,待定):
方案 A — 预打包挂载:Chromium 二进制打包进 server image,随
/opt/memoh/playwright/一起 mount。方案 B — 按需下载:
/opt/memoh/playwright/只挂载 Playwright CLI,Chromium 由playwright install chromium在容器内按需下载,保存在容器可写层(~350MB)。方案 B 的安装触发与进度汇报:
playwright install chromium的 stdout/stderr 原样输出,agent 自行理解进度并反馈给用户2.2 Chromium 启动与生命周期
Bridge 直接 exec 启动 Chromium,不经过 Playwright server,不需要 Node 运行时参与浏览器操控:
headless vs headful:
--headless=new(Chromium 新版 headless 模式)DISPLAY=:99并去掉 headless flag → headful 模式,浏览器窗口在 noVNC 中可见,兼容 Computer-Use生命周期:由 bridge 管理,首次 browser tool call 时拉起 Chromium 进程。Bridge 监控子进程,异常退出后下次调用重新拉起。Container 停止时 bridge 作为 PID 1 自然回收所有子进程。
2.3 Go 侧 Browser 操控(chromedp + CDP)
Go server 通过 Tunnel dial
127.0.0.1:9222获取 CDP WebSocket 连接,使用 chromedp 库实现所有浏览器操控。替代现有
BrowserProvider中通过 HTTP 调用 Browser Gateway 的全部逻辑。Tool 定义(browser_action/browser_observe)对 agent 保持兼容。chromedp 原生支持多 BrowserContext 和多 Tab 管理,替代原 Gateway 中基于内存 Map 的
storage+activePage跟踪逻辑。2.4 Agent Tools
保持现有 tool 定义兼容:
browser_action:navigate、click、dblclick、focus、type、fill、press、hover、select、check/uncheck、scroll、drag、upload、go_back/go_forward、reload、wait、tab_new/tab_select/tab_closebrowser_observe:screenshot、screenshot_annotate、snapshot(accessibility tree)、get_content、get_html、evaluate、get_url、get_title、pdf、tab_list实现从 HTTP Gateway 调用迁移到 chromedp CDP 调用,行为不变。
删除项
apps/browser/docker/Dockerfile.browserdevenv/Dockerfile.browserinternal/browsercontexts/db/migrations/0027_browser_contexts.*scripts/install.sh中 Browser Gateway 相关部分实现顺序