从零开始构建 Manus 系统: 01-Sandbox VNC

0 阅读9分钟

从零开始构建 Manus 系统: 01-Sandbox VNC

📍 导航指南

Manus中重要的组成部分就是沙盒的实现。根据你的背景,选择合适的阅读路径:


目录

第一部分:基础环境搭建 🌱

第二部分:VNC服务实现 📡

第三部分:现代化界面 🎨

第四部分:代码实现 🚀

附录


引言

随着 Agentic AI(代理式人工智能)的兴起,AI 自主操作计算机工具的能力成为了关键。然而,直接在用户主机上运行这些操作面临着安全风险和交互冲突的挑战。作为当前领先的高级 Agent 实现,Manus 通过将 AI 操作限制在安全的沙盒环境中完美解决了这一问题。

沙盒实现方案的核心价值在于:

  1. 安全隔离:通过容器化技术确保 AI 操作与宿主机完全隔离,保障数据与系统安全。
  2. 无感交互:独立桌面环境防止了 AI 与人类用户争夺鼠标键盘控制权,实现并行工作。
  3. 实时观测:基于 WebSocket 的 VNC 传输,让用户能实时“监工” AI 的操作过程。
  4. 现代体验:结合 Vue.js 与 noVNC,提供流畅、无插件的 Web 桌面访问体验。
  5. 一键部署:标准化的 docker-compose 编排,让复杂环境的搭建变得简单高效。

第一部分:基础环境搭建 🌱

第一阶段:Docker容器环境

背景:容器化是沙盒环境的基础,它提供了进程隔离和资源限制,让沙盒内的操作不会影响主机系统。

Ubuntu基础镜像与X11环境

这一阶段的核心是构建一个支持图形界面的 Ubuntu 容器环境。

核心逻辑

  • 选择 Ubuntu 22.04 LTS 作为基础镜像,确保长期支持
  • 安装 X11 虚拟显示相关的核心组件
  • 配置无交互式安装以适应自动化构建

Dockerfile 核心配置

FROM ubuntu:22.04

ENV DEBIAN_FRONTEND=noninteractive

# 安装基础依赖
RUN apt-get update && apt-get install -y \
    xvfb \
    x11vnc \
    fluxbox \
    supervisor \
    novnc \
    websockify \
    # ... 其他工具

关键技术点

  • DEBIAN_FRONTEND=noninteractive:避免安装过程中的交互式提示
  • 镜像大小优化:使用阿里云镜像源加速下载

Supervisor进程管理

在容器环境中,Supervisor 扮演着"进程守护者"的角色,确保所有服务稳定运行。

配置示例

[supervisord]
nodaemon=true

[program:xvfb]
command=/usr/bin/Xvfb :1 -screen 0 1280x800x24
autorestart=true

[program:fluxbox]
command=/usr/bin/fluxbox
environment=DISPLAY=":1"
autorestart=true

优势

  • 自动重启崩溃的服务
  • 统一日志管理
  • 进程依赖控制

第二阶段:虚拟桌面实现

Xvfb无头显示

Xvfb (X Virtual Framebuffer) 是实现无头桌面的核心组件。

工作原理

  • 创建虚拟的 X11 显示服务器 (:1)
  • 不需要物理显示设备
  • 支持标准 X11 协议

Fluxbox窗口管理器

作为轻量级窗口管理器,Fluxbox 提供了基本的桌面环境。

特点

  • 资源占用极低 (< 10MB 内存)
  • 响应速度快
  • 支持基本的窗口操作

第二部分:VNC服务实现 📡

第三阶段:VNC服务器配置

x11vnc高性能服务器

x11vnc 将 X11 显示转换为 VNC 协议。

核心配置

[program:x11vnc]
command=/usr/bin/x11vnc -display :1 -nopw -listen localhost -xkb -ncache 10 -ncache_cr -forever
autorestart=true

性能优化

  • -ncache 10:启用客户端缓存,减少带宽
  • -ncache_cr:压缩重复区域
  • -forever:持续运行

websockify WebSocket代理

websockify 实现 TCP 到 WebSocket 的协议转换。

命令配置

[program:websockify]
command=/usr/bin/websockify --web=/usr/share/novnc/ 6080 localhost:5900
autorestart=true

技术价值

  • 支持浏览器原生 WebSocket
  • 无需 Flash 或 Java 插件
  • 提供 noVNC Web 界面

第四阶段:网络通信层

端口映射与安全

Docker Compose 配置了安全的端口映射:

services:
  sandbox-os:
    ports:
      - "6080:6080"  # WebSocket/VNC Web
      - "5900:5900"  # VNC TCP (可选)

连接协议优化

WebSocket 优势

  • 全双工通信
  • 低延迟 (< 50ms)
  • 防火墙友好

第三部分:现代化界面 🎨

第五阶段:Vue.js前端开发

noVNC客户端集成

Vue.js 与 noVNC 的无缝集成:

import RFB from '@novnc/novnc/core/rfb'

const connect = () => {
  rfb.value = new RFB(screen.value, 'ws://localhost:6080/websockify')
  // 事件处理
}

实时状态管理

响应式状态跟踪连接状态:

const isConnected = ref(false)
const statusText = ref('Disconnected')

第六阶段:用户体验优化

响应式设计

CSS 实现自适应布局:

.screen-container {
  flex: 1;
  background: #000;
  display: flex;
  justify-content: center;
  align-items: center;
}

错误处理机制

完善的连接状态反馈和错误处理。


第四部分:系统集成与部署 🚀

第七阶段:沙盒实现

Dockerfile
FROM ubuntu:22.04

ENV DEBIAN_FRONTEND=noninteractive

# Install dependencies
RUN sed -i 's/ports.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \
    apt-get update && apt-get install -y \
    xvfb \
    x11vnc \
    fluxbox \
    supervisor \
    novnc \
    websockify \
    net-tools \
    python3-numpy \
    xterm \
    && rm -rf /var/lib/apt/lists/*

# Configuration
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf

# Environment variables
ENV DISPLAY=:1
ENV RESOLUTION=1280x800

# Expose ports
# 5900: VNC (TCP)
# 6080: noVNC (HTTP/WebSocket)
EXPOSE 5900 6080

CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]

supervisord.conf

核心配置

; Global supervisord configuration
[supervisord]
nodaemon=true
logfile=/var/log/supervisord.log
pidfile=/var/run/supervisord.pid

; Xvfb - Virtual framebuffer X server for headless display
[program:xvfb]
command=/usr/bin/Xvfb :1 -screen 0 1280x800x24
autorestart=true
priority=100

; Fluxbox - Lightweight window manager for the virtual display
[program:fluxbox]
command=/usr/bin/fluxbox
environment=DISPLAY=":1"
autorestart=true
priority=200

; Xterm - Terminal emulator running on the virtual display
[program:xterm]
command=/usr/bin/xterm -geometry 80x24+10+10 -ls -title "Sandbox Terminal"
environment=DISPLAY=":1"
autorestart=true
priority=300

; x11vnc - VNC server that shares the virtual X display
[program:x11vnc]
command=/usr/bin/x11vnc -display :1 -nopw -listen localhost -xkb -ncache 10 -ncache_cr -forever
autorestart=true
priority=400

; Websockify - WebSocket to TCP proxy for noVNC web client
[program:websockify]
command=/usr/bin/websockify --web=/usr/share/novnc/ 6080 localhost:5900
autorestart=true
priority=500

docker-compose配置

一键部署配置:

services:
  sandbox-os:
    build: ./docker
    ports:
      - "6080:6080"
      - "5900:5900"
    volumes:
      - ./shared:/root/shared

容器执行:

docker-compose up -d --build

前端代码
<template>
  <div class="container">
    <header>
      <h1>Sandbox VNC Viewer</h1>
      <div class="status" :class="statusClass">{{ statusText }}</div>
      <div class="controls">
        <button @click="connect" :disabled="isConnected">Connect</button>
        <button @click="disconnect" :disabled="!isConnected">Disconnect</button>
      </div>
    </header>
    <div ref="screen" class="screen-container">
      <!-- Canvas will be injected here by RFB -->
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import RFB from '@novnc/novnc/core/rfb'

const screen = ref(null)
const rfb = ref(null)
const isConnected = ref(false)
const statusText = ref('Disconnected')
const statusClass = ref('disconnected')

const VNC_URL = 'ws://localhost:6080/websockify'

const connect = () => {
  if (!screen.value) return

  statusText.value = 'Connecting...'
  statusClass.value = 'connecting'

  try {
    rfb.value = new RFB(screen.value, VNC_URL)

    rfb.value.addEventListener('connect', () => {
      isConnected.value = true
      statusText.value = 'Connected'
      statusClass.value = 'connected'
      rfb.value.focus()
    })

    rfb.value.addEventListener('disconnect', (detail) => {
      isConnected.value = false
      statusText.value = 'Disconnected'
      statusClass.value = 'disconnected'
      if (detail.clean) {
        console.log('Clean disconnect')
      } else {
        console.error('Unexpected disconnect', detail)
        statusText.value = 'Connection Lost'
        statusClass.value = 'error'
      }
    })

    rfb.value.addEventListener('credentialsrequired', () => {
        // Handle password if needed
        // rfb.value.sendCredentials({ password: 'your-password' });
    })

  } catch (error) {
    console.error('Connection error:', error)
    statusText.value = 'Error'
    statusClass.value = 'error'
  }
}

const disconnect = () => {
  if (rfb.value) {
    rfb.value.disconnect()
  }
}

onMounted(() => {
  // Auto connect optional
  // connect()
})

onUnmounted(() => {
  disconnect()
})
</script>

<style scoped>
.container {
  display: flex;
  flex-direction: column;
  height: 100vh;
}

header {
  padding: 1rem;
  background: #1a1a1a;
  color: white;
  display: flex;
  align-items: center;
  gap: 20px;
}

h1 {
  margin: 0;
  font-size: 1.2rem;
}

.screen-container {
  flex: 1;
  background: #000;
  display: flex;
  justify-content: center;
  align-items: center;
  overflow: hidden;
}

.screen-container :deep(canvas) {
  box-shadow: 0 0 20px rgba(0,0,0,0.5);
}

.status {
  padding: 4px 8px;
  border-radius: 4px;
  font-size: 0.8rem;
}

.status.connected { background: #4caf50; }
.status.disconnected { background: #9e9e9e; }
.status.connecting { background: #ff9800; }
.status.error { background: #f44336; }

button {
  padding: 6px 12px;
  cursor: pointer;
  background: #333;
  color: white;
  border: 1px solid #555;
  border-radius: 4px;
}

button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
</style>

sandbox.png

常见问题 FAQ

Q: 为什么选择 noVNC 而不是其他解决方案? A: noVNC 是纯 JavaScript 实现,无需插件,在现代浏览器中表现优秀,且开源社区活跃。

Q: 如何处理高分辨率显示? A: 可以调整 Xvfb 参数,如 1280x800x24,支持更高分辨率,但需要考虑带宽和性能。

Q: 安全性如何保证? A: 默认配置为本地访问,如需公网部署,建议添加 VNC 密码、SSL 加密和防火墙规则。


📝 结语

至此,我们已经成功构建了一个安全、可视化的 Docker 沙盒环境。通过 Xvfb、x11vnc 和 noVNC 的组合,我们不仅实现了系统的隔离,还为用户提供了一个直观的观察窗口。

这就像是为未来的 AI 助手建造了一间功能完备的“工作室”。目前,这个工作室虽然安全且可视,但还需要赋予 AI 远程操控这里一切的“双手”。

在接下来的文章中,我们将开始构建 AI Agent 的核心能力。首先,我们将实现 Shell Agent,赋予 AI 在这个沙盒中执行系统命令的能力,让它真正“动”起来。敬请期待!