⚠️ 学习声明:本文档基于 Claude Code 2.1.88 源码分析整理,仅供个人学习研究使用,不做任何商业用途。
内部代号:Chicago(Feature Flag: tengu_malort_pedway)
这是 CC 的桌面控制能力——让 Claude 像人一样操控鼠标、键盘、截图分析 GUI 应用。
一、系统定位
能力边界
| 能力 |
实现层 |
平台 |
| 鼠标移动/点击 |
@ant/computer-use-input(Rust/enigo) |
macOS |
| 键盘输入 |
@ant/computer-use-input(Rust/enigo) |
macOS |
| 截图 |
@ant/computer-use-swift(Swift/SCContentFilter) |
macOS |
| 剪贴板读写 |
pbcopy/pbpaste 系统命令 |
macOS |
| 前台应用检测 |
@ant/computer-use-swift(NSWorkspace) |
macOS |
CLI vs Desktop(Cowork)的关键差异:
| 差异点 |
Cowork(Electron) |
CLI(Claude Code) |
| 点击穿透 |
withClickThrough + setIgnoreMouseEvents |
无窗口,直接操作 |
| 宿主 BundleID |
Electron 应用 BundleID |
哨兵值 CLI_HOST_BUNDLE_ID(永不匹配) |
| 剪贴板 |
Electron clipboard 模块 |
pbcopy/pbpaste |
| 截图排除 |
排除自身 Electron 窗口 |
排除终端模拟器窗口 |
二、核心文件地图
src/utils/computerUse/ ├── common.ts # 常量与工具函数(BundleID 检测) ├── gates.ts # Feature 开关(Chicago 门控) ├── executor.ts # 核心执行器(实现 ComputerExecutor 接口) ├── setup.ts # MCP 服务配置构建 ├── mcpServer.ts # In-Process MCP Server(拦截 stdio 请求) ├── hostAdapter.ts # 宿主适配器 ├── swiftLoader.ts # Swift 原生模块懒加载 ├── inputLoader.ts # Input(Rust)原生模块懒加载 ├── escHotkey.ts # ESC 紧急中止热键 ├── computerUseLock.ts # 并发锁(同一时刻只允许一个 CU 操作) ├── cleanup.ts # 会话结束清理 ├── drainRunLoop.ts # macOS Run Loop 排空(动画帧同步) ├── appNames.ts # 已知应用 BundleID 映射 ├── toolRendering.tsx # 工具调用结果的 React UI 渲染 └── wrapper.tsx # CU 工具的高层包装组件
|
三、Feature 门控机制(gates.ts)
Chicago Config 数据结构
type ChicagoConfig = CuSubGates & { enabled: boolean coordinateMode: CoordinateMode }
{ pixelValidation: boolean clipboardPasteMultiline: boolean mouseAnimation: boolean hideBeforeAction: boolean autoTargetDisplay: boolean clipboardGuard: boolean }
|
开关读取链
GrowthBook Feature Flag: tengu_malort_pedway ↓ getDynamicConfig_CACHED_MAY_BE_STALE() ↓ 与 DEFAULTS 合并(partial JSON 安全) ↓ getChicagoEnabled() — 三重检查: 1. hasRequiredSubscription()(Max/Pro 或内部 ant) 2. readConfig().enabled 3. 内部用户的 MONOREPO_ROOT_DIR 排除(避免 dev 污染)
|
坐标模式冻结:getChicagoCoordinateMode() 首次调用后冻结值,防止 GrowthBook 中途改变导致模型收到 “pixels” 指令但执行层按 “normalized” 换算。
订阅等级要求
function hasRequiredSubscription(): boolean { if (process.env.USER_TYPE === 'ant') return true const tier = getSubscriptionType() return tier === 'max' || tier === 'pro' }
|
四、MCP 集成架构(setup.ts + mcpServer.ts)
为什么用 MCP 而不是内置工具?
API 后端检测 mcp__computer-use__* 工具名 → 自动在 System Prompt 注入 COMPUTER_USE_MCP_AVAILABILITY_HINT → 告诉模型"当前环境支持桌面操控"
|
如果用不同名字的内置工具,API 端不会注入这个提示,模型不知道能用。
MCP 配置构建流程
setupComputerUseMCP() { const allowedTools = buildComputerUseTools(CLI_CU_CAPABILITIES, coordinateMode) .map(t => buildMcpToolName('computer-use', t.name)) return { mcpConfig: { 'computer-use': { type: 'stdio', command: process.execPath, ... } }, allowedTools } }
|
In-Process 拦截:mcpServer.ts 检测到服务名为 computer-use 时,直接在当前进程内处理工具调用,不真正启动子进程。
五、执行器核心(executor.ts)
截图流水线
getDisplayGeometry() # 获取逻辑像素尺寸 + DPI 缩放因子 ↓ computeTargetDims() # 逻辑 → 物理 → API 目标尺寸 ↓ resolvePrepareCapture() # 确定截图参数(排除终端窗口) ↓ prepareDisplay() # 准备显示环境(隐藏无关窗口) ↓ drainRunLoop() # 等待 macOS 动画帧完成 ↓ takeScreenshot(quality=0.75) # JPEG 截图,质量 75% ↓ API 接收 base64 图像
|
终端窗口排除
getTerminalBundleId(): string | null { const cfBundleId = process.env.__CFBundleIdentifier if (cfBundleId) return cfBundleId return TERMINAL_BUNDLE_ID_FALLBACK[env.terminal ?? ''] ?? null }
|
截图时将终端 BundleID 加入排除列表(captureExcluding),保证截图只包含目标应用。
剪贴板操作
async readClipboardViaPbpaste(): Promise<string>
async writeClipboardViaPbcopy(text: string): Promise<void>
|
六、坐标系统
两种坐标模式
| 模式 |
说明 |
适用场景 |
pixels |
逻辑像素坐标(用户熟悉) |
大多数操作 |
normalized |
归一化 [0,1] 坐标 |
分辨率无关场景 |
坐标转换链
API 目标分辨率(targetImageSize 计算) → physW = logicalW × scaleFactor → [targetW, targetH] = API_RESIZE_PARAMS 约束 模型点击坐标(相对于 API 图像) → 还原到逻辑坐标 → 传递给 @ant/computer-use-input 执行
|
七、安全机制
并发锁(computerUseLock.ts)
同一时刻只允许一个 CU 操作执行 → 防止并发鼠标/键盘操作相互干扰 → 超时自动释放,避免死锁
|
ESC 紧急中止(escHotkey.ts)
全局监听 ESC 键 → 注册 notifyExpectedEscape() 告知系统当前 CU 操作 → 用户按 ESC → 中止当前操作,恢复正常状态
|
TCC(系统隐私权限)
computer-use-swift 调用前检查 macOS TCC 权限: - Accessibility(辅助功能) - Screen Recording(屏幕录制) 未授权时 → request_access 工具引导用户授权
|
会话清理(cleanup.ts)
会话结束时: → 恢复被隐藏的窗口 → 释放 computerUseLock → 清理截图临时文件
|
八、与工具系统的集成
用户消息 → QueryEngine ↓ 模型选择 mcp__computer-use__screenshot 工具 ↓ ToolExecutor 识别前缀 "mcp__computer-use__" ↓ McpToolExecutor → 检查是否为 computer-use 服务 ↓ In-Process MCP Server(mcpServer.ts)处理 ↓ 执行 executor.ts 对应方法 ↓ 返回截图/操作结果 → 下一轮模型决策
|
九、关键设计决策
| 决策 |
原因 |
| macOS 独占 |
Swift SCContentFilter + NSWorkspace API,Linux/Windows 无对应实现 |
| MCP 而非内置工具 |
API 端根据工具名注入系统提示,名字不对就不触发 |
| JPEG 而非 PNG |
截图质量 75% 的 JPEG,大幅减少 token 消耗(图像 → base64 tokens) |
| 坐标模式冻结 |
防止 GrowthBook 实验中途改变导致坐标系不一致 |
| In-Process 拦截 |
避免 spawn 子进程的开销,computer-use 服务名作为路由键 |
| 终端作为代理宿主 |
CLI 无窗口,用终端 BundleID 代替 Electron 窗口做截图排除 |
十、截图处理流水线深度剖析
10.1 参考实现:Anthropic Computer Use Demo
Claude Code 的 Computer Use 功能基于 Anthropic 的参考实现(anthropic-quickstarts/computer-use-demo),但做了针对性的工程化改造。
10.2 截图处理流水线
屏幕捕获 (macOS ScreenCaptureKit) │ ├─ 1. 排除 Claude Code 自身窗口(避免递归) ├─ 2. 缩放到合理分辨率(通常 ≤ 1920x1080) ├─ 3. JPEG 编码(quality=75%,base64 比 PNG 小 80%) └─ 4. 以 image content block 发送给 Claude
Claude 返回: ├─ 点击坐标 (x, y) → 通过 Accessibility API 模拟点击 ├─ 键盘输入 → 通过 CGEvent 模拟 └─ 滚动 → 通过 CGEvent 模拟
|
10.3 与 Selenium/Puppeteer 的对比
| 维度 |
Computer Use |
Selenium/Puppeteer |
| 操作对象 |
整个桌面 |
浏览器窗口 |
| 视觉理解 |
截图 → LLM 分析 |
DOM 解析 |
| 跨应用 |
✅ 可操作任何应用 |
❌ 仅浏览器 |
| 可靠性 |
依赖 LLM 视觉能力 |
依赖 DOM 稳定性 |
| 适用场景 |
GUI 自动化、跨应用工作流 |
Web 自动化测试 |
十一、平台截图技术深度对比
11.1 macOS ScreenCaptureKit(SCK)
Apple 在 macOS 12.3 引入的现代截图框架,CC 通过 Swift 原生模块调用:
SCContentFilter(内容过滤器) ├─ 排除指定 BundleID 的窗口(排除终端自身) ├─ 排除桌面壁纸(desktopIndependentWindowID) └─ 按显示器的 targetDisplay 筛选 ↓ SCStreamConfiguration ├─ pixelFormat: BGRA8888(GPU 直接输出格式) ├─ width/height: 受 API_RESIZE_PARAMS 约束 ├─ queueDepth: 3(缓冲 3 帧,平衡延迟与丢帧) └─ capturesAudio: false(截图不需要音频) ↓ SCStream → CMSampleBuffer → CGImage → JPEG
|
为什么 SCK 比 CGDisplayCreateImage 好?
| 维度 |
CGDisplayCreateImage |
ScreenCaptureKit |
| 窗口排除 |
❌ 不支持 |
✅ SCContentFilter |
| DPI 感知 |
需要手动换算 |
自动处理 scaleFactor |
| GPU 加速 |
CPU 编码 |
GPU 原生 pixel buffer |
| 捕获延迟 |
30-60ms |
5-15ms |
| 权限模型 |
无细粒度控制 |
TCC Screen Recording |
11.2 Windows Graphics Capture API
Windows 上对应方案是 Windows.Graphics.Capture (WinRT, Windows 10 1803+):
auto picker = GraphicsCapturePicker(); auto item = picker.PickSingleItemAsync(); auto framePool = Direct3D11CaptureFramePool::Create(...); auto session = framePool.CreateCaptureSession(item);
|
Windows 方案的主要挑战:
- 无编程式窗口过滤:必须通过
EnumWindows + GetWindowText 手动匹配
- HDCP 保护内容:Netflix 等受保护窗口返回黑色帧
- DPI 虚拟化:Win32 应用的 DPI 感知模式导致坐标计算复杂(见第十二节)
11.3 Linux X11/Wayland 碎片化问题
这是 Computer Use 未支持 Linux 的核心原因:
X11 (X.Org): ├─ XGetImage() / XShmGetImage() — 可截图但无窗口排除 ├─ XTestFakeMotionEvent() — 可模拟输入 └─ 问题:无组合器隔离,任何应用都能读到其他窗口内容
Wayland: ├─ org.freedesktop.portal.ScreenCast(PipeWire 管道) │ └─ 需要用户交互授权(弹窗),无法静默截图 ├─ wlr-screencopy(wlroots 专属,非标准协议) │ └─ 仅 Sway/Hyprland 支持,GNOME/KDE 不行 └─ libei(Emulated Input)— 仍在标准化过程中
|
碎片化矩阵:
| 桌面环境 |
显示协议 |
截图方案 |
模拟输入 |
窗口排除 |
| GNOME |
Wayland |
ScreenCast Portal |
❌ libei 未就绪 |
❌ |
| KDE Plasma |
Wayland |
ScreenCast Portal |
❌ |
❌ |
| Sway |
Wayland (wlroots) |
wlr-screencopy |
wlr-virtual-pointer |
部分 |
| Xfce/MATE |
X11 |
XShmGetImage |
XTest |
❌ |
| 通用 X11 |
X11 |
XShmGetImage |
XTest |
❌ |
这就是为什么 Computer Use 目前 macOS 独占——在 Linux 上实现同等能力需要整合至少 3-4 种不同协议栈,且 Wayland 安全模型从根本上限制非用户交互的屏幕访问。
十二、无障碍树与语义理解
12.1 为什么无障碍树比截图更”懂”界面?
纯视觉截图给 LLM,模型只能看到像素。但 GUI 有丰富的语义结构——按钮的可访问标签、元素的层级关系、控件的角色类型——这些是像素里读不出来的。
┌──────────────────────────────────────┐ │ 截图视角(给 LLM 的像素) │ │ ┌──────────┐ ┌──────────┐ │ │ │ Submit │ │ Cancel │ │ ← 模型看到两个矩形,需要推断哪个是按钮 │ └──────────┘ └──────────┘ │ │ │ │ 无障碍树视角(理想状态) │ │ Window "Form" │ │ ├─ Group "Actions" │ │ │ ├─ Button "Submit" │ ← 明确的角色 + 标签 │ │ │ enabled: true │ │ │ │ action: press │ │ │ └─ Button "Cancel" │ │ │ enabled: false │ │ └─ TextField "Email" │ │ value: "user@..." │ └──────────────────────────────────────┘
|
12.2 三大平台无障碍 API 对比
| 维度 |
macOS Accessibility |
Windows UI Automation |
Linux AT-SPI |
| API 前缀 |
AX* (AXUIElement) |
IUIAutomation* |
Atspi* |
| 元素发现 |
AXUIElementCopyAttributeValue |
FindFirst(TreeScope, Condition) |
atspi_accessible_get_child_at_index |
| 角色系统 |
AXRole (AXButton, AXTextField…) |
ControlType (Button, Edit…) |
AtspiRole (ATSPI_ROLE_PUSH_BUTTON…) |
| 事件订阅 |
AXObserverCreate |
AddAutomationEventHandler |
atspi_event_listener_register |
| 性能 |
高(系统级缓存) |
中等(跨进程 COM) |
低(D-Bus IPC 开销) |
| CC 集成度 |
✅ 深度使用 |
❌ 未集成 |
❌ 未集成 |
12.3 CC 中的无障碍信息使用
CC 目前主要通过截图理解 GUI,但 @ant/computer-use-swift 中包含了无障碍树的辅助查询能力:
func getAccessibilityInfo(for pid: pid_t) -> [String: Any]? { let app = AXUIElementCreateApplication(pid) var focusedElement: CFTypeRef? AXUIElementCopyAttributeValue(app, kAXFocusedUIElementAttribute, &focusedElement) guard let element = focusedElement else { return nil } return [ "role": copyAXAttribute(element, kAXRoleAttribute) ?? "unknown", "title": copyAXAttribute(element, kAXTitleAttribute) ?? "", "description": copyAXAttribute(element, kAXDescriptionAttribute) ?? "", "value": copyAXAttribute(element, kAXValueAttribute) ?? "", "position": copyAXPosition(element) ?? [0,0], "size": copyAXSize(element) ?? [0,0], "enabled": copyAXAttribute(element, kAXEnabledAttribute) ?? false, ] }
|
这些信息可以注入到 System Prompt,帮助模型理解”当前焦点在哪个按钮上”而不需要纯视觉推理。
12.4 无障碍树面临的实际问题
在实际 GUI 自动化中,无障碍树远非完美:
- Web 应用的 ARIA 标注:Electron/CEF 应用的无障碍属性取决于前端开发者是否添加
aria-label,大量应用根本没有
- 自定义控件:游戏引擎、Qt Quick 等自绘 UI 没有原生无障碍支持
- 跨进程查询延迟:Windows UIA 的跨进程 COM 调用可能 50-200ms/次,不适合高频查询
- 信息过载:一个复杂页面(如 VS Code)的无障碍树可能有 5000+ 节点,注入 LLM 上下文不现实
这就是 CC 选择”截图为主、无障碍为辅”策略的原因。
十三、坐标映射数学
13.1 五层坐标空间
Computer Use 中一个像素坐标要经过 5 层坐标系 的转换:
1. 物理像素空间(屏幕硬件分辨率) 例:MacBook Pro 16" → 3456×2234 物理像素 ↓ scaleFactor(内建 Retina 通常是 2.0) 2. 逻辑像素空间(macOS "Points") 例:1728×1117 逻辑点 ↓ getDisplayGeometry() → targetImageSize 计算 3. API 目标图像空间(模型看到的图像) 例:受限于 API_RESIZE_PARAMS(如 max 1920×1080) 保持宽高比,长边不超过上限 scale_to_api = targetW / logicalW 4. 模型坐标空间(model 返回的点击坐标) 原点:API 图像左上角 (0,0) 范围:API 图像尺寸 ↓ 逆缩放:modelX / scale_to_api 5. 逻辑像素空间(enigo 执行层) 还原到用户屏幕的逻辑坐标 ↓ 乘以 scaleFactor(如果需要物理坐标) → enigo.mouse_move(x, y)
|
13.2 坐标转换的代码路径
function apiCoordsToLogical( modelX: number, modelY: number, displayInfo: DisplayGeometry, targetDims: ImageDimensions ): { x: number; y: number } { const scaleToLogical = displayInfo.logicalW / targetDims.width; const logicalX = modelX * scaleToLogical; const logicalY = modelY * scaleToLogical; return { x: Math.max(0, Math.min(logicalX, displayInfo.logicalW - 1)), y: Math.max(0, Math.min(logicalY, displayInfo.logicalH - 1)), }; }
|
13.3 DPI 缩放的特殊情况
macOS 的 DPI 缩放不是简单的整数倍:
Retina 显示器 (scaleFactor = 2.0): 物理 3456×2234 → 逻辑 1728×1117 转换:精确的 2x,无精度损失
非 Retina 外接显示器 (scaleFactor = 1.0): 物理 1920×1080 → 逻辑 1920×1080 转换:恒等,无精度损失
"Looks like 1440p" 模式 (scaleFactor ≈ 1.5): 物理 2560×1440 → 逻辑 1680×1050(实际渲染 3360×2100 物理 → 降到 2560×1440) ⚠️ 这产生非整数缩放!scaleFactor ≈ 1.524 这会导致坐标转换出现亚像素误差
|
亚像素误差的处理:enigo 库(Rust 输入模拟)接收 i32 坐标,四舍五入引入 ±0.5 逻辑像素误差。在 1x 屏幕上这是 0.5 物理像素(可忽略),但在上述 “looks like” 模式下可能产生 1-2 物理像素偏移——对按钮边缘区域的点击可能失败。CC 的 pixelValidation 子开关就用于检测这类场景并发出警告。
13.4 多显示器坐标系统
macOS 多显示器坐标空间: (0,0) 是主显示器的左上角 辅助显示器可以位于负坐标区域(如在主显示器左侧) 例:两个 1728×1117 显示器并排 左侧显示器:(-1728, 0) → (0, 1117) 右侧显示器(主):(0, 0) → (1728, 1117) getDisplayGeometry() 返回目标显示器的 frame 偏移量 执行点击时需要:displayFrame.x + localX, displayFrame.y + localY
|
十四、安全沙箱模型
14.1 多层安全防护
Layer 1: Feature Gate(功能级) ├─ GrowthBook 远端开关 → 随时关闭 ├─ 订阅等级检查(Max/Pro) └─ 坐标模式冻结(防中途变更)
Layer 2: TCC 系统权限(OS 级) ├─ Screen Recording 权限 → ScreenCaptureKit 必需 ├─ Accessibility 权限 → 模拟输入必需 └─ 用户可在「系统设置 → 隐私」中随时撤销
Layer 3: 运行时守卫(进程级) ├─ computerUseLock → 同一时刻只允许一个操作 ├─ ESC 热键紧急中止 → 全局键盘钩子 ├─ pixelValidation → 坐标范围检查 └─ clipboardGuard → 剪贴板操作审计
Layer 4: 应用级过滤(操作目标级) ├─ 排除终端窗口(避免操作自身) ├─ 排除系统偏好设置(安全边界) └─ excludeBundleIDs 白名单/黑名单机制
|
14.2 与 macOS TCC 的交互
TCC 权限检查流程: ScreenCaptureKit 首次调用 │ ▼ TCC Daemon (/usr/libexec/tccd) │ 查询 com.apple.TCC 数据库 ├─ 已授权 → 放行 ├─ 未授权且应用有 Info.plist 声明 │ → 弹系统授权对话框 └─ 未授权且未声明 → 拒绝 CC 的处理: 1. swiftLoader.ts 捕获 SCStreamError 2. 映射到 request_access 工具 3. 提示用户在「系统设置 → 隐私与安全性 → 屏幕录制」中勾选
|
14.3 剪贴板安全
剪贴板保护(clipboardGuard): ├─ 读取前检查:是否包含敏感信息(密码模式正则匹配) ├─ 写入时审计:记录写入内容的 SHA256 摘要 └─ 会话结束时:可选择性地清除由 CU 写入的剪贴板内容
|
十五、跨平台挑战与未来方向
15.1 为什么 Linux/Windows 支持困难
基于上述分析,核心阻碍可以归纳为:
| 阻碍 |
严重度 |
说明 |
| Wayland 安全模型 |
🔴 致命 |
任何截图都需要用户交互授权,无法自动化 |
| Windows 窗口过滤 |
🟡 中等 |
需要 HWND 枚举,不如 BundleID 优雅 |
| 输入模拟碎片化 |
🟡 中等 |
X11/Wayland 两套完全不同的协议 |
| 无障碍 API 差异 |
🟢 可克服 |
DBus AT-SPI 和 UIA 都很成熟 |
| 用户基数 |
🟢 可克服 |
Linux 桌面用户少但不是零 |
15.2 可能的实现路径
Linux 路线:
优先支持 X11(XShmGetImage + XTest) └─ 通过 XDG_SESSION_TYPE 环境变量检测 Wayland 作为实验性支持 └─ 需要 GNOME/KDE 的 ScreenCast Portal 权限
|
Windows 路线:
Windows.Graphics.Capture API (WinRT) └─ 需要 C++/WinRT 或 Rust winrt crate 绑定 SendInput API(模拟输入) └─ 已有成熟的 Rust enigo 跨平台支持 UI Automation(无障碍) └─ 作为辅助感知通道
|
15.3 与其他桌面自动化框架的定位差异
| 框架 |
核心范式 |
是否依赖视觉 |
适用场景 |
| CC Computer Use |
LLM 视觉推理 + 操作 |
✅ |
通用 GUI 任务 |
| PyAutoGUI |
脚本驱动 + 图像匹配 |
模板匹配 |
简单重复自动化 |
| Playwright |
DOM 选择器 |
❌ |
Web 自动化 |
| Robot Framework |
关键字驱动 |
❌ |
测试自动化 |
| AppleScript/Automator |
IPC |
❌ |
macOS 应用间通信 |
CC Computer Use 的独特价值:不需要预先编程,LLM 实时理解界面并决定操作——这使它适合非确定性的跨应用工作流(如”帮我把 Figma 设计稿中的文字复制到 VS Code 中”),而传统框架需要精确的预先脚本。
涉及源文件
src/utils/computerUse/
src/utils/computerUse/common.ts
src/utils/computerUse/executor.ts
src/utils/computerUse/swiftLoader.ts
src/utils/computerUse/inputLoader.ts