源码路径:src/utils/computerUse/(15个文件)
内部代号: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 窗口做截图排除 |