给 DeepSeek Harness(DSH)用的 Windows 桌面控制工具:屏幕布局、带标注截图、点击 / 输入 / 按键 / 滚动、窗口管理,还有 UI Automation 结构化查询。零依赖,不常驻服务,不用 API key。
30/100
综合分
2
GitHub 星标
404
npm 周下载
—/5
用户评分
数据截至 2026/10/28 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-guikitnpm 源 · dsh-guikit 的发布者账号已校验与本仓库一致
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
是什么
dsh 原生插件 · vision
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 20 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✓npm 包dsh-guikit @ 0.6.2
✓Node 引擎要求 >=18 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/14 18:55:53
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
读写/删除本地文件
查看扫描证据(共 1 处,展示前 1 条)
- 写入本地文件lib/index.js:368
try { mkdirSync(ART, { recursive: true }) } catch (e) { workDir = process.cwd() }
扫描于 2026/11/2(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-tools给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
功能亮点
- 屏幕布局与窗口管理
- 带标注截图
- 点击输入按键滚动
最近更新
在 GitHub 查看全部 →v0.5.12026-10-13
release: v0.5.1 — 改名 sage-guikit -> dsh-guikit
· 包名与 cordis.patch.yml 的 id / name、lib/index.js 的 export const name 同步跟进
(三者不一致会崩在 readiness 之前)
· 环境变量 SAGE_GUIKIT_DIR -> DSH_GUIKIT_DIR,缓存目录 %TEMP%\dsh-guikit
(breaking change,缓存可再生、无数据损失)
· keywords 补 dsh-plugin / deepseek / cordis / computer-use / uiautomation 等
· 脱敏:清 README 与 NOTICE 的真名署名、CHANGELOG 的开发机绝对路径
· .gitignore 忽略未发布的设计稿,避免误提交
· v0.5.0 之后的提交
· 5e97fe5 release: v0.5.1 — 改名 sage-guikit -> dsh-guikit
本 Release 为补齐历史补建 —— 该版本发布时未打 tag。
v0.5.02026-10-13
feat(0.5.0):gui_verify 语义校验 + 如实交付回执
从 cua-driver(DSH 官方实验性 computer-use 插件)借了两样东西:
1. gui_verify——单个谓词(元素存在 / 可用 / valueEquals,或窗口存在 / 边界)轮询到连续 stableSamples 次检查结果一致,返回 satisfied | unsatisfied | unknown。unknown 表示无法判定(目标窗口不在、UIA 遍历不完整),绝不能当成成功。不存在无法证明,所以拒绝 exists:false。
2. 所有输入工具(click/drag/type/key/scroll)都带 delivery:{sent:true,verified:false}。输入到达操作系统不等于目标有反应;2026-09-25 我们遇到一次点击报 ok,界面却纹丝没动。
gui_click 还会在落点 x,y 旁边回显 requested:{x,y}。
已验证:12 个工具注册成功;5 个 pwsh 探测用例表现正常(命中=satisfied,缺失=unknown,边界错误=unsatisfied 并给出精确差值);delivery 只出现在输入工具上;4 个参数校验正确拒绝。
· v0.4.0 之后的提交
· baf76a6 feat(0.5.0): gui_verify semantic verification + honest delivery receipts
· 067bced chore: snapshot 0.4.0 work (gui_window_shot, gui_drag, annotate grid) before adding verification tooling
本 Release 为补齐历史补建。
v0.4.02026-10-13
release: v0.4.0 — MIT 换 Apache-2.0,加 NOTICE
代码一行未改(lib/index.js 与 cordis.patch.yml 的 hash 与 0.3.0 相同),功能完全一致。换协议的理由是 Apache-2.0 第 3 条给专利授权、第 4(d) 条要求 NOTICE 随下游分发保留——后者 MIT 没有,别人 fork 走可以把署名合法删掉。
配套:LICENSE 换 Apache-2.0 全文;新增 NOTICE(Copyright 2026 Bowen Zheng (gezi-wen));package.json 的 license 字段改为 Apache-2.0,files 白名单加 NOTICE(不加就不会进 npm 包)。
· v0.3.0 之后的提交
· 896327c release: v0.4.0 — MIT 换 Apache-2.0,加 NOTICE
· d5282b2 chore: sync pnpm-lock with the devDependencies removal
· 906ee06 chore: drop @deepseek-ai/* devDependencies so pnpm install cannot shadow the junction bridge
本 Release 为补齐历史补建。
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。
README
由 DeepSeek 最新模型翻译生成dsh-guikit · DSH GUI 工具集
Windows 桌面控制工具集,给 DeepSeek Harness(DSH)的智能体装上眼睛和手:看屏幕、单窗口截图、点鼠标、拖动、打字、按快捷键、滚动、等生效、验证生效、管窗口,并能对标准控件做 Windows UI Automation 结构化定位,以及按元素名直接定位到可点击坐标(gui_locate)。
Windows desktop-control toolset for DeepSeek Harness (DSH): monitor layout, whole-screen and single-window capture, click / drag / type / key / scroll, pixel / window / semantic verification, window management, UI Automation structured queries, and element-name lookup. Thirteen model tools, zero external dependencies — no resident service, no Python, no API key.
- 平台:Windows 10/11(不跨平台)
展开完整文档(README 剩余 1 节)
dsh-guikit · DSH GUI 工具集
Windows 桌面控制工具集,给 DeepSeek Harness(DSH)的智能体装上眼睛和手:看屏幕、单窗口截图、点鼠标、拖动、打字、按快捷键、滚动、等生效、验证生效、管窗口,并能对标准控件做 Windows UI Automation 结构化定位,以及按元素名直接定位到可点击坐标(gui_locate)。
Windows desktop-control toolset for DeepSeek Harness (DSH): monitor layout, whole-screen and single-window capture, click / drag / type / key / scroll, pixel / window / semantic verification, window management, UI Automation structured queries, and element-name lookup. Thirteen model tools, zero external dependencies — no resident service, no Python, no API key.
- 平台:Windows 10/11(不跨平台)
- 依赖:PowerShell 7(pwsh)+ .NET 的 System.Drawing / UIAutomationClient,都是系统自带
- 形态:DSH profile bundle(host 侧注册 13 个模型工具)
安装
1. 进 DSH profile 目录(例如 ~/.dsh/profiles/web)
cd <DSH profile 目录>
pnpm add dsh-guikit
2. 在该目录的 package.json 里把 bundle 挂上
"dsh": { "profile": { "bundles": [ ..., "dsh-guikit" ] } }
3. 重启 dsh web
本地开发用 link: 也行:
"dependencies": { "dsh-guikit": "link:<你 clone 下来的目录>" }
⚠️ link 方式下裸导入从源位置向上解析,够不到 profile 的 node_modules——需要在插件目录自己跑一次 pnpm install 装 peer 依赖(@deepseek-ai/cordis、@deepseek-ai/dsh-tools)。从 npm 安装没有这个问题。
工具(13)
| 工具 | 参数 | 用途 |
| gui_screen | — | 显示器布局 + 坐标契约:每块屏的 physical_rect{x,y,w,h} / logical_rect / dpi_scale / is_primary / display_id,顶层 virtual_desktop_origin{x,y} 与 space:"virtual-desktop-physical"。dpi_scale 按屏取(MonitorFromPoint + GetDpiForMonitor),取不到时是 null——不假装 1.0(logical_rect 同时为 null)。旧的 x/y/w/h/primary/device 与 virtual/cursor 全部保留。任何坐标操作之前先调它 |
| gui_screenshot | screen / x,y,w,h / annotate / step | 截屏存 PNG 并返回路径(用 read_image 看)。回执带 space:"virtual-desktop-physical" / origin{x,y} / image_px{w,h} / scale / note。annotate=true 画顶部+左侧标尺、对齐网格交点的坐标标签和左下角水印(水印内容即 origin/space/scale/region/step)——别按图上像素距离量坐标,读标签文本 |
| gui_window_shot | window / handle / annotate / step | 单窗口截图:PrintWindow 抓窗口自己的内容,被别的窗口盖住也照样抓,不激活、不移动。回执带 space:"window-local" / origin{x,y}(= 旧的 x/y,两个名字都在)/ scale / image_px;annotate 同样画标尺与左下角水印,标尺标签用绝对屏幕坐标。比整屏省一个数量级(实测 DSH 窗口 298 KB vs 整屏 8.8 MB) |
| gui_locate | name(必填) / window(title 是等价别名) / elType / index / depth / max / timeoutMs | 元素名 → 可点击坐标。在目标窗口(window 标题子串或 handle,不给就用前台窗口兜底)按 name(可选 elType / index)用 UIA 定位,返回可直接喂 gui_click 的虚拟桌面物理像素 point{x,y}(元素矩形中心)+ rect + type + enabled + offscreen + uiaPath(祖先链数组,最多 6 层)+ total/matched/depth_reached + space/origin/scale。七个状态里只有 status="found" 会给顶层 point(绝不返回猜测坐标):ambiguous 回 candidates[] 前 5 个候选(各带 rect/point/offscreen;离屏候选的 point 为 null),offscreen / not-found / not-exposed / inconclusive / truncated 一律不给 point。timeoutMs 给了就反复重走直到元素出现(250 ms 间隔;默认 0 = 单次);index 越界 → 明确 error 并附 validIndex(不静默回落 0)。见「UIA 五态」一节 |
| gui_click | x y (必填) / button / clicks / window / space / verify | 真实光标移到物理像素并点击。1/2/3 = 单/双/三击。window 是 per-call 目标(标题子串或 handle,不再只认当前前台);space="window-local" 时(必须给 window)入参按 gui_window_shot 的图内像素解释,换算在 pwsh 里做。回执新增 space / origin / scale / requested / absolute / insideWindow / target(旧 x/y/result/fgTitle/fgHandle 保留)。verify 可选:点完在同一进程内立刻跑一次 gui_verify 等价断言,结果并进 verify:{verdict,detail,observed,tries,streak},默认关;delivery.verified 只在 verdict=satisfied 时为 true |
| gui_drag | fromX fromY toX toY (必填) / button / steps / stepDelayMs / window / space | 按住拖动:选文字、拖标题栏、拉滑块/滚动条、拖放。移动是插值的(只 SetCursorPos 不产生拖拽事件)。steps 调大更稳更慢。无论成败都保证松开按键。window/space 语义与 gui_click 一致(四个坐标一起换算),回执同样回带 space/origin/scale/insideWindow,requested/absolute 是 {from,to} 两点,insideWindow 要求起点与终点都在窗口内 |
| gui_type | text (必填) / mode / x y | 向当前焦点控件打字。unicode(默认)逐字符 SendInput,绕过 IME,中文无损;clipboard 走剪贴板 Ctrl+V(会覆盖用户剪贴板)。可选先点击 (x,y) 定位焦点 |
| gui_key | keys (必填) | 真实键盘按键或组合键:enter、ctrl+s、alt+f4、ctrl+shift+tab、win、printscreen。修饰键 ctrl/alt/shift/win,f1–f24,a–z,0–9,方向键等 |
| gui_scroll | x y (必填) / direction / notches | 在 (x,y) 处滚轮。up/down 纵滚,left/right 横滚,默认 3 格 |
| gui_window | action (必填):list/rect/activate/move | 窗口管理:列出可见窗口(标题/句柄/pid/进程/矩形/z 序)、取单个窗口 bounds、激活到前台、移动改尺寸 |
| gui_wait | mode (必填):pixel/window | 轮询验证。pixel 盯某个像素:compare=change 与调用时基线比变化,eq/neq 比指定 RGB;window 等某个标题的顶层窗口出现。点完先等生效再截图 |
| gui_verify | mode (必填):element/window + title/handle/name/elType/enabled/valueEquals/boundsX..H/tolerancePx/timeoutMs/stableSamples | 语义验证:断言一个 UIA 元素存在(可选再要求可用 / 值相等),或断言某窗口存在(可选再比对 bounds)。连续 stableSamples 次都成立才算数,否则轮询到超时。返回三态 satisfied / unsatisfied / unknown——unknown 是「判不出来」,绝不可当成功读。缺席不可证明,所以 exists:false 会被拒。0.6.0 起与 gui_uia 遍历器同源(RawView + depth 40),Chromium a11y 也够得到——同一断言前后对照:unknown(5201 ms/tries=6) → satisfied(140 ms/tries=1)。输入类工具之后跟一次它,才算拿到结论 |
| gui_uia | action(必填):tree/find/invoke/value + handle/title/name/elType / depth(默认 40,硬顶 40)/ max(默认 2000,硬顶 8000;遍历预算)/ maxRows(默认 120,硬顶 2000;返回预算) | Windows UI Automation 结构化查询,一次只限定一个窗口。0.6.0 改走 RawViewWalker,并把 depth 提到 40(两个机制叠加,见「UIA 五态」一节)。回带 total/matched/truncated/rows_truncated/depth_reached/walker。返回五态 status(见「UIA 五态」一节;只有 empty-but-accessible 能读成「确实没有」)。invoke 走 InvokePattern(回退 TogglePattern),直按按钮,不用坐标;value 走 ValuePattern,直读控件文本。返回的 type 是短名(Button,不再是 ControlType.Button);矩形是物理像素,可直接喂 gui_click |
坐标系契约
三套空间同时存在,工具之间不互相猜:每一步都把空间名与换算所需的数回带出来。
虚拟桌面(本机 5360×1750,origin (0,-278)) ┌─────────────────────────────────────────────────────────────┐ y=-278 │ 副屏 DISPLAY1 2800×1750 @200% ┌──────────────────┐ │ │ │ 主屏 2560×1440 │ │
│ (1) 虚拟桌面物理像素 │ @154% │ │
│ = 所有坐标类工具的输入口径 └──────────────────┘ │ │ gui_screen.virtual_desktop_origin │ └─────────────────────────────────────────────────────────────┘ y=1472
x=0 x=5360
gui_screenshot x=0,y=-278,w=1600,h=1000
┌──────────────── 图像像素(图左上 = 截取区域左上)──────────────┐ │ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ← 顶部标尺:每条网格列上 y=0..13 的不透明刻线 │ │ ──────────────── ← 左侧标尺:每条网格行上 x=0..13 │ │ 0,-278 240,-278 480,-278 ← 标签 = 绝对桌面坐标 │ │ │ │ origin=(0,-278) space=virtual-desktop scale=1 ... │ ← 左下角水印 └──────────────────────────────────────────────────────────────┘ read_image 再降采样 ×1.50(本机 5360→3584):文字还在,尺寸变小
| 空间 | 谁在用 | 原点 |
| 虚拟桌面物理像素 virtual-desktop-physical | gui_click / gui_drag / gui_wait 的入参;gui_screen / gui_uia / gui_verify / gui_locate 的回带 | 虚拟桌面左上(本机 (0,-278),副屏可为负) |
| 截图图像像素(gui_screenshot) | read_image 看到的那张全屏/区域图 | 图左上 = 截取区域左上 |
| 窗口局部像素 window-local(gui_window_shot) | 单窗口截图;gui_click/gui_drag 的 space="window-local" 入参 | 窗口左上 |
换算只有两个方向,都是单程:
- 图内像素 → 桌面坐标:绝对 = origin + 图内像素 × scale
- 桌面坐标 → 图内像素:图内 = (绝对 − origin) ÷ scale
scale 由工具回带,不要自己估:gui_screenshot 恒为 1(图与桌面 1:1);gui_window_shot 与 space="window-local" 在 DPI-unaware 窗口上可能 > 1(= 系统 DPI ÷ 窗口 DPI)。
read_image 还会再缩放一次(本机整屏 5360 → 3584,×1.50)——这一层图素间距不可信,所以坐标一律读图内烧好的标签文本(标签是绝对桌面坐标),或改用下面这条零换算路径。
为什么要加标尺与水印:0.5.x 的 annotate 标签与真实网格线有递增偏差 (实测 240→195px、480→355px、720→515px,随坐标增大),照标签估坐标会系统性点偏;而 read_image 那层降采样又让图素间距无法反推。所以 0.6.0 把「换算依据」直接烧进图里: 顶部/左侧标尺给出网格的真实图内位置、标签逐字对应它标记的交点、左下角水印带上 origin/space/scale/region/step——这三样都可以用像素探针断言,不再依赖「看起来对齐」。
零换算路径(推荐):gui_window_shot 出图 → gui_click space="window-local" window="<同一窗口>" 直接把图内像素喂进去,换算在 pwsh 里做,回执里的 absolute 才是真正投递的桌面坐标。
水印把换算依据跟图绑在一起——图存下来再回看时也不丢。
UIA 五态:gui_uia / gui_locate 的 status
gui_uia(tree/find)与 gui_locate 都回带 status。「查不到」不等于「不存在」,五态把这两件事分开,模型才知道下一步该换路、该加预算,还是该收工:
只有 empty-but-accessible 允许读成「确实没有」。
not-exposed / inconclusive / truncated 都不是「元素不存在」的证明:第一个要换视觉路径,第二个要人工判断(空窗口与 a11y 被藏分不清),第三个要放宽预算重查。
下面是 gui_uia 的五态:
| status | 含义 | 判定(都是「遍历自然走完」的前提下) | 模型该怎么反应 |
| found | 命中(gui_uia 的 invoke/value 也在此态才有元素可用) | 有匹配 | 直接用 rect / point,或 invoke / value |
| empty-but-accessible | 树里有内容(有 Text 或可交互控件),但确实没匹配 | 自然走完 + 树有内容 + 无匹配 | 换个 name / elType 再试,或换窗口 |
| not-exposed | 有节点但零内容节点——自绘界面,或该应用没点亮 Chromium a11y | 自然走完 + textNodes == 0 且可交互控件数 == 0 + total > 0 | 立刻换视觉路径:gui_screenshot annotate=true 读标签,或用 gui_locate/gui_click。回执里带这条 hint |
| inconclusive | 一个节点都没有——空白窗口与「a11y 被藏」(Qt / 自绘)分不清 | 自然走完 + total == 0 | 别重试(不会变);hint 两种可能都提。若你确信这里有内容,走视觉路径 |
| truncated | 遍历被 depth / max 截断且无匹配——有没有答案不知道 | 撞了 depth 或 max 上限 | 提高 depth / max(max 可到 8000),或先用更宽的 name 试;absence 未证明 |
gui_locate 的 status 是更大的集合(它多了「命中几个 / 能不能点」这两维): found(唯一命中,或给了 index,且中心在窗口内)/ ambiguous(命中 >1 又没给 index——回 candidates[] 前 5 个候选,各带 rect/point/offscreen,顶层不给 point,不替你猜)/ offscreen(元素存在但离屏,或中心落在窗口 rect 外——先滚动/切标签/激活窗口让它可见,别点那个坐标)/ not-found(=gui_uia 的 empty-but-accessible:遍历走完、确实没匹配)/ not-exposed / inconclusive / truncated。
「绝不猜测坐标」的可读表达:这 7 个状态里只有 found 会给顶层 point; ambiguous 只给候选列表(离屏候选的 point 是 null),其余五态一个都不给。
offscreen 不是理论顾虑——本机 DSH 窗口实测 elType=Text 里有 1202/1297 个元素 offscreen:true、 1130 个 y < −1000(最深的 y = −15585):直接把这些坐标喂给 gui_click,就会点到窗口外的别处。
gui_locate 的目标窗口用 window(标题子串或 handle,推荐)或它的别名 title (两者都给时 window 优先);都不给就用前台窗口兜底。两者共用「自然走完」这条底线与 total 分流规则。
四条容易踩的细节:
- depth 默认 40 是必需的,不是保守值。两种机制叠加(同一 DSH 窗口实测,可用 probe-control-vs-raw.ps1 复跑):
1. ControlViewWalker 与 FindAll(Descendants) 只返回 IsControlElement=true 的元素——本机同一窗口两者都给 818,而 RawViewWalker 给 1578,所以它们会漏掉非控件节点:实测有 17 个非控件元素落在 depth ≤ 14 内、会被直接跳过。
2. 更主要的是 depth 预算在两种遍历器之间不等价:RawView 的中间层把真实内容推深(743 个非控件元素在 depth > 14),而 ControlView 是塌缩视图,depth 14 就覆盖了全部 818 个控件元素。
⇒ 只换 walker 不提 depth 会比 0.5.x 更差(同一窗口 ControlView@10 = 46 个元素 vs RawView@10 = 14 个); depth 默认 40 是必需。默认给 10 会把有 a11y 的窗口误判成空壳,所以 not-exposed 还额外要求「遍历自然走完」。
- total 才是「遍历覆盖量」(本窗口 1578),matched 是「匹配数」、count 是「回带行数」—— 三者不要混用;五态分流看 total。FindAll(Descendants) 不是全量(818 vs 1578):任何「用 FindAll 数元素」的直觉都不成立,这也解释了旧实现为什么会漏元素。
- max 与 maxRows 是两件事:max 是遍历预算(默认 2000、硬顶 8000),maxRows 是返回预算 (默认 120、硬顶 2000)。被返回预算截断时 rows_truncated=true 且附 hint——默认 120 行不是全部,要全量就显式提高 maxRows(本机 DSH 窗口 total 实测 1500+,默认全量返回等于上百 KB JSON 灌进上下文)。
反过来,max 停在 2000 会让「不存在」永远无法被证明:本机主窗口真树有 2450~2522 个元素,默认预算下工具会诚实回 truncated,要下「确实没有」的结论就得显式 max: 8000 走完。
(元素数随窗口内容漂移,同一窗口不同时刻实测过 1578 / 2450~2522 —— 别按固定值估。)
- hints 是数组,hint 取第一条:状态类 hint(not-exposed / inconclusive / truncated)排在 rows_truncated 的 hint 之前,所以只读 hint 也能拿到「最该先看的那条」。
- 别跑一次就下结论:窗口初始化未完成时,同一窗口的元素数会瞬态翻倍 (实测同一个 3 按钮窗口先回 total=6、后回 total=3)。要么重跑一次确认,要么优先信 gui_verify 那种「连续 N 次都成立才算数」的断言。
not-exposed 时最多再重试 1 次(实测 4 分钟内读数一动不动),重试落在同一个 pwsh 进程里; invoke/value 在 not-exposed / inconclusive / truncated 下不会报误导性的「no element matching」,而是回同样的 status + hint。
精度栈:三层互补
| 层 | 工具 | 适用 |
| 结构层 | gui_uia / gui_locate | 标准控件(Win32 / WPF / WinForms / 已点亮 a11y 的 Chromium):gui_locate 把元素名直接换成可点击坐标,gui_uia 按名称或控件类型直读直按,最省 token 也最可靠 |
| 视觉层 | gui_window_shot / gui_screenshot annotate=true | 自绘界面、游戏、没有点亮 Chromium a11y 的应用:标尺与标签画进图里,智能体读标签报坐标。单看一个窗口用 gui_window_shot(被遮挡也抓得到,还省 token) |
| 验证层 | gui_verify / gui_wait | gui_verify 走语义(元素存在/可用/值相等、窗口存在/bounds)并给出 satisfied/unsatisfied/unknown 三态结论;gui_wait 走像素变化与窗口出现,更便宜但只说明「有变化」 |
日常顺序:gui_screen 拿布局 → gui_locate 试语义定位(命中即得坐标,零换算)→ 没命中再 gui_window_shot 出图、annotate 读标签 → gui_click(窗口内可直接 space="window-local") → gui_verify 拿结论(或 gui_wait 等变化)→ 必要时 gui_uia 直读控件文本核对。
输入类工具的回执:sent 不等于生效
gui_click / gui_drag / gui_type / gui_key / gui_scroll 的返回值都带:
"delivery": { "sent": true, "verified": false, "note": "..." }
它只说明事件送进了 OS,不说明目标有反应——实测过点击回执 ok 而界面纹丝不动(Chromium 丢弃后台 PostMessage、合成光标被实时抢走)。要结论就得跟一次 gui_verify;只读类工具(gui_screen / gui_uia / gui_verify)不挂这个字段。gui_click 另外回显 requested:{x,y},与落到实处的 x,y 并排,两者对不上时一眼可见。
gui_click 另有一个可选参数 verify:参数与 gui_verify 同名(mode 必填,另有 title/handle/ name/elType/enabled/valueEquals/exists/boundsX..H/tolerancePx/timeoutMs/stableSamples/ intervalMs;这个对象设了 additionalProperties: false,键名写错会被 schema 直接拒)。给了它,点击之后会在同一个 pwsh 进程内跑一次与 gui_verify 等价的断言,结果并进回执
verify:{verdict,detail,observed,tries,streak}。此时 delivery.sent 仍是 true(事件确实送进了 OS),
而 delivery.verified 只有在 verify 判定 satisfied 时才为 true——unsatisfied / unknown 都保持 false;delivery.note 会写明这是「工具内断言」的判定结果,并点明只有 satisfied 才算 verified(不给 verify 时仍是原来那句「SENT, not confirmed」)。默认关;关键操作建议开,省掉「再跟一次 gui_verify」这一步。
实现
每次工具调用 = 一个 PowerShell 子进程,注入内联 C#:
- U32.<内容哈希>.dll 编译缓存(原子写入 tmp + Move、>4KB 完整性校验、失败自动回退内存编译),冷启动一次编译,之后只是 Add-Type -Path。文件名取自 C# 源码的内容哈希,源码一改缓存自动失效——固定版本号那种写法会加载到缺新方法的旧 DLL,报「U32 不包含名为 X 的方法」
- user32 SendInput / SetCursorPos / EnumWindows / SetForegroundWindow / PrintWindow;UIA 走 UIAutomationClient。拖拽用 SendInput 绝对坐标 + MOUSEEVENTF_VIRTUALDESK 插值移动
- SetProcessDPIAware:所有坐标一律物理像素,高 DPI 与多屏负坐标都安全;逐屏 DPI 另走 MonitorFromPoint + GetDpiForMonitor(取不到就回 null,不假装 100%)
- 结果以 JSON 从 stdout 回传,转成 text block。stdout 在 PRELUDE 第一句显式钉死为 UTF-8——子进程没有控制台时 .NET 会回退到系统 ANSI 代码页(中文 Windows = gb2312),不钉死则回传的中文界面文本(UIA 元素名、窗口标题)会乱码
截图与编译缓存的落盘目录默认是 %TEMP%\dsh-guikit,可用环境变量 DSH_GUIKIT_DIR 覆盖。
一个非显然的约束:别把脚本写胖
Windows Defender 会经 AMSI 扫描传给 pwsh -Command 的脚本。把「按标题找窗口 + GetWindowRect + PrintWindow + 画网格」全塞进一个脚本时,会被判为恶意脚本直接拒绝执行(报「此脚本包含恶意内容,已被防病毒软件阻止」)——枚举窗口标题 + 捕获窗口正是窥屏软件的特征。实测把窗口截图拆成独立工具、脚本压瘦后就不再触发。加功能时请留意脚本体积与 API 组合,改动后跑一遍 smoke.mjs。
已知边界
- UIPI:点不进管理员权限窗口;锁屏 / UAC 安全桌面完全不可达
- Windows 前台锁:程序化抢焦点会被系统静默拒绝——注入前先 gui_window activate 或点击目标窗口。动作类工具可带 window 显式指定目标,不必依赖「当前前台」
- z 序陷阱:前台窗口切换后原坐标可能落到别的窗口上——靠 click/type 的焦点窗口回显发现
- scale 与 DPI(点/拖/截都受影响,混合缩放最容易踩):gui_window_shot 回带的 scale = 系统DPI ÷ 窗口DPI,是「图像像素 → 虚拟桌面坐标」的换算系数。三种取值都要认得:
- scale = 1:DPI-aware 窗口 + 该显示器缩放与主屏一致(最常见)。图坐标就是屏幕坐标
- scale > 1:DPI-unaware 应用(WinForms 默认、部分 CEF 壳)在缩放 ≠ 100% 时——内容按 96dpi 逻辑尺度渲染、只铺在图的左上角,其余是黑的;SetCursorPos 坐标被缩放甚至返回 false,拖拽位移可能被吃掉(实测 175% 下拖 unaware 窗口:纵向走 90px、横向 0px)。Obsidian / Electron / Edge 这类 DPI-aware 应用不受此影响
- scale < 1:DPI-aware 应用跑在「缩放百分比与主屏不同」的显示器上(实测主屏 175% + 副屏 200% ⇒ 168/192 = 0.875)。这时该窗口的坐标是被 Windows 虚拟化过的,不是 bug 用法三种情况都一样:gui_click / gui_drag 传 space="window-local"、直接给 gui_window_shot 图里的像素,scale 由工具自己乘;要手算就是 屏幕坐标 = origin + 图坐标 × scale。别自己心算比例
- 混合缩放下的截图裁切(已知限制,下个版本修):gui_window_shot 的位图画布按「虚拟桌面坐标空间」分配,而 PrintWindow 按窗口自身像素渲染内容,两者不等时图就不完整:
- scale < 1(该屏缩放 > 主屏)⇒ 内容比画布大,右下被裁掉(实测画布 2823×1773 只装下 3226×2026 内容的一部分,整条底部播放条不见了)
- scale > 1 ⇒ 内容比画布小,右/下多出纯黑边这只影响「看图能看到多少」,不影响坐标正确性:scale 依然正确、space="window-local" 的点按依然命中。要完全避开,把所有显示器设成同一个缩放百分比(scale 恒为 1,图与屏幕逐像素对应)
- 最小化窗口:gui_window_shot 对最小化窗口会出黑图,先 gui_window activate
- 锁屏:锁屏时 SetCursorPos 静默返回 false、光标冻住、前台 Idle。这是环境阻挡不是插件 bug——唯一判据是光标真能移动
- UIA 不暴露 ≠ Electron:能不能走 UIA 取决于该应用有没有点亮 Chromium 的 a11y,不是「Electron/CEF 一律是盲区」。DSH 自己的 Electron 窗口裸 UIA 就有 470 个元素(RawViewWalker 数百到近千个,随窗口内容漂移),而某些第三方 Electron 壳(实测 OrpheusBrowserHost:total=4 / textNodes=0 / 可交互控件=0)只暴露空壳。点亮手法无客户端解法(WM_GETOBJECT、事件订阅、CacheRequest、SPI_SETSCREENREADER 全部实测无效),所以这类应用只能走视觉路径——gui_uia / gui_locate 会明确回 not-exposed 并给 hint,不会假装「控件不存在」
- 拖拽起拖阈值:Windows 从按下到认定「开始拖动」之间的位移不生效,所以 gui_drag 的实际位移总比请求短约 8–11px(实测:请求 +150/+30 → 实际 +142/+28;请求 −260/−28 → 实际 −249/−28;请求 +117 → 实际 +111)。要精确落位用 gui_window move(SetWindowPos,无阈值),拖拽适合「移过去就够」的场景
- Cloudflare Turnstile:勾选框式人机验证对自动化会话不放行(点了也过不去)——反自动化闸门不硬碰,直接换可达站点
- annotate 会静默粗化 step:区域面积 > 500 万像素且 step < 200 时,实际步长被改成 240,而回执里不自报实际值(只有图内水印写了)。要让程序化链路知道步长,显式传一个 ≥ 200 的 step
- 未知参数会被静默忽略:工具参数对象没有设 additionalProperties: false,把 handle 传给只认 window 的 gui_locate 不会报错,而是静默回退前台窗口、再报「没有前台窗口」。按上表用参数名:gui_locate/gui_click/gui_drag 用 window(gui_locate 另收 title),gui_uia/gui_verify 用 title
- gui_type 的 clipboard 模式会覆盖用户剪贴板;unicode 模式更干净
- 工具操作的是真实鼠标键盘——跑自动化期间这块屏幕就是它的实验台,同机其它智能体的输入会互相污染
DSH 兼容性
engines.dsh: >=0.1.2-rc.1 —— 单边开区间、不设上界,任何未来的 DSH 版本都不会被这道声明挡住。
已在两个生产配置档上实测:官方桌面版 0.2.0-rc.2 与网页版 0.2.0-rc.1,13 个工具全部可用。
host 半只依赖 @deepseek-ai/cordis 与 @deepseek-ai/dsh-tools,peer 写 * —— 不锁定宿主版本,也不绑定任何 client 半(本插件没有 UI)。
开发
pnpm install # peer 由 DSH 闭包提供;这里装的是 devDependencies
node smoke.mjs # 被动冒烟:布局 / 区域截图 / 窗口截图 / 窗口列表 / UIA / 等待
node smoke-drag.mjs # 拖拽端到端:起一个 DPI-aware 测试窗口,拖它标题栏并核对位移
smoke-drag.mjs 会接管真实鼠标 1–2 秒,同屏有别的智能体在跑 GUI 自动化时不要跑。
定位
同类方案(如基于 MCP 的 Windows computer-use server)通常走无障碍树 + 视觉模型 + 常驻进程,能力更全,代价是额外运行时(Python/uvx)、常驻内存和 API key。本插件的取舍相反:零依赖、无常驻、坐标直控,会话级即插即忘,代价是没有视觉理解——坐标靠 gui_screen 给契约、gui_locate 直接换出可点坐标、标注截图自证,不需要模型心算换算。
License
Apache-2.0 — 见 LICENSE,署名与第三方声明见 NOTICE。
Copyright 2026 gezi-wen
选 Apache-2.0 而不是 MIT,是因为它多两样东西:明确的专利授权(第 3 条),以及NOTICE 必须随下游分发保留(第 4(d) 条)——后者是 MIT 没有的,换协议之后别人 fork 走也没法把署名合法地删掉。预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
gezi-wen/dsh-guikit
给 DeepSeek Harness(DSH)用的 Windows 桌面控制工具:屏幕布局、带标注截图、点击 / 输入 / 按键 / 滚动、窗口管理,还有 UI Automation 结构化查询。零依赖,不常驻服务,不用 API key。
相关插件 · 视觉能力
智能体工作台Devin-AXIS/iPolloWork
企业级、本地优先的智能体工作台,面向个人和智能体团队——一个工作区,容纳多个智能体引擎;一套统一体系,管理插件和技能;支…
★ 7,359已汉化DSH
72
开源设计工作台nexu-io/open-design
OpenDesign:首个协作式 AI 设计智能体工作区
★ 105,514已汉化DSH
71.8
多智能体编码工作区spacering-net/codeg
多智能体协作的 AI 编程工作区:汇总 Claude Code、Codex、OpenCode、Pi、Grok Build 等来源的会话。支持桌面应用、自托管服务器或 Docker。
★ 4,289已汉化DSH
71.6
GPT 图像提示词库freestylefly/awesome-gpt-image-2
Prompt as Code | GPT Image 2 / 2.5 提示词与案例库,530+ 个案例、20+…
★ 34,534已汉化DSH
71.5
架构图生成器tt-a1i/archify
把你想理解、规划或分享的事,变成可以互动的可视化作品。
★ 92,675已汉化DSH
71.2
AI 用量桌面小部件Javis603/token-monitor
EN | 简 | 繁 | KO | JA | PT
★ 3,522已汉化DSH
71