Skip to content

文档同步

本页由 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
  • 支持多客户端同时连接
  • 无需第三方平台账号,即开即用
  • 适合本地开发和插件调试

安装 ​

bash
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,无需额外账号配置。

yaml
configVersion: 2
# zhin.config.yml(Plugin Runtime)
plugins:
  sandbox: "@zhin.js/adapter-sandbox"
sandbox:
  endpoints: []

可选:若需在启动时即在 bot 列表显示固定名称的离线占位 bot,可显式配置:

yaml
configVersion: 2
plugins:
  sandbox: "@zhin.js/adapter-sandbox"
sandbox:
  endpoints:
    - id: sandbox-bot
      owner: sandbox-user

使用方式 ​

  1. 启动 Zhin 实例:pnpm dev(终端会打印 Host 地址,一般为 http://127.0.0.1:8086)
  2. 打开 Remote Console,API Base 与 Host 地址一致,Token 与 http.token / HTTP_TOKEN 一致
  3. 在 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 断开,客户端重连即可。

客户端 → 服务端:发送消息 ​

json
{
  "type": "group",
  "id": "test-group",
  "content": [{ "type": "text", "data": { "text": "/hello" } }],
  "messageId": "client-msg-001",
  "timestamp": 1700000000000
}
字段类型说明
typeprivate | group | channel | direct | guild会话类型,默认 private。direct 按 private 处理,guild 按 channel 处理
idstring会话 id,默认使用 endpoint 的 owner(默认 sandbox-user)
contentstring 或消息段数组消息段格式为 { "type": string, "data": object };字符串等同于一个 text 段
textstring可选。没有 content 时作为纯文本;content 里没有文本段时也用它作为消息文本
messageIdstring可选。客户端生成的消息 id,须匹配 ^[A-Za-z0-9._:-]{1,160}$,不匹配时忽略;填写后作为这条入站消息的 id,引用回复时可据此定位
timestampnumber可选。毫秒时间戳,默认取服务端收到的时间
agentRunobject可选。Agent 试验台运行配置,见下表
  • 帧内容不是 JSON 时,整段文本按一条纯文本消息处理。
  • 消息的发送者固定为 endpoint 的 owner。
  • 按钮回传:content 中的 { "type": "action", "data": { "id": "...", "payload": "..." } } 段会作为交互动作交给命令或 Agent;只填 id 或 payload 时,另一个取相同的值。消息里没有文本时,payload 作为消息文本。

agentRun 字段:

字段取值默认
workingDirectorystring,最长 4096 字符""
safetyModeread-only | workspace-write | danger-full-accessworkspace-write
approvalModeask | auto | bypassask
networkAccessbooleanfalse;safetyMode 为 danger-full-access 时恒为 true

无法识别的取值按默认值处理。

服务端 → 客户端 ​

连接就绪:每次连接成功后发送一次。

json
{
  "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 隔离环境的探测结果,仅供展示。

回复消息:

json
{
  "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 权限的连接发送消息后,服务端回复:

json
{
  "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