# QimingClaw GUI Agent 实现方案 > 调研时间:2026-03-18 > 调研项目:OSWorld, UI-TARS-desktop, ScreenAgent, Pi-Agent > 目标:独立且小巧的 GUI Agent 方案 --- ## 一、调研结果对比 ### 1.1 项目特性对比 | 项目 | 类型 | 核心特点 | 复杂度 | 适用性 | |------|------|---------|--------|--------| | **OSWorld** | 评测基准 | 完整 GUI 操作空间,跨平台 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | | **UI-TARS-desktop** | 生产 Agent | 多模态 + 视觉定位 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | | **ScreenAgent** | 模型训练 | VLM 微调,屏幕理解 | ⭐⭐⭐ | ⭐⭐ | | **Pi-Agent** | Agent 框架 | 轻量级,事件驱动 | ⭐⭐ | ⭐⭐⭐⭐⭐ | | **Agent-S** | 现有方案 | 已集成 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ### 1.2 GUI 操作原语对比 | 操作类型 | OSWorld | UI-TARS | QimingClaw 建议实现 | |---------|---------|---------|-----------------| | **鼠标移动** | MOVE_TO | - | ✅ `move_to(x, y)` | | **点击** | CLICK | `click(point)` | ✅ `click(x, y, button?)` | | **双击** | DOUBLE_CLICK | `left_double(point)` | ✅ `double_click(x, y)` | | **右键** | RIGHT_CLICK | `right_single(point)` | ✅ `right_click(x, y)` | | **拖拽** | DRAG_TO | `drag(start, end)` | ✅ `drag(x1, y1, x2, y2)` | | **滚动** | SCROLL(dx, dy) | `scroll(point, direction)` | ✅ `scroll(dx, dy)` | | **输入文本** | TYPING | `type(content)` | ✅ `type_text(text, speed?)` | | **按键** | PRESS, KEY_DOWN, KEY_UP | - | ✅ `press(key)` | | **快捷键** | HOTKEY | `hotkey(key)` | ✅ `hotkey(...keys)` | | **截图** | - | screenshot() | ✅ `screenshot(region?)` | | **等待** | WAIT | `wait()` | ✅ `wait(ms?)` | --- ## 二、QimingClaw GUI Agent 架构设计 ### 2.1 整体架构 ``` ┌─────────────────────────────────────────────────────┐ │ QimingClaw Main Agent (ACP Engine) │ │ │ │ ┌───────────────────────────────────────────────┐ │ │ │ GUI Agent (独立微服务/MCP Server) │ │ │ │ │ │ │ │ ┌─────────────┐ ┌──────────────────┐ │ │ │ │ │ GUI Tools │──────▶│ Desktop Operator │ │ │ │ │ │ │ │ │ │ │ │ │ │ - screenshot│ │ - pyautogui │ │ │ │ │ │ - click │ │ - robotjs │ │ │ │ │ │ - type │ │ - nut.js │ │ │ │ │ │ - scroll │ │ │ │ │ │ │ └─────────────┘ └──────────────────┘ │ │ │ │ │ │ │ │ │ │ ▼ ▼ │ │ │ │ ┌─────────────────────────────────────────┐ │ │ │ │ │ Pi-Agent Core (轻量运行时) │ │ │ │ │ │ │ │ │ │ │ │ - AgentState (状态管理) │ │ │ │ │ │ - EventStream (事件流) │ │ │ │ │ │ - Hook System (权限控制) │ │ │ │ │ │ - ToolExecutor (工具执行) │ │ │ │ │ └─────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ ┌──────────────────────┐ │ │ │ │ │ VLM (视觉语言模型) │ │ │ │ │ │ │ │ │ │ │ │ - GPT-4V │ │ │ │ │ │ - Claude Vision │ │ │ │ │ │ - Gemini Vision │ │ │ │ │ │ - 自定义 VLM │ │ │ │ │ └──────────────────────┘ │ │ │ └───────────────────────────────────────────────┘ │ │ ▲ │ │ │ MCP / IPC │ │ │ │ │ ┌──────────┴──────────┐ │ │ │ Main Agent Bridge │ │ │ └─────────────────────┘ │ └─────────────────────────────────────────────────────┘ ``` ### 2.2 核心模块 #### 2.2.1 GUI Tools (工具层) **参考:OSWorld ACTION_SPACE + UI-TARS BrowserGUIAgent** ```typescript import { Type } from '@sinclair/typebox'; import type { AgentTool, AgentToolResult } from '@qimingclaw/agent'; export const guiTools: AgentTool[] = [ { name: 'screenshot', label: '📸 截取屏幕', description: '截取屏幕或指定区域,返回 base64 图片', parameters: Type.Object({ region: Type.Optional(Type.Object({ x: Type.Number({ minimum: 0 }), y: Type.Number({ minimum: 0 }), width: Type.Number({ minimum: 1 }), height: Type.Number({ minimum: 1 }), })), format: Type.Optional(Type.Union([ Type.Literal('png'), Type.Literal('jpeg'), Type.Literal('webp'), ])), quality: Type.Optional(Type.Number({ minimum: 1, maximum: 100 })), }), execute: async (callId, params, signal, onUpdate) => { onUpdate({ status: 'capturing', progress: 0 }); const screenshot = await desktopCapturer.capture(params.region); // 压缩图片(借鉴 UI-TARS) if (params.format === 'webp' || params.quality) { const compressed = await imageCompressor.compress(screenshot.buffer, { format: params.format || 'webp', quality: params.quality || 80, }); screenshot.buffer = compressed; } onUpdate({ status: 'capturing', progress: 100 }); return { content: [{ type: 'image', data: screenshot.base64, mimeType: `image/${params.format || 'png'}`, }], details: { width: screenshot.width, height: screenshot.height, size: screenshot.buffer.length, timestamp: Date.now(), }, }; }, }, { name: 'click', label: '👆 点击', description: '点击屏幕指定位置', parameters: Type.Object({ x: Type.Number({ minimum: 0 }), y: Type.Number({ minimum: 0 }), button: Type.Optional(Type.Union([ Type.Literal('left'), Type.Literal('right'), Type.Literal('middle'), ])), numClicks: Type.Optional(Type.Number({ minimum: 1, maximum: 3 })), }), execute: async (callId, params, signal) => { // 权限检查(通过 Hook 系统) // 移动鼠标 await robotjs.moveMouse(params.x, params.y); // 点击 await robotjs.mouseClick(params.button || 'left', params.numClicks || 1); return { content: [{ type: 'text', text: `已点击 (${params.x}, ${params.y})` }], details: params, }; }, }, { name: 'type_text', label: '⌨️ 输入文本', description: '输入文本(支持流式进度)', parameters: Type.Object({ text: Type.String({ minLength: 1 }), typingSpeed: Type.Optional(Type.Number({ minimum: 0, maximum: 1000 })), }), execute: async (callId, params, signal, onUpdate) => { const chars = params.text.length; const speed = params.typingSpeed || 50; // ms per char for (let i = 0; i < chars; i++) { // 检查取消信号 if (signal?.aborted) { throw new Error('Typing aborted by user'); } // 输入字符 await robotjs.typeString(params.text[i]); // 流式进度更新 if (i % 10 === 0 || i === chars - 1) { onUpdate({ progress: (i / chars) * 100, charsTyped: i + 1, totalChars: chars, }); } // 延迟 if (speed > 0) { await sleep(speed); } } return { content: [{ type: 'text', text: `已输入 ${chars} 个字符` }], details: { length: chars, speed }, }; }, }, { name: 'scroll', label: '📜 滚动', description: '滚动鼠标滚轮', parameters: Type.Object({ dx: Type.Optional(Type.Number()), dy: Type.Number(), }), execute: async (callId, params, signal) => { await robotjs.scrollMouse(params.dx || 0, params.dy); return { content: [{ type: 'text', text: `已滚动 (${params.dx || 0}, ${params.dy})` }], details: params, }; }, }, { name: 'hotkey', label: '⌨️ 快捷键', description: '按下组合键', parameters: Type.Object({ keys: Type.Array(Type.String()), }), execute: async (callId, params, signal) => { await robotjs.keyTap(params.keys.join('+')); return { content: [{ type: 'text', text: `已按下 ${params.keys.join(' + ')}` }], details: { keys: params.keys }, }; }, }, { name: 'wait', label: '⏳ 等待', description: '等待指定时间或条件', parameters: Type.Object({ ms: Type.Optional(Type.Number({ minimum: 0 })), condition: Type.Optional(Type.String()), }), execute: async (callId, params, signal, onUpdate) => { if (params.ms) { await sleep(params.ms); } else if (params.condition) { // 等待条件满足(轮询检查) const startTime = Date.now(); while (Date.now() - startTime < 30000) { // 最多 30 秒 if (signal?.aborted) { throw new Error('Wait aborted'); } // 检查条件(例如:窗口出现、元素可见等) const satisfied = await checkCondition(params.condition); if (satisfied) { break; } onUpdate({ elapsed: Date.now() - startTime }); await sleep(500); } } return { content: [{ type: 'text', text: '等待完成' }], details: params, }; }, }, ]; ``` #### 2.2.2 Pi-Agent Core (运行时) **借鉴:Pi-Agent 事件系统 + Hook 机制** ```typescript import { Agent, type AgentEvent } from '@qimingclaw/pi-agent'; export class QimingClawGUIAgent { private agent: Agent; constructor(config: { vlmModel: ModelConfig; tools?: AgentTool[]; permissions?: PermissionConfig; }) { this.agent = new Agent({ initialState: { systemPrompt: `你是 QimingClaw 的 GUI 操作专家。 支持的操作: - screenshot: 截取屏幕 - click: 点击 - type_text: 输入文本 - scroll: 滚动 - hotkey: 快捷键 - wait: 等待 每次操作前: 1. 先截图观察当前状态 2. 分析需要操作的元素位置 3. 执行操作 4. 再次截图验证结果 注意: - 操作前需要用户确认(可通过配置跳过) - 支持撤销操作 - 失败时自动重试(最多 3 次)`, model: config.vlmModel, tools: config.tools || guiTools, }, }); // 设置权限控制 Hook this.agent.setBeforeToolCall(async (context, signal) => { // 检查是否需要用户确认 if (this.needsConfirmation(context.toolCall.name)) { const confirmed = await this.showConfirmationDialog(context); if (!confirmed) { return { block: true, reason: '用户拒绝操作', }; } } // 记录审计日志 this.logAction(context); return undefined; // 继续执行 }); // 设置结果处理 Hook this.agent.setAfterToolCall(async (context, signal) => { // 截图后自动添加标记 if (context.toolName === 'screenshot' && !context.isError) { // 在图片上标记可点击元素(借鉴 UI-TARS) const markedImage = await this.markClickableElements(context.result); return { content: [{ type: 'image', ...markedImage }], }; } // 失败时自动重试 if (context.isError && this.shouldRetry(context)) { return this.retryToolCall(context); } return undefined; }); } async execute(instruction: string): Promise { return this.agent.prompt(instruction); } subscribe(listener: (event: AgentEvent) => void): () => void { return this.agent.subscribe(listener); } abort(): void { this.agent.abort(); } } ``` #### 2.2.3 MCP Server 集成 **将 GUI Agent 打包为 MCP Server** ```typescript // mcp-servers/gui-agent/index.ts import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { QimingClawGUIAgent } from '@qimingclaw/gui-agent'; const server = new Server({ name: 'qimingclaw-gui-agent', version: '1.0.0', }, { capabilities: { tools: {}, }, }); const guiAgent = new QimingClawGUIAgent({ vlmModel: { provider: 'anthropic', id: 'claude-3-5-sonnet-20241022', }, }); // 注册工具 server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: guiTools.map(tool => ({ name: tool.name, description: tool.description, inputSchema: tool.parameters, })), }; }); // 执行工具 server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; const tool = guiTools.find(t => t.name === name); if (!tool) { throw new Error(`Tool ${name} not found`); } const result = await tool.execute( generateCallId(), args, undefined, (update) => { // 发送进度更新(通过 MCP 通知) server.notification({ method: 'notifications/progress', params: update, }); }, ); return { content: result.content.map(c => { if (c.type === 'text') { return { type: 'text', text: c.text }; } else if (c.type === 'image') { return { type: 'image', data: c.data, mimeType: c.mimeType, }; } }), }; }); // 启动服务器 const transport = new StdioServerTransport(); server.connect(transport); ``` #### 2.2.4 Main Agent 集成 **在 QimingClaw 主 Agent 中调用** ```typescript // qimingclaw-agent/src/core/gui-bridge.ts import type { AgentTool } from '@qimingclaw/agent'; export const guiBridgeTools: AgentTool[] = [ { name: 'gui_execute', label: '🖥️ 执行 GUI 操作', description: '委托 GUI Agent 执行屏幕操作', parameters: Type.Object({ instruction: Type.String(), timeout: Type.Optional(Type.Number()), }), execute: async (callId, params, signal, onUpdate) => { onUpdate({ status: 'delegating', instruction: params.instruction }); // 通过 MCP 调用 GUI Agent const mcpClient = getMCPClient('qimingclaw-gui-agent'); const result = await mcpClient.callTool('execute', { instruction: params.instruction, timeout: params.timeout || 60000, }); return { content: [{ type: 'text', text: result.summary }], details: { actions: result.actions, screenshots: result.screenshots, duration: result.duration, }, }; }, }, { name: 'gui_screenshot', label: '📸 获取屏幕截图', description: '从 GUI Agent 获取当前屏幕截图', parameters: Type.Object({ region: Type.Optional(Type.Object({ x: Type.Number(), y: Type.Number(), width: Type.Number(), height: Type.Number(), })), }), execute: async (callId, params, signal) => { const mcpClient = getMCPClient('qimingclaw-gui-agent'); const result = await mcpClient.callTool('screenshot', params); return { content: [result.image], details: result.details, }; }, }, ]; ``` --- ## 三、实施路线图 ### Phase 1: 核心实现(1-2 周) **目标:MVP 可用** - [ ] **GUI Tools 实现** - [ ] screenshot(截图 + 压缩) - [ ] click(点击 + 坐标验证) - [ ] type_text(输入 + 流式进度) - [ ] scroll(滚动) - [ ] hotkey(快捷键) - [ ] wait(等待) - [ ] **Desktop Operator 实现** - [ ] 跨平台鼠标/键盘控制(robotjs / nut.js) - [ ] 屏幕捕获(desktopCapturer) - [ ] 图片压缩(ImageCompressor) - [ ] **Pi-Agent Core 集成** - [ ] Agent 类封装 - [ ] 事件系统(4 级生命周期) - [ ] Hook 系统(权限 + 审计) ### Phase 2: MCP 集成(1 周) **目标:独立微服务化** - [ ] **MCP Server 打包** - [ ] 工具注册 - [ ] 进度通知 - [ ] 错误处理 - [ ] **配置管理** - [ ] VLM 模型配置 - [ ] 权限策略配置 - [ ] 性能参数配置 - [ ] **测试** - [ ] 单元测试 - [ ] 集成测试 - [ ] E2E 测试(OSWorld benchmark) ### Phase 3: Main Agent 集成(1 周) **目标:与 QimingClaw 主 Agent 协同** - [ ] **Bridge Tools** - [ ] gui_execute - [ ] gui_screenshot - [ ] **权限控制 UI** - [ ] 确认对话框 - [ ] 审计日志查看 - [ ] 撤销操作 - [ ] **配置界面** - [ ] VLM 模型选择 - [ ] 权限级别设置 - [ ] 性能调优 ### Phase 4: 优化与增强(持续) **目标:生产级稳定性** - [ ] **性能优化** - [ ] 图片压缩优化 - [ ] 操作延迟优化 - [ ] 内存管理 - [ ] **错误恢复** - [ ] 自动重试 - [ ] 操作回滚 - [ ] 状态恢复 - [ ] **高级功能** - [ ] 元素识别(OCR + 视觉定位) - [ ] 录制回放 - [ ] 脚本生成 --- ## 四、关键技术细节 ### 4.1 跨平台实现 ```typescript // 选择合适的底层库 const desktopOperator = { // 方案 A: robotjs (Node.js 原生) mouse: { move: (x, y) => robotjs.moveMouse(x, y), click: (button) => robotjs.mouseClick(button), scroll: (dx, dy) => robotjs.scrollMouse(dx, dy), }, keyboard: { type: (text) => robotjs.typeString(text), tap: (key) => robotjs.keyTap(key), hotkey: (...keys) => robotjs.keyTap(keys.join('+')), }, screen: { capture: (region) => desktopCapturer.capture(region), }, // 方案 B: nut.js (更现代,支持 Promise) // ... }; ``` ### 4.2 图片压缩(借鉴 UI-TARS) ```typescript import sharp from 'sharp'; export class ImageCompressor { async compress( buffer: Buffer, options: { format: 'webp' | 'jpeg'; quality: number }, ): Promise { return sharp(buffer) .toFormat(options.format, { quality: options.quality }) .toBuffer(); } } // 使用 const compressed = await compressor.compress(screenshot, { format: 'webp', quality: 80, }); // 通常可减少 70-90% 体积 ``` ### 4.3 坐标归一化 ```typescript // 借鉴 UI-TARS 的坐标处理 export function normalizeCoordinates( coords: { x: number; y: number }, screenWidth: number, screenHeight: number, ): { normalized: { x: number; y: number }; absolute: { x: number; y: number } } { return { normalized: { x: coords.x / 1000, // 归一化到 [0, 1] y: coords.y / 1000, }, absolute: { x: (coords.x / 1000) * screenWidth, y: (coords.y / 1000) * screenHeight, }, }; } ``` --- ## 五、测试与验证 ### 5.1 OSWorld Benchmark 测试 ```bash # 运行 OSWorld 评测 cd OSWorld python run.py --agent qimingclaw --max_steps 50 --test_set mini ``` ### 5.2 单元测试 ```typescript describe('QimingClawGUIAgent', () => { it('should take screenshot', async () => { const agent = new QimingClawGUIAgent(config); const result = await agent.execute('截取屏幕'); expect(result.content[0].type).toBe('image'); expect(result.details.width).toBeGreaterThan(0); }); it('should click with confirmation', async () => { const agent = new QimingClawGUIAgent(config); // Mock confirmation dialog mockConfirmation(true); const result = await agent.execute('点击 (100, 100)'); expect(result.content[0].text).toContain('已点击'); }); }); ``` --- ## 六、总结 ### 6.1 核心优势 1. **独立且小巧**:基于 Pi-Agent 轻量运行时 2. **借鉴业界最佳实践**:OSWorld 操作空间 + UI-TARS 视觉定位 3. **灵活集成**:支持 MCP / IPC / 直接调用 4. **完善的安全机制**:Hook 权限控制 + 审计日志 5. **生产级可靠性**:错误恢复 + 自动重试 ### 6.2 与现有方案对比 | 特性 | Agent-S (现有) | QimingClaw GUI Agent (新) | |------|----------------|------------------------| | **架构** | 集成式 | 独立微服务 | | **模型** | 共享 | 独立 VLM 配置 | | **权限** | 简单 | 完善的 Hook 系统 | | **事件** | 基础 | 4 级生命周期 | | **测试** | 手动 | OSWorld benchmark | | **复用性** | 低 | 高(MCP Server) | ### 6.3 关键决策 ✅ **推荐采用:Pi-Agent + OSWorld 操作空间 + UI-TARS 视觉定位** **理由:** 1. Pi-Agent 最轻量,事件系统完善 2. OSWorld 操作空间最标准化 3. UI-TARS 图片压缩和坐标处理最成熟 4. 可独立演进,不影响主 Agent --- **下一步行动:** 1. ✅ 在 `worktree/docs/gpui-research` 分支创建 PoC 2. ✅ 实现核心 GUI Tools(screenshot/click/type) 3. ✅ 集成 Pi-Agent 运行时 4. ✅ 打包为 MCP Server 5. ✅ 与主 Agent 集成测试 --- **参考资源:** - OSWorld: https://github.com/xlang-ai/OSWorld - UI-TARS-desktop: https://github.com/bytedance/UI-TARS-desktop - Pi-Agent: `/Users/apple/workspace/pi-mono/packages/agent` - Pi-Agent 深度研究: `/Users/apple/workspace/pi-mono/PI-AGENT-DEEP-RESEARCH.md`