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

57 KiB
Raw Permalink Blame History

GUI Agent — Spec 规范文档

文档类型: Spec规范文档— 描述"做什么",定义功能范围与边界

状态: Draft

日期: 2026-03-17


1. 项目概述

1.1 是什么

一个以 MCP Server 形式运行的 GUI 桌面操作服务,供文本模型 Agent如 claude-code、qimingcode通过 MCP 协议调用,实现截图识别 + 键鼠模拟来自动操作桌面应用程序。

1.2 解决什么问题

  • 文本模型 Agent 擅长代码/文件操作,但无法操作图形界面
  • 需要一个标准化的 MCP 服务,让文本 Agent 可以委托 GUI 操作任务
  • 文本 Agent 在工作中遇到 GUI 场景时(验证 UI、操作浏览器、填写表单等能无缝调用

1.3 核心定位

用户
  │  自然语言对话
  ▼
文本 Agent (claude-code / qimingcode / 其他)     ← 决策者,理解用户意图
  │  MCP 协议调用
  ▼
GUI Agent MCP Server (本项目)                   ← 执行者,操作桌面
  │
  ├── 模式 A: 原子操作(截图/点击/输入)         ← 文本 Agent 自己编排步骤
  │
  └── 模式 B: 完整任务(自然语言 → Agent 循环)   ← GUI Agent 内部自主完成

关键区分:用户不直接与 GUI Agent 交互。文本 Agent 是"大脑"GUI Agent 是"眼睛和手"。


2. 使用场景

2.1 典型场景

场景 文本 Agent 做什么 GUI Agent 做什么
验证 UI 效果 改完前端代码后,调 gui_screenshot 截图查看效果 截图返回
操作浏览器 gui_execute_task("打开浏览器访问 http://localhost:3000 并截图首页") 自主完成:找到浏览器 → 输入 URL → 等待加载 → 截图返回
填写表单 逐步调 gui_click + gui_type 填写各字段 执行单次操作并返回
跨应用操作 gui_execute_task("从 Excel 复制 A1:B10 的数据,粘贴到浏览器表单") 自主完成多步跨应用操作
安装软件 gui_execute_task("双击桌面上的安装包,按默认选项完成安装") 自主完成安装向导的多步点击

2.2 调用方角色

  • 主要: 文本模型 Agentclaude-code、qimingcode 或任何支持 MCP 的 Agent
  • 次要: QimingClaw Electron 客户端(通过 SDK 嵌入方式集成)

2.3 GUI Agent 不面对的角色

  • 终端用户 — 用户与文本 Agent 对话,不直接操作 GUI Agent
  • 因此不需要 CLI 交互模式,不需要 TUI/Web UI

3. 功能需求

3.1 MCP 服务(核心功能)

GUI Agent 的唯一入口是 MCP 协议。暴露两类 Tool

3.1.1 原子操作工具(文本 Agent 自行编排)

文本 Agent 可以逐步调用这些工具,自己决定操作顺序:

MCP Tool 描述 入参 出参
gui_screenshot 全屏截图 displayIndex? { image, mimeType, imageWidth, imageHeight, logicalWidth, logicalHeight, scaleFactor }
gui_click 鼠标单击 x, y, button?, coordinateMode? { success, elapsed }
gui_double_click 鼠标双击 x, y, button?, coordinateMode? { success, elapsed }
gui_move_mouse 移动鼠标 x, y, coordinateMode? { success }
gui_drag 鼠标拖拽 startX, startY, endX, endY, button?, coordinateMode? { success, elapsed }
gui_scroll 滚动 x, y, deltaY, deltaX?, coordinateMode? { success }
gui_type 输入文本 text { success, elapsed }
gui_press_key 按下单键 key { success }
gui_hotkey 组合键 keys[] { success }
gui_cursor_position 获取光标位置 { x, y }
gui_list_displays 获取显示器列表 { displays: [...] }
gui_find_image 在屏幕上查找图像 templateImage, confidence? { found, region?, confidence? }
gui_wait_for_image 等待图像出现 templateImage, timeout?, confidence? { found, region?, elapsed }
gui_analyze_screen 视觉模型分析屏幕 prompt, displayIndex? { analysis, imageWidth, imageHeight }

3.1.2 完整任务工具GUI Agent 自主执行)

文本 Agent 下发自然语言任务GUI Agent 内部启动 Agent 循环自主完成:

MCP Tool 描述 入参 出参
gui_execute_task 执行完整的 GUI 任务(同步阻塞) task: string, maxSteps? { success, result?, finalScreenshot?, steps[], error? }

同步阻塞执行gui_execute_task 是标准的 MCP tool callhandler 内部 await Agent 循环完成后返回结果。利用 MCP SDK 原生机制:

能力 MCP SDK 机制 说明
进度通知 notifications/progress + extra.sendNotification() 每步操作通过 progressToken 推送进度
请求取消 extra.signalAbortSignal 客户端可随时取消handler 中监听 signal 终止 Agent 循环
连接断开 Transport 关闭自动触发 AbortSignal 无需手动管理 session 清理

互斥执行:桌面同一时间只能有一个 GUI 操作者。通过互斥锁Mutex确保同时只有一个 gui_execute_task 在执行,第二个调用等待锁释放后再执行。

3.1.3 MCP Resources信息暴露

Resource 描述
gui://status 服务状态(运行中、平台、权限)
gui://permissions 当前平台权限状态
gui://audit-log 最近操作审计日志

3.1.4 MCP 传输方式

传输 启动方式 适用场景
Streamable HTTP(主模式) gui-agent --port 60008 持久化本地 HTTP 服务,多个 Agent 客户端通过 URL 连接
stdio(备选) gui-agent --transport stdio 单个文本 Agent spawn 子进程,简单场景

Streamable HTTP 主模式说明

参考 qiming-mcp-stdio-proxy 的 proxy 模式GUI Agent 作为持久化本地 HTTP 服务启动:

  • 监听 127.0.0.1:<port>,长期运行
  • 多个文本 Agentclaude-code、qimingcode 等)通过 MCP URL 连接,各自独立 session
  • 每个客户端连接创建独立的 StreamableHTTPServerTransport + MCP Server 实例
  • Session 通过 mcp-session-id HTTP header 跟踪,支持 session 清理
  • 桌面是共享资源,gui_execute_task 通过互斥锁确保同时只有一个在执行(见 3.1.2

3.2 Agent 循环(gui_execute_task 内部实现)

当文本 Agent 调用 gui_execute_taskGUI Agent 内部启动一个自主循环:

收到任务文本
  ↓
截取屏幕 → 缩放到模型目标分辨率 → 转 JPEG base64
  ↓
构造多模态消息(截图 + 任务描述 + 上下文)→ 调用 LLM
  ↓
LLM 分析 UI 并返回 tool call含模型格式的坐标
  ↓
CoordinateResolver: 模型坐标 → 归一化 → 逻辑坐标
  ↓
执行桌面操作nut.js 使用逻辑坐标)
  ↓
再次截图验证操作结果
  ↓
循环直到LLM 判断任务完成 / 达到 maxSteps / 被 abort
  ↓
返回结果给调用方(通过 MCP response

pi-mono 集成方式

  • 使用 @mariozechner/pi-ai 作为 LLM 调用层(多 Provider 统一接口)
  • 使用 @mariozechner/pi-agent-core 的 tool calling 循环
  • 桌面操作工具通过 pi-mono 的 extension/tool 注册机制注入
  • 利用 pi-mono 的 context compaction 处理长任务的 token 溢出

借鉴 TuriX-CUA 的三层记忆管理

  • Summary:更早步骤的高度压缩摘要(由 LLM 总结),超限时做"摘要的摘要"
  • Recent:最近完成的步骤记录 + 评估结果
  • Pending:当前正在执行的步骤,完成后移入 Recent
  • 目的Agent 循环每步都带截图base64 很大),历史截图如果不压缩会迅速撑爆 context window
  • 当 Recent 超预算时触发 LLM 摘要压缩:总结文字 → 移入 Summary丢弃旧截图 base64保留文字描述
  • 记忆摘要可使用独立的更便宜模型(通过 GUI_AGENT_MEMORY_MODEL 配置)

3.3 多模型 LLM 支持

  • 必须支持: Anthropic (Claude)、OpenAI 协议
  • 支持任何 OpenAI 兼容端点Azure OpenAI、本地 vLLM 等)
  • 支持多模态输入(截图图片 + 文本)
  • 支持 tool callingLLM 返回要执行的桌面操作)
  • 模型配置通过环境变量或配置文件传入

3.4 截图与坐标系统(核心难点)

3.4.1 问题本质

GUI Agent 的核心循环是"截图 → 视觉模型分析 → 输出坐标 → 执行点击"。

关键认知:视觉模型输出的坐标格式是训练数据决定的,不是推理时可以改变的。不同模型的训练方式完全不同,坐标格式各异。坐标转换必须由系统层完成,绝不能让模型自己计算缩放——模型做数学运算误差极大。

3.4.2 三层坐标空间

┌─────────────────────────────────────────────────────────┐
│  Layer 1: 物理分辨率 (Physical Pixels)                   │
│  macOS Retina 15": 2880 × 1800                          │
│  Windows 150% DPI: 2880 × 1620                          │
│  说明: 屏幕的实际像素数                                   │
├─────────────────────────────────────────────────────────┤
│  Layer 2: 逻辑分辨率 (Logical Points)                    │
│  macOS Retina 15": 1440 × 900  (scaleFactor = 2)        │
│  Windows 150% DPI: 1920 × 1080 (scaleFactor = 1.5)      │
│  说明: OS 报告的坐标空间nut.js 操作使用此坐标            │
├─────────────────────────────────────────────────────────┤
│  Layer 3: 发送给模型的图片像素 (Image Pixels)             │
│  需要按模型要求缩放到特定尺寸再发送                       │
│  不同模型要求不同(见 3.4.3                             │
└─────────────────────────────────────────────────────────┘

3.4.3 各视觉模型的坐标格式(调研数据)

模型 坐标格式 坐标范围 推荐截图分辨率 说明
Claude computer_use 图片绝对像素 (0, 0) ~ (imgW, imgH) 1024×7681280×800 Anthropic 推荐缩放到 XGA/WXGA模型输出该图片空间的绝对坐标。最大边不超过 1568px
GPT-4o / GPT-5.4 图片绝对像素 (0, 0) ~ (imgW, imgH) 原始分辨率(最大 10.24M 像素) OpenAI CUA 输出图片像素空间的绝对坐标
Gemini 2.5 归一化 0-999 (0, 0) ~ (999, 999) 1440×900 Google 推荐;输出归一化网格坐标,与分辨率无关
UI-TARS (ByteDance) 归一化 0-1000 (0, 0) ~ (1000, 1000) 任意 输出千分比坐标,与图片实际尺寸无关
Qwen2.5-VL 图片绝对像素 (0, 0) ~ (imgW, imgH) 任意 输出 bbox 格式 [x1, y1, x2, y2] 的图片绝对坐标
CogAgent 图片绝对像素 (0, 0) ~ (imgW, imgH) ≤1120×1120 输出 box 格式的图片绝对坐标
SeeClick / ShowUI 归一化 0-1 (0.0, 0.0) ~ (1.0, 1.0) 任意 输出小数比例坐标,完全分辨率无关

三大坐标家族

家族 代表模型 特征
图片绝对像素 Claude CU、GPT CUA、Qwen2.5-VL、CogAgent 坐标与发送的图片尺寸强绑定,换图片大小坐标就变
归一化千分制 UI-TARS、Gemini 0-999 或 0-1000与图片尺寸无关
归一化小数 SeeClick、ShowUI 0.0-1.0,与图片尺寸无关

3.4.4 截图策略:统一分级缩放

核心原则:截图必须从物理分辨率缩放到逻辑分辨率(吸收 scaleFactor否则坐标会偏移 scaleFactor 倍。逻辑分辨率过大时进一步等比缩放。

统一分级缩放策略(参考 TuriX-CUA不按模型区分

不同模型的坐标转换都会经过归一化步骤(modelX / imageWidthmodelX / 1000 等),数学上与截图发送的分辨率无关。因此不需要按模型匹配不同截图分辨率

屏幕 (逻辑 1440×900, 物理 2880×1800)
  │
  │  全屏截图 → 原始图片 (物理分辨率 2880×1800)
  │
  │  Step 1: 缩放到逻辑分辨率 (吸收 scaleFactor)
  │          2880×1800 → 1440×900
  │
  │  Step 2: 逻辑分辨率最长边 > 1920 时,等比缩放到最长边 1920
  │          例: 2560×1440 → 1920×1080
  │
  │  Step 3: 转 JPEG quality=75 进一步压缩
  ▼
发送给视觉模型 (base64 JPEG)
逻辑分辨率最长边 缩放策略 示例
≤ 1920 不缩放(保持逻辑分辨率) 1440×900 → 1440×900
1921 ~ 2560 等比缩放到最长边 1920 2560×1440 → 1920×1080
> 2560 等比缩放到最长边 1920 3840×2160 → 1920×1080

3.4.5 坐标转换链路

截图 (全屏, 物理分辨率)
  │
  │  统一分级缩放(见 3.4.4
  ▼
发送给视觉模型
  │
  │  模型输出坐标 (model_x, model_y),格式由训练决定
  ▼
CoordinateResolver四步转换
  │
  │  Step 1: 坐标顺序修正Gemini yx → xy swap
  │  Step 2: 模型坐标 → 归一化 (0~1)
  │  Step 3: 归一化 → 逻辑坐标(相对于目标显示器)
  │  Step 4: 逻辑坐标 + 显示器偏移 → 全局坐标
  ▼
nut.js mouse.click(global_x, global_y)

Step 1 — 坐标顺序修正

坐标顺序 处理 适用模型
xy(默认) 不变,rawX = model_x, rawY = model_y Claude、GPT、UI-TARS、Qwen 等
yxGemini swaprawX = model_y, rawY = model_x Gemini 系列

Step 2 — 模型坐标 → 归一化 (0~1)

坐标家族 归一化公式
图片绝对像素 (Claude/GPT/Qwen) norm_x = rawX / imageWidth
norm_y = rawY / imageHeight
归一化 0-1000 (UI-TARS) norm_x = rawX / 1000
norm_y = rawY / 1000
归一化 0-999 (Gemini) norm_x = rawX / 999
norm_y = rawY / 999
归一化 0-1 (SeeClick/ShowUI) norm_x = rawX
norm_y = rawY

Step 3 — 归一化 → 逻辑坐标(所有模型统一):

local_x = norm_x × logicalWidth
local_y = norm_y × logicalHeight

Step 4 — 逻辑坐标 + 显示器偏移 → 全局坐标(多显示器场景):

global_x = local_x + display.origin.x
global_y = local_y + display.origin.y

主显示器 origin 为 (0,0),副屏有偏移(见 3.6.5)。边界校验:结果 clamp 到目标显示器范围内。

3.4.6 完整示例

场景macOS Retina逻辑分辨率 1440×900scaleFactor=2使用 Claude computer_use

1. 全屏截图 → 物理 2880×1800 的原始图
2. 系统缩放到 Claude 目标分辨率 → 1280×800 的图片
3. 发送 1280×800 图片给 Claude
4. Claude 输出: "click at (640, 400)"  ← 图片绝对像素坐标
5. 归一化: norm_x = 640/1280 = 0.5, norm_y = 400/800 = 0.5
6. 逻辑坐标: x = 0.5 × 1440 = 720, y = 0.5 × 900 = 450
7. nut.js: mouse.click(720, 450)  ← 屏幕正中央 ✓

场景:同一屏幕,使用 UI-TARS

1. 全屏截图 → 物理 2880×1800 的原始图
2. 系统缩放到合理尺寸 → 1440×900 的图片
3. 发送 1440×900 图片给 UI-TARS
4. UI-TARS 输出: (500, 500)  ← 归一化千分比
5. 归一化: norm_x = 500/1000 = 0.5, norm_y = 500/1000 = 0.5
6. 逻辑坐标: x = 0.5 × 1440 = 720, y = 0.5 × 900 = 450
7. nut.js: mouse.click(720, 450)  ← 屏幕正中央 ✓

场景同一屏幕使用未知视觉模型fallback

1. 全屏截图 → 物理 2880×1800 的原始图
2. 未知模型,缩放到逻辑分辨率 → 1440×900 的图片
   (必须从物理缩到逻辑,否则坐标会偏移 scaleFactor 倍)
3. 发送 1440×900 图片给未知模型
4. 模型输出: "click at (720, 450)"  ← 按 image-absolute 处理
5. 归一化: norm_x = 720/1440 = 0.5, norm_y = 450/900 = 0.5
6. 逻辑坐标: x = 0.5 × 1440 = 720, y = 0.5 × 900 = 450
7. nut.js: mouse.click(720, 450)  ← 屏幕正中央 ✓

为什么 scaleFactor 不需要显式出现在坐标公式中?

因为截图从物理分辨率缩放到目标/逻辑分辨率时scaleFactor 已被"吸收"。 最终 Step 2 乘的是 logicalWidth 而非 physicalWidth,这本身就是对 DPI 的处理。 如果跳过缩放直接把物理分辨率的截图发给模型,模型输出的坐标会比逻辑坐标大 scaleFactor 倍,点击位置会偏到屏幕外。

3.4.7 截图图片约束API 限制)

视觉模型 API 对输入图片有严格的大小和格式限制,截图必须在发送前处理:

模型/Provider 单图大小限制 最大分辨率 推荐格式
Claude (Anthropic) 基于 token 计算(~1600 token/图) 最长边 1568px总 ≤1.15MP JPEG/PNG/WebP
GPT-4o (OpenAI) 20MB 最大 10.24MP;低分辨率模式 512×512 JPEG/PNG/WebP
Gemini (Google) 20MB 无硬限,但推荐 ≤1440×900 JPEG/PNG/WebP
Qwen2.5-VL 取决于部署配置 建议 ≤1280×800 JPEG/PNG
UI-TARS 取决于部署配置 建议 ≤1280×800 JPEG/PNG

截图处理管线

全屏截图物理分辨率PNG lossless
  │
  │  1. 缩放到逻辑分辨率(吸收 scaleFactor
  │     物理截图必须缩放,至少缩到逻辑分辨率,
  │     否则模型坐标 → 逻辑坐标的转换会因 scaleFactor 偏移
  │     逻辑分辨率最长边 > 1920 时,等比缩放到最长边 1920
  │     使用 LANCZOS 重采样保证质量
  ▼
  │  2. 转为 JPEG 格式 + 调整质量
  │     quality 参数平衡文件大小与清晰度
  │     默认 quality=75
  ▼
  │  3. 检查文件大小是否超限
  │     超限则降低 quality 重新编码
  ▼
  │  4. 转 base64 编码
  ▼
发送给视觉模型 (base64 JPEG)

为什么用 JPEG 而不是 PNG

  • 全屏截图的 PNG 通常 2-10MBJPEG quality=75 可压缩到 100-300KB
  • 视觉模型不需要像素级无损精度JPEG 的压缩损失对 UI 识别无影响
  • 大幅减少 base64 编码后的 token 消耗和网络传输时间
  • 连续截图场景Agent 循环每步都截图),累计节省非常显著

quality 参数策略

  • 默认 quality=75:清晰度与文件大小的最佳平衡
  • 小文字/高密度 UI可提高到 quality=90
  • Token 敏感场景:可降低到 quality=60
  • 可通过 GUI_AGENT_JPEG_QUALITY 环境变量覆盖(仅内部调试用)

3.4.8 截图元数据

每次截图必须携带完整元数据,坐标转换依赖这些值:

interface ScreenshotResult {
  image: string;           // base64 编码的 JPEG 图片数据
  mimeType: string;        // "image/jpeg"(默认)| "image/png"
  imageBytes: number;      // 编码前的图片字节数(用于检查是否超限)
  imageWidth: number;      // 发送给模型的图片宽度(按模型要求缩放后)
  imageHeight: number;     // 发送给模型的图片高度
  logicalWidth: number;    // 屏幕逻辑宽度OS 坐标空间nut.js 操作空间)
  logicalHeight: number;   // 屏幕逻辑高度
  physicalWidth: number;   // 屏幕物理宽度
  physicalHeight: number;  // 屏幕物理高度
  scaleFactor: number;     // DPI 缩放因子(物理/逻辑)
  displayIndex: number;    // 截图来源的显示器索引
}

3.4.9 CoordinateResolver坐标解析器

CoordinateResolver
  ├── 输入: model_x, model_y, coordinateMode, coordinateOrder, screenshotMeta, displayInfo
  ├── 输出: global_x, global_y
  ├── 四步: 坐标顺序修正 → 归一化(0~1) → 逻辑坐标 → 全局偏移
  └── coordinateMode/coordinateOrder 来源:
        ├── 内置模型配置表(已知模型自动匹配)
        └── 环境变量 GUI_AGENT_COORDINATE_MODE 手动覆盖

内置模型配置表(可扩展):

模型名匹配规则 坐标模式 坐标顺序 说明
claude-* image-absolute xy Anthropic Computer Use API 标准格式
gpt-4o*, gpt-5* image-absolute xy OpenAI CUA
gemini* normalized-999 yx Google Gemini坐标顺序是 [y, x] 而非 [x, y],这是 Google 训练数据的固有格式
ui-tars* normalized-1000 xy UI-TARS
qwen2.5-vl*, qwen-vl* image-absolute xy 通义千问 VL
cogagent* image-absolute xy CogAgent
seeclick*, showui* normalized-0-1 xy SeeClick/ShowUI
未匹配fallback image-absolute xy 保守策略

截图分辨率不按模型区分:统一使用分级缩放策略(见 3.4.4),不在模型配置表中配置目标分辨率。

Gemini 坐标顺序Gemini 输出坐标格式为 [y, x](非 [x, y]),这是 Google 训练数据的固有格式。CoordinateResolver 必须在归一化前根据 coordinateOrder 做 swap。

3.4.10 原子操作中的坐标处理

对于原子操作工具(gui_click 等),坐标由文本 Agent 提供。需要支持两种输入方式:

参数 说明
x, y 默认为逻辑坐标(文本 Agent 自己换算后传入)
coordinateMode? 可选,指定坐标格式。如果文本 Agent 直接传视觉模型输出的原始坐标,需声明格式以便 GUI Agent 转换

3.4.11 现有 Electron 内嵌版的问题

当前 systemPrompt.ts 中让 Agent 自己做坐标缩放计算("multiply pixel coords by 1/scale"),这是错误的

  • 视觉模型的坐标输出格式由训练数据决定,不是推理时可以改变的
  • 让 LLM 做数学乘除运算误差大,尤其是 Retina 下多层缩放
  • 正确做法:系统层自动完成全部坐标转换,模型只管输出它训练时学到的坐标格式

3.5 桌面操作能力

基于 nut.js@nut-tree/nut-js

能力 nut.js API 说明
截图 screen.capture() 全屏截图(始终全屏)
鼠标移动 mouse.moveTo(point) 移动光标(逻辑坐标)
鼠标点击 mouse.click(button) 左/右/中键
鼠标拖拽 mouse.drag(path) 从 A 拖到 B
滚动 mouse.scrollUp/Down(amount) 垂直滚动
键盘输入 keyboard.type(text) 输入文本
按键 keyboard.press(key) / release(key) 单键按下释放
图像查找 screen.find(image) / screen.findAll(image) 模板匹配
等待图像 screen.waitFor(image, timeout) 等待 UI 元素出现
光标位置 mouse.getPosition() 获取当前光标坐标(逻辑坐标)
显示器 系统 API 获取多显示器信息(含 scaleFactor

3.6 操作时序与可靠性

GUI 自动化的核心挑战不只是"点哪里",还包括"什么时候点"、"怎么输入"、"出了意外怎么办"。

3.6.1 操作后等待策略

问题点击按钮后UI 动画/页面跳转/弹窗需要时间渲染。如果操作后立即截图,截到的是过渡状态(动画中/加载中),视觉模型会误判。

等待策略(参考 TuriX-CUA 的分层延迟机制)

层级 延迟 说明
微操作延迟 10-30ms 单个鼠标事件内部mouseDown → mouseUp 之间)
连续操作间延迟 300-500ms 同一步骤内多个操作之间(如先点击再输入)
步骤间延迟 1-2s默认 1.5s Agent 循环中每步操作完成后、截图验证前的等待
错误重试延迟 5-10s API 限流或操作失败后的退避等待
执行操作 (click/type/etc.)
  │
  │  步骤间延迟 (默认 1.5s)
  │  目的: 等待 UI 动画完成、页面渲染稳定
  ▼
截取验证截图
  │
  │  发送给视觉模型分析
  ▼
决定下一步操作

可配置:通过环境变量 GUI_AGENT_STEP_DELAY_MS(默认 1500ms调整步骤间延迟。

特殊场景延迟Agent 循环中LLM 可以主动选择 wait 动作(如等待页面加载完成),此时额外等待指定时间后再截图。

3.6.2 截图时鼠标光标处理

问题:鼠标光标出现在截图中,可能遮挡 UI 元素(按钮文字、输入框等),影响视觉模型识别准确率。

策略

方案 说明 采用
截图前移动光标到角落 简单有效,但用户能看到光标跳动 备选
接受光标在截图中 最简单,依赖视觉模型忽略光标 v1 采用
操作后光标归位 参考 TuriX-CUA 的隐形点击:操作完成后光标恢复原位 v2 考虑

v1 方案:不主动处理光标,接受光标出现在截图中。原因:

  • 主流视觉模型Claude CU、GPT CUA的训练数据中包含光标能正确忽略
  • 移动光标本身是一次额外操作,增加复杂度和失败概率
  • 如果实测发现光标遮挡严重影响准确率v2 再引入光标隐藏

3.6.3 文本输入策略

问题 1CJK/非 ASCII 字符输入

nut.js 的 keyboard.type() 基于键码模拟,无法直接输入中文、日文、韩文等需要 IME输入法的字符。

解决方案:剪贴板粘贴

输入文本
  │
  │  检测: 是否包含非 ASCII 字符?
  │
  ├── 纯 ASCII + 短文本 → keyboard.type() 逐字模拟
  │
  └── 包含非 ASCII 或长文本 → 剪贴板粘贴模式:
        1. 保存当前剪贴板内容
        2. 将目标文本写入剪贴板
        3. 模拟 Cmd+V (macOS) / Ctrl+V (Windows/Linux)
        4. 恢复原剪贴板内容

参考TuriX-CUA 使用 macOS Quartz 的 CGEventKeyboardSetUnicodeString 直接发送 Unicode 事件,但这是 macOS 专用 API。我们需要跨平台方案剪贴板粘贴是最通用的方式。

问题 2长文本输入

keyboard.type() 逐字模拟击键,长文本(>50 字符)存在问题:

  • 速度慢(每字符需要 keyDown + keyUp 两个事件)
  • 中途焦点切换会导致文本输入到错误窗口
  • 可能触发系统的按键重复机制

策略

文本特征 输入方式 原因
纯 ASCII≤50 字符 keyboard.type() 逐字模拟 最自然,兼容性最好
纯 ASCII>50 字符 剪贴板粘贴 速度快,避免焦点丢失
包含非 ASCII 字符 剪贴板粘贴 IME 兼容

剪贴板恢复:粘贴完成后必须恢复用户原有的剪贴板内容,避免破坏用户的复制粘贴工作流。

3.6.4 意外弹窗处理

问题:操作过程中可能出现意外遮挡:

  • 系统通知macOS 通知中心、Windows Toast Notification
  • 权限请求弹窗("xxx 想要访问你的麦克风"
  • 应用自身的弹窗更新提醒、错误对话框、Cookie 同意)
  • 屏保/锁屏触发

策略(参考 TuriX-CUA依赖 LLM 识别)

v1 不做自动弹窗检测,交给 Agent 循环中的视觉模型处理:

  • 视觉模型截图后能看到弹窗
  • 在 system prompt 中明确指导 LLM如果发现意外弹窗/通知,优先关闭弹窗再继续任务
  • 连续多步操作未有效推进(前后截图无变化),触发 stuck 检测

system prompt 指导gui_execute_task 内部)

如果你在截图中发现:
- 系统通知弹窗:忽略,通知会自动消失
- 权限请求对话框:点击"允许"或"确定",然后继续任务
- 应用错误弹窗:点击"关闭"或"确定",然后继续任务
- 如果弹窗阻挡了目标区域且无法关闭,报告失败原因

stuck 检测:连续 N 步(默认 3 步)操作后截图无明显变化(可通过图像相似度粗判),判定为卡死,自动终止并返回错误。

3.6.5 多显示器坐标偏移

问题多显示器环境下OS 使用全局连续坐标系统。副屏的坐标原点不是 (0,0),而是相对于主屏有偏移:

┌──────────────┐┌──────────────────┐
│  Display 1   ││    Display 0     │
│  (副屏)      ││    (主屏)        │
│  origin:     ││  origin: (0,0)   │
│  (-2560, 0)  ││  size: 1440×900  │
│  size:       ││                  │
│  2560×1440   ││                  │
└──────────────┘└──────────────────┘

nut.js 的鼠标操作使用全局坐标。如果 GUI Agent 选择了副屏displayIndex=1点击该屏幕上的 (100, 200) 实际需要传给 nut.js 的是 (-2560+100, 0+200) = (-2460, 200)。

策略

CoordinateResolver 输出逻辑坐标 (local_x, local_y)
  │  这是相对于目标显示器左上角 (0,0) 的坐标
  │
  │  获取目标显示器的全局偏移量 (display.origin.x, display.origin.y)
  ▼
全局坐标: global_x = display.origin.x + local_x
         global_y = display.origin.y + local_y
  │
  ▼
nut.js mouse.click(global_x, global_y)

获取显示器偏移量

  • macOS: screen.getAllDisplays() 返回的 bounds.x/y 即为全局偏移
  • Windows: EnumDisplayMonitors + MONITORINFO.rcMonitor
  • Linux X11: XRRGetScreenResources + XRRCrtcInfo

截图也需要对应截图时只截取目标显示器的画面不是整个桌面。nut.js 的 screen.capture() 支持指定区域参数。

3.6.6 屏幕状态前置检查

问题:在执行 GUI 操作前,屏幕可能处于不可用状态。

状态 影响 处理
屏幕锁定/屏保 截图为锁屏画面,操作无效 检测到后返回错误,不继续操作
目标应用最小化 截图中看不到目标窗口 交给 LLM 判断(截图中无目标时自行处理)
系统模态对话框 阻止其他操作 同 3.6.4 弹窗处理

3.7 安全需求

S1: 危险操作防护

  • 阻断危险热键:Cmd+Q/Alt+F4(关闭应用)、Ctrl+Alt+Delete
  • 可配置的热键黑名单

S2: 审计日志

  • 记录所有操作:时间戳、操作类型、坐标/文本、成功/失败
  • 通过 MCP Resource gui://audit-log 可查询
  • 环形缓冲(默认 1000 条)

S3: 最大轮次限制

  • gui_execute_task 的 Agent 循环有 maxSteps 上限(默认 50
  • 超限后自动终止并返回当前状态和已完成的步骤

S4: 输入校验

  • 坐标范围校验
  • 文本长度限制≤10000 字符)
  • 请求体大小限制

3.8 平台需求

能力 macOS Windows Linux (X11) Linux (Wayland)
截图 需 Screen Recording 权限 无需 支持 受限
键鼠控制 需 Accessibility 权限 无需 需 xdotool 受限
图像查找 支持 支持 支持 受限

3.9 运行模式

模式 描述 入口
MCP Server 模式(主要) 作为持久化本地 HTTP 服务启动,多 Agent 通过 URL 连接 gui-agent --port 60008Streamable HTTPgui-agent --transport stdiostdio 备选)
SDK 嵌入模式 被 QimingClaw Electron 客户端等应用集成 import { createGuiAgentMcpServer }

注意:没有 CLI 交互模式。用户不直接与 GUI Agent 对话。


4. 技术选型

组件 选型 理由
Agent 框架 pi-mono (@mariozechner/pi-ai + @mariozechner/pi-agent-core) 极简、多 ProviderAnthropic/OpenAI/Gemini、tool calling 成熟、context compaction
桌面自动化 nut.js (@nut-tree/nut-js) 成熟的 Node.js 桌面自动化库,跨平台,支持截图/键鼠/图像查找
MCP SDK @modelcontextprotocol/sdk MCP 官方 SDK支持 stdio / Streamable HTTP
语言 TypeScript 与 pi-mono 和 nut.js 生态一致
运行时 Node.js 22+ nut.js 要求

5. 架构概览

5.1 进程模型

文本 Agent 进程 (claude-code / qimingcode)
  │
  │  spawn 子进程 (stdio) 或 HTTP 连接
  ▼
┌─────────────────────────────────────────────────────┐
│             GUI Agent 进程                            │
│                                                       │
│  ┌─────────────────────────────────────────────┐     │
│  │          MCP Server 层                        │     │
│  │  接收 MCP tool call → 分发到对应 handler       │     │
│  └──────┬──────────────────────┬───────────────┘     │
│         │                      │                      │
│    原子操作                  完整任务                  │
│    (直接执行)            (启动 Agent 循环)             │
│         │                      │                      │
│         ▼                      ▼                      │
│  ┌─────────────┐     ┌──────────────────┐            │
│  │  桌面工具层   │     │  pi-mono Agent   │            │
│  │  (nut.js)    │◄────│  循环引擎        │            │
│  │  截图/键鼠    │     │  LLM + Tools     │            │
│  └─────────────┘     └──────────────────┘            │
│         │                                             │
│  ┌─────────────┐                                     │
│  │  安全层      │                                     │
│  │  热键审计    │                                     │
│  └─────────────┘                                     │
└─────────────────────────────────────────────────────┘

5.2 两种调用路径对比

原子操作 (gui_click 等) 完整任务 (gui_execute_task)
决策者 文本 Agent GUI Agent 内部 LLM
步骤编排 文本 Agent 自行规划 GUI Agent 自主循环
LLM 调用 不涉及(纯工具执行) GUI Agent 调用 pi-ai
适用场景 简单操作、文本 Agent 有视觉能力时 复杂多步 GUI 任务
延迟 低(单次操作) 高(多轮 LLM + 多次截图)

5.3 与现有 Electron 内嵌版的关系

QimingClaw Electron 客户端
  │
  ├── 现有内嵌版 (HTTP API, src/main/services/gui/)
  │     特点: 依赖 Electron API, 工具层, 无自主决策
  │     适用: 引擎自己能看图决策时的简单 GUI 操作
  │
  └── 本项目 (MCP Server, 独立进程)
        特点: 独立于 Electron, 自带 LLM, 自主决策
        适用: 复杂 GUI 任务, 标准 MCP 集成, 任何 Agent 可调用

6. Electron 客户端集成需求crates/agent-electron-client 改造)

GUI Agent 作为独立 MCP 服务运行,但需要 QimingClaw Electron 客户端提供两项配套能力:显示器选择视觉模型配置接口

6.1 显示器选择

6.1.1 需求背景

  • 用户可能有多个显示器(如笔记本 + 外接屏)
  • GUI Agent 截图和操作需要明确针对哪一个显示器
  • 用户应在使用 GUI 操作前选择目标显示器,未选择则默认主显示器

6.1.2 功能描述

功能点 说明
显示器列表获取 调用 screen.getAllDisplays() 获取所有显示器信息名称、分辨率、scaleFactor、是否主屏
默认选择 未设置时默认使用主显示器(screen.getPrimaryDisplay()
用户可选 在 GUIAgentSettings.tsx 中提供下拉选择器,显示所有显示器及其分辨率
持久化 选择结果存入 SQLitegui_agent_config 中新增 displayIndex 字段)
热更新 显示器插拔时自动刷新列表;如果已选显示器被拔掉,回退到主显示器

6.1.3 配置扩展

GuiAgentConfig 新增字段:

interface GuiAgentConfig {
  // ... 现有字段 ...
  /** 目标显示器索引,默认 0主显示器 */
  displayIndex: number;
}

6.1.4 UI 设计

在 GUIAgentSettings.tsx 的配置区新增"目标显示器"选择器:

目标显示器:  [Display 0 (Primary) - 1440×900 @2x  ▼]
             ├── Display 0 (Primary) - 1440×900 @2x
             ├── Display 1 - 2560×1440 @1x
             └── Display 2 - 1920×1080 @1.5x
  • 显示格式:Display {index}{primary标记} - {width}×{height} @{scaleFactor}x
  • 选中后实时预览(可选:在目标显示器上短暂闪烁边框确认)

6.1.5 IPC 接口

IPC 方法 说明
guiAgent:getDisplays 返回当前所有显示器列表
guiAgent:setConfig({ displayIndex }) 持久化选择(复用现有 setConfig

6.1.6 传递给 GUI Agent

选中的 displayIndex 通过以下方式传递给独立 GUI Agent 进程:

  • MCP 模式:作为 MCP Server 启动参数或环境变量 GUI_AGENT_DISPLAY_INDEX
  • SDK 模式:直接传入配置对象

6.2 视觉模型配置 HTTP 接口

6.2.1 需求背景

  • GUI Agent 的 gui_execute_task 内部需要调用视觉模型LLM来分析截图并决策
  • 视觉模型的配置provider、model、api_key、base_url 等)需要一种方式传入
  • 复用现有 computerServer 的 agent 端口,新增一个 HTTP 路径来接收和管理视觉模型配置
  • 参考现有 /computer/chat 接口的 ModelProviderConfig 结构

6.2.2 接口设计

复用现有 computerServersrc/main/services/computerServer.ts),在 handleRequest 中新增路由:

接口 方法 路径 说明
设置视觉模型配置 POST /computer/gui-agent/vision-model 设置/更新视觉模型配置
获取视觉模型配置 GET /computer/gui-agent/vision-model 读取当前视觉模型配置
获取显示器列表 GET /computer/gui-agent/displays 返回所有显示器信息
设置目标显示器 POST /computer/gui-agent/display 设置目标显示器

6.2.3 视觉模型配置结构

参考现有 ModelProviderConfig/computer/chat 入参),定义视觉模型配置:

/** 视觉模型配置 — 用于 GUI Agent 内部的 gui_execute_task 决策 */
interface GuiVisionModelConfig {
  /** LLM Provider: "anthropic" | "openai" | "qwen" | "gemini" 等 */
  provider: string;
  /** API Key */
  api_key?: string;
  /** API Base URLOpenAI 兼容端点) */
  base_url?: string;
  /** 模型名称(如 "claude-sonnet-4-20250514", "gpt-4o", "qwen2.5-vl-72b" */
  model: string;
  /** API 协议: "anthropic" | "openai"(默认 "openai" */
  api_protocol?: string;
  /** 坐标模式覆盖(不传则按模型名自动匹配内置配置表) */
  coordinate_mode?: 'image-absolute' | 'normalized-1000' | 'normalized-999' | 'normalized-0-1';
}

设计决策jpeg_quality 不暴露给外部接口。

  • 截图分辨率:统一分级缩放(逻辑分辨率 + 最长边 ≤1920不按模型区分
  • JPEG 质量:内部默认 75清晰度与文件大小的最佳平衡无需外部配置

与现有 /computer/chatModelProviderConfig 对比:

字段 /computer/chat /computer/gui-agent/vision-model 说明
provider 相同
api_key 相同
base_url 相同
model 相同
api_protocol 相同
coordinate_mode 新增(可选) GUI 专用:视觉模型坐标格式,不传则按模型名自动匹配

6.2.4 请求/响应示例

POST /computer/gui-agent/vision-model — 设置视觉模型配置:

// Request
{
  "provider": "anthropic",
  "model": "claude-sonnet-4-20250514",
  "api_key": "sk-ant-xxx",
  "api_protocol": "anthropic"
}

// Response
{
  "code": 200,
  "success": true,
  "message": "Vision model config updated",
  "data": { "provider": "anthropic", "model": "claude-sonnet-4-20250514" }
}

GET /computer/gui-agent/vision-model — 获取当前配置(含内部自动推断的参数):

// Response
{
  "code": 200,
  "success": true,
  "data": {
    "provider": "anthropic",
    "model": "claude-sonnet-4-20250514",
    "api_protocol": "anthropic",
    "coordinate_mode": "image-absolute",
    "screenshot_strategy": "logical_resolution, max_longest_edge=1920",
    "jpeg_quality": 75
  }
}

GET 响应中的 screenshot_strategyjpeg_quality 仅供查看,不可通过 POST 设置。

GET /computer/gui-agent/displays — 获取显示器列表:

// Response
{
  "code": 200,
  "success": true,
  "data": {
    "displays": [
      { "index": 0, "label": "Display 0 (Primary)", "width": 1440, "height": 900, "scaleFactor": 2, "isPrimary": true },
      { "index": 1, "label": "Display 1", "width": 2560, "height": 1440, "scaleFactor": 1, "isPrimary": false }
    ],
    "selectedIndex": 0
  }
}

6.2.5 持久化

  • 视觉模型配置存入 SQLitekey = gui_vision_model_config
  • 显示器选择存入 SQLitegui_agent_config 中(同 6.1
  • 配置更新后通过 engineHooks 环境变量注入给 GUI Agent 进程

6.2.6 配置传递给 GUI Agent

Electron 客户端
  │
  ├── 用户在 Settings UI 配置视觉模型 + 选择显示器
  │     ↓ 持久化到 SQLite
  │
  ├── 外部调用 POST /computer/gui-agent/vision-model 配置
  │     ↓ 持久化到 SQLite
  │
  └── 启动 GUI Agent MCP Server 时
        ↓ 注入环境变量
        GUI_AGENT_PROVIDER=anthropic
        GUI_AGENT_MODEL=claude-sonnet-4-20250514
        GUI_AGENT_API_KEY=sk-xxx
        GUI_AGENT_BASE_URL=...
        GUI_AGENT_COORDINATE_MODE=image-absolute
        GUI_AGENT_DISPLAY_INDEX=0

7. 非目标v1 不做什么)

不做 理由
不做 CLI 交互模式 用户不直接使用,仅被文本 Agent 调用
不做 TUI / Web UI 同上,无需用户界面
不做浏览器专用自动化 不替代 Playwright/Puppeteer专注通用桌面 GUI
不做 RPA 流程编排 Agent 是 LLM 驱动的,不做可视化流程编辑
不做移动端 仅桌面macOS/Windows/Linux
不做远程桌面控制 仅控制本机桌面
不做实时视频流分析 逐帧截图方式
v1 不做 Accessibility Tree 先依赖纯视觉方案保持简单通用v2 可参考 TuriX-CUA 的 Accessibility Tree 标注辅助方案(截图上叠加元素编号,提升点击准确率)

8. 配置

8.1 环境变量

变量 说明 默认值
GUI_AGENT_PROVIDER LLM Provider (anthropic/openai/...) anthropic
GUI_AGENT_MODEL 模型名 claude-sonnet-4-20250514
GUI_AGENT_API_KEY API Key 必填
GUI_AGENT_BASE_URL API Base URLOpenAI 兼容端点) Provider 默认
GUI_AGENT_MAX_STEPS Agent 循环最大轮次 50
GUI_AGENT_STEP_DELAY_MS 步骤间等待时间 (ms),操作完成到截图验证之间 1500
GUI_AGENT_STUCK_THRESHOLD 连续无变化步数阈值,超过则判定卡死 3
GUI_AGENT_TRANSPORT MCP 传输方式 (http/stdio) http
GUI_AGENT_PORT HTTP 传输端口 60008
GUI_AGENT_COORDINATE_MODE 坐标模式覆盖 (image-absolute/normalized-1000/normalized-999/normalized-0-1) 自动按模型匹配
GUI_AGENT_JPEG_QUALITY JPEG 编码质量 (1-100),仅内部调试用 75
GUI_AGENT_DISPLAY_INDEX 目标显示器索引 0(主显示器)
GUI_AGENT_MEMORY_MODEL 记忆摘要用的模型(可选,可用更便宜的模型降低成本) 复用 GUI_AGENT_MODEL
GUI_AGENT_MEMORY_PROVIDER 记忆模型 Provider可选 复用 GUI_AGENT_PROVIDER

8.2 文本 Agent 的 MCP 配置示例

Streamable HTTP 模式(推荐,多 Agent 共享):

先启动 GUI Agent 服务:

GUI_AGENT_API_KEY=sk-xxx gui-agent --port 60008

文本 Agent 通过 URL 连接:

{
  "mcpServers": {
    "gui-agent": {
      "url": "http://127.0.0.1:60008/mcp"
    }
  }
}

stdio 模式(单 Agent 专用):

{
  "mcpServers": {
    "gui-agent": {
      "command": "npx",
      "args": ["-y", "@qiming-ai/gui-agent", "--transport", "stdio"],
      "env": {
        "GUI_AGENT_PROVIDER": "anthropic",
        "GUI_AGENT_MODEL": "claude-sonnet-4-20250514",
        "GUI_AGENT_API_KEY": "sk-xxx"
      }
    }
  }
}

9. 验收标准

9.1 MCP 服务

  • 可作为 MCP Server 启动stdio 模式)
  • 文本 Agent 可通过 MCP 调用所有原子操作工具13 个)
  • 文本 Agent 可通过 MCP 调用 gui_execute_task 执行完整 GUI 任务
  • gui_execute_task 返回结果包含步骤日志和最终截图
  • gui_execute_task 通过 MCP progress notification 推送每步进度
  • gui_execute_task 支持通过 MCP AbortSignal 取消执行
  • 互斥锁生效:同时只有一个 gui_execute_task 在执行

9.2 Agent 循环

  • gui_execute_task 内部可调用 Anthropic (Claude) 模型分析截图并决策
  • gui_execute_task 内部可调用 OpenAI 协议模型分析截图并决策
  • 可执行完整的"截图→分析→操作→验证"循环
  • maxSteps 限制生效

9.3 截图与坐标

  • 全屏截图正确返回 imageWidth/imageHeight/logicalWidth/logicalHeight/scaleFactor
  • 归一化千分比坐标 (0-1000) 正确转换为逻辑坐标并点击准确
  • 图片绝对坐标Qwen2.5-VL 格式)正确转换为逻辑坐标并点击准确
  • Retina/高 DPI 屏幕下坐标转换正确scaleFactor > 1
  • 截图缩放 (scale < 1.0) 不影响坐标转换的准确性
  • 模型类型自动匹配坐标模式,也可通过环境变量手动覆盖
  • 截图转 JPEG 后文件大小在 API 限制内Claude ≤~1.15MPGPT ≤20MB
  • JPEG quality 可配置,默认 75

9.4 操作时序与可靠性

  • 操作后有步骤间延迟(默认 1.5s),等待 UI 渲染完成后再截图
  • 中文/CJK 文本输入通过剪贴板粘贴正确工作
  • 长文本(>50 字符)自动切换为剪贴板粘贴模式
  • 剪贴板粘贴后恢复用户原有的剪贴板内容
  • 多显示器场景下,副屏坐标正确加上全局偏移量
  • 连续 N 步截图无变化时触发 stuck 检测并终止
  • Agent 循环 system prompt 包含意外弹窗处理指导

9.5 安全与稳定

  • 危险热键被阻断
  • 审计日志完整记录

9.6 平台兼容

  • macOS 上截图和键鼠均正常(授权后)
  • Windows 上截图和键鼠均正常
  • Linux X11 上截图和键鼠均正常

10. 开放问题

# 问题 影响 建议
Q1 pi-mono 不支持 MCPMCP Server 与 Agent 循环如何协同? 架构核心 MCP Server 是外壳,收到 gui_execute_task 后创建 pi-mono AgentSession 执行,完成后返回 MCP 结果。原子操作不经过 pi-mono
Q2 gui_execute_task 执行期间是阻塞还是异步? 调用体验 已解决同步阻塞。MCP SDK 原生支持长时间 tool call + progress notification + AbortSignal 取消。无需自定义 TaskQueue/gui_task_status/gui_abort_task
Q3 长任务的上下文管理? token 溢出 已解决三层记忆管理Summary/Recent/Pending+ LLM 摘要压缩 + pi-mono transformContext hook。见 3.2 及 Plan 3.7
Q4 nut.js prebuilt binary 分发? 安装体验 跟随 npm 安装自动下载,需评估离线场景
Q5 gui_execute_task 中 Agent 循环的模型,是否可以与文本 Agent 的模型不同? 灵活性 是,通过 model 参数覆盖,允许用更便宜/更快的模型做 GUI 决策
Q6 新增视觉模型的坐标格式如何扩展? 可维护性 内置模型映射表 + 环境变量覆盖。新增模型只需在映射表中加一行
Q7 多显示器场景下的坐标如何处理? 多屏用户 已解决,见 3.6.5 多显示器坐标偏移
Q8 Agent 循环中连续失败如何处理? 稳定性 已解决,见 3.6.4 stuck 检测 + 3.7 S4 最大轮次限制。参考 TuriX-CUA 连续失败计数器
Q9 截图中的历史图片如何管理 token token 爆炸 已解决pruneScreenshots 策略:保留最近 3 步完整截图,更早步骤移除 base64 替换为文字描述。见 Plan 3.7.5

11. 项目结构(初步)

模块位于 crates/agent-gui-server/,与现有模块平级:

crates/agent-gui-server/
├── src/
│   ├── index.ts                  # CLI 入口: 参数解析 + 启动 MCP Server
│   ├── lib.ts                    # SDK 入口: 导出 createGuiAgentMcpServer()
│   ├── config.ts                 # 统一配置: 环境变量解析、校验、Fail Fast
│   │
│   ├── mcp/                      # MCP 协议层(外部接口)
│   │   ├── server.ts             # MCP Server 实例 (stdio + HTTP 双模式)
│   │   ├── atomicTools.ts        # 13 个原子操作 tool handler
│   │   ├── taskTools.ts          # gui_execute_task 互斥执行 + 进度通知
│   │   └── resources.ts          # MCP Resources (status/permissions/audit)
│   │
│   ├── agent/                    # Agent 循环引擎gui_execute_task 内部)
│   │   ├── taskRunner.ts         # 循环核心: pi-mono Agent + 截图→LLM→操作
│   │   ├── systemPrompt.ts       # GUI Agent 专用 system prompt
│   │   ├── memoryManager.ts      # 三层记忆管理 (Summary/Recent/Pending) + LLM 摘要压缩
│   │   └── stuckDetector.ts      # 卡死检测: 连续截图相似度比对
│   │
│   ├── desktop/                  # 桌面操作层(底层能力封装,不依赖 MCP
│   │   ├── screenshot.ts         # 截图管线: capture → scale → JPEG → base64
│   │   ├── mouse.ts              # 鼠标操作 (nut.js mouse)
│   │   ├── keyboard.ts           # 键盘操作 (nut.js keyboard)
│   │   ├── clipboard.ts          # 剪贴板操作CJK/长文本粘贴、剪贴板备份恢复)
│   │   ├── display.ts            # 显示器信息
│   │   └── imageSearch.ts        # 图像查找 (nut.js template matcher)
│   │
│   ├── coordinates/              # 坐标系统(核心难点,独立目录)
│   │   ├── resolver.ts           # CoordinateResolver: 模型坐标 → 逻辑坐标 → 全局坐标
│   │   └── modelProfiles.ts      # 模型配置表: 坐标模式、坐标顺序、缩放策略
│   │
│   ├── safety/                   # 安全层
│   │   ├── hotkeys.ts            # 危险热键黑名单拦截
│   │   └── auditLog.ts           # 环形缓冲审计日志
│   │
│   └── utils/
│       ├── logger.ts             # 日志: stderr + 可选文件
│       ├── platform.ts           # 平台检测与权限检查
│       └── errors.ts             # 结构化错误类型
│
├── tests/                        # Vitest 测试
├── package.json
├── tsconfig.json
└── vitest.config.ts

12. 参考项目与借鉴

12.1 TuriX-CUAPython GUI Agent 参考实现)

项目: /Volumes/soddygo/git_work/TuriX-CUA — Python 实现的桌面 GUI 自动化 Agent。

架构亮点

设计 说明 对我们的启发
多角色 LLM 架构 Brain分析状态+ Actor生成操作+ Memory压缩历史各用不同 model 可考虑 gui_execute_task 内部用更便宜的模型做决策,视觉分析用强模型
三层记忆管理 Recent近期详细→ Summary历史摘要→ High-level全局总结token 接近上限时自动压缩 必须实现,否则每步带截图 base64 会迅速撑爆 context
坐标自动检测 if position > 1 则为 0-1000 格式,否则为 0-1 格式,运行时自动适配 简洁实用,我们的 CoordinateResolver 可以借鉴这种运行时检测
截图分级缩放 ≤1080p 不缩放2K-4K 缩 50%8K 缩 25%LANCZOS 重采样 比固定 scale 更合理,按实际分辨率自适应
Accessibility Tree 标注 截图上叠加编号框标注可交互元素(红/蓝/绿/黄/紫循环) v2 可引入,显著提升点击准确率
连续失败熔断 consecutive_failures 计数,超过 max_failures(默认 5自动终止 必须实现,防止 Agent 卡在无效循环
强制停止热键 pynput.keyboard.GlobalHotKeys 监听,用户可随时中断 MCP 模式下通过 AbortSignal客户端取消请求实现等价功能
隐形点击 鼠标事件通过 Quartz kCGHIDEventTap 直发,不移动光标 减少视觉干扰,但可能影响某些应用的响应
操作后等待 每步操作后固定等待 2s 再截图验证 等待是必要的UI 动画/渲染需要时间),但应可配置

TuriX-CUA 的局限(我们需要改进的)

局限 我们的方案
仅 macOS依赖 Quartz + Cocoa Accessibility API 跨平台nut.js 抽象层)
无 MCP 暴露 MCP Server 为唯一入口
坐标无显式 DPI 处理(依赖 pyautogui 隐式处理) 显式三层坐标空间 + CoordinateResolver
不支持 Claude computer_use 的图片绝对坐标格式 内置模型配置表,支持所有三大坐标家族
无多显示器支持 支持 displayIndex

12.2 社区 MCP 桌面自动化项目

调研结论:没有现成项目同时解决"多模型坐标适配 + 跨平台 DPI + MCP 暴露"。以下项目各有值得借鉴之处。

跨平台

项目 地址 亮点 局限
computer-use-mcp github.com/domdomegg/computer-use-mcp 最接近 Anthropic 官方方案MIT 协议 仅适配 Claude不处理多模型坐标差异
mcp-desktop-automation github.com/tanob/mcp-desktop-automation 基于 RobotJS 的通用桌面自动化 无 DPI 处理
mcp-pyautogui-server github.com/hetaoBackend/mcp-pyautogui-server PyAutoGUI 封装为 MCP PythonWayland 支持差

Windows 专项

项目 地址 亮点
precision-desktop github.com/ikoskela/precision-desktop DPI 校准方案:用 landmark 检测实际 scale factor不盲信 OS 报告值。影响 47% 高分屏用户
mcp-windows github.com/sbroenne/mcp-windows Win11 专用,正确处理多显示器 + DPI + 虚拟桌面

macOS 专项

项目 地址 亮点
mcp-desktop-pro github.com/lksrz/mcp-desktop-pro 显式 Retina 2x 支持窗口相对坐标AI 优化截图WebP 压缩)
mcp-remote-macos-use github.com/baryhuang/mcp-remote-macos-use 自动坐标缩放,支持远程 Mac 控制

Linux 专项

项目 地址 亮点
kwin-mcp github.com/isac322/kwin-mcp 解决 Wayland 问题29 个工具,隔离虚拟 KWin 会话支持无头环境CI
ubuntu-desktop-control github.com/charettep/ubuntu-desktop-control-mcp HiDPI 自动缩放 + 网格调试 overlay

标准参考

项目 地址 亮点
Anthropic quickstarts github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo 坐标缩放回映的标准参考实现Python
GUI-Actor (微软研究) microsoft.github.io/GUI-Actor 无坐标方案token-based grounding未来演进方向

12.3 关键借鉴总结

来源 借鉴点 落地位置
TuriX-CUA 三层记忆管理Recent/Summary/High-level 3.2 Agent 循环
TuriX-CUA 连续失败熔断机制 3.7 安全需求
TuriX-CUA 截图分级缩放(按分辨率自适应) 3.4.4 截图策略
TuriX-CUA 分层操作延迟(微操作/操作间/步骤间) 3.6.1 操作后等待策略
TuriX-CUA 依赖 LLM 截图分析处理弹窗 3.6.4 意外弹窗处理
TuriX-CUA Accessibility Tree 标注辅助v2 7. 非目标
precision-desktop DPI 校准(不盲信 OS 报告值) 3.4 坐标系统
mcp-desktop-pro 窗口相对坐标 未来优化
Anthropic quickstarts 截图缩放 + 坐标回映公式 3.4.5 坐标转换链路
GUI-Actor 无坐标 token-based 方案 未来研究方向

下一步: 基于本 Spec 编写 Plan技术方案和 Task执行任务文档