50 KiB
Computer Agent Runner 详细实施计划
文档版本: v1.0
创建日期: 2025-12-10
作者: Claude (基于需求设计文档)
项目: rcoder - AI 驱动开发平台
基准文档: specs/computer-agent-runner/0001-spec-claude.md
一、项目概述与目标
1.1 功能简介
Computer Agent Runner 是 rcoder 项目的扩展功能模块,旨在为 AI Agent 提供完整的虚拟桌面环境,使其能够:
- 操作浏览器: 在虚拟桌面中打开 Chromium,自主搜索和访问网络资料
- 远程监控: 用户可通过 VNC 远程查看 Agent 的操作过程
- 复杂任务处理: Agent 在容器内完成复杂的多步骤任务(如网页抓取、数据处理等)
- 资源共享: 一个用户可以有多个项目,共享同一个桌面环境容器
1.2 核心特点
| 特点 | 说明 |
|---|---|
| 用户级容器 | 一个 user_id 对应一个带桌面环境的容器 |
| 多 Agent 实例 | 容器内可同时运行多个 project_id 对应的 AI Agent 实例 |
| 统一架构 | 使用 ContainerKey 和 UnifiedContainerInfo 统一管理两种模式 |
| VNC 访问 | 提供 VNC 远程桌面访问,用户可实时查看 Agent 操作 |
| 浏览器操作 | 集成 Chrome DevTools MCP,赋予 Agent 浏览器操作能力 |
| 智能清理 | 只有当用户下所有项目都闲置时才销毁容器 |
1.3 与现有 RCoder 的对比
| 维度 | RCoder (现有) | Computer Agent Runner (新) |
|---|---|---|
| 容器标识 | project_id |
user_id |
| 容器命名 | rcoder-agent-{project_id} |
computer-agent-runner-{user_id} |
| Agent 实例数 | 1 个 | 多个(按 project_id 区分) |
| 工作目录 | /app/project_workspace/{project_id} |
/app/computer-project-workspace/{user_id} |
| 桌面环境 | 无 | XFCE4 + noVNC |
| 浏览器 | 无 | Chromium + CDP |
| 闲置策略 | project_id 闲置即销毁 | user_id 下所有 project_id 都闲置才销毁 |
二、实施准备
2.1 环境准备
开发环境配置
- Rust: 1.75+ (2024 Edition)
- Docker: 支持 BuildKit 和多架构构建
- Docker Compose: v2.0+
- 开发工具: cargo, rustfmt, clippy
Docker 镜像准备
- 确认
docker/rcoder-agent-runner/Dockerfile包含:- XFCE4 桌面环境
- noVNC 服务(端口 6080)
- Chromium 浏览器(CDP 端口 9222)
- 所有必要的系统依赖
依赖库版本确认
[dependencies]
tokio = { version = "1.35", features = ["full"] }
tonic = "0.14.2"
agent-client-protocol = "0.6"
dashmap = "5.5"
chrono = { version = "0.4", features = ["serde"] }
2.2 技术栈确认
| 技术 | 版本/组件 | 用途 |
|---|---|---|
| Rust | 1.75+ | 核心开发语言 |
| gRPC | Tonic 0.14.2 | 内部通信协议 |
| ACP | 0.6 / 0.4 | Agent 客户端协议 |
| Docker | 最新稳定版 | 容器化部署 |
| VNC/noVNC | 标准协议 | 远程桌面访问 |
| Chromium | Debian 12 stable | 浏览器环境 |
| XFCE4 | Debian 12 stable | 桌面环境 |
三、详细实施步骤
阶段 1:核心数据结构实现(1-2天,P0)
目标
实现统一架构的核心数据模型,使用 ContainerKey 枚举和 UnifiedContainerInfo 结构统一管理 RCoder 和 ComputerAgentRunner 两种模式的容器。
步骤 1.1:创建 ContainerKey 枚举
文件: crates/shared_types/src/model/computer_agent_model.rs
任务清单:
- 定义
ContainerKey枚举,包含Project(String)和User(String)两种变体 - 实现
as_str()方法:获取容器标识符的字符串形式 - 实现
service_type()方法:返回对应的ServiceType - 实现
from_project()和from_user()构造方法 - 实现
Displaytrait,格式为"project:{id}"或"user:{id}" - 实现
Hash,Eq,PartialEqtrait(用于 DashMap) - 实现
Serialize,Deserializetrait(用于持久化)
关键代码模式:
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub enum ContainerKey {
/// RCoder 模式:一个 project_id 对应一个容器
Project(String),
/// ComputerAgentRunner 模式:一个 user_id 对应一个容器
User(String),
}
impl ContainerKey {
pub fn as_str(&self) -> &str {
match self {
ContainerKey::Project(id) => id,
ContainerKey::User(id) => id,
}
}
pub fn service_type(&self) -> ServiceType {
match self {
ContainerKey::Project(_) => ServiceType::RCoder,
ContainerKey::User(_) => ServiceType::ComputerAgentRunner,
}
}
}
步骤 1.2:实现 UnifiedContainerInfo 结构
任务清单:
- 定义
UnifiedContainerInfo结构体,合并 RCoder 和 ComputerAgentRunner 两种模式 - 实现
new_rcoder()构造方法(RCoder 模式) - 实现
new_computer()构造方法(ComputerAgentRunner 模式) - 实现
update_activity()方法:更新活动时间 - 实现 ComputerAgentRunner 专用方法:
upsert_project(): 添加或更新项目get_project(): 获取项目remove_project(): 移除项目list_projects(): 列出所有项目 ID
- 实现
is_fully_idle()方法:统一的闲置判断逻辑 - 实现
container_ip()和container_id()便捷方法
关键代码模式:
#[derive(Debug, Clone)]
pub struct UnifiedContainerInfo {
pub key: ContainerKey,
pub container: ContainerBasicInfo,
pub service_type: ServiceType,
pub created_at: DateTime<Utc>,
pub last_activity: DateTime<Utc>,
// RCoder 模式字段
pub session_id: Option<String>,
pub status: Option<AgentStatus>,
pub model_provider: Option<ModelProviderConfig>,
// ComputerAgentRunner 模式字段
pub projects: Option<Arc<DashMap<String, Arc<ProjectInfo>>>>,
}
impl UnifiedContainerInfo {
pub fn is_fully_idle(&self, idle_timeout: Duration) -> bool {
let now = Utc::now();
let idle_duration = now - self.last_activity;
let is_timeout = idle_duration >
chrono::Duration::from_std(idle_timeout).unwrap_or_default();
match self.service_type {
ServiceType::RCoder => {
let is_idle_status = matches!(
self.status,
Some(AgentStatus::Idle) | None
);
is_idle_status && is_timeout
}
ServiceType::ComputerAgentRunner => {
if let Some(projects) = &self.projects {
if projects.is_empty() { return true; }
projects.iter().all(|entry| {
let project = entry.value();
let project_idle_duration = now - project.last_activity;
let project_is_timeout = project_idle_duration >
chrono::Duration::from_std(idle_timeout).unwrap_or_default();
let is_idle_status = matches!(
project.status,
Some(AgentStatus::Idle) | None
);
is_idle_status && project_is_timeout
})
} else {
true
}
}
}
}
}
步骤 1.3:实现 ProjectInfo 结构
任务清单:
- 定义
ProjectInfo结构体(简化版项目元数据) - 实现
new()构造方法 - 实现
update_activity()方法 - 实现
update_session()方法 - 实现
update_status()方法
结构定义:
#[derive(Debug, Clone)]
pub struct ProjectInfo {
pub project_id: String,
pub session_id: Option<String>,
pub status: Option<AgentStatus>,
pub model_provider: Option<ModelProviderConfig>,
pub created_at: DateTime<Utc>,
pub last_activity: DateTime<Utc>,
}
步骤 1.4:实现 SessionInfo 结构
任务清单:
- 定义
SessionInfo结构体(统一会话信息) - 实现
new()构造方法 - 关联
ContainerKey和project_id
结构定义:
#[derive(Debug, Clone)]
pub struct SessionInfo {
pub session_id: String,
pub container_key: ContainerKey,
pub project_id: String,
pub created_at: DateTime<Utc>,
}
步骤 1.5:更新模块导出
文件: crates/shared_types/src/model/mod.rs
任务清单:
- 添加
mod computer_agent_model; - 添加
pub use computer_agent_model::*;
验收标准
- 所有结构体编译通过,无警告
is_fully_idle()单元测试通过(RCoder 和 ComputerAgentRunner 两种模式)ContainerKey可以正确序列化和反序列化- 所有方法都有完整的文档注释
- cargo clippy 无警告
阶段 2:容器管理服务实现(1天,P0)
目标
实现 ComputerContainerManager 服务,负责根据 user_id 获取或创建容器。
步骤 2.1:创建 ComputerContainerManager
文件: crates/rcoder/src/service/computer_container_manager.rs
任务清单:
- 定义
ComputerContainerManager结构体 - 实现
get_or_create_container_for_user()公开方法- 输入:
user_id: &str,resource_limits: Option<ServiceResourceLimits> - 输出:
Result<ContainerBasicInfo, AppError> - 逻辑:查询是否存在 → 存在则返回 → 不存在则创建
- 输入:
- 实现
create_container_for_user()私有方法- 准备用户级工作目录
- 解析宿主机路径
- 调用
DockerManager::start_agent_container()
- 实现
get_user_workspace()方法- 返回
/app/computer-project-workspace/{user_id}
- 返回
- 实现
create_user_workspace()方法- 创建用户工作区目录
- 确保目录权限正确
关键代码模式:
pub struct ComputerContainerManager;
impl ComputerContainerManager {
pub async fn get_or_create_container_for_user(
user_id: &str,
resource_limits: Option<ServiceResourceLimits>,
) -> Result<ContainerBasicInfo, AppError> {
let docker_manager = docker_manager::global::get_global_docker_manager()
.await
.map_err(|e| AppError::internal_server_error(&format!("获取 DockerManager 失败: {}", e)))?;
// 检查容器是否已存在
if let Ok(Some(info)) = docker_manager.get_agent_info(user_id).await {
return Ok(info);
}
// 创建新容器
Self::create_container_for_user(user_id, &docker_manager, resource_limits).await
}
async fn create_container_for_user(
user_id: &str,
docker_manager: &Arc<docker_manager::DockerManager>,
resource_limits: Option<ServiceResourceLimits>,
) -> Result<ContainerBasicInfo, AppError> {
// 准备工作目录
let user_workspace = Self::get_user_workspace(user_id).await?;
Self::create_user_workspace(user_id).await?;
// 解析宿主机路径
let host_path = resolve_container_path_to_host(&user_workspace).await?;
// 调用 DockerManager 启动容器
let container_info = docker_manager
.start_agent_container(
user_id,
&host_path.to_string_lossy(),
ServiceType::ComputerAgentRunner,
resource_limits,
)
.await?;
Ok(container_info)
}
}
步骤 2.2:集成 DockerManager
任务清单:
- 调用
docker_manager::global::get_global_docker_manager()获取全局实例 - 使用
get_agent_info()查询现有容器 - 使用
start_agent_container()创建新容器,传递:user_id作为容器标识host_path宿主机路径ServiceType::ComputerAgentRunner服务类型resource_limits资源限额配置
步骤 2.3:路径管理
任务清单:
- 工作区路径:
/app/computer-project-workspace/{user_id} - 使用
tokio::fs::create_dir_all()创建目录 - 使用
resolve_container_path_to_host()解析宿主机路径 - 确保目录权限为 777(容器内外都可访问)
步骤 2.4:更新服务模块导出
文件: crates/rcoder/src/service/mod.rs
任务清单:
- 添加
pub mod computer_container_manager; - 添加
pub use computer_container_manager::*;
验收标准
- 可以成功创建用户容器
- 容器命名规则正确:
computer-agent-runner-{user_id} - 工作区目录创建成功:
/app/computer-project-workspace/{user_id} - 容器 IP 地址可以正确获取
- 容器内外路径映射正确
- 集成测试通过(调用 API 创建容器)
阶段 3:HTTP 接口实现(1天,P0)
目标
实现 Computer Agent 的 HTTP 接口,包括聊天、停止 Agent 和进度流。
步骤 3.1:实现 computer_chat_handler
文件: crates/rcoder/src/handler/computer_chat_handler.rs
任务清单:
- 定义
ComputerChatRequest结构体user_id: String(必填)project_id: Option<String>(可选,自动生成)prompt: String- 其他字段与现有
ChatRequest保持一致
- 实现
handle_computer_chat()函数- 生成或使用提供的
project_id - 调用
ComputerContainerManager::get_or_create_container_for_user() - 获取或创建
UnifiedContainerInfo(ContainerKey::User) - 创建或更新
ProjectInfo - 通过 gRPC 转发请求到 agent_runner
- 更新会话映射
- 返回
ChatResponse
- 生成或使用提供的
- 实现
forward_computer_request_to_container()函数- 仅使用 gRPC 通信(不回退 HTTP)
- 调用
grpc_pool.get_or_create_channel() - 调用
ChatRPC
- 实现
computer_session_notification()SSE 处理器- 通过
sessions查找SessionInfo - 建立 SSE 连接到容器
- 实时推送进度事件
- 通过
请求处理流程:
POST /computer/chat
↓
1. 验证 user_id
2. 生成 project_id(若未提供)
3. get_or_create_container_for_user(user_id)
4. 获取或创建 UnifiedContainerInfo (ContainerKey::User)
5. 创建/更新 ProjectInfo
6. gRPC Chat RPC → agent_runner (带 project_id)
7. 更新会话映射 (session_id → SessionInfo)
8. 返回 ChatResponse
步骤 3.2:实现 computer_agent_stop_handler
文件: crates/rcoder/src/handler/computer_agent_stop_handler.rs
任务清单:
- 定义
ComputerAgentStopRequest结构体user_id: String(必填)project_id: String(必填)session_id: Option<String>(可选)
- 实现
computer_agent_stop()函数- 查找
containers[ContainerKey::User(user_id)] - 从
container_info.projects移除project_id - 通过 gRPC StopAgent RPC 停止 agent
- 清理会话映射
- 返回成功响应
- 查找
注意: 容器不会被销毁,继续运行其他 project_id。
步骤 3.3:添加路由
文件: crates/rcoder/src/router.rs
任务清单:
- 添加
/computer/chat路由:post(handler::handle_computer_chat) - 添加
/computer/agent/stop路由:post(handler::computer_agent_stop) - 添加
/computer/progress/{session_id}路由:get(handler::computer_session_notification)
步骤 3.4:重构 AppState 使用统一架构
文件: crates/rcoder/src/router.rs
任务清单:
- 重构
AppState结构体,从 6 个 DashMap 精简到 3 个:pub struct AppState { pub config: AppConfig, // 核心映射(统一管理) pub containers: DashMap<ContainerKey, Arc<UnifiedContainerInfo>>, pub sessions: DashMap<String, Arc<SessionInfo>>, pub project_to_container: DashMap<String, ContainerKey>, pub pingora_service: Option<Arc<pingora_proxy::PingoraProxyService>>, pub grpc_pool: Arc<crate::grpc::GrpcChannelPool>, } - 实现便捷方法:
get_container(&self, key: &ContainerKey): 获取容器信息get_container_by_project(&self, project_id: &str): 通过 project_id 获取容器get_session(&self, session_id: &str): 获取会话信息upsert_container(&self, key, info): 添加或更新容器add_session(&self, session_id, info): 添加会话remove_container(&self, key): 统一清理方法(清理所有相关映射)
关键代码模式:
impl AppState {
pub fn remove_container(&self, key: &ContainerKey) -> Option<Arc<UnifiedContainerInfo>> {
// 移除容器
let container = self.containers.remove(key).map(|(_, v)| v)?;
// 清理 project_to_container 索引
match key {
ContainerKey::Project(project_id) => {
self.project_to_container.remove(project_id);
}
ContainerKey::User(_) => {
if let Some(projects) = &container.projects {
for entry in projects.iter() {
self.project_to_container.remove(entry.key());
}
}
}
}
// 清理相关会话
let sessions_to_remove: Vec<String> = self.sessions
.iter()
.filter(|entry| &entry.value().container_key == key)
.map(|entry| entry.key().clone())
.collect();
for session_id in sessions_to_remove {
self.sessions.remove(&session_id);
}
Some(container)
}
}
步骤 3.5:更新处理器模块导出
文件: crates/rcoder/src/handler/mod.rs
任务清单:
- 添加
pub mod computer_chat_handler; - 添加
pub mod computer_agent_stop_handler; - 添加
pub use computer_chat_handler::*; - 添加
pub use computer_agent_stop_handler::*;
验收标准
POST /computer/chat接口可用,返回正确的ChatResponse- 自动创建
user_id对应的容器(首次请求) project_id自动生成(若未提供)- SSE 进度流正常工作(
GET /computer/progress/{session_id}) - 可以停止特定
project_id的 agent(POST /computer/agent/stop) - AppState 重构后所有现有接口仍然正常工作
- 集成测试通过(发送聊天请求 → 接收进度 → 停止 agent)
阶段 4:agent_runner 集成 agent_abstraction(2-3天,P0)
目标
复用现有的 agent_abstraction 模块,避免重复代码,实现多 Agent 实例管理。
步骤 4.1:扩展 gRPC Proto
文件: crates/shared_types/proto/agent.proto
任务清单:
- 添加
StopAgentRPC 定义service AgentService { // 现有方法 rpc Chat(GrpcChatRequest) returns (GrpcChatResponse); rpc SubscribeProgress(ProgressRequest) returns (stream ProgressEvent); rpc CancelSession(CancelRequest) returns (CancelResponse); rpc GetStatus(StatusRequest) returns (StatusResponse); // 新增:停止特定 project_id 的 agent rpc StopAgent(StopAgentRequest) returns (StopAgentResponse); } - 添加
StopAgentRequest消息定义message StopAgentRequest { string project_id = 1; } - 添加
StopAgentResponse消息定义message StopAgentResponse { bool success = 1; string message = 2; string project_id = 3; } - 运行
cargo build重新生成 Proto 代码
步骤 4.2:修改 agent_runner 主程序
文件: crates/agent_runner/src/main.rs
任务清单:
- 引入
agent_abstraction模块:use agent_abstraction::{ AcpSessionManager, AgentLifecycleManager, AcpAgentWorker, }; - 创建
AcpSessionManager实例(多会话管理) - 创建
AgentLifecycleManager实例(生命周期管理) - 创建
AcpAgentWorker实例(Worker 模式) - 在
LocalSet中初始化所有组件 - 将实例传递给 gRPC 服务实现
关键代码模式:
struct AgentRunnerState<N: SessionNotifier, C: Client + 'static> {
session_manager: Arc<AcpSessionManager<N, C>>,
lifecycle_manager: Arc<AgentLifecycleManager>,
worker: Arc<AcpAgentWorker<N, C>>,
}
impl<N: SessionNotifier, C: Client + 'static> AgentRunnerState<N, C> {
pub fn new(
notifier: Arc<N>,
lifecycle_manager: Arc<AgentLifecycleManager>,
) -> Self {
let session_manager = Arc::new(AcpSessionManager::new(notifier));
let worker = Arc::new(AcpAgentWorker::new(session_manager.clone()));
Self {
session_manager,
lifecycle_manager,
worker,
}
}
}
步骤 4.3:修改 gRPC 服务实现
文件: crates/agent_runner/src/grpc/agent_service_impl.rs
任务清单:
- 修改
ChatRPC 实现:- 调用
worker.process_request()处理请求 project_id自动路由到对应的 agent 实例
- 调用
- 实现
StopAgentRPC:- 从
session_manager移除会话 - 调用
lifecycle_manager.stop_agent(project_id) - 清理 Agent 资源
- 返回
StopAgentResponse
- 从
- 确保所有操作在
LocalSet中执行(ACP 协议要求)
关键代码模式:
#[tonic::async_trait]
impl<N: SessionNotifier, C: Client + 'static> AgentService for AgentServiceImpl<N, C> {
async fn chat(
&self,
request: Request<GrpcChatRequest>,
) -> Result<Response<GrpcChatResponse>, Status> {
let req = request.into_inner();
// 构建 WorkerRequest
let worker_request = WorkerRequest {
prompt_message: /* 转换 */,
model_provider: req.model_provider,
attachment_blocks: /* 转换 */,
};
// 调用 worker 处理(自动路由到 project_id)
let response = self.state.worker.process_request(worker_request)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(GrpcChatResponse {
session_id: response.session_id,
project_id: response.project_id,
// ...
}))
}
async fn stop_agent(
&self,
request: Request<StopAgentRequest>,
) -> Result<Response<StopAgentResponse>, Status> {
let req = request.into_inner();
// 从会话管理器移除
self.state.session_manager.remove_session(&req.project_id);
// 停止 agent 进程
self.state.lifecycle_manager.stop_agent(&req.project_id)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(StopAgentResponse {
success: true,
message: format!("Agent {} stopped", req.project_id),
project_id: req.project_id,
}))
}
}
步骤 4.4:测试多 Agent 实例
任务清单:
- 在同一容器内启动多个
project_id的 agent - 验证 Agent 之间互不干扰(独立的工作区和会话)
- 验证可以独立停止单个 Agent(容器继续运行)
- 验证内存和资源使用合理(无泄漏)
验收标准
- 同一容器内可以运行多个
project_id的 agent - Agent 之间无上下文污染(独立的
AcpSessionManager会话) - 可以按
project_id停止单个 agent - 容器不会因停止单个 agent 而销毁
StopAgentRPC 测试通过- 集成测试通过(多 agent 并发)
- 内存泄漏测试通过
阶段 5:闲置检测和清理优化(1天,P1)
目标
实现统一的闲置检测和清理逻辑,自动处理 RCoder 和 ComputerAgentRunner 两种模式。
步骤 5.1:修改清理任务
文件: crates/rcoder/src/proxy_agent/cleanup_task.rs
任务清单:
- 重构
cleanup_idle_agents()方法,使用统一逻辑:async fn cleanup_idle_agents(&mut self) -> Result<CleanupStats> { let mut stats = CleanupStats::default(); let current_time = Utc::now(); let mut containers_to_clean = Vec::new(); for entry in self.state.containers.iter() { let container_key = entry.key(); let container_info = entry.value(); // 容器保护期(创建后 5 分钟内不清理) let protection_time = chrono::Duration::minutes(5); if current_time - container_info.created_at < protection_time { continue; } // 使用统一的闲置判断方法 if container_info.is_fully_idle(self.config.idle_timeout) { containers_to_clean.push(container_key.clone()); } } // 执行清理 for container_key in containers_to_clean { match self.cleanup_container(&container_key).await { Ok(_) => stats.cleaned_count += 1, Err(e) => { stats.failed_count += 1; warn!("清理失败: {} - {}", container_key, e); } } } Ok(stats) } - 实现
cleanup_container()统一清理方法:- 获取容器信息
- 销毁 Docker 容器
- 清理 gRPC 连接池
- 调用
AppState::remove_container()清理所有映射
- 添加清理日志(记录闲置时长、项目数量等)
步骤 5.2:实现容器保护期
任务清单:
- 容器创建后 5 分钟内不进行清理
- 检查
container_info.created_at时间 - 记录保护期跳过的日志
步骤 5.3:实现孤立容器检测
任务清单:
- 列出所有
rcoder-agent-*和computer-agent-runner-*容器 - 与
AppState.containers对比,找出孤立容器 - 清理不在 AppState 中的容器
- 记录孤立容器清理日志
关键代码模式:
async fn cleanup_orphaned_containers(&mut self) -> u64 {
let mut cleaned_count = 0;
let docker_manager = /* 获取 DockerManager */;
// 收集所有应该存在的容器 ID
let expected_containers: HashSet<String> = self.state.containers
.iter()
.map(|entry| entry.value().container.container_id.clone())
.collect();
// 列出所有容器
let patterns = vec!["rcoder-agent-", "computer-agent-runner-"];
for pattern in patterns {
if let Ok(containers) = docker_manager.list_containers_by_pattern(pattern).await {
for container in containers {
if !expected_containers.contains(&container.id) {
// 清理孤立容器
if let Err(e) = docker_manager.stop_container_by_id(&container.id).await {
warn!("清理孤立容器失败: {} - {}", container.id, e);
} else {
cleaned_count += 1;
}
}
}
}
}
cleaned_count
}
步骤 5.4:添加清理日志
任务清单:
- 记录清理操作(容器 key、清理原因)
- 记录闲置时长(
now - last_activity) - ComputerAgentRunner 模式:记录项目数量和各项目状态
- 记录清理成功/失败统计
验收标准
- RCoder 模式:
project_id闲置即清理 - ComputerAgentRunner 模式:所有
project_id都闲置才清理 - 容器保护期生效(创建后 5 分钟内不清理)
- 孤立容器被正确清理
- 清理日志完整且可读
- 清理统计准确(
CleanupStats) - 单元测试覆盖
is_fully_idle()各种场景
阶段 6:VNC 代理实现(1-2天,P1)
目标
实现 VNC 桌面访问功能,提供 WebSocket 透明代理。
步骤 6.1:创建 computer_desktop_handler
文件: crates/rcoder/src/handler/computer_desktop_handler.rs
任务清单:
- 实现
computer_desktop_vnc()函数:pub async fn computer_desktop_vnc( State(state): State<Arc<AppState>>, Path((user_id, project_id)): Path<(String, String)>, ) -> Result<Response, AppError> { // 1. 查找 containers[ContainerKey::User(user_id)] // 2. 获取容器 IP // 3. 构建目标 URL: ws://{container_ip}:6080 // 4. 使用 Pingora/Nginx 代理 WebSocket 连接 } - 查找
user_id对应的容器 - 获取容器 IP 地址
- 构建 WebSocket 代理 URL
步骤 6.2:实现 WebSocket 代理(方案选择)
方案 A(推荐):Pingora WebSocket 中间件
任务清单:
- 调研 Pingora WebSocket 支持情况
- 实现 HTTP Upgrade 处理
- 透明转发 WebSocket 帧到容器的 6080 端口
- 处理连接错误和超时
方案 B(备用):Nginx 反向代理
任务清单:
- 配置 Nginx location:
location ~ ^/computer/desktop/([^/]+)/([^/]+)$ { set $user_id $1; # 动态查询容器 IP(通过 Lua 脚本或 upstream 模块) proxy_pass http://$container_ip:6080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } - 实现动态 upstream 配置
方案 C(临时):直接返回容器 URL
任务清单:
- 返回
http://{container_ip}:6080/vnc.html给前端 - 仅用于开发测试
步骤 6.3:添加 VNC 路由
文件: crates/rcoder/src/router.rs
任务清单:
- 添加
/computer/desktop/:user_id/:project_id路由 - 使用
get(handler::computer_desktop_vnc)处理器
步骤 6.4:测试 VNC 访问
任务清单:
- 创建测试 HTML 页面(
fixtures/vnc-test.html):<!DOCTYPE html> <html> <head> <title>VNC Desktop Test</title> </head> <body> <h1>Computer Agent VNC Desktop</h1> <form id="vnc-form"> <label>User ID: <input type="text" id="user_id" value="test_user_1"></label><br> <label>Project ID: <input type="text" id="project_id" value="proj_1"></label><br> <label>Server URL: <input type="text" id="server" value="http://localhost:8087"></label><br> <button type="submit">Connect</button> </form> <iframe id="vnc-frame" width="100%" height="800"></iframe> <script> document.getElementById('vnc-form').addEventListener('submit', function(e) { e.preventDefault(); const userId = document.getElementById('user_id').value; const projectId = document.getElementById('project_id').value; const server = document.getElementById('server').value; const vncUrl = `${server}/computer/desktop/${userId}/${projectId}`; document.getElementById('vnc-frame').src = vncUrl; }); </script> </body> </html> - 验证 WebSocket 连接建立
- 验证桌面画面传输流畅
- 测试鼠标和键盘输入
验收标准
- 可以通过浏览器访问 VNC 桌面
- WebSocket 连接稳定(不频繁断开)
- 桌面画面流畅(延迟 < 500ms)
user_id隔离生效(不同用户互不干扰)- 测试 HTML 页面可用
- 性能测试通过(多用户并发访问)
阶段 7:MCP 配置和优化(0.5天,P2)
目标
创建 ComputerAgentRunner 专用的 MCP 配置文件。
步骤 7.1:创建配置文件
文件: crates/agent_config/configs/computer_agent_default.json
任务清单:
- 复制
default_agents.json内容作为基础 - 添加 Chrome DevTools MCP 配置:
{ "agent_servers": { "claude-code-acp": { "agent_id": "claude-code-acp", "agent_type": "claude", "command": "claude-code-acp", "args": [], "env": { "ANTHROPIC_API_KEY": "{MODEL_PROVIDER_API_KEY}", "ANTHROPIC_MODEL": "{MODEL_PROVIDER_DEFAULT_MODEL}", "ANTHROPIC_BASE_URL": "{MODEL_PROVIDER_BASE_URL}", "RUST_LOG": "info" }, "system_prompt": { "source": "embedded", "template": "", "enabled": true }, "user_prompt": { "template": "{user_prompt}", "enabled": false }, "installation": { "package_manager": "npm", "package_name": "@anthropics/claude-code-acp", "version": "latest" }, "enabled": true, "metadata": { "description": "Claude Code ACP Agent - Computer Agent Runner 配置", "version": "1.0.0" } } }, "context_servers": { "chrome-devtools": { "source": "custom", "enabled": true, "command": "npx", "args": ["-y", "chrome-devtools-mcp@latest"], "env": { "CHROME_REMOTE_DEBUGGING_PORT": "9222" }, "metadata": { "description": "Chrome DevTools MCP - 浏览器操作能力", "documentation": "https://github.com/ChromeDevTools/chrome-devtools-mcp" } }, "context7": { "source": "custom", "enabled": true, "command": "bunx", "args": ["-y", "@upstash/context7-mcp"], "env": {} }, "fetch": { "source": "custom", "enabled": true, "command": "uvx", "args": ["mcp-server-fetch"], "env": {} } } } - 设置
CHROME_REMOTE_DEBUGGING_PORT=9222
步骤 7.2:配置加载逻辑
任务清单:
- 修改配置加载代码,根据
ServiceType选择配置文件:fn get_agent_config_path(service_type: ServiceType) -> PathBuf { match service_type { ServiceType::RCoder => PathBuf::from("configs/default_agents.json"), ServiceType::ComputerAgentRunner => PathBuf::from("configs/computer_agent_default.json"), } } - 确保配置文件正确加载
步骤 7.3:验证 MCP 工具
任务清单:
- 启动 agent 后验证 MCP 工具加载日志
- 测试 Chrome DevTools MCP 功能:
- 导航到网页
- 截图
- 元素操作
- 测试浏览器操作能力(agent 可以控制 Chromium)
验收标准
- 配置文件格式正确(JSON 验证通过)
- Chrome DevTools MCP 加载成功(日志显示连接到 CDP)
- Agent 可以操作 Chromium 浏览器
- 支持网页导航和元素操作
- 支持截图功能
- 配置文件文档完整(metadata 字段)
四、集成测试
4.1 功能测试用例
测试用例 1:基本聊天流程
目标: 验证 Computer Agent 的基本聊天功能
步骤:
# 1. 发送聊天请求
curl -X POST http://localhost:8087/computer/chat \
-H "Content-Type: application/json" \
-d '{
"user_id": "test_user_1",
"prompt": "Hello, help me create a simple React app"
}'
# 预期输出:
# {
# "success": true,
# "data": {
# "session_id": "sess_xxx",
# "project_id": "proj_yyy",
# "message": "Task started"
# }
# }
# 2. 验证容器创建
docker ps | grep computer-agent-runner-test_user_1
# 预期输出:显示一个运行中的容器
# 3. 订阅进度流
curl http://localhost:8087/computer/progress/{session_id}
# 预期输出:SSE 事件流,显示 agent 执行进度
验收标准:
- API 返回正确的响应(
session_id和project_id) - 容器自动创建(命名正确)
- SSE 进度流实时推送事件
测试用例 2:多项目并发
目标: 验证同一用户下多个项目可以并发运行
步骤:
# 在同一用户下创建多个项目
curl -X POST http://localhost:8087/computer/chat \
-H "Content-Type: application/json" \
-d '{"user_id": "test_user_1", "project_id": "proj_1", "prompt": "Task 1"}'
curl -X POST http://localhost:8087/computer/chat \
-H "Content-Type: application/json" \
-d '{"user_id": "test_user_1", "project_id": "proj_2", "prompt": "Task 2"}'
# 验证容器数量(应该只有 1 个)
docker ps | grep computer-agent-runner-test_user_1 | wc -l
# 预期输出:1
# 验证两个项目都在运行
# (通过查看日志或 agent 状态)
验收标准:
- 只创建一个容器
- 两个项目的 agent 都在运行
- 项目之间互不干扰(独立的工作区)
测试用例 3:Agent 停止
目标: 验证可以停止单个项目的 agent,而不销毁容器
步骤:
# 停止特定项目的 agent
curl -X POST http://localhost:8087/computer/agent/stop \
-H "Content-Type: application/json" \
-d '{"user_id": "test_user_1", "project_id": "proj_1"}'
# 预期输出:
# {
# "success": true,
# "message": "Agent stopped"
# }
# 验证容器仍然运行(proj_2 还在)
docker ps | grep computer-agent-runner-test_user_1
# 预期输出:容器仍在运行
验收标准:
- proj_1 的 agent 停止
- 容器仍然运行
- proj_2 的 agent 不受影响
测试用例 4:VNC 访问
目标: 验证可以通过浏览器访问 VNC 桌面
步骤:
# 访问 VNC 桌面
open http://localhost:8087/computer/desktop/test_user_1/proj_1
# 或使用测试 HTML 页面
open fixtures/vnc-test.html
验收标准:
- 浏览器显示 VNC 桌面
- 可以看到 XFCE4 桌面环境
- 鼠标和键盘输入正常
- 画面流畅(无明显延迟)
测试用例 5:闲置清理
目标: 验证闲置清理逻辑正确
步骤:
# 1. 启动两个项目
curl -X POST http://localhost:8087/computer/chat \
-d '{"user_id": "test_user_1", "project_id": "proj_1", "prompt": "Task 1"}'
curl -X POST http://localhost:8087/computer/chat \
-d '{"user_id": "test_user_1", "project_id": "proj_2", "prompt": "Task 2"}'
# 2. 停止所有 agent
curl -X POST http://localhost:8087/computer/agent/stop \
-d '{"user_id": "test_user_1", "project_id": "proj_1"}'
curl -X POST http://localhost:8087/computer/agent/stop \
-d '{"user_id": "test_user_1", "project_id": "proj_2"}'
# 3. 等待闲置超时(默认 30 分钟,测试时可改为 1 分钟)
sleep 70
# 4. 验证容器被销毁
docker ps | grep computer-agent-runner-test_user_1
# 预期输出:无容器运行
验收标准:
- 只有当所有项目都闲置时才清理容器
- 闲置超时时间准确
- 容器保护期生效(创建后 5 分钟内不清理)
4.2 性能测试
测试场景 1:容器创建时间
目标: 验证容器创建时间 < 30 秒
步骤:
time (curl -X POST http://localhost:8087/computer/chat \
-H "Content-Type: application/json" \
-d '{"user_id": "test_user_perf", "prompt": "Hello"}')
验收标准:
- 容器创建时间 < 30 秒
- 记录实际耗时
测试场景 2:多 Agent 并发
目标: 验证单个容器可以稳定运行 3+ 个 agent
步骤:
# 并发启动 3 个 agent
for i in {1..3}; do
curl -X POST http://localhost:8087/computer/chat \
-d "{\"user_id\": \"test_user_1\", \"project_id\": \"proj_$i\", \"prompt\": \"Task $i\"}" &
done
wait
# 监控资源使用
docker stats computer-agent-runner-test_user_1
验收标准:
- 3 个 agent 都成功启动
- 响应时间合理(< 2 秒)
- 内存占用 < 4GB
- CPU 占用 < 2 核
测试场景 3:VNC 延迟
目标: 验证 VNC 桌面访问延迟 < 500ms
步骤:
- 使用浏览器开发工具测量 WebSocket 帧传输延迟
- 记录平均延迟、最大延迟
验收标准:
- 平均延迟 < 500ms
- 画面流畅,无明显卡顿
测试场景 4:资源占用
目标: 验证容器资源占用符合限额
步骤:
# 查看容器资源使用
docker stats computer-agent-runner-test_user_1 --no-stream
验收标准:
- 内存占用 < 4GB(可配置)
- CPU 占用 < 2 核(可配置)
- 磁盘 IO 合理
4.3 安全测试
测试场景 1:用户隔离
目标: 验证不同用户之间的隔离
步骤:
# 用户 A 创建项目
curl -X POST http://localhost:8087/computer/chat \
-d '{"user_id": "user_a", "project_id": "proj_1", "prompt": "Task A"}'
# 用户 B 尝试访问用户 A 的容器
curl -X POST http://localhost:8087/computer/agent/stop \
-d '{"user_id": "user_b", "project_id": "proj_1"}'
# 预期:失败(找不到容器)
# 用户 B 尝试访问用户 A 的 VNC
curl http://localhost:8087/computer/desktop/user_a/proj_1
# 预期:失败(无权限或找不到)
验收标准:
- user_a 无法访问 user_b 的容器
- user_a 无法访问 user_b 的 VNC
- 错误消息清晰
测试场景 2:项目隔离
目标: 验证项目之间无上下文污染
步骤:
# 在两个项目中创建同名文件
# proj_1 创建 test.txt
curl -X POST http://localhost:8087/computer/chat \
-d '{"user_id": "test_user_1", "project_id": "proj_1", "prompt": "echo proj1 > test.txt"}'
# proj_2 创建 test.txt
curl -X POST http://localhost:8087/computer/chat \
-d '{"user_id": "test_user_1", "project_id": "proj_2", "prompt": "echo proj2 > test.txt"}'
# 验证文件内容不同
# (通过 agent 读取文件内容)
验收标准:
- 两个项目的 test.txt 文件内容不同
- 工作区目录独立(
/app/computer-project-workspace/{user_id}/{project_id})
测试场景 3:资源限额
目标: 验证容器资源限额生效
步骤:
# 创建容器时设置资源限额
curl -X POST http://localhost:8087/computer/chat \
-H "Content-Type: application/json" \
-d '{
"user_id": "test_user_1",
"prompt": "Task",
"agent_config": {
"resource_limits": {
"memory_limit": 2147483648,
"cpu_limit": 1.0
}
}
}'
# 验证限额生效
docker inspect computer-agent-runner-test_user_1 | jq '.[0].HostConfig.Memory'
docker inspect computer-agent-runner-test_user_1 | jq '.[0].HostConfig.NanoCpus'
验收标准:
- 内存限额生效(Docker inspect 显示正确值)
- CPU 限额生效(Docker inspect 显示正确值)
- agent 在限额内正常运行
五、部署和上线
5.1 Docker 镜像更新
检查清单:
docker/rcoder-agent-runner/Dockerfile包含所有依赖- XFCE4 桌面环境配置正确
- noVNC 服务启动脚本正确(端口 6080)
- Chromium 浏览器配置正确(CDP 端口 9222)
- 所有系统依赖已安装(Node.js, npm, bun, uv 等)
- 镜像大小合理(< 5GB)
5.2 Docker Compose 配置
文件: docker/docker-compose.yml
检查清单:
- 挂载了
computer-project-workspace目录:volumes: - ./project_workspace:/app/project_workspace - ./computer-project-workspace:/app/computer-project-workspace - ./logs:/app/logs - 端口映射正确(8087:8087)
- 环境变量配置完整(ANTHROPIC_API_KEY, RUST_LOG 等)
5.3 环境变量配置
必需环境变量:
# API 密钥
ANTHROPIC_API_KEY=sk-ant-xxx
# 日志级别
RUST_LOG=info
# Docker 配置
DOCKER_SOCKET_PATH=/var/run/docker.sock
可选环境变量:
# 资源限额(默认值)
DEFAULT_MEMORY_LIMIT=4294967296 # 4GB
DEFAULT_CPU_LIMIT=2.0 # 2 核
# 闲置超时(默认值)
IDLE_TIMEOUT=1800 # 30 分钟
5.4 监控和告警
监控指标:
- 容器创建/销毁日志
- Agent 状态变更日志
- 资源使用情况监控(CPU、内存、磁盘)
- gRPC 连接池状态
- SSE 连接数量
告警配置:
- 容器创建失败告警
- 资源超限告警
- gRPC 连接失败告警
- 闲置清理失败告警
六、验收标准总结
功能验收
- 可以通过
POST /computer/chat发送请求,自动创建 user_id 对应的容器 - 同一 user_id 的多个 project_id 可以在同一容器内运行
- 可以通过
GET /computer/desktop/{user_id}/{project_id}访问 VNC 桌面 - Agent 可以通过 Chrome DevTools MCP 操作 Chromium 浏览器
- 可以通过
POST /computer/agent/stop停止单个 project_id 的 agent(不销毁容器) - 只有当 user_id 下所有 project_id 都闲置时才销毁容器
性能验收
- 单个容器可以稳定运行 3+ 个 project_id 的 agent
- VNC 桌面访问延迟 < 500ms
- 容器创建时间 < 30s
安全验收
- user_id 只能访问自己的容器和 VNC
- project_id 之间没有上下文污染
- 容器资源限额生效
七、文件清单
新建文件(6个)
-
crates/shared_types/src/model/computer_agent_model.rs- ContainerKey 枚举
- UnifiedContainerInfo 结构
- ProjectInfo 结构
- SessionInfo 结构
-
crates/rcoder/src/service/computer_container_manager.rs- ComputerContainerManager 服务
- 容器创建和管理逻辑
-
crates/rcoder/src/handler/computer_chat_handler.rs- ComputerChatRequest 结构
- handle_computer_chat() 函数
- forward_computer_request_to_container() 函数
- computer_session_notification() SSE 处理器
-
crates/rcoder/src/handler/computer_agent_stop_handler.rs- ComputerAgentStopRequest 结构
- computer_agent_stop() 函数
-
crates/rcoder/src/handler/computer_desktop_handler.rs- computer_desktop_vnc() 函数
- VNC 代理逻辑
-
crates/agent_config/configs/computer_agent_default.json- ComputerAgentRunner 专用的 MCP 配置
- Chrome DevTools MCP 配置
修改文件(8个)
-
crates/shared_types/src/model/mod.rs- 添加 computer_agent_model 模块导出
-
crates/rcoder/src/service/mod.rs- 添加 computer_container_manager 模块导出
-
crates/rcoder/src/handler/mod.rs- 添加 computer_chat_handler 模块导出
- 添加 computer_agent_stop_handler 模块导出
- 添加 computer_desktop_handler 模块导出
-
crates/rcoder/src/router.rs- 重构 AppState(从 6 个 DashMap 精简到 3 个)
- 添加 Computer 相关路由
- 实现便捷方法
-
crates/rcoder/src/proxy_agent/cleanup_task.rs- 统一清理逻辑
- 容器保护期
- 孤立容器检测
-
crates/agent_runner/src/main.rs- 集成 agent_abstraction 模块
- 创建 AcpSessionManager 实例
- 创建 AgentLifecycleManager 实例
- 创建 AcpAgentWorker 实例
-
crates/agent_runner/src/grpc/agent_service_impl.rs- 修改 Chat RPC 实现
- 实现 StopAgent RPC
-
crates/shared_types/proto/agent.proto- 添加 StopAgent RPC 定义
- 添加 StopAgentRequest 消息
- 添加 StopAgentResponse 消息
八、时间估算
| 阶段 | 工作量 | 依赖 | 关键风险 |
|---|---|---|---|
| 阶段 1:核心数据结构 | 1-2 天 | 无 | 低 |
| 阶段 2:容器管理服务 | 1 天 | 阶段 1 | 低 |
| 阶段 3:HTTP 接口 | 1 天 | 阶段 1, 2 | 低 |
| 阶段 4:agent_runner 集成 | 2-3 天 | 阶段 1 | 中(LocalSet 并发) |
| 阶段 5:闲置检测优化 | 1 天 | 阶段 1, 3 | 低 |
| 阶段 6:VNC 代理 | 1-2 天 | 阶段 3 | 中(WebSocket 代理) |
| 阶段 7:MCP 配置 | 0.5 天 | 阶段 4 | 低 |
| 集成测试 | 1 天 | 所有阶段 | 中 |
| 总计 | 8.5-11.5 天 |
九、风险和缓解措施
| 风险 | 影响 | 概率 | 缓解措施 |
|---|---|---|---|
| agent_runner LocalSet 并发管理复杂 | 高 | 高 | 复用 agent_abstraction 模块,避免重复造轮 |
| Pingora 不支持 WebSocket | 中 | 中 | 备用方案:使用 Nginx 作为 VNC 专用代理 |
| 容器资源耗尽 | 高 | 中 | 实施严格的资源限额和监控 |
| VNC 安全风险 | 高 | 低 | user_id 绑定和访问控制(后续:一次性 token) |
| 多 agent 内存泄漏 | 中 | 中 | AgentLifecycleManager 管理,定期清理 |
十、后续优化(MVP 后)
第一阶段优化
-
VNC 会话录制和回放
- 录制用户与桌面的交互
- 回放功能用于调试和复现问题
-
剪贴板 API 支持
- 实现剪贴板读取和写入接口
- 支持跨容器的剪贴板共享
-
增强 Agent 监控和日志
- 详细的性能指标(CPU、内存、网络)
- Agent 操作日志(浏览器操作、文件操作等)
第二阶段扩展
-
多用户协作(共享 VNC 桌面)
- 多个用户可以同时查看和操作同一个桌面
- 实现光标同步和权限控制
-
集成更多 MCP 工具
- 文件操作 MCP
- 数据库操作 MCP
- API 测试 MCP
-
Agent 性能分析和优化
- 性能分析工具
- 瓶颈识别
- 优化建议
长期愿景
-
Agent 市场
- 用户可以共享和复用 Agent 配置
- 预置模板库
- 社区贡献
-
自定义桌面环境和应用
- 支持用户自定义桌面环境
- 预装常用应用
- 应用商店
-
Agent 集群调度和负载均衡
- 多节点部署
- 自动负载均衡
- 高可用性保障
附录
A. 术语表
| 术语 | 说明 |
|---|---|
| user_id | 用户唯一标识符 |
| project_id | 项目唯一标识符 |
| ContainerKey | 统一容器标识符枚举(Project/User) |
| UnifiedContainerInfo | 统一容器信息结构 |
| ProjectInfo | 项目信息结构 |
| SessionInfo | 会话信息结构 |
| ServiceType | 服务类型枚举(RCoder, ComputerAgentRunner) |
| ACP | Agent Client Protocol,AI Agent 通信协议 |
| MCP | Model Context Protocol,模型上下文协议 |
| CDP | Chrome DevTools Protocol,浏览器调试协议 |
| noVNC | 基于 HTML5 的 VNC 客户端 |
| LocalSet | Tokio 的本地任务集,支持 !Send 任务 |
| DashMap | 高性能的并发 HashMap |
B. 参考资料
-
rcoder 项目文档:
CLAUDE.md- 项目概述specs/computer-agent-runner/0001-spec-claude.md- 需求与设计文档
-
外部依赖文档:
-
技术博客:
- Rust Tokio LocalSet 使用指南
- WebSocket 代理实现最佳实践
- Docker 容器资源限制和管理
文档变更历史
| 版本 | 日期 | 作者 | 变更说明 |
|---|---|---|---|
| v1.0 | 2025-12-10 | Claude | 初始版本,基于 0001-spec-claude.md 生成 |
审批记录
| 角色 | 姓名 | 审批意见 | 日期 |
|---|---|---|---|
| 产品负责人 | |||
| 技术负责人 | |||
| 安全负责人 |
下一步行动
- 立即开始: 阶段 1 - 核心数据结构实现
- 并行开发: 阶段 2 和阶段 3(容器管理 + HTTP 接口)
- 关键里程碑: 阶段 4 - agent_runner 集成(最复杂)
- 集成测试: 完成所有阶段后进行完整测试
- 部署上线: 通过验收后部署到生产环境
联系方式
- 技术支持: [技术团队邮箱]
- 项目管理: [项目经理邮箱]
- 文档反馈: [文档维护者邮箱]