**Computer** 是 Claude Code 运行的执行环境,是一个统一抽象,可以是容器(Box)或宿主机(Host)。
```
Computer
├── Box —— Tai 节点上的 Docker/Kubernetes 容器
└── Host —— 裸机,无容器
```
框架根据 `sandbox.yao` 自动选择并管理 Computer。你的 Hook 通过 `ctx.computer` 与它交互。
## Computer 的创建过程
当请求到来时:
1. 框架读取 `sandbox.yao`
2. 找到符合 `filter` 约束的可用 **Tai 节点**
3. 根据 `lifecycle`,创建新容器或复用已有容器
4. 容器以配置的镜像启动,挂载 workspace 卷,运行 `entrypoint.sh`
容器内的 `entrypoint.sh` 自动启动:
- `tai relay` —— 与 Yao 的 gRPC 桥接
- `tai a2o` —— Claude 模型调用的 OpenAI 兼容代理
- VNC 栈(若 `computer.vnc` 已启用):Xvfb、窗口管理器、x11vnc、websockify
无需手动配置。这些都在容器镜像启动时自动完成。
## `ctx.computer` API
`ctx.computer` 在 sandbox 配置生效时,于 Create Hook 和 Next Hook 中均可使用。
### `ctx.computer.id`
Computer ID —— 容器对应 Box ID,宿主机对应 Node ID。
```typescript
const id = ctx.computer.id;
```
### `ctx.computer.Exec(cmd)`
在容器内执行命令。返回 `{ stdout, stderr, exit_code }`。
```typescript
// 字符串形式(按空白符分割)
const result = ctx.computer.Exec("git status");
// 数组形式(精确参数,不进行 shell 展开)
const result = ctx.computer.Exec(["git", "log", "--oneline", "-10"]);
if (result.exit_code !== 0) {
ctx.Send(`命令失败:${result.stderr}`);
}
ctx.Send(result.stdout);
```
### `ctx.computer.VNC()`
返回该容器的 VNC WebSocket URL。若节点上 VNC 不可用则抛出异常。
仅在 `sandbox.yao` 中启用了 `computer.vnc` 且镜像支持 VNC 时才调用。
```typescript
const vncUrl = ctx.computer.VNC();
// 例:"ws://tai-node:16080/vnc/container-id"
ctx.Send({
type: "action",
props: {
name: "navigate",
payload: { route: `/sandbox/vnc?url=${encodeURIComponent(vncUrl)}` },
},
});
```
### `ctx.computer.Proxy(port, path?)`
返回访问容器内服务的代理 URL。
```typescript
// 访问 3000 端口的开发服务器
const devUrl = ctx.computer.Proxy(3000);
// 访问特定路径
const apiUrl = ctx.computer.Proxy(8080, "/api/health");
ctx.Send(`预览地址:${devUrl}`);
```
在 `prepare` exec 步骤启动后台服务、或 Claude Code 启动开发服务器后使用。
### `ctx.computer.Info()`
返回 Computer 的元数据。
```typescript
const info = ctx.computer.Info();
// {
// kind: "box",
// node_id: "node-abc",
// tai_id: "tai-xyz",
// status: "online",
// system: { os: "linux", arch: "amd64", hostname: "...", num_cpu: 4, shell: "/bin/sh" },
// box_id: "box-123", // 仅 box
// container_id: "sha256...", // 仅 box
// image: "yaoapp/tai-sandbox-claude:latest", // 仅 box
// policy: "session" // 仅 box
// }
```
---
## VNC 与桌面环境
启用 `computer.vnc` 后,容器运行完整的 Linux 桌面,可通过 VNC 或浏览器版 noVNC 客户端访问。
### 启用 VNC
```json
{
"computer": {
"image": "yaoapp/tai-sandbox-claude-desktop-lite:latest",
"vnc": {
"enabled": true,
"resolution": "1920x1080",
"password": "$ENV.VNC_PASSWORD"
}
}
}
```
简写 —— `"vnc": true` 等同于 `"vnc": { "enabled": true }`。
### 镜像选择
| 镜像 | 桌面 | Chromium | 适用场景 |
|------|------|----------|---------|
| `yaoapp/tai-sandbox-claude:latest` | 无 | 无 | 代码生成、文件操作,无 GUI 需求 |
| `yaoapp/tai-sandbox-claude-desktop-lite:latest` | XFCE(精简版) | 有 | Web 自动化、截图任务、轻量 GUI |
| `yaoapp/tai-sandbox-claude-desktop:latest` | XFCE4(完整版)+ CJK 字体 | 有 | 重度 GUI 任务、视觉验证、中日韩内容 |
### 在 Hook 中访问桌面
```typescript
export function Create(
ctx: agent.Context,
messages: agent.Message[]
): agent.Create {
// VNC() 若 VNC 不可用会抛出异常 —— 仅在 sandbox.yao 中启用 vnc 时调用
const vncUrl = ctx.computer.VNC();
ctx.Send({
type: "action",
props: {
name: "navigate",
payload: { route: `/sandbox/vnc?url=${encodeURIComponent(vncUrl)}` },
},
});
return { messages };
}
```
---
## 端口代理
若 Claude Code 或 prepare 步骤在容器内启动了服务,通过 `ctx.computer.Proxy` 访问:
```json
{
"computer": {
"ports": [3000]
},
"prepare": [
{
"action": "exec",
"cmd": "npm run dev",
"background": true
}
]
}
```
```typescript
// Next Hook —— Claude Code 执行完毕后
const previewUrl = ctx.computer.Proxy(3000);
ctx.Send(`应用运行于:${previewUrl}`);
```
---
## Workspace 卷
每个 Computer 都在 `work_dir`(默认 `/workspace`)处挂载了 workspace 卷。Claude Code 在此读写文件。`ctx.workspace` API 让 Hook 访问同一卷。
```
容器(/workspace) ←→ Tai 卷 ←→ ctx.workspace(Hook 中)
```
Claude Code 在 `/workspace` 中写入的文件,可在 Next Hook 中通过 `ctx.workspace.ReadFile(...)` 立即读取。
---
## 下一步
你的 Agent 已能运行代码并访问桌面。现在添加可复用能力包。
→ **[Skills](./skills)**