文档同步
本页由 plugins/adapters/sandbox/README.md 自动生成。请修改包内 README 后运行 pnpm sync:adapter-docs。
@zhin.js/adapter-sandbox
Zhin.js Sandbox 适配器,基于 WebSocket 的本地测试适配器;浏览器端聊天 UI 在 Remote Console(Host 仅 Console API)中打开 Sandbox 窗口调试。
功能特性
- Node Host:WebSocket
/sandbox - 浏览器端 React 聊天 UI
- 支持多客户端同时连接
- 无需第三方平台账号,即开即用
- 适合本地开发和插件调试
安装
pnpm add @zhin.js/adapter-sandbox前置条件
Sandbox 不需要外部账号。只需由 zhin runtime start 装配 HTTP Host,并确保浏览器能访问启动日志中的 Host 地址。
依赖
Plugin Runtime(新,zhin runtime start)
@zhin.js/adapter— 约定式adapters/sandbox/index.ts@zhin.js/host-http— Root 提供的httpHostToken(WebSocket/sandbox+ Console HTTP)@zhin.js/core—Endpoint.emit(...)入站、outboundMessageToken出站@zhin.js/page+pages/index/index.tsx— ADR 0046 约定页(definePage;路由/sandbox)
Root 在 zhin runtime start 时装载 @zhin.js/host-http、ConsoleRuntime 与 ClientBuildModuleRuntime。打开 http://<host>:<port>/console 可浏览页面;Sandbox 页 (路由 /sandbox,与 WebSocket /sandbox 同 path:GET 开页、Upgrade 走 WS)内置聊天壳。
旧 client/(register(api) / pageManager.addEntry)仅保留给 legacy Host 栈参考, 不是 Plugin Runtime 生产入口。
旧 Host 栈(已删除)
原 legacy 插件包 @zhin.js/host-router(HTTP 服务)与 @zhin.js/host-api(Host 侧 Console API,addEntry 注册 Sandbox 扩展)已删除;zhin dev 现由 @zhin.js/cli 自动装配 Console/HTTP Host(@zhin.js/host-http + @zhin.js/pagemanager),无需安装任何 Host 插件。
@zhin.js/client— Remote Console 客户端 SDK(UI 在 zhin-console 仓库)
出站 wire 只做 JSON 封装;旧 segment-mapper(canonical segments)归一化上移到 gateway/core 渲染链。
Sandbox 声明 html: direct,聊天页在隔离 iframe 中展示 canonical html,因此 JSX 卡片无需截图引擎即可预览。允许内联样式和 data/blob 图片;组件脚本、事件属性、外部资源、表单提交和父页面访问均被限制。高度按内容调整,超过 1200px 可在框内滚动。
配置
推荐(与 minimal-bot 一致):sandbox.endpoints: [],运行时提供默认 sandbox-bot endpoint,无需额外账号配置。
configVersion: 2
# zhin.config.yml(Plugin Runtime)
plugins:
sandbox: "@zhin.js/adapter-sandbox"
sandbox:
endpoints: []可选:若需在启动时即在 bot 列表显示固定名称的离线占位 bot,可显式配置:
configVersion: 2
plugins:
sandbox: "@zhin.js/adapter-sandbox"
sandbox:
endpoints:
- id: sandbox-bot
owner: sandbox-user使用方式
- 启动 Zhin 实例:
pnpm dev(终端会打印 Host 地址,一般为http://127.0.0.1:8086) - 打开 Remote Console,API Base 与 Host 地址一致,Token 与
http.token/HTTP_TOKEN一致 - 在 Console 沙盒 页连接后发送消息进行测试
浏览器客户端连接到默认 sandbox-bot,也可以通过多个显式 endpoint 使用不同 WebSocket 路径。
每个 endpoint 通过 Root 提供的 HTTP Host(httpHostToken)挂载一个 WebSocket 路径,协议见下文「消息格式(wire 协议)」。
消息格式(wire 协议)
Console 沙盒页和自己写的测试客户端都用这套协议。每个 WebSocket 文本帧是一个 UTF-8 JSON 对象。
连接
- 地址:
ws://<host>:<port>/sandbox。同一个 HTTP Host 上的第一个 Sandbox endpoint 挂在/sandbox,其余 endpoint 挂在/sandbox/<endpoint id>(重名时依次加-2、-3),启动日志会打印ws mounted <path>。 - 鉴权:Host 配置了 token 时,握手请求需带
Authorization: Bearer <token>或查询参数?token=<token>,否则返回401。demo 权限的 token 只能连/sandbox,而且是只读的:消息会被接收,但不会进入命令和 Agent,服务端回一条type: "error"。 - 单连接:endpoint 使用固定名称(默认),同一 endpoint 只保留最新的浏览器连接。新连接接入时,旧连接以关闭码
4000(replaced by new sandbox client)断开;endpoint 停止时以1001断开;插件热重载使当前 generation 退役时,Host 以1012断开,客户端重连即可。
客户端 → 服务端:发送消息
{
"type": "group",
"id": "test-group",
"content": [{ "type": "text", "data": { "text": "/hello" } }],
"messageId": "client-msg-001",
"timestamp": 1700000000000
}| 字段 | 类型 | 说明 |
|---|---|---|
type | private | group | channel | direct | guild | 会话类型,默认 private。direct 按 private 处理,guild 按 channel 处理 |
id | string | 会话 id,默认使用 endpoint 的 owner(默认 sandbox-user) |
content | string 或消息段数组 | 消息段格式为 { "type": string, "data": object };字符串等同于一个 text 段 |
text | string | 可选。没有 content 时作为纯文本;content 里没有文本段时也用它作为消息文本 |
messageId | string | 可选。客户端生成的消息 id,须匹配 ^[A-Za-z0-9._:-]{1,160}$,不匹配时忽略;填写后作为这条入站消息的 id,引用回复时可据此定位 |
timestamp | number | 可选。毫秒时间戳,默认取服务端收到的时间 |
agentRun | object | 可选。Agent 试验台运行配置,见下表 |
- 帧内容不是 JSON 时,整段文本按一条纯文本消息处理。
- 消息的发送者固定为 endpoint 的
owner。 - 按钮回传:
content中的{ "type": "action", "data": { "id": "...", "payload": "..." } }段会作为交互动作交给命令或 Agent;只填id或payload时,另一个取相同的值。消息里没有文本时,payload作为消息文本。
agentRun 字段:
| 字段 | 取值 | 默认 |
|---|---|---|
workingDirectory | string,最长 4096 字符 | "" |
safetyMode | read-only | workspace-write | danger-full-access | workspace-write |
approvalMode | ask | auto | bypass | ask |
networkAccess | boolean | false;safetyMode 为 danger-full-access 时恒为 true |
无法识别的取值按默认值处理。
服务端 → 客户端
连接就绪:每次连接成功后发送一次。
{
"type": "ready",
"id": "sandbox-user",
"endpoint": "sandbox-bot",
"workingDirectory": "/path/to/bot",
"canExecute": true,
"shellIsolation": { "available": false, "provider": "docker", "message": "Docker daemon is unavailable" },
"content": [{ "type": "text", "data": { "text": "已连接 Sandbox「sandbox-bot」…" } }],
"timestamp": 1700000000000
}canExecute 表示当前连接是否为 full 权限(能运行命令和 Agent 任务);shellIsolation 是 Docker 隔离环境的探测结果,仅供展示。
回复消息:
{
"type": "group",
"id": "test-group",
"bot": "sandbox-bot",
"endpoint": "sandbox-bot",
"content": [{ "type": "text", "data": { "text": "Hello!" } }],
"timestamp": 1700000000001
}type/id是回复所在的会话(direct已归一为private,guild已归一为channel),客户端按这两个字段把消息放进对应会话;bot是 endpoint id,endpoint是收到消息的连接名。content是消息段数组。文本、html(JSX 卡片,见上文html: direct)、按钮等段原样透传。image/audio/video/file段只读data.media(MediaRef):url原样透传;base64补上base64://前缀后内联;path由服务端读取文件后转成base64内联;其他类型和缺少data.media的媒体段会被丢弃,并在日志中记录sandbox_outbound_media_dropped。- 插件直接发送完整消息对象时,其他字段会原样保留。
只读连接的提示:demo 权限的连接发送消息后,服务端回复:
{
"type": "error",
"id": "sandbox-user",
"endpoint": "sandbox-bot",
"content": [{ "type": "text", "data": { "text": "当前连接是只读演示权限,不能运行 Agent 任务。" } }],
"timestamp": 1700000000000
}适用场景
- 本地开发调试插件逻辑
- 测试命令和 AI 工具调用
- 不依赖外部平台的功能验证
AI 工具
技能说明见 skills/sandbox/SKILL.md(本地沙箱调试约束)。
故障排查
| 现象 | 排查 |
|---|---|
| Console 无法连接 | 以启动日志中的 Host、端口和 token 为准 |
| Sandbox 页面空白 | 检查 HTTP Host 是否因端口占用软降级,以及鉴权/CORS 错误 |
| 刷新后没有历史消息 | 核对 Endpoint 与频道,并检查 history RPC 与 recovery gap 日志 |
| 命令或工具未生效 | 在运行时能力页确认它已进入当前 generation |
许可证
MIT License