@wangyong1972/dsh-computer-use-macos
v0.1.0
Published
Native macOS Computer Use (screenshot + mouse/keyboard control) tool for DeepSeek Harness
Maintainers
Readme
@wangyong1972/dsh-computer-use-macos
一个 DeepSeek Harness (DSH) 插件,为模型提供
Anthropic "Computer Use" 能力的原生 macOS 实现:一个 computer 工具,可以对真实
显示器截图、驱动真实的鼠标/键盘,仅使用 macOS 自带的命令行工具(screencapture、
sips、osascript)——零安装依赖,零网络调用,v1 中零编译辅助二进制文件。
功能说明
该插件注册一个工具 computer,让模型可以:
- 对真实屏幕截图,并在对话中内联查看;
- 移动鼠标、在某个像素坐标处左键/右键/双击;
- 输入文本、发送组合键(
Return、cmd+c、ctrl+shift+t等); - 上下左右滚动;
- 读取当前鼠标光标位置(尽力而为);
- 等待一段有限的、有上限的时长。
每一次与操作系统的交互都通过 execFile('screencapture', [...])、
execFile('sips', [...]) 或
execFile('osascript', ['-l', 'JavaScript', ...]) 以 argv 数组方式执行——绝不会
用模型输入拼接 shell 字符串。详见下方"安全态势"一节。
截图会在发送给模型之前自动被缩小:任何长边超过 1568 像素的截图都会原地缩放
(通过 macOS 自带的 sips -Z,保持宽高比),使其安全地低于常见视觉模型的单边像
素上限——这正是让 screenshot 在 Retina 或 6K 显示器上也能正常工作的关键,否则
原始截图往往会被直接拒绝("Image exceeds the configured per-side pixel
limit")。没有任何配置项可以更改这个目标尺寸。 如果缩放后屏幕上的文字或控件
变得难以辨认,请在被控制的应用内部放大(调大字号、放大视图等),而不要尝试
更改显示器分辨率或本插件的缩放目标——这与
Claude Code 自身文档中记录的行为
(其原生 macOS computer-use 工具)保持一致。模型读取或提供的每一个 coordinate
始终处于这张缩放后截图自身的像素空间中,绝不是显示器的原生像素空间——坐标换算
由本插件内部完成。
与 Claude Code 对齐的 UX 特性(均可单独开关,见下方配置表):机器级全局锁,
防止同一台 Mac 上的第二个 DSH 进程同时操控鼠标/键盘;可选的"操作时隐藏其他应
用";可选的"截图时排除宿主/终端窗口";工具开始/停止操作时的 macOS 系统通知;以
及当前台应用是终端/IDE、Finder 或系统设置类应用时,在审批提示中附加额外警告文
字。经过调研后认定全局 Esc 中止热键在本插件的架构下不可行——完整分析见
DESIGN.md §10.5。
环境要求
- 仅支持 macOS(
"os": ["darwin"]——在其他平台上安装会直接失败,不会静默安装一个 只能在运行时报错的插件)。 - 挂载了
tools与attachments服务的 DSH 宿主(在 DSH web/桌面 profile 中是标 配)。 screenshot动作特别需要一个具备视觉能力的模型路由(纯文本路由会收到明确的错 误提示,而不是白白浪费一次截图)。
一次性 macOS 权限设置
在该插件能够执行任何操作之前,macOS 要求两项手动的一次性权限授予。该插件无法 自行授予这些权限——macOS 出于设计故意让 TCC(隐私)授权无法被请求进程自动脚本 化。这与你之前为需要"完全磁盘访问"或"屏幕录制"权限的应用所经历的一次性流程是同 一类操作。
- 辅助功能(Accessibility)——每一个鼠标/键盘动作
(
mouse_move、left_click、right_click、double_click、type、key、scroll,以及尽力而为的cursor_position)都需要它。 打开 系统设置 → 隐私与安全性 → 辅助功能,并启用相关应用。 - 屏幕录制(Screen Recording)——
screenshot需要它才能返回真实像素,而不 是一张空白图片(近期的 macOS 版本在缺少该权限时会静默返回空白截图,且不会报 错)。 打开 系统设置 → 隐私与安全性 → 屏幕录制,并启用相关应用。
哪个应用会出现在上述列表中,取决于 DSH 是如何启动的。 该插件的每一个鼠标/键
盘/截图动作都通过 osascript(截图则是 screencapture)执行,因此 TCC 会把权
限归属到实际执行该命令的进程——在实践中,这可能是 osascript 本身,也可能是负责
进程链中的终端/宿主应用,具体取决于 macOS 版本以及 DSH 的启动方式。请先触发一
次 computer 动作(它会因缺少权限而失败,并给出明确提示),然后再去检查系
统设置——正确的条目只有在第一次尝试之后才会出现在列表中。请留意查找
osascript、Terminal 或你使用的终端应用。
两项权限都授予后,重试刚才失败的动作即可。
安装到 DSH profile
方式一:npm(推荐)
dsh plugin --profile <name> add @wangyong1972/dsh-computer-use-macosnpm 包:@wangyong1972/dsh-computer-use-macos
方式二:本地源码(开发/贡献)
dsh plugin --profile <name> add /path/to/dsh-computer-use-macos该插件自带 cordis.patch.yml(由 package.json 的 dsh.bundle.patch 字段引
用),因此添加它会自动以合理的默认配置注册 computer-use-macos 插件。
配置参考
| 字段 | 默认值 | 说明 |
|---|---|---|
| enabled | true | 总开关。为 false 时完全不注册 computer 工具——零 prompt token 开销。 |
| requireConfirmation | true | 每一个改变状态的动作(click/move/type/key/scroll)在执行前都经过审批环节。screenshot/cursor_position/wait 永远不受此限制。未挂载审批服务时默认拒绝(fail closed)。 |
| allowedDisplayIndex | 0 | 向后兼容的默认 0-based 显示器索引;动作未传 display 时沿用它。每次动作都可独立覆盖,无需修改配置。 |
| screenshotFormat | 'png' | 为未来格式预留;v1 只支持 png。 |
| actionTimeoutMs | 10000 | 每一次 screencapture/sips/osascript/ps 子进程调用的硬超时时间。 |
| maxTypeTextLength | 4096 | 超过该 UTF-16 码元数的 action=type 调用会被拒绝(拒绝而非截断)。 |
| enableMachineLock | true | 在改变状态的动作执行期间持有一个机器级锁文件,防止同一台 Mac 上的第二个 DSH 进程同时操控真实鼠标/键盘。失效的锁(持有者进程已死,或持有过久)会被自动回收。 |
| hideOtherAppsWhileActing | false | 在改变状态的动作执行期间隐藏所有其他可见应用,只保留本插件自身的宿主进程可见,结束后恢复原样。默认关闭——这是明显的界面侵入行为,需要主动开启。 |
| excludeHostFromScreenshots | false | 仅在 screencapture 调用期间短暂隐藏宿主进程,使 screenshot 不会截到终端窗口而不是真正的目标应用。默认关闭,原因同上。 |
| enableSessionNotifications | true | 工具开始/停止操作时发出一条 macOS 系统通知。尽力而为,绝不会导致底层动作失败。 |
| enableAppRiskWarnings | true | 当前台应用是终端/IDE、Finder 或系统设置类应用时,在审批提示中附加额外警告文字。纯粹是附加信息——绝不改变审批结果,requireConfirmation 为 false 时无效。 |
点击诊断与日志
每一次 mouse_move/left_click/right_click/double_click 调用都会通过 DSH
自带的 ctx.logger() 机制发出一条结构化、隐私安全的诊断记录(绝不是自建日志文
件,也绝不使用 console.*)——日志名为 computer-use-macos,方便宿主自己的日
志导出器按名称过滤。示例行(真实格式,已针对真实的 @deepseek-ai/cordis
Context 实测验证):
computer.left_click display=0 pixel=[500,400] point=(500,400) pid=69234 exit=0 elapsed=112ms outcome=ok cursorVerified=true字段包括:动作名称、allowedDisplayIndex、请求的像素坐标、解析出的 Quartz 全
局点、osascript 子进程的 pid/退出码、耗时、一个粗粒度的结果分类(ok /
accessibility-denied / timeout / aborted / other-error),以及一次
"点击后光标位置自检"是否确认光标真的到达了请求的点。绝不会记录:原始
stderr/stdout 文本、输入的 text 内容,或组合键内容——完整的隐私约定见
DESIGN.md §11.2。除了干净且已验证的结果外都会以 warn 级别记录,其余为
info 级别。
这能证明什么、不能证明什么:光标自检可以确认一次点击底层的 CGEventPost
调用确实到达了操作系统层,且光标已经跳转到了正确的屏幕坐标——但它无法确认
目标应用真的接收或响应了这次点击(那需要针对具体应用的 accessibility 树内省能
力,超出本插件范围,详见 DESIGN.md §11.3)。若需要确认点击的实际效果,请在
点击后再调用一次 screenshot 自行判断。
支持的动作(v1)
list_displays、screenshot、left_click、right_click、double_click、
mouse_move、type、key、scroll、wait、cursor_position(尽力而为)。
选择显示器
调用 list_displays 可取得每个活动显示器的 0-based index、数字
CGDirectDisplayID(id)、是否主屏、Quartz 原点/尺寸、缩放倍数,以及显式的
1-based screenshotOrdinal。
screenshot、cursor_position、鼠标移动/点击动作及 scroll 接受可选的
display 字段:
- 方位字符串:
main、leftmost、rightmost、topmost、bottommost; - JSON 数字表示 0-based 活动显示器索引;
- 十进制字符串表示
CGDirectDisplayID,也可加id:前缀。
省略 display 时完全保留现有 allowedDisplayIndex 行为。每次调用都会重新发现显示器;
无效或越界选择器会在发送任何 OS 输入事件前失败;坐标始终是所选显示器截图的局部空间。
main 必须恰好解析到一台显示器;若两个显示器共享同一个方位极值则视为歧义并安全失败。
scroll 未给 coordinate 时虽接受 display,但不会移动鼠标,仍在当前光标位置滚动。
截图身份限制: macOS 的 screencapture -D 只接受 1-based ordinal,不接受
CGDirectDisplayID。screenshotOrdinal 是从同一份 NSScreen 清单得到并显式传递的
当前最佳映射,但 Apple 并未保证 NSScreen 与 screencapture 排序永远一致,因此它
不是身份安全保证。鼠标/光标几何通过 CGDisplayBounds 按 ID 获取;在换用直接
CoreGraphics 截图后端之前,不应宣称按 CG ID 选择的截图绝对安全。
暂不支持(推迟到 v2): left_click_drag、middle_click、
triple_click、hold_key、zoom、对 macOS "自然滚动"偏好设置的补偿,以及基于
窗口/应用(accessibility 树)的定位。请不要为这些已知的、有意为之的 v1 范围裁剪
提交 bug——它们不是疏漏。
安全态势
- 任何地方都不存在 shell 字符串执行。 每一条操作系统命令都通过 Node 的
execFile以 argv 数组方式调用;每一个鼠标/键盘动作中不受信任的输入 (text、coordinate)都以单个 JSON 字符串的形式穿过 JXA 子进程边界,绝不 会被拼接进 AppleScript 或 shell 源文本。 - 该插件不发起任何网络调用。
- 所有数值输入都会在真实、实时查询到的屏幕边界范围内被校验和裁剪,然后才会 用于合成任何鼠标/键盘事件——即便模型只是回显此前展示给它的坐标,也绝不会被直 接信任。
- 不接受任意文件路径。 该插件仅会接触两类文件系统路径:它自己在同一次工具调
用内创建、(必要时通过
sips原地缩放)并删除的随机命名临时截图文件,以及一个 固定路径的机器级锁文件(§10.1)。 - 机器级锁在发生争用时会拒绝失败——第二个进程持有该锁是硬性拒绝,绝不会静默 放行;失效锁的回收同时受"持有者进程是否存活"与"绝对时长上限"双重约束。
- 应用隐藏功能只会恢复自己隐藏过的应用,绝不是无差别地"全部显示",因此用户 自己提前隐藏的应用不会被意外唤出。
- 按应用的风险警告纯粹是附加信息——它们绝不能把一次"需要确认"的审批变成自动 "允许",且风险分级表是固定的,不受模型或配置驱动。
- 每一个改变状态的动作都可通过
requireConfirmation进行门控,在未挂载审批 服务时默认拒绝(fail closed)——绝不会默认放行(fail open)。 enabled: false会完全禁用插件:不注册工具,零 token 开销。- 绝不包含
postinstall/preinstall脚本。 - 对所有
@deepseek-ai/*包使用peerDependencies而非dependencies。
开发
pnpm install
pnpm run build # tsc -> lib/,然后原样复制 JXA 辅助脚本
pnpm run typecheck # tsc --noEmit
pnpm run verify # 静态检查,不产生 macOS 副作用,可在 CI 中运行
pnpm test # 针对 lib/ 的单元测试(key-spec、validate、permission-errors、screenshot-scale、lock、app-tiers、notify)任何需要真实辅助功能/屏幕录制权限的测试,都记录在 tests/e2e.manual.md 的手动
检查清单中。
许可证
MIT
