|
| 1 | +# Windows MCP + UIAutomation 集成设计方案 |
| 2 | + |
| 3 | +## 1. 现状分析 |
| 4 | + |
| 5 | +### 1.1 当前 Agent 执行链 |
| 6 | + |
| 7 | +``` |
| 8 | +用户消息 → main_server (ZMQ) → agent_server |
| 9 | + → DirectTaskExecutor.analyze_and_execute() |
| 10 | + → 并行评估: |
| 11 | + ├─ _assess_computer_use() → ComputerUseDecision |
| 12 | + ├─ _assess_browser_use() → BrowserUseDecision |
| 13 | + └─ _assess_user_plugin() → UserPluginDecision |
| 14 | + → 决策优先级: UserPlugin > BrowserUse > ComputerUse |
| 15 | + → agent_server dispatch (队列调度 / 直接执行) |
| 16 | +``` |
| 17 | + |
| 18 | +### 1.2 当前 ComputerUse 的工作方式 |
| 19 | + |
| 20 | +`brain/computer_use.py` 中的 `ComputerUseAdapter.run_instruction()`: |
| 21 | + |
| 22 | +``` |
| 23 | +循环 (最多 50 步): |
| 24 | + 1. pyautogui.screenshot() ← 硬编码,前台截图 |
| 25 | + 2. compress_screenshot() → jpg |
| 26 | + 3. VLM predict() → thought + action + code |
| 27 | + 4. exec(code) with _ScaledPyAutoGUI ← 硬编码,前台操作 |
| 28 | +``` |
| 29 | + |
| 30 | +**问题:** |
| 31 | +- 每步都需要 VLM 推理(慢、费 token) |
| 32 | +- 只能操作前台(抢占用户桌面) |
| 33 | +- 通过坐标操作(受 DPI/分辨率影响) |
| 34 | +- 没有语义理解(不知道控件是什么,只看像素) |
| 35 | + |
| 36 | +### 1.3 已有的 MCP 基础设施 |
| 37 | + |
| 38 | +`plugin/plugins/mcp_adapter/` 已实现完整的 MCP Client: |
| 39 | +- 支持 stdio / SSE / streamable-http 三种 transport |
| 40 | +- 自动发现 MCP server 的 tools 并注册为 NEKO entries |
| 41 | +- 通过 UserPlugin 执行路径调用 MCP tools |
| 42 | +- 有 reconnect、error handling、payload normalization |
| 43 | + |
| 44 | +--- |
| 45 | + |
| 46 | +## 2. 设计目标 |
| 47 | + |
| 48 | +1. **引入 UIAutomation 快车道**:标准 Windows 控件操作直接走 UIAutomation(毫秒级,零 VLM token) |
| 49 | +2. **保留 VLM 兜底**:UIAutomation 搞不定的场景(自绘 UI、游戏等)回退到现有 ComputerUse |
| 50 | +3. **复用已有 MCP adapter**:不重新造轮子,通过 mcp_adapter 桥接 Windows MCP 服务器 |
| 51 | +4. **支持后台窗口操作**:UIAutomation 天然支持后台控件交互,为未来虚拟桌面方案铺路 |
| 52 | +5. **对现有代码侵入最小**:不改 ComputerUse 核心逻辑,新增平行路径 |
| 53 | + |
| 54 | +--- |
| 55 | + |
| 56 | +## 3. 架构设计 |
| 57 | + |
| 58 | +### 3.1 整体架构 |
| 59 | + |
| 60 | +``` |
| 61 | + DirectTaskExecutor.analyze_and_execute() |
| 62 | + │ |
| 63 | + ┌───────────────────┼───────────────────┐ |
| 64 | + │ │ │ |
| 65 | + _assess_uia() _assess_computer_use() _assess_browser_use() |
| 66 | + (NEW - 新增) (现有) (现有) |
| 67 | + │ │ │ |
| 68 | + UIADecision CUDecision BUDecision |
| 69 | + │ │ │ |
| 70 | + ▼ ▼ ▼ |
| 71 | +
|
| 72 | + 决策优先级: UserPlugin > UIA > BrowserUse > ComputerUse |
| 73 | + ^^^ |
| 74 | + (新增,高于 BU/CU) |
| 75 | +
|
| 76 | + 执行路径: |
| 77 | + ┌─────────────────────────────────────────────────────┐ |
| 78 | + │ execution_method == "uia" │ |
| 79 | + │ → 通过 mcp_adapter 调用 Windows MCP Server │ |
| 80 | + │ → MCP Server 内部走 UIAutomation / pywinauto │ |
| 81 | + │ → 返回操作结果(文本/截图/状态) │ |
| 82 | + └─────────────────────────────────────────────────────┘ |
| 83 | + ┌─────────────────────────────────────────────────────┐ |
| 84 | + │ execution_method == "computer_use" │ |
| 85 | + │ → 现有 VLM + pyautogui 流程(完全不变) │ |
| 86 | + └─────────────────────────────────────────────────────┘ |
| 87 | +``` |
| 88 | + |
| 89 | +### 3.2 Windows MCP Server 选型 |
| 90 | + |
| 91 | +推荐 **pywinauto-mcp** 或 **Windows-MCP.Net** 作为外部 MCP Server,理由: |
| 92 | + |
| 93 | +| 特性 | pywinauto-mcp | Windows-MCP.Net | |
| 94 | +|------|--------------|-----------------| |
| 95 | +| UIAutomation 支持 | ✓ (pywinauto 底层) | ✓ (.NET UIA) | |
| 96 | +| 后台窗口操作 | 部分支持 | 部分支持 | |
| 97 | +| OCR | 需额外配置 | 内置 | |
| 98 | +| MCP 协议版本 | 3.1 (stdio) | stdio | |
| 99 | +| 语言 | Python | C# (.NET 8) | |
| 100 | +| 与 NEKO 技术栈 | 同栈(Python) | 异栈 | |
| 101 | + |
| 102 | +**建议优先用 pywinauto-mcp**(Python 同栈,如需定制可以直接改源码)。 |
| 103 | + |
| 104 | +### 3.3 MCP Server 配置 |
| 105 | + |
| 106 | +在 NEKO 的 MCP adapter 配置中添加 Windows 桌面自动化 server: |
| 107 | + |
| 108 | +```json |
| 109 | +{ |
| 110 | + "name": "windows-desktop", |
| 111 | + "transport": "stdio", |
| 112 | + "command": "python", |
| 113 | + "args": ["-m", "pywinauto_mcp"], |
| 114 | + "enabled": true |
| 115 | +} |
| 116 | +``` |
| 117 | + |
| 118 | +mcp_adapter 会自动发现该 server 暴露的 tools(如 `automation_windows`, `automation_elements`, `automation_mouse`, `automation_keyboard`, `automation_visual`)并注册为 NEKO entries。 |
| 119 | + |
| 120 | +--- |
| 121 | + |
| 122 | +## 4. 代码改动详细设计 |
| 123 | + |
| 124 | +### 4.1 新增 `UIADecision` 数据类 |
| 125 | + |
| 126 | +**文件**: `brain/task_executor.py` |
| 127 | + |
| 128 | +```python |
| 129 | +@dataclass |
| 130 | +class UIADecision: |
| 131 | + """UIAutomation (via Windows MCP) 可行性评估结果""" |
| 132 | + has_task: bool = False |
| 133 | + can_execute: bool = False |
| 134 | + task_description: str = "" |
| 135 | + tool_name: str = "" # MCP tool 名称,如 "automation_elements" |
| 136 | + tool_args: Dict = None # MCP tool 参数 |
| 137 | + reason: str = "" |
| 138 | +``` |
| 139 | + |
| 140 | +### 4.2 新增 `_assess_uia()` 方法 |
| 141 | + |
| 142 | +**文件**: `brain/task_executor.py` |
| 143 | + |
| 144 | +核心思路:判断任务是否可以通过 UIAutomation 的语义操作完成(而非截图+坐标)。 |
| 145 | + |
| 146 | +```python |
| 147 | +async def _assess_uia( |
| 148 | + self, |
| 149 | + conversation: str, |
| 150 | + uia_tools: List[Dict[str, Any]], # 从 mcp_adapter 获取的 windows-desktop tools |
| 151 | +) -> UIADecision: |
| 152 | + """ |
| 153 | + 评估任务是否适合 UIAutomation(精确控件操作)而非 VLM+坐标。 |
| 154 | +
|
| 155 | + 适合 UIA 的场景: |
| 156 | + - 打开/关闭/切换已知应用 |
| 157 | + - 点击已知名称的按钮/菜单 |
| 158 | + - 在文本框中输入内容 |
| 159 | + - 读取窗口/控件中的文本 |
| 160 | + - 操作标准 Windows 控件(列表、树形、选项卡等) |
| 161 | +
|
| 162 | + 不适合 UIA 的场景: |
| 163 | + - 操作目标是屏幕上某个视觉位置("点击左上角的图标") |
| 164 | + - 目标应用使用自绘 UI(游戏、某些 Electron 应用) |
| 165 | + - 需要视觉理解("看看屏幕上显示了什么") |
| 166 | + """ |
| 167 | + if not uia_tools: |
| 168 | + return UIADecision(has_task=False, can_execute=False, reason="No UIA tools available") |
| 169 | + |
| 170 | + # ... LLM 评估逻辑,与 _assess_computer_use 结构类似 |
| 171 | + # 但 system_prompt 专注于判断"这个任务能否通过控件名/控件类型精确操作完成" |
| 172 | +``` |
| 173 | + |
| 174 | +**评估 prompt 的关键区分点**: |
| 175 | + |
| 176 | +``` |
| 177 | +你是一个 Windows 桌面自动化评估 agent。判断用户的任务是否可以通过 |
| 178 | +UIAutomation(按控件名称/类型精确操作)完成,还是必须通过截图+坐标操作。 |
| 179 | +
|
| 180 | +可以用 UIAutomation 的例子: |
| 181 | +- "打开记事本" → 可以,通过 shell 或 automation_system 启动 |
| 182 | +- "在搜索框里输入xxx" → 可以,定位 Edit 控件并输入 |
| 183 | +- "点击'保存'按钮" → 可以,按名称定位 Button 控件并 Invoke |
| 184 | +- "把音量调到50%" → 可以,通过 automation_system |
| 185 | +
|
| 186 | +必须用截图+坐标的例子: |
| 187 | +- "点击屏幕中间那个红色图标" → 不行,需要视觉定位 |
| 188 | +- "看看网页上显示了什么" → 不行,需要截图理解 |
| 189 | +- "在游戏里移动角色" → 不行,游戏 UI 没有 UIA 控件树 |
| 190 | +``` |
| 191 | + |
| 192 | +### 4.3 修改 `analyze_and_execute()` 决策逻辑 |
| 193 | + |
| 194 | +**文件**: `brain/task_executor.py` |
| 195 | + |
| 196 | +在现有的并行评估中增加 UIA 分支: |
| 197 | + |
| 198 | +```python |
| 199 | +# === 新增:获取 UIA (Windows MCP) 工具列表 === |
| 200 | +uia_tools = [] |
| 201 | +uia_enabled = agent_flags.get("uia_enabled", False) |
| 202 | +if uia_enabled: |
| 203 | + # 从 mcp_adapter 获取 windows-desktop server 的 tools |
| 204 | + uia_tools = await self._get_uia_tools() |
| 205 | + |
| 206 | +# 并行评估 |
| 207 | +if uia_enabled and uia_tools: |
| 208 | + assessment_tasks.append(('uia', self._assess_uia(conversation, uia_tools))) |
| 209 | + |
| 210 | +# ... 现有的 cu / bu / up 评估 ... |
| 211 | + |
| 212 | +# 决策优先级调整: |
| 213 | +# 1. UserPlugin (精确匹配用户插件) |
| 214 | +# 2. UIA (精确控件操作,零 VLM 开销) ← 新增 |
| 215 | +# 3. BrowserUse (网页自动化) |
| 216 | +# 4. ComputerUse (VLM + 坐标,兜底) |
| 217 | +``` |
| 218 | + |
| 219 | +### 4.4 新增 UIA 执行路径 |
| 220 | + |
| 221 | +**文件**: `agent_server.py` |
| 222 | + |
| 223 | +在 `_do_analyze_and_plan()` 的 dispatch 逻辑中新增: |
| 224 | + |
| 225 | +```python |
| 226 | +elif result.execution_method == 'uia': |
| 227 | + # UIA 任务通过 mcp_adapter plugin 执行 |
| 228 | + # 复用 user_plugin 的执行路径,因为 MCP tools 已经注册为 NEKO entries |
| 229 | + if Modules.agent_flags.get("uia_enabled", False) and Modules.task_executor: |
| 230 | + # ... 与 user_plugin dispatch 类似的逻辑 |
| 231 | + # 通过 _execute_user_plugin() 调用 mcp_adapter 暴露的 entry |
| 232 | +``` |
| 233 | + |
| 234 | +### 4.5 agent_flags 新增开关 |
| 235 | + |
| 236 | +**文件**: `agent_server.py` (Modules 类) |
| 237 | + |
| 238 | +```python |
| 239 | +agent_flags: Dict[str, Any] = { |
| 240 | + "computer_use_enabled": False, |
| 241 | + "browser_use_enabled": False, |
| 242 | + "user_plugin_enabled": False, |
| 243 | + "uia_enabled": False, # ← 新增 |
| 244 | +} |
| 245 | +``` |
| 246 | + |
| 247 | +前端设置界面对应新增一个"Windows 智能操作 (UIAutomation)"开关。 |
| 248 | + |
| 249 | +### 4.6 UIA 工具发现 |
| 250 | + |
| 251 | +**文件**: `brain/task_executor.py` |
| 252 | + |
| 253 | +```python |
| 254 | +async def _get_uia_tools(self) -> List[Dict[str, Any]]: |
| 255 | + """从 mcp_adapter 获取 windows-desktop MCP server 的工具列表""" |
| 256 | + plugins = await self.plugin_list_provider(force_refresh=False) |
| 257 | + uia_tools = [] |
| 258 | + for p in plugins: |
| 259 | + if not isinstance(p, dict): |
| 260 | + continue |
| 261 | + pid = p.get("id", "") |
| 262 | + # mcp_adapter 注册的 entries 的 id 格式为 "mcp_{server_name}_{tool_name}" |
| 263 | + # 匹配 windows-desktop server 的 tools |
| 264 | + entries = p.get("entries", []) |
| 265 | + for entry in entries: |
| 266 | + eid = entry.get("id", "") if isinstance(entry, dict) else "" |
| 267 | + if "automation_" in eid or "windows" in pid.lower(): |
| 268 | + uia_tools.append(entry) |
| 269 | + return uia_tools |
| 270 | +``` |
| 271 | + |
| 272 | +--- |
| 273 | + |
| 274 | +## 5. 执行流程对比 |
| 275 | + |
| 276 | +### 场景 A: "打开计算器" |
| 277 | + |
| 278 | +**现有流程 (ComputerUse):** |
| 279 | +``` |
| 280 | +1. pyautogui.screenshot() → 300ms |
| 281 | +2. VLM: "看到桌面,需要打开计算器" → 2-5s, ~500 tokens |
| 282 | +3. exec: pyautogui.hotkey('win') → 100ms |
| 283 | +4. pyautogui.screenshot() → 300ms |
| 284 | +5. VLM: "看到开始菜单搜索框" → 2-5s, ~500 tokens |
| 285 | +6. exec: pyautogui.write('calculator') → 200ms |
| 286 | +7. pyautogui.screenshot() → 300ms |
| 287 | +8. VLM: "看到搜索结果" → 2-5s, ~500 tokens |
| 288 | +9. exec: pyautogui.press('enter') → 100ms |
| 289 | +总计: ~10-18s, ~1500 tokens, 3次 VLM 调用 |
| 290 | +``` |
| 291 | + |
| 292 | +**新流程 (UIA via MCP):** |
| 293 | +``` |
| 294 | +1. LLM assess: "打开计算器 → UIA 可处理" → 1-2s, ~200 tokens (一次性) |
| 295 | +2. MCP call: automation_system.launch("calc.exe") → 200ms |
| 296 | +总计: ~1-2s, ~200 tokens, 1次 LLM 调用 (仅评估) |
| 297 | +``` |
| 298 | + |
| 299 | +### 场景 B: "点击屏幕上那个蓝色按钮" |
| 300 | + |
| 301 | +**新流程:** |
| 302 | +``` |
| 303 | +1. LLM assess: "蓝色按钮 → 需要视觉定位 → UIA 不适合" |
| 304 | +2. Fallback to ComputerUse (现有流程,不变) |
| 305 | +``` |
| 306 | + |
| 307 | +--- |
| 308 | + |
| 309 | +## 6. 未来扩展:虚拟桌面集成点 |
| 310 | + |
| 311 | +当前设计中,UIA 路径与 ComputerUse 路径完全解耦。未来接入虚拟桌面时: |
| 312 | + |
| 313 | +``` |
| 314 | +ScreenBackend (抽象层) |
| 315 | + ├─ ForegroundBackend → pyautogui (现有) |
| 316 | + └─ VirtualDesktopBackend → CreateDesktop / RDP / Agent Workspace |
| 317 | + ↑ |
| 318 | + UIA 路径天然支持后台窗口 |
| 319 | + 只需要将 MCP Server 启动在 |
| 320 | + 虚拟桌面的会话中即可 |
| 321 | +``` |
| 322 | + |
| 323 | +UIA 路径(通过 MCP Server)只需要把 MCP Server 进程启动在虚拟桌面/RDP 会话里,NEKO 侧代码零改动。 |
| 324 | + |
| 325 | +--- |
| 326 | + |
| 327 | +## 7. 改动文件清单 |
| 328 | + |
| 329 | +| 文件 | 改动 | 工作量 | |
| 330 | +|------|------|--------| |
| 331 | +| `brain/task_executor.py` | 新增 UIADecision, _assess_uia(), _get_uia_tools(), 修改 analyze_and_execute() 决策逻辑 | 中 | |
| 332 | +| `agent_server.py` | 新增 uia dispatch 分支, agent_flags 新增 uia_enabled | 小 | |
| 333 | +| `config/` | MCP server 配置新增 windows-desktop | 小 | |
| 334 | +| `static/` + `templates/` | 前端设置新增 UIA 开关 | 小 | |
| 335 | +| `brain/result_parser.py` | 新增 parse_uia_result() | 小 | |
| 336 | +| (外部) pywinauto-mcp | 安装 + 配置为 MCP Server | 配置 | |
| 337 | + |
| 338 | +**总工作量估算:2-3 天核心开发,1 天集成测试。** |
| 339 | + |
| 340 | +--- |
| 341 | + |
| 342 | +## 8. 风险与缓解 |
| 343 | + |
| 344 | +| 风险 | 缓解措施 | |
| 345 | +|------|---------| |
| 346 | +| pywinauto-mcp 的 UIA 工具覆盖不全 | 可以 fork 后补充自定义 tools;MCP 协议让替换 server 实现零成本 | |
| 347 | +| LLM 评估误判(该走 UIA 的走了 CU,或反过来) | 初期可以加规则硬匹配(如"打开xxx"直接走 UIA),减少 LLM 依赖 | |
| 348 | +| Windows MCP Server 进程崩溃 | mcp_adapter 已有 reconnect 机制 | |
| 349 | +| 部分应用 UIA 控件树残缺 | assess 阶段已考虑 fallback to CU | |
| 350 | +| Docker 部署环境无 Windows | UIA 路径自动禁用,不影响现有功能 | |
0 commit comments