dsh-plugin
DayuanJiang/next-ai-draw-iodsh 的 draw.io 绘图插件:通过 next-ai-drawio MCP 服务器作图,点回复里的链接在右侧栏打开实时预览,可以手动修改图,AI 的每次修改都是可以恢复的版本。需 Node.js 18+,首次使用由 npx 下载 MCP 服务器。
让 Agent 像人一样用鼠标和键盘操作 Windows:截图 → OCR 定位界面文字 → 真实点击/拖动/滚轮/输入 → 前后画面与文字双重复核。 不依赖任何视觉大模型 API,定位用 Windows 自带的
Windows.Media.Ocr(离线、约 200ms、中英双语)。
English TL;DR — A DeepSeek Harness plugin that lets the agent drive Windows by sight: it screenshots the active window, indexes every line of text with Windows OCR, clicks/drags/scrolls/types by target text (or normalized coordinates), and verifies every action with a per-pixel diff plus an OCR text delta. Emergency stop is F6+F7, watched by a separate process. Windows only, no API key, no vision model.
一句话:把「屏幕上的文字」变成可点击、可拖拽、可输入的目标,并且每次操作都告诉你有没有生效。
ctrl+a、shift+a、enter、delete…)。gui_do 可把「点击 → 等待 → 滚轮 → 键入 → 按键」串成最多 10 步,出错即停并报告卡在哪一步。纯像素 GUI Agent 最脆的一环是「把语义变成像素」:模型看缩略图估坐标,偏差几十像素,按钮就点空了。 这个插件把这件事做成确定性的能力:
gui_look 看图给 0~1 坐标;思路参考字节跳动 GUI Agent 那条路线(UI-TARS 的「截图 → 定位 → 动作 → 再截图」闭环、 Midscene 的「先拿到可点元素清单再决策」),但把「视觉大模型」换成了系统 OCR —— 更便宜、更快、完全离线。
| 工具 | 作用 |
|---|---|
gui_look | 截屏 + 文字索引:识别活动窗口每一行文字,附 0~1 归一化中心坐标 |
gui_click | 左/右/中键、单击/双击、holdMs 长按、hover 只悬停;按目标文字或坐标定位 |
gui_drag | 按住拖动:from / to 各写目标文字或坐标(拖文件、滑块、框选、窗口) |
gui_scroll | 滚轮:在目标文字/坐标处上下左右滚动 N 格 |
gui_type | 键入文字:大小写、数字、中文、符号;pressEnter 输完回车 |
gui_key | 组合键/功能键:enter、tab、esc、backspace、delete、方向键、f1-f12、ctrl+a、shift+a… |
gui_wait | 等文字出现 / 消失(加载完成、弹窗关闭),比盲睡可靠 |
gui_do | 一次执行一串步骤(点击/悬停/拖动/滚轮/键入/按键/等待,≤10 步),出错即停 |
gui_status | 急停状态、看门狗、输入进程、OCR 是否可用 |
gui_rearm | 急停之后重新武装(必须 confirm: true) |
一次典型交互:
gui_look { query: "保存" } → 「保存」在 (0.83, 0.06)
gui_click { target: "保存" } → 点击 + 复核:新出现「已保存」
| 动作 | 调用 |
|---|---|
| 左键单击 / 双击 | gui_click {target:"确定"} / {..., count:2} |
| 右键、中键 | gui_click {target:"文件", button:"right"} |
| 长按(按住不放再松开) | gui_click {target:"…", holdMs:800} |
| 悬停(触发 hover 菜单 / tooltip) | gui_click {target:"…", hover:true} |
| 按住拖动 | gui_drag {from:{target:"a.txt"}, to:{target:"回收站"}} |
| 滚轮上下 / 左右 | gui_scroll {target:"列表", direction:"down", amount:5} |
| 键入文字(含大小写/中文) | gui_type {text:"Abc123"} |
| 输完回车(搜索 / 提交) | gui_type {text:"关键词", pressEnter:true} |
| 大写字母 / 组合键 | gui_key {keys:"shift+a"} / {keys:"ctrl+shift+s"} |
| 删除 / 退格 / 方向键 | gui_key {keys:"delete"}、{keys:"backspace", count:3}、{keys:"tab"} |
实现细节:
SendInput + KEYEVENTF_UNICODE):字符按字面发送,"AbC" 就是 AbC、中文直接进、
不受 CapsLock 与输入法状态影响,也不会切走用户当前的输入法;组合键才走虚拟键。try/finally),不会留下按住的左键。flowchart LR
A["gui_look<br/>截图 + OCR 文字索引"] --> B["模型<br/>决定点哪里 / 输什么"]
B --> C["gui_click / gui_drag<br/>gui_scroll / gui_type / gui_key"]
C --> D["自动复核<br/>局部差分 + 全屏差分 + 文字增删"]
D --> B
C -.-> E["F6+F7 看门狗<br/>独立进程,随时急停"]
每次动作后自动做三件事:
LockBits,约 40ms),报告「落点附近是否变化、变化了多少格、变化区域在哪」。新出现:已保存;消失:保存中。时钟、纯数字这类噪声会被过滤。结论明确写成「有反应 / 没有可测量的变化」。没反应时不要连点同一处,先 gui_look 看新画面。
Windows OCR 在整屏 2560×1600 上很不稳定:小字会被合并成一行(网易有道翻译 MATLAB)甚至整块丢失。
所以定位按下面的顺序退让,命中即停:
同一行里还会额外尝试词级片段(1~4 个词的组合):OCR 经常把相邻按钮并成一行, 行中心会落在两个按钮之间,而词片段的包围盒是准的。命中之后还会补一次整窗识别, 确认没有同名的第二个候选 —— 有就拒绝点击,把候选列出来。
/vision arm 或 /vision off;状态每次都从宿主的 /vision status 回读,
不会出现「按钮显示已允许、宿主却拒绝」的情况。插件重载后出现。allowControl 默认 false。未经用户授权时,所有工具直接报错,不截屏、不动鼠标。
两种授权方式:① 点上面的开关,或在本会话执行 /vision arm(用户命令,不产生模型消息);
② 在插件设置里勾选 allowControl。
/vision off 可随时收回,/vision status 查看当前状态;gui_status 也会报告 authorized。scripts/stop-watcher.ps1)监视,不依赖 agent 循环。触发时依次:
写急停标记 → 按 pid 杀掉输入进程 → 松开所有鼠标键与修饰键 → 通知插件取消当前回合。
看门狗没运行 → 所有工具失败即关闭(fail-closed):没有刹车的输入控制不能存在。pick 或 near 指定。%TEMP%\dsh-vision-click\<pid>\audit.log。作为 DSH(DeepSeek Harness)插件,需要一个 profile。
普通安装(不 clone 仓库,推荐):
# 预构建 tarball(release 附件,文件名不带版本号)
dsh plugin --profile desktop add https://github.com/zdd3143/dsh-vision-click/releases/latest/download/dsh-vision-click.tgz
# 或者直接从源码安装
dsh plugin --profile desktop add https://github.com/zdd3143/dsh-vision-click
本地开发(改代码即时生效):
git clone https://github.com/zdd3143/dsh-vision-click.git
dsh plugin --profile desktop add "link:C:/path/to/dsh-vision-click"
或者直接把目录放进 profile 的 dsh.profile.bundles。插件加载后 gui_* 工具即可用;
native/DshOcr.dll 已经随仓库提供,无需编译即可运行。
本地开发(
link:)插件在「插件市场 → 已安装」卡片上不会显示package.json/locale里的描述: 那张卡片渲染的是市场目录中的作者简介,本地插件没有目录条目(同样link:安装的dsh-desktop-control、dsh-ui-capture也是如此)。需要在卡片上也有说明时,点卡片里的 「+ 添加备注」 写一段即可 —— 备注会代替作者简介显示,只保存在本机。 发布到市场后,meta.description(见locale/zh.json)就会作为作者简介展示。
cordis.patch.yml 的 config)| 字段 | 默认 | 说明 |
|---|---|---|
allowControl | false | 用户开关;默认不截屏、不输入,需显式授权 |
emergencyHotkey | 'F6+F7' | 急停组合键 |
cancelTurnOnStop | true | 急停时取消当前回合 |
maxWidth | 1600 | 返回给模型的图宽(OCR 始终用原始像素) |
maxCaptureAgeMs | 20000 | 坐标点击的截图有效期 |
settleMs | 260 | 动作后到复核截图的等待 |
restoreCursor | true | 点完把光标放回原处 |
ocrScale | 1 | OCR 放大倍数;只有极小字体才需要调大 |
ocrLang | '' | OCR 语言(如 zh-Hans-CN);空 = 跟随系统 |
minMatchScore | 0.7 | 低于此分不点 |
maxActionsPerMinute | 60 | 输入限速 |
dragSteps / dragStepMs | 16 / 12 | 拖动插值步数与每步间隔 |
maxTypeChars | 2000 | 单次 gui_type 的字符上限 |
diffCellsX / diffCellsY | 24 / 16 | 全屏差分的网格 |
auditLog | true | 是否写审计日志 |
powershellPath | '' | 显式指定 PowerShell |
visionEnabled | false | OCR 没把握时是否调用本地视觉模型(UI-TARS 等) |
visionBaseUrl | http://127.0.0.1:11434 | Ollama 地址 |
visionModel | ui-tars:7b | 模型名(ollama list 里看到的) |
visionMode | fallback | fallback 仅兜底;always 总是问模型 |
visionTimeoutMs | 30000 | 单次模型调用超时 |
visionMaxWidth | 1280 | 送模型前把窗口缩到这个宽度 |
visionSnapRadius | 0.08 | 把模型点吸附到 OCR 词框的半径(相对短边) |
visionRetries | 1 | 点了没反应时围绕模型点微调重试的次数 |
visionOnDragRetry | true | 拖拽没生效时,用模型重新定位两端再拖一次 |
visionOnScrollRetry | true | 滚动没生效时,用模型重新定位滚动区域再滚一次 |
dsh-vision-click/
├── index.js # 宿主插件:10 个工具、截图/校验编排、急停联动
├── cordis.patch.yml # bundle 注册与默认配置
├── scripts/
│ ├── agent.ps1 # 常驻进程:截图、OCR、点击/拖动/滚轮/键入、差分
│ ├── stop-watcher.ps1 # 独立急停看门狗(F6+F7)
│ └── build-ocr.ps1 # 重新编译 native/DshOcr.dll
├── native/
│ ├── DshOcr.cs # WinRT OCR + 逐像素差分(C#)
│ └── DshOcr.dll # 已编译产物(10KB)
├── tests/ # 端到端测试 + 可交互测试窗口
└── docs/ # 架构与测试说明
native/DshOcr.dll 已随仓库提供。只有修改 native/DshOcr.cs 时才需要重新编译:
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/build-ocr.ps1
依赖 .NET Framework 的 csc.exe 与 Windows SDK 的 Windows.winmd(Win10/11 常见)。
编译失败时插件仍可工作,只是 gui_click 只能按坐标点。
powershell -NoProfile -ExecutionPolicy Bypass -File tests/target-window.ps1 # 打开测试窗口
node tests/run-click-test.mjs # 端到端跑一遍
测试窗口里有按钮、可拖动方块、滚动列表、可输入文本框和一对同名标签; 每个动作都会改变可见文字,所以断言的是可观察的事实(详见 docs/TESTING.md)。

31 项断言覆盖:文字索引、目标点击、重复点击、歧义拒绝、批量步骤、悬停、右键、长按、拖动、滚轮、
键入、全选覆盖、退格、回车,以及 gui_do 混排键盘步骤。
这个插件默认只看文字。OCR 读不到下面这些东西:
这些情况下只有两种出路:给 point 坐标(人看得见,模型看不见),或者启用视觉模型。
图标类控件必须靠视觉模型识别——OCR 对它们完全无能为力,这也是这个可选后端存在的主要原因。
开启后,只在 OCR 没有候选或分数不够时,把活动窗口裁出来发给本地模型(默认 UI-TARS-7B + Ollama), 模型给一个 0~1000 归一化坐标,再吸附到最近的、文字相关的 OCR 词框上——语义来自模型,精度仍由 OCR 保证。
config:
visionEnabled: true
visionModel: ui-tars:7b
visionMode: fallback # fallback | always
visionOnDragRetry: true # 拖拽没生效时,用模型重新定位两端再试一次
visionOnScrollRetry: true # 滚动没生效时,用模型重新找滚动区域再试一次
gui_click 会在结果里标出 source: "vision"、retried 与模型耗时/是否吸附成功;
gui_drag / gui_scroll 会标出 retried 与 source。
默认关闭:模型常驻约占 5~6 GB 显存,只有你显式打开才会加载;ollama stop ui-tars:7b 可立即卸载。
实测一个 6 步复杂任务(含画布上的无文字图标、无文字的滑块拇指):
仅 OCR 4/6,OCR+视觉 6/6,总耗时 10.0s → 16.9s——视觉只在真正需要的那两步被调用。
部署步骤、实测精度与坑见 docs/VISION.md。
point 坐标点击作为兜底,
失败信息会列出「这一帧实际读到了哪些文字」。