Files
qiming/qimingclaw/specs/gui-agent/gui-agent-task.md

14 KiB
Raw Permalink Blame History

GUI Agent MCP Server — Task 任务文档

文档类型: Task任务文档— 描述"具体执行",拆解后的最小行动单元

基于: specs/gui-agent/gui-agent-plan.md Plan 计划文档

模块路径: crates/agent-gui-server/


Context

构建 GUI Agent MCP Server — 一个桌面自动化服务,文本 Agentclaude-code、qimingcode通过 MCP 协议调用它,执行截图识别 + 键鼠模拟。项目基于 pi-mono Agent 循环引擎,采用 Streamable HTTP 主模式 + stdio 备选Mutex 互斥锁保证同时只有一个 GUI 任务执行。


约定

  • ID 格式: T{phase}.{seq}(如 T1.1
  • [P] = 可与同 Wave 内其他任务并行
  • depends = 必须等待前置任务完成
  • 所有文件路径相对于 crates/agent-gui-server/
  • 参考项目约定: crates/qiming-mcp-stdio-proxy/ESM、Node≥22、tsc+esbuild、Vitest

Phase 1: 项目脚手架 + 原子操作

目标: MCP Server 启动Streamable HTTP13 个原子操作可调用

Wave 1 — 项目初始化

T1.1 创建项目脚手架

创建文件:

  • package.json"type": "module", Node≥22, "bin": { "agent-gui-server": "dist/index.js" }, "main": "./dist/lib.js"
  • tsconfig.json — target ES2022, module/moduleResolution Node16, strict, declaration
  • vitest.config.ts — globals, node env, 15s timeout, v8 coverage
  • scripts/build.mjs — esbuild 单文件 bundle + shebang

依赖:

dependencies:
  @modelcontextprotocol/sdk ^1.27.1
  @nut-tree/nut-js ^4.2.0
  sharp ^0.33.0
  clipboardy ^4.0.0

devDependencies:
  typescript ^5.7.0
  vitest ^2.1.8
  @vitest/coverage-v8 ^2.1.9
  esbuild ^0.27.3
  @types/node ^22.0.0

参考: crates/qiming-mcp-stdio-proxy/package.json, tsconfig.json, vitest.config.ts, scripts/build.mjs

验收: pnpm install 成功解析新 workspace 成员


Wave 2 — 基础设施层(全部 [P] 并行)

T1.2 src/utils/errors.ts [P]

5 个结构化错误类,继承 Errorreadonly context 字段:

  • ConfigError(message)
  • DesktopError(operation, cause)
  • CoordinateError(coordinateMode, rawX, rawY)
  • SafetyError(keys, reason)
  • TaskExecutionError(taskText, step, cause)

参考: crates/qiming-mcp-stdio-proxy/src/errors.ts

T1.3 src/utils/logger.ts [P]

  • stderr 输出,格式: [YYYY-MM-DD HH:mm:ss.SSS] [LEVEL] [gui-agent] message
  • GUI_AGENT_LOG_FILE 环境变量启用文件日志,日轮转,保留 7 天
  • 导出: logInfo, logWarn, logError, logDebug

参考: crates/qiming-mcp-stdio-proxy/src/logger.ts

T1.4 src/utils/platform.ts [P]

  • getPlatform(): 'macos' | 'windows' | 'linux'
  • checkScreenRecordingPermission(): Promise<boolean>
  • checkAccessibilityPermission(): Promise<boolean>
  • getPlatformPasteKeys(): Key[]macOS: Cmd+V, 其他: Ctrl+V

Wave 3 — 配置 + 坐标 + 桌面操作(大部分 [P] 并行)

T1.5 src/config.ts

depends: T1.2, T1.3

  • GuiAgentConfig 接口,解析所有 GUI_AGENT_* 环境变量
  • Fail Fast: API_KEY 缺失直接 throw ConfigError
  • 数值范围校验: jpegQuality(1-100), maxSteps(1-200), port(1-65535), stepDelayMs(100-30000)
  • 默认值: provider=anthropic, model=claude-sonnet-4-20250514, port=60008, maxSteps=50, stepDelayMs=1500, stuckThreshold=3, jpegQuality=75, displayIndex=0, transport=http

T1.6 src/coordinates/modelProfiles.ts [P]

depends: T1.2

  • 7 条正则匹配规则 → ModelProfile { coordinateMode, coordinateOrder }
  • Gemini → { normalized-999, yx },其余 → xy
  • fallback: image-absolute + xy
  • getModelProfile(modelName, overrideMode?): ModelProfile

T1.7 src/coordinates/resolver.ts

depends: T1.6

纯函数,零 I/O四步转换

  1. 坐标顺序修正yx swap for Gemini
  2. 归一化到 0~1
  3. 逻辑坐标 = norm × logicalWidth/Height
  4. 全局偏移 = local + display.origin

边界 clamp + warning 日志

T1.8 src/desktop/display.ts [P]

depends: T1.2, T1.3

  • DisplayDescriptor { index, label, width, height, scaleFactor, isPrimary, origin }
  • listDisplays(), getDisplay(index), getPrimaryDisplay()

T1.9 src/desktop/screenshot.ts

depends: T1.8

截图管线:

  1. nut.js screen.capture(region) 物理分辨率
  2. sharp resize 到逻辑分辨率(吸收 scaleFactor
  3. 最长边 > 1920 则等比缩放到 1920
  4. JPEG encode quality=75
  5. 超限降 quality 重试
  6. 返回 ScreenshotResultbase64 + imageWidth/Height + logicalWidth/Height + physicalWidth/Height + scaleFactor

T1.10 src/desktop/mouse.ts [P]

depends: T1.2

click, doubleClick, moveTo, drag, scroll, getPosition — 包装 nut.js

T1.11 src/desktop/keyboard.ts [P]

depends: T1.2, T1.4

  • typeText(text) — 非 ASCII 或 >50 字符 → clipboard.pasteText否则 nut.js type
  • pressKey(key), hotkey(keys[])

T1.12 src/desktop/clipboard.ts [P]

depends: T1.2, T1.4

  • pasteText(text) — 备份剪贴板 → 写入 → Cmd/Ctrl+V → 100ms → 恢复try-catch
  • readClipboard(), writeClipboard(text) — 使用 clipboardy

T1.13 src/desktop/imageSearch.ts [P]

depends: T1.2

  • findImage(templateBase64, confidence?){ found, region?, confidence? }
  • waitForImage(templateBase64, timeout?, confidence?){ found, region?, elapsed }

Wave 4 — 安全层([P] 并行)

T1.14 src/safety/hotkeys.ts [P]

depends: T1.2, T1.4

  • validateHotkey(keys[]): { blocked, reason? }
  • 平台黑名单: macOS(Cmd+Q, Cmd+W, Cmd+Opt+Esc), Windows(Alt+F4, Ctrl+Alt+Del), Linux(Ctrl+Alt+Del)

T1.15 src/safety/auditLog.ts [P]

depends: T1.2

  • AuditLog class — 环形缓冲 1000 条
  • record(entry), getEntries(count?), clear()

Wave 5 — MCP 协议层

T1.16 src/mcp/atomicTools.ts

depends: T1.7, T1.9, T1.10, T1.11, T1.12, T1.13, T1.14, T1.15

注册 13 个 MCP tool handler: gui_screenshot, gui_click, gui_double_click, gui_move_mouse, gui_drag, gui_scroll, gui_type, gui_press_key, gui_hotkey, gui_cursor_position, gui_list_displays, gui_find_image, gui_wait_for_image

每个 handler: 输入校验 → safety 检查 → 坐标解析 → desktop 操作 → audit 记录 → 返回结果

T1.17 src/mcp/resources.ts [P with T1.16]

depends: T1.4, T1.15

3 个 MCP Resource: gui://status, gui://permissions, gui://audit-log

T1.18 src/mcp/server.ts

depends: T1.16, T1.17

双模式 MCP Server:

  • HTTP 主模式: http.createServer() 监听 127.0.0.1:<port>/mcp 路径per-session StreamableHTTPServerTransport + Serversession 管理 + 定期清理
  • stdio 备选: 单 Server + StdioServerTransport

参考: crates/qiming-mcp-stdio-proxy/src/bridge.tsPersistentMcpBridge 模式)

T1.19 src/index.ts

depends: T1.5, T1.18

CLI 入口: --port <number>, --transport <http|stdio>, SIGINT/SIGTERM graceful shutdown


Wave 6 — Phase 1 验收

T1.20 冒烟测试

depends: T1.19

  • tools/list 返回 13 个工具
  • gui_screenshot 返回 base64 + 正确 metadata
  • gui_list_displays 返回显示器数组
  • HTTP 模式支持多客户端并发连接

Phase 2: 坐标转换验证 + 多屏

目标: 坐标转换准确,多屏正确

T2.1 tests/coordinates/resolver.test.ts [P]

覆盖: 4 种坐标模式 × Gemini yx swap × Retina/HiDPI × 多屏偏移 × 边界 clamp × Spec 3.4.6 示例

T2.2 tests/coordinates/modelProfiles.test.ts [P]

覆盖: 7 条模型匹配 + fallback + coordinateOrder + override

T2.3 tests/desktop/screenshot.test.ts [P]

mock nut.js + sharp覆盖: Retina 2880×1800 → 1440×900、4K → 1920×1080、1080p 不缩放、quality 降级重试

T2.4 多屏端到端测试

depends: T2.1

mock 多显示器,验证主屏/副屏坐标偏移正确


Phase 3: Agent 循环 (gui_execute_task)

目标: gui_execute_task 能执行自然语言 GUI 任务

T3.1 添加 pi-mono 依赖

新增 dependencies: @mariozechner/pi-ai, @mariozechner/pi-agent-core, @sinclair/typebox

T3.2 src/agent/systemPrompt.ts [P]

depends: T3.1

buildSystemPrompt(taskText, memoryText): string — 角色描述、7 个内部工具说明、坐标指引、popup 处理、computer_done 使用指导、memory 注入点

T3.3 src/agent/stuckDetector.ts [P]

StuckDetector class — 截图缩放 32×32 → 像素均值差异 < 5% 连续 N 次 → 判定卡死

T3.4 src/agent/memoryManager.ts

depends: T3.1

三层记忆管理(参考 TuriX-CUA:

summaryMemory (budget 2000 chars) ← LLM 摘要压缩
recentMemory  (budget 500 chars)  ← finalizeStep 写入
pendingMemory (no budget)         ← addPendingStep 写入

核心 API:

  • addPendingStep(stepId, goal)
  • async finalizeStep(stepId, evaluation) — pending→recent超 budget 触发 complete(memoryModel, context) 摘要
  • compose(): string — 组合三层文本
  • pruneScreenshots(messages): AgentMessage[] — 保留最近 3 步截图,老截图替换为文字占位

记忆摘要通过独立 complete() 调用(不走 Agent 循环)

T3.5 src/agent/taskRunner.ts

depends: T3.2, T3.3, T3.4, desktop/, safety/

核心: 配置 pi-mono Agent 实例,不手写循环

const agent = new Agent({
  initialState: { systemPrompt, model, thinkingLevel: 'off', tools: guiTools, messages: [] },
  toolExecution: 'sequential',
  transformContext: (msgs) => memoryManager.pruneScreenshots(msgs),
  convertToLlm: (msgs) => msgs.filter(m => ['user','assistant','toolResult'].includes(m.role)),
  beforeToolCall: ({ toolCall, args }) => hotkeys 拦截,
  afterToolCall: ({ toolCall, args, result, isError }) => auditLog 记录,
});

7 个 AgentTool[]: computer_screenshot, computer_click, computer_type, computer_scroll, computer_hotkey, computer_wait, computer_done

事件订阅: turn_end → stepCount++, finalizeStep, stuckDetector, 更新 systemPrompt(compose()), maxSteps 检查

返回 TaskResult { success, result?, finalScreenshot?, steps[], error? }

T3.6 src/mcp/taskTools.ts

depends: T3.5

  • gui_execute_task MCP tool handler
  • Mutex 互斥锁(let currentTask: Promise | null
  • handler: acquire mutex → taskRunner.run(taskText, extra.signal, progressCb) → release in finally
  • 进度: extra.sendNotification({ method: 'notifications/progress', params: { progressToken, progress, total } })
  • 取消: extra.signalagent.abort()

T3.7 更新 src/mcp/server.ts

depends: T3.6

在 session Server 注册中添加 registerTaskTools()

验收: tools/list 返回 14 个工具,gui_execute_task("打开 Finder") 自动完成并返回步骤日志

T3.8 Agent 单元测试 [P with T3.7]

  • tests/agent/taskRunner.test.ts — mock Agent/getModel/desktop测试正常完成、abort、maxSteps
  • tests/agent/memoryManager.test.ts — budget 触发、摘要调用、compose 输出、pruneScreenshots
  • tests/agent/stuckDetector.test.ts — 相同/不同截图判定

Phase 4: SDK 导出

T4.1 src/lib.ts

depends: T3.7

  • createGuiAgentMcpServer(config: Partial<GuiAgentConfig>): { start(), stop() }
  • Re-export: GuiAgentConfig, ScreenshotResult, DisplayDescriptor, TaskResult

Phase 5: Electron 客户端集成

T5.1 类型定义

修改 crates/agent-electron-client/src/shared/types/computerTypes.ts — 新增 GuiVisionModelConfig

T5.2 computerServer 路由

depends: T5.1

修改 crates/agent-electron-client/src/main/services/computerServer.ts — 4 个新路由:

  • POST/GET /computer/gui-agent/vision-model
  • GET /computer/gui-agent/displays
  • POST /computer/gui-agent/display

T5.3 GUIAgentSettings 组件 [P with T5.2]

新建 crates/agent-electron-client/src/renderer/components/GUIAgentSettings.tsx — 显示器选择 + 视觉模型配置

T5.4 集成 SettingsPage

depends: T5.3

修改 crates/agent-electron-client/src/renderer/components/SettingsPage.tsx — 添加 GUIAgentSettings 标签页

T5.5 workspace 集成

修改 crates/agent-electron-client/package.json"agent-gui-server": "workspace:*" + extraResources


Phase 6: 集成测试 + 文档

T6.1 MCP 集成测试

claude-code 通过 MCP URL 连接list tools调用 gui_screenshot

T6.2 跨平台验证

macOS + Windows + Linux(X11) 基本功能验证

T6.3 README.md

安装、MCP 配置示例HTTP + stdio、环境变量说明、开发指南


执行顺序总览

Phase 1 (脚手架 + 原子操作):
  Wave 1: T1.1
  Wave 2: T1.2 | T1.3 | T1.4
  Wave 3: T1.5, T1.6 | T1.8 | T1.10 | T1.11 | T1.12 | T1.13
  Wave 4: T1.7, T1.9, T1.14 | T1.15
  Wave 5: T1.16 | T1.17
  Wave 6: T1.18
  Wave 7: T1.19
  Wave 8: T1.20

Phase 2 (坐标验证):
  T2.1 | T2.2 | T2.3 → T2.4

Phase 3 (Agent 循环):
  T3.1 → T3.2 | T3.3 | T3.4 → T3.5 → T3.6 → T3.7
                                        T3.8 (parallel)

Phase 4: T4.1
Phase 5: T5.1 → T5.2 | T5.3 → T5.4, T5.5
Phase 6: T6.1 → T6.2 → T6.3

验证方式

  1. Phase 1 验收: 启动 server → curl POST /mcp 调用 tools/list 返回 13 工具 → gui_screenshot 返回 base64
  2. Phase 2 验收: pnpm test 全部通过,坐标覆盖率 >90%
  3. Phase 3 验收: gui_execute_task("打开 Finder") → 自动截图分析+点击 → 返回 steps + finalScreenshot
  4. Phase 4 验收: import { createGuiAgentMcpServer } from 'agent-gui-server' 可用
  5. Phase 5 验收: Electron Settings UI 可配置视觉模型和显示器
  6. Phase 6 验收: claude-code MCP 配置后能调用 gui_execute_task

关键参考文件

文件 用途
specs/gui-agent/gui-agent.md Spec 规范文档(权威需求源)
specs/gui-agent/gui-agent-plan.md Plan 计划文档(技术方案源)
crates/qiming-mcp-stdio-proxy/package.json 工程约定模板
crates/qiming-mcp-stdio-proxy/scripts/build.mjs esbuild 构建脚本模板
crates/qiming-mcp-stdio-proxy/src/bridge.ts Streamable HTTP + session 管理模式
crates/qiming-mcp-stdio-proxy/src/logger.ts 日志模块模板
crates/qiming-mcp-stdio-proxy/src/errors.ts 错误类模板