Feature: MCP 远程控制(remote_open/close/keyboard/mouse/clipboard) #4

Merged
yuanyuanxiang merged 8 commits from feature/mcp-remote-control into main 2026-08-26 12:05:24 +00:00
2 changed files with 485 additions and 1 deletions
Showing only changes of commit 84e3565de1 - Show all commits

View File

@@ -0,0 +1,484 @@
# MCP 远程控制Remote Control设计文档
> 版本v1设计基线 状态待开发 关联`docs/Mcp_Design.md`、`docs/Mcp_Terminal_Design.md`
> 定位本文件是「AI 远程控制一台 Windows 客户端(像人一样点鼠标、敲键盘、看画面完成任务)」功能的**唯一参照设计**,后续开发一律以本文为准,改需求先改本文。
---
## 1. 背景与目标
### 1.1 问题
现有 MCP 工具只能做「文本/命令行」层面的操作(`exec_command` 一次性命令、`terminal_*` 持久终端、`list_files`/`get_screenshot` 等只读)。很多真实任务只能在 **GUI 上完成**:点按钮、填表单、拖拽窗口、操作只有图形界面的软件。
AI 无法「看懂」视频流。把 25fps 的远程画面直接丢给视觉模型是行不通的——每秒 25 张、每张数十万像素,海量且绝大部分帧对决策无用。
### 1.2 目标
提供一组 MCP 工具,让 AI 以**「截图 → 观察 → 决策 → 注入单个输入动作 → 等待 → 重复」**的闭环驱动远程桌面:
- **Observe**按需取一帧解码后的位图JPEG按视觉模型能力缩放到合适尺寸。
- **Act**:注入鼠标/键盘事件,复用现有远程桌面的输入注入链路。
- **Decide**:由 AI/宿主Claude Code / 任何 MCP 客户端)在工具返回结果之上编排循环——**YAMA 只提供原语,不提供黑盒 `perform_task`**(与终端设计同一哲学)。
### 1.3 关键设计决策(已确认)
| # | 决策 | 理由 |
|---|---|---|
| D1 | **截图驱动,非视频流** | 每次决策一张图38s/步;视觉模型天然面向静态图 |
| D2 | **原语 + AI 编排,非黑盒任务** | 可控、可审、可中断AI 在循环外可插判断/确认 |
| D3 | **归一化坐标0..1** | 彻底规避「截图缩放 vs 注入物理像素」的比例错位(最佳实践第一大坑) |
| D4 | **复用现有注入链路** | `COMMAND_SCREEN_CONTROL` + `MSG64` + 客户端 `SendInput` 已成熟 |
| D5 | **Observe 复用 `get_screenshot`** | 主连接按需抓 JPEG无需常驻子连接最省 |
| D6 | **独立开关 + 全程审计** | 与终端同级:`McpRemoteControl=1 && McpReadonly=0`,默认关 |
---
## 2. 行业最佳实践借鉴
本节是「科学设计」的依据,逐条落到下文对应章节。主要参考 Anthropic 官方 Computer Use / Browser Use 最佳实践与参考实现(见文末「参考」)。
| 最佳实践 | 本设计落地 |
|---|---|
| 视觉模型是「规划者+眼睛」,宿主是「手」;工具调用=动作,返回=截图 | §3 循环归属YAMA=手AI=规划者 |
| **客户端先自行缩图**,绝不让模型 API 静默缩放(否则坐标系统性偏移) | §5客户端按 `max_width` 缩图后 JPEG 回传,模型见到的就是声明尺寸 |
| 声明 `display_width/height` 必须 == 实际发送的图尺寸 | §7归一化坐标服务器侧映射物理像素不依赖声明尺寸 |
| 动作原语集合move/click/drag/scroll/type/key/…) | §4`remote_mouse`/`remote_keyboard` 的动作枚举对齐 |
| 文本指令放在截图**之前**(内容顺序) | §8/宿主侧建议:由 MCP 客户端保证,本文仅记录 |
| 缩图上限:长边 ~1568px、总像素 ~1.15MPClaude 4.6 系);推荐 1280×720 | §5.3`max_width` 默认建议 1280可配 |
| 输入注入 vs 高危动作不可逆操作应人工确认human-in-the-loop | §9.4:审计 + 宿主确认YAMA 不做每动作确认(防延迟) |
| 提示注入是首要风险(模型处理不可信 UI 文本) | §9.5:全审计 + 只读关才可用 + 建议宿主隔离 |
| 长会话上下文管理(滚动缓冲/压缩) | §3宿主职责本文提示 |
---
## 3. 总体架构
```
┌──────────────────── 宿主Claude Code / MCP 客户端)────────────────────┐
│ 系统提示(含远程控制约定) + 任务文本 │
│ ↓ │
│ ┌──────────────────────── 智能体循环 ─────────────────────────┐ │
│ │ get_screenshot(max_width) ──→ 视觉模型看图 ──→ 决策 ──→ │ │
│ │ remote_mouse/remote_keyboard(归一化坐标/文本) ──→ 等待0.52s │ │
│ │ 循环,直到任务完成或模型判定需用户介入 │ │
│ └────────────────────────────────────────────────────────────┘ │
└───────────────────────────────┬───────────────────────────────────────┘
│ JSON-RPC over HTTP POST /mcp (Bearer)
┌───────────────────────────────▼───────────────────────────────────────┐
│ YAMA 服务器McpServer.cpp
│ · get_screenshotCOMMAND_SCREEN_PREVIEW_REQ → 客户端缩图 JPEG │
│ · remote_openCOMMAND_SCREEN_SPY → 建立屏幕子连接(控制会话) │
│ · remote_mouse/remote_keyboard归一化→物理像素 → MSG64 → │
│ COMMAND_SCREEN_CONTROL经屏幕子连接
└───────────────────────────────┬───────────────────────────────────────┘
│ 主连接(控制)+ 屏幕子连接(注入)
┌───────────────────────────────▼───────────────────────────────────────┐
│ 客户端ClientDll / ScreenManager / ScreenPreview
│ · CaptureAndEncodePreview抓主屏 + StretchBlt 缩图 + JPEG │
│ · ScreenManager.ProcessCommand解码 MSG64 → SendInput │
└───────────────────────────────────────────────────────────────────────┘
```
**核心事实(已核实,见 §13 源码索引)**
- **Observe** 走**主连接**、无子连接、无需控制模式:`COMMAND_SCREEN_PREVIEW_REQ`(247) → 客户端抓主屏缩图 JPEG → `TOKEN_SCREEN_PREVIEW_RSP`(248)。现有 `get_screenshot` 已完整实现。
- **Act注入必须走屏幕子连接**`COMMAND_SCREEN_CONTROL`(20) 只能经 `COMMAND_SCREEN_SPY`(16) 建立的子连接发送(`hub.go` 注释input events MUST go through the sub-connection。故需要一个 `remote_open` 建立/持有该子连接。
- 服务器端同时维护一张**原始 BGRA 32bpp DIB**`m_BitmapData_Full`),但 MCP 的 `get_screenshot` **不使用**它——它拿的是客户端已编码的 JPEG。两者坐标空间不同见 §7。
---
## 4. 工具集设计
### 4.1 工具清单
| 工具 | 作用 | 入参required 标注) | 出参 |
|---|---|---|---|
| `get_screenshot`**已有,复用** | Observe | `id`;可选 `max_width`(64..1920) | JPEG 图片(见 §5 |
| `remote_open` | 建立控制会话(屏幕子连接) | `id`;可选 `timeout_ms` | `session_id`(32hex)、`screen_w`/`screen_h`(物理虚拟桌面像素) |
| `remote_close` | 关闭会话 | `id``session_id` | `closed`(bool) |
| `remote_mouse` | 注入鼠标 | `id``session_id``action``x``y`+ 按 action 需 `x2`/`y2`/`button`/`delta`/`clicks` | `{}`(成功即空对象) |
| `remote_keyboard` | 注入键盘 | `id``session_id``action`+ 按 action 需 `text`/`key`/`modifiers` | `{}` |
> 命名对齐现有 `terminal_*`open/exec/closeObserve 沿用 `get_screenshot` 避免重复造轮子。鼠标/键盘各一个工具、用 `action` 枚举区分具体动作——既对齐 Anthropic 的 `computer` 工具动作集,又贴合现有 `WebService::HandleMouse/HandleKey` 的「鼠标/键盘分家」实现。
### 4.2 `remote_mouse` 动作枚举
| action | 参数 | 语义 |
|---|---|---|
| `move` | `x`,`y` | 移动光标(不按键) |
| `down` | `x`,`y`,`button`(left/middle/right) | 按下 |
| `up` | `x`,`y`,`button` | 抬起 |
| `click` | `x`,`y`,`button`(默认 left)、`clicks`(1/2/3默认 1) | 单击/双击/三击 |
| `right_click` | `x`,`y` | 右键(`click` 的便捷别名) |
| `middle_click` | `x`,`y` | 中键 |
| `drag` | `x`,`y`,`x2`,`y2`,`button`(默认 left) | 从 (x,y) 拖到 (x2,y2) |
| `scroll` | `x`,`y`,`delta`(+/- 滚动量)、`button`(可选,垂直默认) | 滚轮 |
### 4.3 `remote_keyboard` 动作枚举
| action | 参数 | 语义 |
|---|---|---|
| `type` | `text` | 输入一串文本(字符级映射,非粘贴) |
| `key_down` | `key``modifiers`(可选) | 按下组合键(如 `Ctrl`+`C` |
| `key_up` | `key``modifiers`(可选) | 抬起 |
| `key_press` | `key``modifiers`(可选) | 按下并抬起(最常用) |
`key` 取值对齐 Windows 虚拟键名(`VK_*` 去掉前缀,如 `ENTER`/`TAB`/`F5`/`LEFT`)与常见修饰键 `CTRL`/`ALT`/`SHIFT`/`WIN``type` 文本中的字符经客户端 `VkKeyScan`/Unicode 注入映射为键盘事件(非剪贴板粘贴,保证焦点内输入)。
### 4.4 可选/扩展工具(`remote_clipboard`、`remote_focus_window` 为 §17 代聊实验所需,建议随阶段一落地)
- `remote_clipboard`写远程剪贴板UTF-8→Unicode非 ASCII 文本输入的关键路径;复用 `COMMAND_C2C_TEXT`(90) 的 UTF-8 逻辑或新增 UTF-8 设剪贴板命令§6.4)。
- `remote_focus_window``COMMAND_SCREEN_WINDOW`(153) / 客户端 `SetForegroundWindow`,把焦点给到指定窗口(配合 `list_windows``hwnd`);代聊第一步。
- `remote_cursor_position`:读当前光标(`TOKEN_NEXTSCREEN` 帧头已带 `POINT cursor`)。
- `remote_screenshot`(会话内/窗口抓屏):从子连接 DIB 服务端缩图,或按窗口抓取(`COMMAND_SCREEN_WINDOW`供高分辨率读聊天文字与多显示器场景§12 阶段二、§17
---
## 5. 屏幕捕获与编码Observe
### 5.1 现有链路(复用)
`get_screenshot``McpServer.cpp` `BuildGetScreenshot`~1597已实现
1. `ParseHostIdArg``FindMainContext` → 能力门 `ctx->SupportsScreenPreview()``CLIENT_CAP_SCREEN_PREVIEW`,不支持 → `-32005`)。
2. `BeginPending(devId,"get_screenshot")` 设备级互斥(忙 → `-32003`)。
3. `reqId = NextPreviewReqId()``SetPendingReqId(devId, reqId)`stale-drop 机制)。
4. `parent->ChooseScreenPreviewParams(...)` 按 RTT/FRP 自适应缩图档位 + `max_width` 覆盖clamp 64..1920)。
5. `SendScreenPreviewRequest``COMMAND_SCREEN_PREVIEW_REQ`
6. `WaitPending``[ScreenPreviewRspHeader][JPEG]`,校验 `status==OK && format==JPEG`
7. base64 JPEG 返回:`structuredContent.image = {mimeType:"image/jpeg", width, height, bytes}``content=[{type:"image", data:<base64>, mimeType:"image/jpeg"}]`
客户端 `CaptureAndEncodePreview``client/ScreenPreview.cpp:107`)抓**主屏** + `StretchBlt`(HALFTONE) 缩图 + GDI+ `Bitmap::Save` JPEG——**位图永不离开客户端,只回传 JPEG**。
### 5.2 缩放策略(对齐最佳实践 D5
- 缩图在**客户端**完成(`max_width` 长边约束),服务器/MCP 宿主**不再二次缩放**,模型看到的就是声明尺寸——避免「模型 API 静默缩放导致坐标漂移」这个头号坑。
- `max_width` 由宿主按视觉模型能力显式传入YAMA 提供**默认建议 1280**(长边),上限维持 1920。
### 5.3 推荐参数
| 项 | 建议值 | 说明 |
|---|---|---|
| `max_width`(长边) | 1280默认/ 1568上限 | 1280×720 ≈ 80% 像素预算、训练常见分辨率 |
| 编码 | JPEGquality ~7085 | 客户端 `jpegQuality` 已有,按 `ChooseScreenPreviewParams` 自适应 |
| 返回 | base64 `image/jpeg` | 已实现 |
---
## 6. 输入注入Act
### 6.1 协议(复用,已核实)
- **包格式**`[cmd:1][MSG64:48]`,可批量重复(`commands.h` `COMMAND_SCREEN_CONTROL=20``MSG64` 15811623
- **MSG64 布局**`{uint64 hwnd, message, wParam, lParam, time; POINT pt;}` = 48 字节(`_WIN64``MYMSG``MSG`;客户端亦接受 28 字节 `MSG32`,由 `ulLength % 28/48` 判定)。
- **客户端解码**`ScreenManager.cpp:1175``ProcessCommand(1727)``SendInput`
- 鼠标:坐标取 `LOWORD/HIWORD(lParam)``WM_MOUSEMOVE``MOUSEEVENTF_MOVE``WM_LBUTTONDOWN/UP``LEFTDOWN/LEFTUP``WM_LBUTTONDBLCLK`→额外 `LEFTDOWN``WM_MBUTTONDOWN/UP``WM_MOUSEWHEEL``MOUSEEVENTF_WHEEL`(`mouseData=GET_WHEEL_DELTA_WPARAM`)。
- 键盘:`wVk=(WORD)wParam``wScan=(lParam>>16)&0xFF``KEYEVENTF_EXTENDEDKEY``(lParam>>24)&1`
### 6.2 服务端可复用实现
`WebService::HandleMouse``WebService.cpp:773-865`)与 `HandleKey``867-952`)已从 JSON`type/x/y/button/delta``keyCode/down/altKey`)构造 `MSG64``[COMMAND_SCREEN_CONTROL][MSG64]` 发送。其中 `HandleKey` 已正确处理 `lParam`repeat=1、`MapVirtualKey` 扫瞄码、扩展键表、Alt 上下文位 29、keyup 位 3031。
**结论**`remote_mouse`/`remote_keyboard` 是这两个函数的 MCP 薄封装——先归一化→物理像素,再套用其 `MSG64` 构造逻辑。**不要新写注入协议**。
### 6.3 子连接前置条件(关键约束)
注入**必须**经屏幕子连接:
1. 主连接发 `COMMAND_SCREEN_SPY`(16)(载荷 `{cmd, USING_DXGI|2, ALGORITHM_*, MultiScreen}`,见 `2015RemoteDlg.cpp:8443-8470`)→ 客户端开子连接(`ClientDll.cpp:89-94`)。
2. 客户端回 `TOKEN_BITMAPINFO`(含物理 `BITMAPINFOHEADER``biWidth/biHeight`)——**这是归一化坐标映射所需的物理分辨率来源**。
3. 服务器发 `COMMAND_NEXT`(30) 开始流/允许控制。
4. `GetScreenContext(device_id)``WebService.cpp:2084`)从 `m_ScreenContexts` 取该子连接上下文。
> 实现提示:上述 14 的建立/持有逻辑**耦合在 `CScreenSpyDlg`(隐藏对话框)里**——子连接、帧 DIB、`TOKEN_BITMAPINFO` 处理都由它完成。MCP 的 `remote_open` 应复用 `WebService::StartRemoteDesktop`(1725) 的「隐藏对话框」路径而非另造无对话框持有者§13.2)。
`remote_open` 即上述 14 的封装;`screen_w/screen_h` 取自 `TOKEN_BITMAPINFO`
### 6.4 文本输入ASCII 直注 vs 非 ASCII 走剪贴板
`remote_keyboard``type` 走物理键事件(`wVk`/`wScan``SendInput`),只能可靠注入 ASCII/ANSI 字符——**中文等非 ASCII 文本无法靠模拟键盘键入**(需 IME 组合,键事件层面做不到)。
非 ASCII 文本的可靠路径是**剪贴板 + 粘贴**
- 服务器设远程剪贴板命令 `COMMAND_SCREEN_SET_CLIPBOARD`(25),包 `[cmd:1][text:N]`,经屏幕子连接发送;客户端 `UpdateClientClipboard``ScreenManager.cpp:1223/1599`)落到剪贴板。
- 随后 `remote_keyboard` 注入 `Ctrl+V` 粘贴、`Enter` 发送。
- **编码注意(已核实)**`UpdateClientClipboard``SetClipboardData(CF_TEXT,...)`——ANSI/GBK 格式(`CF_TEXT`,非 `CF_UNICODETEXT`。MVP 下中文可经 `ToAnsi(utf8, 936)` 转 GBK 后直发(与终端命令同模式);但 emoji/非 GBK 字符会丢、部分新式聊天软件粘贴偏好 Unicode。更稳做法是复用 `COMMAND_C2C_TEXT`(90) 的 UTF-8→Unicode→`CF_UNICODETEXT` 逻辑(`KernelManager.cpp:1573-1587`,且已内置「设完剪贴板后自动模拟 Ctrl+V」或新增一个 UTF-8 设剪贴板变体。见 §17 实验案例。
---
## 7. 坐标约定与映射
### 7.1 归一化坐标D3
- AI 在**所有鼠标动作**里输出 **0..1 浮点**`x = 目标横向比例y = 目标纵向比例`,相对**它看到的那张截图**。
- 服务器侧映射:`phys_x = round(norm_x * screen_w)``phys_y = round(norm_y * screen_h)`,其中 `screen_w/h``remote_open` 时从 `TOKEN_BITMAPINFO` 得到的**物理捕获分辨率**。
- 好处:无论 `max_width` 怎么缩、显示器多大AI 永远只说「在图的 40%/60% 处点一下」,比例错位风险为零。
### 7.2 坐标空间一致性(最重要正确性点)
Observe`get_screenshot` 抓**主屏**)与 Act`SendInput` 绝对坐标在**虚拟桌面**空间)默认不同源。**阶段一只支持单显示器**:主屏 == 虚拟桌面,两者重合,映射严格正确。
多显示器(`MultiScreen`)在**阶段二**处理:届时 Observe 必须改抓**整个虚拟桌面**(而非主屏),`screen_w/h` 用虚拟桌面尺寸,归一化坐标才继续成立。**在阶段二落地前,`remote_open` 对多显示器配置应返回显式错误或降级为主屏**(见 §12
### 7.3 边界与钳制
- 归一化值越界(`<0``>1`)→ 钳制到 `[0,1]` 或返回 `-32602`(建议钳制,模型偶发 `1.0001`)。
- `phys_x/y` 不得越过 `screen_w/h`,注入前 `clamp(0, screen_w-1)`
---
## 8. 控制会话状态机
### 8.1 生命周期
```
remote_open ──► 建立子连接 ──► [remote_mouse/remote_keyboard × N + get_screenshot × M]
│ │
└──busy 互斥 / idle 回收 / 断线清理)──────────┴──► remote_close幂等
```
### 8.2 状态与不变量(镜像 `docs/Mcp_Terminal_Design.md` §并发)
- **单设备单控制会话**`remote_open` 对已存在会话 → `-32003`(忙)。
- **互斥**同一设备上MCP 远程控制会话与**人类远程桌面观看**`m_ScreenContexts` 已被占用)互斥——`remote_open` 发现已有屏幕子连接 → `-32003`,避免 AI 向人类正在观看/控制的画面注入。`remote_mouse/keyboard` 要求 `session_id` 匹配且会话未关闭。
- **busy 标志**:一条注入在飞时 `busy=true`,保证等待线程是唯一擦除者(沿用终端的 `m_TermMutex`+`busy` 模式;远程控制建议独立 `m_ScreenCtrlMutex`/`m_ScreenCtrlCv`)。
- **idle 回收**:会话闲置超时(默认 300s自动关闭并 `CancelIO`(镜像 `SweepIdleTerminals`)。
- **断线清理**`OfflineProc`/`OnTerminalClosed` 同位置加 `McpServer().OnScreenControlClosed(subCtx)`,防悬空 `subCtx` 被连接池复用(复刻终端设计里发现的第 2 个修复点)。
- **锁外 `CancelIO`**:所有取消动作在锁外执行(终端的 P2 教训)。
### 8.3 并发P5 教训)
`httplib` 多线程并发处理请求;`remote_mouse` 会高频调用。会话状态一律 `m_ScreenCtrlMutex` 串行化,**不要假定请求串行**。高频注入可加**节流**(沿用 `SendScaledMouseMessage` 的鼠标移动节流思路,`ScreenSpyDlg.cpp:2728-2743`)。
---
## 9. 安全模型
延续既有「可控、可关、可审、可隔离」原则(与终端同级):
### 9.1 开关与门控
- 新增 `McpRemoteControl`(默认 **0=关**)。
- `remote_*` 生效条件:`McpRemoteControl=1 && McpReadonly=0`,否则 `-32006`
- `get_screenshot` 是只读工具,不受此门控,仍仅受现有能力门(供纯观察场景)。
### 9.2 全程审计
每条 `remote_open/close/mouse/keyboard` 调用 → `PostMessageA(WM_SHOWERRORMSG, ...)``m_MessageLog``get_audit_log` 可查(复用 `McpServer.cpp:2050` 的审计模式,标题 `MCP远程控制`,标记「不可关闭」)。审计内容含:设备 id、动作、归一化坐标与换算后的物理坐标、session_id、时间戳。
### 9.3 会话级鉴权
- `session_id``GenerateRandomToken()`32hex每次 `remote_open` 独立随机,防串用(镜像终端 P6
- 注入工具校验 `session_id` 归属与设备匹配,不匹配 → `-32002`
### 9.4 不可逆操作与人工确认
- YAMA **不做逐动作确认**(会引入每步 0.5s+ 延迟,破坏闭环节奏)。
- 高危动作(提交表单、删除、付款、改系统设置)的确认由**宿主/AI 编排层**负责Claude Code 的权限系统 / 系统提示约定「不可逆操作前暂停询问用户」。本文记录此分工YAMA 提供审计留痕兜底。
### 9.5 提示注入Prompt Injection
视觉模型会读到客户端屏幕上的**不可信 UI 文本**,存在注入风险。缓解:远程控制本身即高危能力(默认关 + 只读关才开 + 全审计建议宿主将远程控制会话隔离在独立权限域并对「屏幕文本里的指令」保持怀疑。YAMA 侧不做内容分类(无意义且加延迟),记录即可。
---
## 10. 错误码
沿用现有约定(`BuildError(id, code, msg)`
| code | 含义 | 触发 |
|---|---|---|
| -32602 | 参数非法 | action 未知、必填参数缺失、`max_width` 越界、归一化值格式错 |
| -32000 | 通用失败 | 未分类错误 |
| -32001 | 超时 | `remote_open` 等子连接/`get_screenshot` 等帧超时 |
| -32002 | 设备/会话不存在 | `id` 无在线主机、`session_id` 不存在或不匹配 |
| -32003 | 设备/会话忙 | 已有控制会话、人类远程桌面占用、`get_screenshot` 在飞 |
| -32004 | 发送失败 | 注入命令发送失败 |
| -32005 | 能力不支持 | 客户端无 `CLIENT_CAP_SCREEN_PREVIEW`Observe/ 不支持屏幕控制Act |
| -32006 | 被开关禁用 | `McpRemoteControl=0``McpReadonly=1` |
| -32008 | 非法/被拒参数 | 多显示器未支持时的 `remote_open` 降级拒绝(阶段一) |
---
## 11. 配置项
新增 1 项(`THIS_CFG` `settings` 节,键名沿用蛇形):
| 键 | 默认 | 说明 |
|---|---|---|
| `McpRemoteControl` | `0` | 远程控制开关;要求 `McpReadonly=0` 才生效 |
其余沿用:`McpEnabled`/`McpPort`/`McpBind`/`McpToken`/`McpReadonly`/`McpTerminal`/`McpCmdWhitelist`。改动需重启生效(与终端一致)。
---
## 12. 已知限制与后续扩展
### 12.1 阶段一(本期)
- **单显示器**Observe 抓主屏,坐标空间与虚拟桌面重合;多显示器 `remote_open` 显式报错(`-32008`)。
- 注入基于**绝对坐标** `SendInput`,要求客户端处于可注入的桌面会话(已登录、非锁屏/非 UAC 安全桌面);锁屏/UAC 提示场景注入无效(客户端 `SendInput` 限制),设计文档记录为已知限制。
- **代聊实验使能项**:窗口抓屏(`COMMAND_SCREEN_WINDOW`、UTF-8 剪贴板、窗口聚焦随阶段一落地§17
### 12.2 阶段二(后续)
1. **多显示器虚拟桌面捕获**`get_screenshot` 增加虚拟桌面抓屏,或新增 `remote_screenshot` 从子连接 DIB 服务端缩图(复用 `BmpToJpeg`/`CacheThumbnail` GDI+ 路径),使 Observe/Act 同源。
2. **窗口定向**`remote_focus_window`(复用 `COMMAND_SCREEN_WINDOW` + 客户端 `SetForegroundWindow`)。
3. **光标读取 / 剪贴板**
4. **服务端缩图**:从 `m_BitmapData_Full`BGRA DIB直接按 API 上限缩图编码,进一步省客户端往返。
---
## 13. 改动清单(供实现参照)
> 行号以当前树为准(实现前请 grep 复核)。新增/改动尽量「复用」而非「重写」。
### 13.1 `server/2015Remote/McpServer.h`
- 新增 `struct ScreenCtrlSession`(仿 `TermSession``sessionId``deviceId``busy``started``closed``lastActiveAt``screenW/screenH`(来自 `TOKEN_BITMAPINFO`)、`subCtx` 引用。
- 新增方法:`SetRemoteControlEnabled/IsRemoteControlEnabled``BeginScreenCtrlOpen/…/CloseScreenCtrlSession/SweepIdleScreenCtrl``OnScreenControlClosed(context*)``ResolveScreenCtrlSessionHost`
- 新增成员:`bool m_remoteControlEnabled=false;``std::mutex m_ScreenCtrlMutex; std::condition_variable m_ScreenCtrlCv; std::map<context*,uint64_t> m_ScreenCtrlContextToDevice; std::map<uint64_t,ScreenCtrlSession> m_ScreenCtrlSessions;`(或复用 WebService 的 `m_ScreenContexts` 生命周期,见下)。
### 13.2 `server/2015Remote/McpServer.cpp`
- 新增 4 个 schema 构建器 + 3 个 handler`BuildRemoteOpen/Close/Mouse/Keyboard`(镜像 `BuildTerminalOpen` 等,`BuildToolsListResult` ~1278 后加条目,`BuildToolsCall` ~2124 加分派)。
- `remote_open``SweepIdleScreenCtrl``ResolveScreenCtrlSessionHost` → 已有会话 `-32003``GenerateRandomToken`**复用 Web 远程桌面的会话建立路径 `WebService::StartRemoteDesktop`(1725)**(发 `COMMAND_SCREEN_SPY` → 以 `SW_HIDE` 打开隐藏 `CScreenSpyDlg` 持有子连接 → 客户端子连接到达 → `RegisterScreenContext` 记入 `m_ScreenContexts`)→ 等 `TOKEN_BITMAPINFO``screenW/H``COMMAND_NEXT` → 审计 → 返回 `{session_id, screen_w, screen_h}`。**注意:屏幕子连接/帧 DIB/`TOKEN_BITMAPINFO` 处理都与 `CScreenSpyDlg` 耦合,不要另造无对话框持有者——沿 Web 的隐藏对话框路径是低风险正解。**
- `remote_mouse/keyboard``ResolveScreenCtrlSessionHost` → 校验 `session_id`/`busy` → 归一化→物理像素(`round(norm * screenW/H)` + clamp**复用 `WebService::HandleMouse/HandleKey` 的 `MSG64` 构造**(或抽取成共享 helper `BuildMouseMsg64/BuildKeyMsg64`,避免 MCP 与 Web 两处重复)→ `subCtx->Send2Client([COMMAND_SCREEN_CONTROL][MSG64])` → 审计。
- `remote_close`:幂等 `CloseScreenCtrlSession`(不存在 → `{closed:true}`sid 不匹配 → `-32002`)→ 锁外 `CancelIO` → 审计。
- `OnScreenControlClosed`:镜像终端修复点——空闲会话直接擦路由+会话busy 会话唤醒等待线程由其清理。
- `SweepIdleScreenCtrl`:镜像 `SweepIdleTerminals``difftime` 防时钟回拨)。
### 13.3 `server/2015Remote/McpSettingsDlg.h/.cpp`
- 新增复选框 `IDC_MCP_REMOTECONTROL = 1008`,文案 `_TR("启用远程控制AI 操控桌面)")`;回填/落盘 `McpRemoteControl`;对话框高度 +30。
### 13.4 `server/2015Remote/2015RemoteDlg.cpp`
- 启动读配置处加 `McpServer().SetRemoteControlEnabled(THIS_CFG.GetInt("settings","McpRemoteControl",0)!=0);`
- `OfflineProc``if (McpServer().IsRunning() && McpServer().IsScreenCtrlContext(ContextObject)) McpServer().OnScreenControlClosed(ContextObject);`(复刻终端修复点)。
### 13.5 语言文件
- 沿用终端约定MCP 相关文案本就在 `lang/*.ini` 无条目(`_TR` 对未命中键原样返回中文)。若日后翻译,用 **GBK 工具**改 `lang/{en_US,zh_TW}.ini`ANSI/GBK**不能用 Write/Edit 直接写**)。
---
## 14. 关键点(实现时务必遵守)
- **P1坐标一致性最重要**:归一化坐标映射所用 `screenW/H` 必须与**当前截图来源的分辨率**一致。阶段一单屏下 `TOKEN_BITMAPINFO` 的主屏尺寸 == 截图主屏物理尺寸,才成立;任何「用虚拟桌面尺寸映射主屏截图」都会造成系统性偏移(最佳实践第一大坑)。
- **P2注入走子连接**`COMMAND_SCREEN_CONTROL` **必须**经 `GetScreenContext` 取得的子连接发送,主连接无效。
- **P3锁外 CancelIO**:所有 `CancelIO``m_ScreenCtrlMutex` 之外(终端 P2 教训)。
- **P4复用勿重写**`MSG64` 构造逻辑复用 `WebService::HandleMouse/HandleKey`;理想做法抽共享 helper否则未来两处漂移。
- **P5并发串行化**`remote_mouse` 高频并发,状态全部走锁;勿假定请求串行(终端 P5 教训)。
- **P6session_id 独立随机 + 互斥)**:会话 token 用 `GenerateRandomToken`;单设备单会话;与人类远程桌面观看互斥(`-32003`)。
- **P7非黑盒**:不实现 `perform_task`;循环归宿主/AIYAMA 只出原语D2
---
## 15. 验证端到端127.0.0.1:6544Bearer `a2176213eddcf0acfdb285940ec4eb2a`
前置:设置开 `McpEnabled=1``McpReadonly=0``McpRemoteControl=1`;目标 Windows 客户端在线、已登录桌面。`id``list_online_hosts` 的十进制主机 id。
```bash
B='Authorization: Bearer a2176213eddcf0acfdb285940ec4eb2a'
U=http://127.0.0.1:6544/mcp
# 1) tools/list 应出现 remote_open/close/mouse/keyboard 四个工具
curl -s $U -H "$B" -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
# 2) 打开会话 → session_id + screen_w/h
curl -s $U -H "$B" -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"remote_open","arguments":{"id":"<HOSTID>"}}}'
# 3) Observe截图复用
curl -s $U -H "$B" -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_screenshot","arguments":{"id":"<HOSTID>","max_width":1280}}}'
# 4) Act移动 + 单击归一化坐标0.5,0.5 = 屏幕中心)
curl -s $U -H "$B" -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"remote_mouse","arguments":{"id":"<HOSTID>","session_id":"<SID>","action":"move","x":0.5,"y":0.5}}}'
curl -s $U -H "$B" -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"remote_mouse","arguments":{"id":"<HOSTID>","session_id":"<SID>","action":"click","x":0.5,"y":0.5,"button":"left"}}}'
# 5) 键盘:按下并抬起 Ctrl+Esc 或输入文本
curl -s $U -H "$B" -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"remote_keyboard","arguments":{"id":"<HOSTID>","session_id":"<SID>","action":"key_press","key":"WIN"}}}'
# 6) 负例:未开会话/错误 sid → -32002已开会话再 open → -32003只读/开关关 → -32006
# 7) 关闭 + 幂等关闭
curl -s $U -H "$B" -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":9,"method":"tools/call","params":{"name":"remote_close","arguments":{"id":"<HOSTID>","session_id":"<SID>"}}}'
# 8) 审计链
curl -s $U -H "$B" -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":12,"method":"tools/call","params":{"name":"get_audit_log","arguments":{}}}'
```
人工核对:注入后目标机光标/焦点确实变化审计列表出现标题「MCP远程控制」的 open/close/mouse/keyboard 条目;闲置 >300s 后旧 `session_id` 调用 → `-32002`。编译由你以 VS2019MSBuild v143完成我侧仅代码审查工具集 v142无法在此构建
---
## 16. 涉及文件
- `server/2015Remote/McpServer.h` / `.cpp`(会话状态机 + schema + handler + 分派)
- `server/2015Remote/McpSettingsDlg.h` / `.cpp`(新开关)
- `server/2015Remote/2015RemoteDlg.cpp`(启动读配置 + `OfflineProc` 清理钩子)
- `common/commands.h`**只读参照**`COMMAND_SCREEN_SPY`/`COMMAND_SCREEN_CONTROL`/`MSG64`/`COMMAND_SCREEN_PREVIEW_REQ` 等,无需改)
- `server/2015Remote/WebService.cpp`**复用参照**`HandleMouse`/`HandleKey`/`GetScreenContext`
- `lang/{en_US,zh_TW}.ini`可选GBK 工具改)
---
## 17. 实验案例AI 代聊chat-on-behalf
> 目标以「AI 通过远程桌面,在聊天软件里代表你与朋友聊天」为第一个实验用例,验证整条「看 → 想 → 动 → 确认」链路。低风险、目标清晰、信号明确(有新消息 / 已发出)。
### 17.1 闭环
1. `list_windows` 找到聊天窗口(`hwnd`/`title`)。
2. `remote_focus_window`(或 `remote_mouse` 点击输入框)聚焦聊天窗。
3. 抓**聊天窗口**高分辨率截图 → 视觉模型读出朋友最新消息。
4. 模型生成回复。
5. `remote_clipboard` 把回复写入远程剪贴板UTF-8
6. `remote_keyboard` 注入 `Ctrl+V`(粘贴)+ `Enter`(发送)。
7. 再截图确认已发出,等待下一条。
### 17.2 两个已验证的关键依赖
- **中文输入(走剪贴板,已解决)**:见 §6.4。`COMMAND_SCREEN_SET_CLIPBOARD`(25) 为 ANSI/GBK`CF_TEXT`),中文可经 `ToAnsi(utf8, 936)` 直发跑通 MVP要支持 emoji/完整 Unicode复用 `COMMAND_C2C_TEXT`(90) 的 UTF-8→`CF_UNICODETEXT` 逻辑或新增 UTF-8 变体。客户端 `KernelManager.cpp:1590` 已内置「设剪贴板后自动模拟 Ctrl+V」。
- **读消息(需窗口抓屏)**`get_screenshot` 抓整屏,缩到 1280 后聊天文字过小、OCR 易错。用 `COMMAND_SCREEN_WINDOW`(153) 按窗口抓取聊天窗(更高有效分辨率),或对该区域用更高 `max_width`
### 17.3 使能清单(相对现有代码的增量)
| 能力 | 状态 | 复用点 |
|---|---|---|
| `get_screenshot` / `list_windows` | ✅ 已有 | — |
| 剪贴板写入 | ⚠️ 客户端已有GBK 直发可跑通),缺 MCP 封装 | `COMMAND_SCREEN_SET_CLIPBOARD`(25) / `COMMAND_C2C_TEXT`(90) |
| `remote_open` / `remote_close`(屏幕子连接) | ❌ 待建 | `COMMAND_SCREEN_SPY`(16) 建立逻辑 `2015RemoteDlg.cpp:8443` |
| `remote_mouse` / `remote_keyboard` | ❌ 待建 | `WebService::HandleMouse/HandleKey` |
| `remote_focus_window` | ❌ 待建 | `COMMAND_SCREEN_WINDOW`(153) |
| `remote_clipboard` | ❌ 待建 | 见 §6.4 |
### 17.4 最小 MVP 实施顺序(按依赖)
1. `remote_open` / `remote_close`(屏幕子连接 + `TOKEN_BITMAPINFO` 取分辨率)。
2. `remote_keyboard`(先只做 `key_press`/`type` + 修饰键,够发 `Ctrl+V`/`Enter`)。
3. `remote_mouse``click`/`move`,用于点输入框;聚焦可用点击替代)。
4. `remote_clipboard`(先 GBK 直发跑通,再上 UTF-8 变体)。
5. `remote_focus_window`(或用步骤 3 的点击替代,二选一即可跑通)。
> 1→2→3 已足以发出一条消息(点输入框 + 粘贴 + 回车4 是中文关键路径5 是体验优化,可后置。
### 17.5 实验注意事项
- 目标机必须**已登录且未锁屏**(锁屏/UAC 安全桌面下抓屏与注入均失效§12.1)。
- 「代聊」会让朋友误以为是真人在聊——实验前建议告知对方;避免让 AI 在承诺/隐私等话术上自由发挥§9.4 的人工确认原则同样适用)。
- 新消息检测先用「轮询截图」即可MVP 接受),后续用未读角标/窗口通知优化§17.6)。
### 17.6 后续优化(非 MVP
- 新消息触发:从「定时截图」改为「未读角标/窗口通知驱动」,大幅省 token。
- 窗口定向抓屏(`COMMAND_SCREEN_WINDOW`)并入 `remote_screenshot`
- 会话历史AI 保留最近几轮聊天上下文避免回复脱节宿主侧滚动缓冲§3
---
## 参考(行业最佳实践)
- Anthropic《Best practices for computer and browser use with Claude》— https://claude.com/fr/blog/best-practices-for-computer-and-browser-use-with-claude
- Anthropic 参考实现 `computer.py`(动作枚举、缩放、坐标换算)— https://github.com/anthropics/claude-quickstarts/blob/main/computer-use-demo/computer_use_demo/tools/computer.py
- 《How Claude Computer Use Works: Architecture Internals》— https://callsphere.ai/blog/how-claude-computer-use-works-architecture-internals

View File

@@ -451,7 +451,7 @@ McpServer().SetTerminalEnabled(THIS_CFG.GetInt("settings", "McpTerminal", 0) !=
### 10.6 中文/乱码
- 输出乱码:检查 `isPty` 与 `cp` 是否匹配PTY=UTF-8老管道=GBK
- **`get_audit_log` 中文乱码**已知问题(GBK 写入 UTF-8 JSON影响**所有**审计条目,非终端特有,暂未修复
- **`get_audit_log` 中文正确**`BuildGetAuditLog` 用 `ToUtf8(type/time/msg, 936)` 把 GBK UTF-8jsoncpp 输出 `\uXXXX` 转义,客户端解析后中文正常(已实测 `MCP持久终端`/`MCP命令执行`/`操作成功`/`主机上线` 等均无乱码)
### 10.7 会话泄漏 / 子链接不关