AgentBay Linux 桌面迁移 FC Agent Sandbox Desktop 指南

更新时间:
复制 MD 格式

本文介绍如何将无影 AgentBay 迁移到 FC Agent Sandbox e2b-desktop,包括能力映射、Desktop 模板构建与 noVNC 集成。

能力映射说明

方法名与签名以 e2b-desktop SDK 发布为准。部分能力通过沙箱内 shell 命令补齐,在下文表格中标注为shell 对齐。

无影 AgentBay session.computer.*

e2b-desktop 对应

说明

鼠标

click_mouse(x, y)

left_click(x, y)

直接传坐标;move_mouse + left_click() 两步式亦可。

click_mouse(x, y, button="right")

right_click(x, y)

同上。

move_mouse(x, y)

move_mouse(x, y)

同名。

drag_mouse(x1, y1, x2, y2)

drag((x1, y1), (x2, y2))

签名不同,元组参数。

double_click()

double_click()

同名。

scroll(x, y, direction, amount)

move_mouse(x, y) + scroll(direction, amount)

无坐标参数,先移动再滚动。

get_cursor_position()

commands.run("xdotool getmouselocation")

shell 对齐。

键盘

input_text(text)

write(text)

方法名不同。

press_keys(keys)

press(key) / press(["ctrl", "c"])

单个键或组合键列表。

release_keys

commands.run("xdotool keyup <KEY>")

shell 对齐。

截图

screenshot()

screenshot()

同名,PNG 字节。

beta_take_screenshot(format)

commands.run("scrot -q 80 /tmp/s.jpg") + files.read(path, format="bytes")

shell 对齐(scrot 支持 jpg/png;jpg 必须显式按 bytes 读)。

屏幕信息

get_screen_size()

commands.run("xdpyinfo -display :0 | grep dimensions")

shell 对齐。

get_current_window_id()

get_current_window_id()

同名。

窗口管理

list_root_windows

commands.run('xdotool search --onlyvisible --name ""')

shell 对齐。

activate_window

commands.run("xdotool windowactivate --sync <WINDOW-ID>")

shell 对齐。

maximize_window

commands.run("xprop -id <WINDOW-ID> -f _NET_WM_STATE 32a -set _NET_WM_STATE _NET_WM_STATE_MAXIMIZED_VERT,_NET_WM_STATE_MAXIMIZED_HORZ")

shell 对齐。

minimize_window

commands.run("xdotool windowminimize <WINDOW-ID>")

shell 对齐。

resize_window

commands.run("xdotool windowsize --sync <WINDOW-ID> <WIDTH> <HEIGHT>")

shell 对齐(终端类应用按字符单元格取整)。

close_window

commands.run("xdotool windowclose <WINDOW-ID>")

shell 对齐。

focus_mode

commands.run("xfconf-query -c xfwm4 -p /general/click_to_focus -s ...")

shell 对齐。

应用管理

start_app(app, work_directory)

commands.run("cd <YOUR-WORK-DIR> && <YOUR-APP> &")

shell 对齐(launch(app[, url]) 亦可)。

stop_app

commands.run("pkill <YOUR-APP>")

shell 对齐。

get_installed_apps

commands.run("ls /usr/share/applications/")

shell 对齐。

list_visible_apps

commands.run('xdotool search --onlyvisible --name ""')

shell 对齐。

Agent(差异项)

execute_task

无

C-1:暂不支持。

get_task_status

无

C-2:暂不支持。

terminate_task

无

C-3:暂不支持。

桌面观察

get_link_url / VNC(Virtual Network Computing)

stream.start() + get_url()

内置鉴权,请参见VNC 实时流。

  • C-1~C-3(自然语言 Agent)暂无提供计划,不在 FC Agent Sandbox 产品设计范畴内;其余能力均可通过 SDK 或 shell 命令对齐。

  • 无需使用 Computer Use 的场景:仅操作网页时优先使用 Browser Use(CDP,Chrome DevTools Protocol);纯批处理场景使用 commands/code。

  • 以上映射已使用 e2b-desktop==2.4.1 + desktop:v0.0.44 模板逐项实跑验证通过(29/29)。

  • 模板已预装 xdotool / scrot / xdpyinfo / xprop / xwininfo / xfconf-query,未预装 wmctrl。窗口管理可使用上表中的 xdotool/xprop 等价命令;如需 wmctrl 语义,可执行 apt-get install -y wmctrl 安装,沙箱可访问 Debian 源。

  • WebSDK 集成章节的前端示例已在真实 Chrome 中验证(RFB 连接、画面渲染、桌面变化实时刷新均通过)。

使用限制

Linux 桌面(Computer Use)迁移采用 e2b-desktop SDK:

  • 鼠标 / 键盘 / 截图为已有的 SDK 方法(两步式);VNC 实时流内置且带鉴权。

  • 必须使用其 Sandbox 子类创建沙箱,沙箱生命周期语义与 e2b 一致(仍按 create/connect/list/kill 使用)。

  • 窗口 / 应用管理等超集能力不在本 SDK 范围,请参见能力映射说明。

内置模板映射

AgentBay

FC E2B

说明

linux_latest(Computer Use)

desktop 模板(内测镜像 fc-e2b-registry.cn-beijing.cr.aliyuncs.com/runtime/desktop:v0.0.44)

FC 镜像已配置桌面环境,通过 Template.build() 构建,请参见前置准备:Desktop 模板。

前置准备:Desktop 模板

Desktop 模板不是开通即用的内置模板,需从 FC Desktop 镜像构建。镜像已内置 Xvfb + xfce4 + x11vnc + websockify + Chrome,无需自行编写 Dockerfile。

设置环境变量

export E2B_API_KEY=<YOUR-API-KEY>
export E2B_API_URL=https://api.cn-beijing.e2b.fc.aliyuncs.com
export E2B_DOMAIN=cn-beijing.e2b.fc.aliyuncs.com

构建模板

  1. 安装依赖。

    pip install "e2b>=2.31.0" e2b-desktop==2.4.1 python-dotenv
  2. 将以下内容保存为 build.py。

    # build.py
    from dotenv import load_dotenv
    from e2b import Template, default_build_logger
    
    load_dotenv()
    
    # 这是当前的稳定版本,后续会更新
    FROM_IMAGE = "fc-e2b-registry.cn-beijing.cr.aliyuncs.com/runtime/desktop:v0.0.44"
    
    build = Template.build(
        Template().from_image(FROM_IMAGE),
        name="my-desktop-template",  # 这个可以任意设置
        cpu_count=4,
        memory_mb=8192,
        on_build_logs=default_build_logger(),
    )
    print(f"template_id: {build.template_id}")
  3. 执行构建。

    python build.py
  • 镜像仓库地域需与 E2B_API_URL / E2B_DOMAIN 一致。其他地域替换镜像地址中的 cn-beijing。

  • 推荐规格:4 vCPU、8192 MB 内存。模板无需配置启动命令或就绪命令,Desktop SDK 会自动启动桌面环境和实时流。

主要迁移示例

常见交互循环(截图+action)

# === 迁移前:AgentBay ===
session = agent_bay.create(CreateSessionParams(image_id="linux_latest")).session
session.computer.move_mouse(640, 400)
session.computer.click_mouse(640, 400)
session.computer.input_text("echo hello")
session.computer.press_keys(["enter"])
png = session.computer.screenshot()

# === 迁移后:e2b-desktop SDK ===
from e2b_desktop import Sandbox

desktop = Sandbox.create(template="my-desktop-template", timeout=600)
try:
    # 启动 Chrome 并等待渲染
    desktop.launch("google-chrome", "https://example.com")
    desktop.wait(10000)

    # 鼠标:E2B SDK 为两步式(move + click);拖拽用 drag 元组
    desktop.move_mouse(640, 400)
    desktop.left_click()
    desktop.double_click()
    desktop.drag((100, 100), (300, 300))
    desktop.scroll("down", 5)

    # 键盘:write 输入文本,press 单键或组合键
    desktop.launch("xfce4-terminal")
    desktop.wait(3000)
    desktop.write("echo hello")
    desktop.press("enter")
    desktop.press(["ctrl", "c"])

    # 截图(PNG 字节,交给视觉模型)
    image = desktop.screenshot()
    with open("desktop.png", "wb") as f:
        f.write(image)
finally:
    desktop.kill()

VNC 实时流(人工接管)

# 对应无影桌面场景的 link_url 观察方式;E2B SDK stream 内置鉴权
desktop.stream.start()
stream_url = desktop.stream.get_url()
# 将 stream_url 交给受控后端 / 浏览器 noVNC
desktop.stream.stop()

WebSDK 集成

浏览器端通过 noVNC(CDN JS)直连沙箱 VNC 实时流,无需控制面 SDK 或 npm 构建。沙箱生命周期由后端(Python)管理,前端仅负责桌面渲染。

后端:获取 VNC 流地址

# 后端 Python:创建沙箱并获取 noVNC 流地址
from e2b_desktop import Sandbox

desktop = Sandbox.create(template="my-desktop-template", timeout=600)
desktop.stream.start()
stream_url = desktop.stream.get_url()

# 若 FC E2B 侧对数据面要求 traffic access token,通过 query 参数携带

# token = getattr(desktop, "traffic_access_token", None)
# if token:
#     stream_url += f"&traffic_access_token={token}"

# 将 stream_url 返回给前端(如通过 API 响应或 WebSocket)

前端:HTML + CDN noVNC

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>FC Desktop</title>
  <style>
    #vnc-screen { width: 100vw; height: 100vh; }
  </style>
</head>
<body>
  <div id="vnc-screen"></div>

  <script type="module">
    // ESM 引入 noVNC 官方构建(固定版本)
    import RFB from 'https://cdn.jsdelivr.net/gh/novnc/noVNC@v1.6.0/core/rfb.js';

    async function initVNC() {
      // 从后端 API 获取 stream_url
      const resp = await fetch('/api/sandbox/vnc-url');
      const { streamUrl } = await resp.json();

      const rfb = new RFB(
        document.getElementById('vnc-screen'),
        streamUrl,           // https://{sandbox-host}/vnc.html?autoconnect=true&resize=scale
                             // 同一 URL 双重角色:浏览器直接打开是该 noVNC 页面文件;RFB 会把它升级为
                             // wss://{sandbox-host}/vnc.html 作为 WebSocket 端点(由 websockify 代理到 x11vnc)
        { credentials: { password: '' } }   // stream.start() 未开鉴权时为空
      );

      rfb.scaleViewport = true;
      rfb.resizeSession = false;
    }

    initVNC();
  </script>
</body>
</html>
  • 浏览器直连 wss://{sandbox-host}/vnc.html 无需自定义请求头(实测 GET 200、RFB 握手成功);VNC 鉴权由 stream.start(require_auth=True) + get_url(auth_key=...) 控制,开启后把 auth_key 一并返回前端并传入 credentials.password。

  • 数据面如需 traffic access token,FC 后端支持以 query 参数方式传递(由后端拼进 stream_url 返回,请参见后端:获取 VNC 流地址),浏览器 WebSocket 无需自定义头,也无需中继服务;noVNC 对额外的 query 参数透明(已随 password= 参数实测)。

  • 前端无需自研 Web SDK:noVNC(CDN ESM)即完整数据面客户端,连接参数全部在 URL 中。

Context 存储

FC Agent Sandbox 以组合式能力提供存储服务,可通过 AgentFS 或 OSS 实现数据持久化和共享:

  1. 动态挂载 OSS

  2. 挂载 AgenticFS Volume

附录:noVNC 集成 demo 示例

以下示例提供一个可本地运行的 noVNC 集成 demo,包含三个文件:后端 demo.py、前端 index.html 和凭证配置 .env。沙箱生命周期与凭证由后端管理,前端仅负责桌面渲染。

示例文件

demo.py(后端:沙箱生命周期 + HTTP 服务)

"""Linux Desktop + noVNC WebSDK 集成 demo(后端)。

配套三文件:demo.py(本文件)、index.html(前端)、.env(凭证)。

依赖:pip install "e2b>=2.31.0" e2b-desktop==2.4.1 python-dotenv
运行:python demo.py   (浏览器打开打印的 http://127.0.0.1:<port>/ 即可看到实时桌面)
"""
from __future__ import annotations

import json
import os
import signal
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path

from dotenv import load_dotenv
from e2b import Template, default_build_logger
from e2b_desktop import Sandbox

BASE_DIR = Path(__file__).resolve().parent
load_dotenv(BASE_DIR / ".env")

TEMPLATE = os.environ["E2B_DESKTOP_TEMPLATE"]
IMAGE = os.environ.get("E2B_DESKTOP_IMAGE", "")
HOST = "127.0.0.1"


def ensure_template() -> str:
    if Template.exists(TEMPLATE):
        print(f"[demo] 复用模板: {TEMPLATE}", flush=True)
        return TEMPLATE
    if not IMAGE:
        raise SystemExit(
            f"[demo] 模板 {TEMPLATE!r} 不存在,请在 .env 配置 E2B_DESKTOP_IMAGE 以自动构建"
        )
    print(f"[demo] 从镜像构建模板 {TEMPLATE!r}: {IMAGE}", flush=True)
    build = Template.build(
        Template().from_image(IMAGE),
        name=TEMPLATE,
        cpu_count=4,
        memory_mb=8192,
        on_build_logs=default_build_logger(),
    )
    return build.template_id


class Handler(BaseHTTPRequestHandler):
    stream_url: str = ""

    def do_GET(self) -> None:
        if self.path == "/api/sandbox/vnc-url":
            body = json.dumps({"streamUrl": Handler.stream_url}).encode("utf-8")
            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Content-Length", str(len(body)))
            self.end_headers()
            self.wfile.write(body)
            return
        if self.path in ("/", "/index.html"):
            body = (BASE_DIR / "index.html").read_bytes()
            self.send_response(200)
            self.send_header("Content-Type", "text/html; charset=utf-8")
            self.send_header("Content-Length", str(len(body)))
            self.end_headers()
            self.wfile.write(body)
            return
        if self.path == "/favicon.ico":
            self.send_response(204)
            self.end_headers()
            return
        self.send_error(404)

    def log_message(self, *args) -> None:
        pass  # 不输出访问日志,避免任何 URL(含 query 参数)落盘


def _stop(_signum, _frame) -> None:
    raise KeyboardInterrupt


def main() -> None:
    signal.signal(signal.SIGTERM, _stop)

    template = ensure_template()
    print("[demo] 创建桌面沙箱(Xvfb + xfce4 + x11vnc + noVNC)...", flush=True)
    desktop = Sandbox.create(template=template, timeout=600, secure=True)
    server: ThreadingHTTPServer | None = None
    try:
        desktop.stream.start()
        stream_url = desktop.stream.get_url()
        # 若数据面要求 traffic access token,FC 后端支持以 query 参数方式传递,在此拼接:
        # token = getattr(desktop, "traffic_access_token", None)
        # if token:
        #     stream_url += f"&traffic_access_token={token}"

        Handler.stream_url = stream_url
        server = ThreadingHTTPServer((HOST, 0), Handler)
        print(f"[demo] 浏览器打开: http://127.0.0.1:{server.server_port}/", flush=True)
        print("[demo] Ctrl-C 退出(自动停止流并销毁沙箱)", flush=True)
        server.serve_forever()
    except KeyboardInterrupt:
        print("\n[demo] 退出中...", flush=True)
    finally:
        if server is not None:
            server.server_close()
        try:
            desktop.stream.stop()
        except Exception:
            pass
        desktop.kill()
        print("[demo] 已停止 VNC 流并销毁沙箱", flush=True)


if __name__ == "__main__":
    main()

index.html(前端:noVNC CDN ESM 直连渲染)

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>FC Linux Desktop (noVNC)</title>
  <style>
    html, body { margin: 0; height: 100%; }
    #vnc-screen { width: 100vw; height: 100vh; }
  </style>
</head>
<body>
  <div id="vnc-screen"></div>

  <script type="module">
    // ESM 引入 noVNC 官方构建(固定版本;npm 包 lib/rfb.js 为 CJS,不能用 <script src> 直载)
    import RFB from 'https://cdn.jsdelivr.net/gh/novnc/noVNC@v1.6.0/core/rfb.js';

    async function initVNC() {
      // 从后端 API 获取 stream_url(沙箱生命周期与凭证都在后端,前端不接触)
      const resp = await fetch('/api/sandbox/vnc-url');
      const { streamUrl } = await resp.json();

      const rfb = new RFB(
        document.getElementById('vnc-screen'),
        // https://{sandbox-host}/vnc.html?... —— 同一 URL 双重角色:-
        // 浏览器直接打开是该 noVNC 页面文件;RFB 会升级为 wss://.../vnc.html 作为 WebSocket 端点
        streamUrl,
        { credentials: { password: '' } }  // stream.start() 未开鉴权时为空
      );

      rfb.scaleViewport = true;
      rfb.resizeSession = false;
    }

    initVNC();
  </script>
</body>
</html>
警告

.env 文件填入真实值后即含密钥,请勿提交代码库、分享或粘贴至聊天和工单。

.env(凭证配置,5 个键)

# Linux Desktop + noVNC demo 配置(填入真实值后本文件即含密钥:勿提交版本库、勿分享、勿粘贴进聊天/工单)
E2B_API_KEY=
E2B_API_URL=https://api.cn-beijing.e2b.fc.aliyuncs.com
E2B_DOMAIN=cn-beijing.e2b.fc.aliyuncs.com

# 桌面模板名:已存在则复用;不存在时用 E2B_DESKTOP_IMAGE 自动构建一次
E2B_DESKTOP_TEMPLATE=my-desktop-template
E2B_DESKTOP_IMAGE=fc-e2b-registry.cn-beijing.cr.aliyuncs.com/runtime/desktop:v0.0.44

数据流

浏览器 (index.html, noVNC CDN ESM)
  │  GET /api/sandbox/vnc-url ──→ demo.py(仅返回 streamUrl)
  │  RFB 直连 wss://{sandbox-host}/vnc.html ──→ websockify ──→ x11vnc ──→ Xvfb 桌面
  └─ 渲染 + 键鼠输入(数据面不经后端)

运行方法

  1. 安装依赖。

    pip install "e2b>=2.31.0" e2b-desktop==2.4.1 python-dotenv
  2. 在 .env 中填入 E2B_API_KEY 等凭证的真实值。

  3. 运行 demo。

    python demo.py
  4. 在浏览器打开终端输出的服务地址(形如 http://127.0.0.1:<RANDOM-PORT>/),即可查看并操作实时桌面。

  5. 按下 Ctrl-C 退出,程序自动停止 VNC 流并销毁沙箱。

验证记录

  • 后端 API:/api/sandbox/vnc-url 返回以 https:// 开头且含 /vnc.html 的 streamUrl;/ 正常返回页面。

  • 退出:SIGTERM/Ctrl-C 后自动停止 VNC 流并销毁沙箱。

  • 前端:已在真实 Chrome 中验证 RFB 连接、画面渲染和桌面变化实时刷新。

相关文档

  1. wuying-agentbay-sdk

  2. noVNC