适配器开发指南
wesgine 的适配器(Adapter)体系为 Cell 提供外设能力扩展——浏览器自动化、桌面操作、语音转写、Plugin 子进程等。所有适配器都是 per-Cell 的,通过 CellSpec 声明式装配。
适配器架构
Cell (数据面)
├── adapter/channel/ # IM 渠道(企业微信/飞书/钉钉/...)
├── adapter/email/ # 邮件收发
├── adapter/mcp/ # MCP Server 代理
├── adapter/cron/ # 定时任务
├── adapter/knowledge/ # 知识库数据源
├── adapter/browser/ # 内置浏览器安全阀(AlwaysOn)
├── adapter/headless-browser/ # 无头浏览器(可选)
├── adapter/playwright/ # 用户浏览器自动化(可选)
├── adapter/desktop/ # 桌面自动化 Device Agent(可选)
├── adapter/stt/ # 语音转写 Whisper(可选)
├── adapter/plugin/ # JSON-RPC 子进程 Plugin Runtime
└── adapter/runtime/ # 外设运行时共享装配
装配原则
- 声明式:通过 CellSpec 字段启用,非 nil 即启用
- Per-Cell:每个 Cell 独立拥有自己的适配器实例
- 生命周期绑定:适配器随 Cell Start/Stop 启停
- AX-1 隔离:Cell A 的适配器对 Cell B 物理不可见
CellSpec 外设装配
spec := wesgine.CellSpec{
CellAdapterSpec: wesgine.CellAdapterSpec{
Channels: []channel.ChannelConfig{...},
Email: []email.Config{...},
MCPs: []mcp.ServerConfig{...},
Cron: []cron.JobConfig{...},
HITL: &wesgine.HITLConfig{Timeout: 60 * time.Second},
HeadlessBrowser: &wesgine.HeadlessBrowserConfig{...},
Playwright: &wesgine.PlaywrightConfig{...},
Desktop: &wesgine.DesktopConfig{...},
STT: &wesgine.STTConfig{...},
Plugins: []string{"/opt/plugins/my-plugin"},
},
}
Headless Browser
服务端无头浏览器,使用 chromedp 实现。适用于匿名网页抓取、截图等不需要用户登录态的场景。
启用
HeadlessBrowser: &wesgine.HeadlessBrowserConfig{
ChromePath: "/usr/bin/chromium", // 可选,自动检测
Timeout: 30 * time.Second,
MaxPages: 5,
},
提供的工具
| 工具 | 说明 |
|---|---|
browser_navigate | 导航到 URL |
browser_screenshot | 截取页面截图 |
browser_extract | 提取页面内容 |
browser_click | 点击元素 |
browser_type | 输入文本 |
API
GET /cells/{id}/headless-browser/state
返回浏览器连接状态和活跃页面数。
安全阀
adapter/browser/ 提供 browser_wait_for_user 工具——这是 AlwaysOn 的安全阀,Cell boot 时无条件注册。当 Agent 遇到需要人工介入的浏览器操作(如验证码、二步验证)时触发 HITL。
Playwright 集成
用户浏览器自动化 [B-13],用于需要登录态的场景。Playwright 通过 MCP 协议与引擎通信。
架构
wesgine Cell
└── adapter/playwright/
└── Playwright MCP subprocess
└── 用户浏览器(通过 Chrome DevTools Protocol)
配置
Playwright: &wesgine.PlaywrightConfig{
Mode: "user", // "user"(用户浏览器) | "sandbox"
Headless: false, // 用户浏览器通常 headed
BrowserType: "chromium",
Timeout: 60 * time.Second,
},
两种模式
| 模式 | 说明 | 适用场景 |
|---|---|---|
user | 连接用户已安装的浏览器 | 需要登录态的操作 |
sandbox | 启动隔离的浏览器实例 | 自动化测试 |
扩展 Token
Playwright 支持自动连接浏览器扩展:
PUT /cells/{id}/playwright/token
Content-Type: application/json
{"token": "auto-connect-token-xxx"}
状态查询
GET /cells/{id}/playwright/status
Desktop Agent
桌面自动化 Device Agent [B-14],通过 MCP 协议将桌面操作暴露给 Cell。
架构要点
- 代码共享,二进制在客户端运行(AX-5 物理边界)
- wesgine 提供 adapter 代码,消费应用负责编译和分发二进制
- 通过 stdio 或 SSE 与 Cell 通信
配置
Desktop: &wesgine.DesktopConfig{
Command: "/usr/local/bin/device-agent", // 二进制路径
Transport: "stdio", // "stdio" | "sse"
URL: "", // SSE 模式下的 URL
Args: []string{"--verbose"},
},
传输协议
| 传输 | 说明 | 适用场景 |
|---|---|---|
stdio | 标准输入输出通信 | 本地桌面应用(wesclaw/wescode) |
sse | HTTP SSE 通信 | 远程桌面(teleclaw 员工 PC) |
消费方实现示例
以 wesclaw 为例:
// apps/desktop/cmd/wesclaw/deviceagent.go
func deviceAgentServerConfig() mcp.ServerConfig {
binary := findDeviceAgentBinary()
return mcp.ServerConfig{
Name: "wesclaw-device",
Command: binary,
Transport: "stdio",
}
}
// Boot 时注册
cell.MCPs().Upsert(ctx, deviceAgentServerConfig())
提供的工具(由消费方定义)
desktop_click(app, target) -- 点击 UI 元素
desktop_screenshot() -- 截屏
desktop_clipboard_read() -- 读取剪贴板
desktop_clipboard_write(text) -- 写入剪贴板
desktop_open_app(bundleID) -- 打开应用
desktop_notify(title, body) -- 系统通知
状态查询
GET /cells/{id}/desktop/status
STT 语音转写
语音转写适配器,使用 Whisper 模型将音频转为文本。
启用
STT: &wesgine.STTConfig{
Model: "whisper-large-v3",
Language: "zh",
Device: "cpu", // "cpu" | "cuda"
},
工作流程
音频文件 → ffmpeg 预处理 → Whisper 转写 → 文本结果
注意事项
- STT 适配器故意脱离请求 ctx(转写需要活过请求生命周期)
- 因此必须自己设置
SysProcAttr建立进程组(INV-EXEC-PROC-01 豁免) - ffmpeg 是外部依赖,需要预装
Plugin Runtime
JSON-RPC 子进程 Plugin 管理,通过 CellSpec.Plugins 声明。
目录结构
/opt/plugins/my-plugin/
├── manifest.yaml # 插件声明(名称、版本、工具列表)
├── bin/
│ └── server # 可执行文件
└── README.md
manifest.yaml
name: my-plugin
version: 1.0.0
description: 自定义工具插件
tools:
- name: my_custom_tool
description: 执行自定义操作
parameters:
type: object
properties:
input:
type: string
description: 输入数据
生命周期
Cell Boot
→ Manager 扫描 Plugin 目录
→ 启动子进程(JSON-RPC over stdio)
→ 桥接 Tools 到 Cell ToolRegistry
Cell Drain
→ Shutdown 全部子进程
配额与安全
MaxPlugins限制同时运行的 Plugin 数量PluginPolicy支持 ed25519 签名验证- 超出
MaxPlugins时多余目录被截断(boot 期)
配置
Plugins: []string{
"/opt/plugins/my-plugin",
"/opt/plugins/another-plugin",
},
开发新适配器
步骤
- 在
adapter/<name>/下创建实现 - 在 CellSpec 添加对应配置字段
- 在 Cell boot 流程中注册适配器
- 适配器通过 Cell Handle 暴露管理 API
模板
// adapter/mydevice/adapter.go
package mydevice
type Config struct {
Enabled bool
Address string
}
type Adapter struct {
cfg Config
cell *wesgine.Cell
cancel context.CancelFunc
}
func New(cell *wesgine.Cell, cfg Config) *Adapter {
return &Adapter{cfg: cfg, cell: cell}
}
func (a *Adapter) Start(ctx context.Context) error {
ctx, a.cancel = context.WithCancel(ctx)
// 启动适配器
return nil
}
func (a *Adapter) Stop() error {
a.cancel()
return nil
}
func (a *Adapter) Tools() []tool.Tool {
return []tool.Tool{
// 注册工具
}
}
关键约束
- 不加 engine 级
AttachXxx()——适配器是 per-Cell 的 - 使用
proc.Command创建子进程(INV-EXEC-PROC-01) - 通过
QuotaFn向 Cell 查询配额
最佳实践
- 优先复用 wesgine 标准 adapter——避免重复实现 MCP 生命周期
- HeadlessBrowser 用于匿名抓取,Playwright 用于登录态——不要混淆
- Desktop Agent 是客户端二进制——wesgine 只提供 adapter 代码
- Plugin 必须有 manifest.yaml——否则跳过不加载
- STT 的 ffmpeg 必须预装——引擎不会自动安装依赖