Files
cs-note/claude-code-best/docs/features/computer-use-architecture-v2.md

11 KiB
Raw Permalink Blame History

tags, create time
tags create time
computer-use
架构
windows
跨平台
SendMessage
2026-06-09 22:30

Computer Use 架构修正方案 v2

概述

针对 Computer Use v1 架构中的三个核心问题——平台代码混放、全局输入干扰用户、截图能力归属不清——提出的修正方案。核心思路是将跨平台抽象层 platforms/ 从 @ant/ 包中独立出来,Windows 使用 SendMessage 无焦点输入替代全局 SendInput。

[!info] 更新时间:2026-04-04

正文

1. 当前架构的问题

问题 A:平台代码混在错误的包里

@ant/computer-use-swift 是 macOS Swift 原生模块的包装器,但我们把 Windows(backends/win32.ts)和 Linux(backends/linux.ts)的截图/应用管理代码塞进了这个包。"swift" 在名字里就意味着 macOS,后期维护者无法区分。

@ant/computer-use-input 同样——原本是 macOS enigo Rust 模块,我们也往里面塞了 win32/linux 后端。

问题 B:输入方式不对

当前 Windows 后端使用 SetCursorPos + SendInput + keybd_event——这是全局输入:

  • 鼠标真的会移动到屏幕上
  • 键盘真的打到当前前台窗口
  • 会影响用户当前的操作

绑定窗口句柄后,应该用 SendMessage/PostMessage 向目标 HWND 发送消息:

  • WM_CHAR — 发送字符,不移动光标
  • WM_KEYDOWN/WM_KEYUP — 发送按键
  • WM_LBUTTONDOWN/WM_LBUTTONUP — 发送鼠标点击(窗口客户区相对坐标)
  • PrintWindow — 截取窗口内容,不需要窗口在前台
  • 不抢焦点、不影响用户当前操作

Tip

已验证:向记事本 SendMessage(WM_CHAR) 成功写入文字,记事本在后台,终端保持前台。

问题 C:截图是公共能力,不属于 swift

截图(screenshot)、显示器枚举(display)、应用管理(apps)是所有平台都需要的公共能力,不应该放在 @ant/computer-use-swift(macOS 专属包名)里。

2. 修正后的架构

2.1 分层原则

graph TB
    subgraph "packages/@ant/ macOS 原生模块包装器"
        A["computer-use-input: macOS enigo 键鼠 (仅 darwin)"]
        B["computer-use-swift: macOS Swift 截图/应用 (仅 darwin)"]
        C["computer-use-mcp: MCP server + 工具定义 (跨平台)"]
    end
    subgraph "src/utils/computerUse/ 跨平台抽象层"
        D["platforms/types.ts: 公共接口"]
        E["platforms/index.ts: 平台分发器"]
        F["platforms/darwin.ts: 委托 @ant 包"]
        G["platforms/win32.ts: SendMessage + PrintWindow + EnumWindows"]
        H["platforms/linux.ts: xdotool + scrot + xrandr"]
    end
    subgraph "src/utils/computerUse/win32/ Windows专属增强"
        I["windowCapture.ts: PrintWindow 窗口绑定截图"]
        J["windowEnum.ts: EnumWindows 窗口枚举"]
        K["windowMessage.ts: SendMessage 无焦点输入"]
        L["uiAutomation.ts: UI 元素操作"]
        M["ocr.ts: Windows.Media.Ocr 文字识别"]
    end
    E --> F
    E --> G
    E --> H
    G --> I
    G --> J
    G --> K
    G --> L
    G --> M

2.2 公共接口(platforms/types.ts)

/** 窗口标识 — 跨平台 */
export interface WindowHandle {
  id: string           // macOS: bundleId, Windows: HWND string, Linux: window ID
  pid: number
  title: string
  exePath?: string     // Windows/Linux: 进程路径
}

/** 输入平台接口 — 两种模式 */
export interface InputPlatform {
  // 模式 A: 全局输入(macOS/Linux 默认,向前台窗口发送)
  moveMouse(x: number, y: number): Promise<void>
  click(x: number, y: number, button: 'left' | 'right' | 'middle'): Promise<void>
  typeText(text: string): Promise<void>
  key(name: string, action: 'press' | 'release'): Promise<void>
  keys(combo: string[]): Promise<void>
  scroll(amount: number, direction: 'vertical' | 'horizontal'): Promise<void>
  mouseLocation(): Promise<{ x: number; y: number }>

  // 模式 B: 窗口绑定输入(Windows SendMessage,不抢焦点)
  sendChar?(hwnd: string, char: string): Promise<void>
  sendKey?(hwnd: string, vk: number, action: 'down' | 'up'): Promise<void>
  sendClick?(hwnd: string, x: number, y: number, button: 'left' | 'right'): Promise<void>
  sendText?(hwnd: string, text: string): Promise<void>
}

/** 截图平台接口 */
export interface ScreenshotPlatform {
  captureScreen(displayId?: number): Promise<ScreenshotResult>
  captureRegion(x: number, y: number, w: number, h: number): Promise<ScreenshotResult>
  captureWindow?(hwnd: string): Promise<ScreenshotResult | null>
}

/** 显示器平台接口 */
export interface DisplayPlatform {
  listAll(): DisplayInfo[]
  getSize(displayId?: number): DisplayInfo
}

/** 应用管理平台接口 */
export interface AppsPlatform {
  listRunning(): WindowHandle[]
  listInstalled(): Promise<InstalledApp[]>
  open(name: string): Promise<void>
  getFrontmostApp(): FrontmostAppInfo | null
  findWindowByTitle(title: string): WindowHandle | null
}

2.3 平台分发器(platforms/index.ts)

import type { InputPlatform, ScreenshotPlatform, DisplayPlatform, AppsPlatform } from './types.js'

export interface Platform {
  input: InputPlatform
  screenshot: ScreenshotPlatform
  display: DisplayPlatform
  apps: AppsPlatform
}

export function loadPlatform(): Platform {
  switch (process.platform) {
    case 'darwin':
      return require('./darwin.js').platform
    case 'win32':
      return require('./win32.js').platform
    case 'linux':
      return require('./linux.js').platform
    default:
      throw new Error(`Computer Use not supported on ${process.platform}`)
  }
}

2.4 各平台实现

platforms/darwin.ts — 委托给 @ant 包(保持兼容):

// macOS: 通过 @ant/computer-use-input 和 @ant/computer-use-swift
import { requireComputerUseInput } from '../inputLoader.js'
import { requireComputerUseSwift } from '../swiftLoader.js'

export const platform = {
  input: { /* 委托给 requireComputerUseInput() */ },
  screenshot: { /* 委托给 requireComputerUseSwift().screenshot */ },
  display: { /* 委托给 requireComputerUseSwift().display */ },
  apps: { /* 委托给 requireComputerUseSwift().apps */ },
}

platforms/win32.ts — 使用 src/utils/computerUse/win32/ 模块:

// Windows: SendMessage 输入 + PrintWindow 截图 + EnumWindows 应用
import { sendChar, sendKey, sendClick, sendText } from '../win32/windowMessage.js'
import { captureWindow } from '../win32/windowCapture.js'
import { listWindows } from '../win32/windowEnum.js'

export const platform = {
  input: {
    // 全局模式: PowerShell SetCursorPos/SendInput(fallback)
    // 窗口模式: SendMessage(首选)
    sendChar, sendKey, sendClick, sendText,
    moveMouse, click, typeText, /* ... */
  },
  screenshot: {
    captureScreen,     // CopyFromScreen
    captureRegion,     // CopyFromScreen(rect)
    captureWindow,     // PrintWindow(不抢焦点)
  },
  display: { /* Screen.AllScreens */ },
  apps: { /* EnumWindows */ },
}

platforms/linux.ts — 使用 xdotool/scrot:

// Linux: xdotool + scrot + xrandr + wmctrl
export const platform = {
  input: { /* xdotool mousemove/click/key/type */ },
  screenshot: { /* scrot */ },
  display: { /* xrandr */ },
  apps: { /* wmctrl + ps */ },
}

2.5 executor.ts 改造

// 之前: 直接调 requireComputerUseSwift() 和 requireComputerUseInput()
// 之后: 通过 platforms/ 统一获取

import { loadPlatform } from './platforms/index.js'

const platform = loadPlatform()

// 截图
platform.screenshot.captureScreen()
platform.screenshot.captureWindow(hwnd)  // 窗口绑定

// 输入(窗口绑定模式,不抢焦点)
platform.input.sendText?.(hwnd, 'Hello')
platform.input.sendClick?.(hwnd, 100, 200, 'left')

// 输入(全局模式,fallback)
platform.input.moveMouse(500, 500)
platform.input.click(500, 500, 'left')

3. Windows 输入模式对比

方式 API 抢焦点 移鼠标 窗口可最小化 适用场景
全局输入 SetCursorPos + SendInput ✅ 抢 ✅ 动 ❌ 不行 需要坐标点击(fallback)
窗口消息 SendMessage(WM_CHAR/WM_KEYDOWN) ❌ 不抢 ❌ 不动 ✅ 可以 打字、按键(首选)
窗口消息 SendMessage(WM_LBUTTONDOWN) ❌ 不抢 ❌ 不动 ⚠️ 部分 窗口内点击
窗口截图 PrintWindow(hwnd, PW_RENDERFULLCONTENT) ❌ 不抢 ❌ 不动 ✅ 可以 窗口截图
UI 操作 UIAutomation InvokePattern ❌ 不抢 ❌ 不动 ✅ 可以 按钮点击、文本写入

Tip

策略:优先用窗口消息 + UIAutomation(不干扰用户),全局输入作为 fallback。

4. 需要新增的文件

文件 说明
src/utils/computerUse/platforms/types.ts 公共接口定义
src/utils/computerUse/platforms/index.ts 平台分发器
src/utils/computerUse/platforms/darwin.ts macOS: 委托给 @ant 包
src/utils/computerUse/platforms/win32.ts Windows: 组合 win32/ 下各模块
src/utils/computerUse/platforms/linux.ts Linux: xdotool/scrot
src/utils/computerUse/win32/windowMessage.ts 新增: SendMessage 无焦点输入

5. 需要移除/清理的文件

文件 操作 原因
packages/@ant/computer-use-input/src/backends/win32.ts 删除 Windows 代码不应在 macOS 包里
packages/@ant/computer-use-input/src/backends/linux.ts 删除 Linux 代码不应在 macOS 包里
packages/@ant/computer-use-swift/src/backends/win32.ts 删除 同上
packages/@ant/computer-use-swift/src/backends/linux.ts 删除 同上
packages/@ant/computer-use-input/src/types.ts 删除 移到 platforms/types.ts
packages/@ant/computer-use-swift/src/types.ts 删除 移到 platforms/types.ts

6. 需要修改的文件

文件 改动
packages/@ant/computer-use-input/src/index.ts 恢复为仅 darwin dispatcher(去掉 win32/linux case)
packages/@ant/computer-use-swift/src/index.ts 恢复为仅 darwin dispatcher(去掉 win32/linux case)
src/utils/computerUse/executor.ts 通过 platforms/ 获取平台实现,不直接调 @ant 包
src/utils/computerUse/swiftLoader.ts 仅 darwin 加载
src/utils/computerUse/inputLoader.ts 仅 darwin 加载

7. @ant 包的定位(修正后)

包 职责 平台
@ant/computer-use-input macOS enigo 键鼠原生模块包装 仅 darwin
@ant/computer-use-swift macOS Swift 截图/应用原生模块包装 仅 darwin
@ant/computer-use-mcp MCP Server + 工具定义 + 调用路由 跨平台(不含平台代码)

Windows/Linux 的平台实现全部在 src/utils/computerUse/platforms/ 和 src/utils/computerUse/win32/ 中。

8. 执行顺序

graph TD
    A["Phase 1: 创建 platforms/ 抽象层"] --> B["Phase 2: 创建 Windows 平台实现"]
    B --> C["Phase 3: 创建 Linux 平台实现"]
    C --> D["Phase 4: 改造 executor.ts"]
    D --> E["Phase 5: 清理 @ant 包"]
    E --> F["Phase 6: 验证 + PR"]

关联笔记