Skip to content

dsh-desktop-tools-plugin

Verified

dsh-desktop-tools-plugin · v0.4.0 · MIT

DeepSeek Harness 桌面工具插件:注册 desktop_step / get_screen_metrics 两个工具,用 user32.dll 直接操作跑着 dsh 的那台机器的屏幕、鼠标与键盘(Windows)。desktop_step 把「截图→操作→等监视区变化→再截图」做成同一次调用,画面以图像块直接返回。

Install

dsh plugin add dsh-desktop-tools-plugin

Confirm the layer applied with dsh --profile default --dump-config — see the install guide.

Source

Published to npm without a public repository. Inspect the package contents before installing.

Tags

Readme

dsh-desktop-tools-plugin —— 桌面操作

DeepSeek Harness 插件:让 Agent 直接操作跑着 dsh 的那台机器的屏幕、鼠标与键盘。

两个工具

工具 作用 副作用
desktop_step 一整步:截下全屏并记下监视区基线 → 按顺序执行一批动作 → 盯住监视区等它变化(并等画面稳定)→ 把新的全屏画面和一份状态报告一起返回 ⚠ 真实的鼠标与键盘操作、真实的截屏
get_screen_metrics 读虚拟桌面范围、各显示器矩形、光标位置、前台窗口标题 无(只读)

截图不是工具。 它是 desktop_step 的内部步骤:画面只会在一次明确的桌面操作(或一次明确"只看一眼"的调用)里产生,并且图像块直接附在结果里——不需要模型去猜"哪张图最新",也不需要 read_image 再去打开文件。

循环

desktop_step(actions, watch) ──► 新画面 + 报告(图像块 + 文本)
        ▲                                   │
        └──────── 模型看图决定下一步 ◄───────┘
直到任务完成(模型不再调用它)

每一轮都是"图 + 指令":结果里既有画面,也有"这一轮做了什么、监视区变没变、下一步该怎么走"的文字。循环不需要模型去判断"要不要等、等多久"——等待由监视区决定。

desktop_step

参数 类型 说明
watch array 监视区:1–4 个矩形(虚拟桌面物理像素)。动作执行之前先记下这些矩形的像素,动作之后高频比对,一变化就返回新画面。省略 = 不做变化检测,动作后固定静置 0.1 秒
actions array 按顺序执行的动作,最多 20 个。省略或空数组 = 不操作,只截一张图
timeout_ms integer 监视区的最长等待,0–2000,默认 2000。到点还没变化就照常截图并在报告里写"未检测到变化";0 = 完全不等
annotate boolean 默认 true:图上叠加记号(监视区红框、光标青十字、点击落点绿/橙圈)
grid boolean 默认 false:叠加 200 px 网格与坐标刻度(刻度写的是虚拟桌面坐标)

actions 的动作

kind 必填 可选 说明
move x y duration_ms 移动光标;duration_ms > 0 时匀速滑过去(上限 10000)
click — x y button count 单击 / 双击(count: 2)。省略 x/y = 就点光标当前位置
text text submit 把整段文字输入当前焦点(SendInput 的 Unicode 注入,不碰剪贴板);submit: true 再按一次回车
key key repeat 按一次键或组合键:enter/tab/esc/ctrl+a/alt+f4…
scroll direction amount x y 滚轮;滚轮发给光标下的窗口,所以通常给 x/y
wait ms — 原地等待(0–10000),用于"等动画跑完再继续"这类批内节奏

一批动作连续执行、期间不回传画面也不等界面反应,所以有先后依赖的操作要拆成两步:

  • 拆开:先点菜单 → 下一步 点菜单项(菜单得先开出来);
  • 可以放一批:点输入框 → 打字 → 回车。

另外:一批里"移动时长 + wait"合计上限 15 秒,动作上限 20 个。

返回

  • 图像块:整个虚拟桌面的全屏画面(见下面"图像与文件");
  • 文本:本轮动作(含实际落点)、监视区的判定(每块的"强变化/明显变化/最大像素差"、多久检出、静置结果)、画面尺寸与坐标对应关系、叠加记号的含义、光标位置与前台窗口标题、以及一句"下一步"。

例子:

本轮:在 (700, 522) 左键单击 → 输入 5 个字符 → 按 Enter
监视:R1(560, 380) 700×260 变了(强变化 2.31%、明显变化 3.14%、最大像素差 211)(0.19 s 后检出,静置 0.45 s 后画面已稳定)。
画面:整个虚拟桌面 1920×1080(图 x/y 与屏幕坐标一致);叠加记号:红框配 R1/R2… = 本次监视区、绿圈(左键)/ 橙圈(右键)= 本次点击落点、青色十字 = 当前光标位置(是插件画的,不是屏幕内容)。
光标 (700, 522);前台窗口:记事本 - 未命名。
下一步:看上面的画面决定下一次 desktop_step;任务已完成就直接回答用户,不要再操作桌面。

监视区:为什么不需要模型判断等待时间

不动点:画面稳定时,两次抓屏得到的像素是逐位相同的(屏幕采集没有传感器噪声),所以任何非零的像素差都是真实信号。于是判定分两档:

档位 判据 用途
强变化 单通道差 > 24 的像素占监视区 ≥ 0.08% 文字、边框、窗口弹出、整块重绘
明显变化 单通道差 > 8 的像素占监视区 ≥ 1% 悬停高亮、轻微变色这类小幅度但成片的变化

两档都不满足就当没变化——这正是"闪烁的光标"(几个像素)不会误触发的原因。检出之后还有一段静置:连续两次采样都低于稳定阈值(0.04% / 0.4%)才算稳定,最多等 1.2 秒,避免把正在打开的窗口拍成半张。轮询间隔 150 ms,循环体在同一个 PowerShell 进程里做字节比对(见"实现")。

超时(默认 2 秒)到点也会返回画面,并在报告里写明"未检测到变化,要么动作没生效,要么变化不在这几块矩形里"——因为每一步都带回新画面,监视区只决定"这次等多久",决定不了"能不能看到结果"。循环不会因为一次误判而丢信息,最坏就是多一轮。

图像与文件

  • 一步的画面先落成系统临时目录里的一张 PNG,交给附件存储后立刻删掉:不再往磁盘上攒"一堆不知道哪张最新"的截图;
  • 图像以附件引用(attachmentId)承载,附件存储按内容哈希去重、按尺寸归一化(超过 2048² 像素或 4 MB 会重编码),所以同一张画面重复出现不会重复占盘;
  • 若图像被缩小,报告里会写明"附加的图像已缩到 W×H,图上坐标 ×k 才是屏幕坐标";否则写明"图 x/y 与屏幕坐标一致"。

坐标系

虚拟桌面的物理像素,不是逻辑像素、也不是窗口坐标:

  • 原点在主显示器左上角,x 向右、y 向下;
  • 主显示器左侧/上方还挂着显示器时,那些屏上的坐标是负数;
  • 合法范围就是 get_screen_metrics 给出的闭区间(单屏 1920×1080 是 x ∈ [0, 1919]、y ∈ [0, 1079]);
  • 坐标或监视区落在虚拟桌面之外时直接报错并给出可用范围,不静默钳制。监视区越界时这次调用不会执行任何动作(校验发生在动作之前);
  • 返回的图像永远是整块虚拟桌面("所有显示器拼起来的完整画面"),所以图上的像素坐标与屏幕坐标一一对应。

脚本每次都先尝试 SetProcessDpiAwarenessContext(PER_MONITOR_AWARE_V2),让量范围、截图范围和落点处在同一套物理像素坐标系里;高 DPI 缩放下这一步很关键。

实现

node:child_process 起一个短命的 PowerShell 进程(优先 pwsh,缺失时退回系统自带的 powershell),内部用 P/Invoke 直接调 user32.dll:

SetCursorPos / GetCursorPos / GetSystemMetrics / EnumDisplayMonitors
SendInput / GetForegroundWindow / GetWindowText

抓屏与画记号用 System.Drawing(Graphics.CopyFromScreen + LockBits),PNG 由 Bitmap.Save 写出。不依赖 robotjs 之类的第三方原生模块。

几处刻意的取舍:

  • 监视区轮询跑在同一个原生进程里。每次采样都要 CopyFromScreen + LockBits,如果由宿主进程"起一个 PowerShell → 比对 → 退出"来轮询,一次进程启动(几百毫秒到一秒)就决定了轮询只能是秒级;放进同一个进程,一次调用只付一次启动与 Add-Type 编译,循环体是 C# 里的字节比对,150 ms 一次毫无压力。
  • 分工:需要编译的 P/Invoke 部分(键鼠注入、光标、显示器枚举、像素比对)是纯 C#、不引用任何外部程序集;图像部分(抓屏、画记号、存 PNG)走 PowerShell 的 Add-Type -AssemblyName System.Drawing。这样 C# 不必引用 System.Drawing,避开"Windows PowerShell 5.1 与 PowerShell 7 引用的程序集名字不同"这个坑。
  • 脚本落在 %TEMP%\dsh-desktop-tools\native-script-<脚本哈希>.ps1(同一版本复用同一份),载荷是 JSON 临时文件、路径经环境变量传入,结果也写成 UTF-8 文件再读回。原因:-EncodedCommand 会把脚本塞进命令行,而 Windows 命令行上限是 32767 字符,长文本输入会直接撞上限;Windows PowerShell 5.1 的 stdout 还是 OEM 编码,非 ASCII 的窗口标题会在管道里变成乱码。
  • 脚本体与 C# 源码保持纯 ASCII(有断言守着):Windows PowerShell 5.1 对无 BOM 的 .ps1 按 ANSI 代码页解码,掺进中文会被解成别的字节、可能把整段语法搞崩。中文文案都在 TypeScript 一侧拼。
  • 键鼠事件用 SendInput(keybd_event/mouse_event 是官方标注废弃的老接口);Unicode 文字走 KEYEVENTF_UNICODE + wScan,所以中文、emoji 不是靠剪贴板也能打出来。

单次调用要付一次 PowerShell 启动 + Add-Type 编译的开销:实测 Windows PowerShell 5.1 上约 0.45–1.1 秒(动作与轮询本身是毫秒级)。

平台

只支持 Windows。

⚠ 安全与影响面

  • 这些都是真实的桌面操作,直接作用于跑着 dsh 的那台机器:调用瞬间,坐在机器前的人会感觉鼠标被"抢走"、按键被替自己敲下去。没有沙箱、没有二次确认。
  • click 会真的按下鼠标键:一个乱点的坐标可能关掉窗口、点掉按钮。先让 desktop_step 看一眼画面(不带 actions)再动手。
  • text 输入到当前焦点窗口——焦点不在期望的输入框里,文字就进到别处;submit: true 还会顺手回车。同一批里用 click 点一下目标输入框最稳妥。
  • key 打在当前前台窗口上:ctrl+a 会全选、delete 会删内容、alt+f4 会关窗口。
  • scroll 滚的是光标下的窗口,不是"当前应用"。
  • text 不碰剪贴板。
  • 目标窗口以管理员身份运行、或会话被锁屏/在安全桌面时,SendInput 会被系统拒绝,此时工具明确报错而不是静默失败。
  • 画面里的屏幕上的一切都可能进入会话上下文(聊天窗口、密码框、私人页面)。用完把 cordis.patch.yml 里那行改成 disabled: true。
  • 同一个桌面被人同时使用时:监视区看的是像素,人的操作也算"变化",可能让某一步提前返回;这只多一轮,不会出错。

装进 Harness

Web 侧边栏 → Plugins → Add plugin,填本目录的绝对路径:

C:\DSH\dsh-desktop-tools-plugin

开发

# 在工作区根目录(C:\DSH)执行一次,统一装好共享依赖
pnpm install

pnpm --filter dsh-desktop-tools-plugin run build   # 重建 lib/

运行时装的是 lib/(src/ 只是源码):

  • src/native-script.ts —— 交给 PowerShell 的脚本体(含 C# 源码与"纯 ASCII"断言);
  • src/native.ts —— 宿主侧管道:载荷/结果临时文件、shell 选择、结果解析与错误翻译;
  • src/desktop-tools.ts —— 两个工具的定义、动作校验、报告文案。

类型检查(本目录不在工作区根 tsconfig.json 的 include 里,所以用自带的那份配置):

node node_modules\typescript\bin\tsc -p tsconfig.typecheck.json

本目录的 node_modules/ 里是对工作区共享依赖(@deepseek-ai/dsh-tools 等)的链接。⚠ 本目录用 Move-Item/Copy-Item 移动或复制过的话,pnpm 的 junction 会被展开成真实目录拷贝,tsdown/typescript 的依赖可能随之解析失败——遇到过就回工作区根跑一次 pnpm install。要移动请用 robocopy /XJ(不跟随 junction)。

已知边界

  • 没有拖拽(按下—移动—抬起)与长按;
  • 画面永远是整块虚拟桌面,没有"只看某块矩形"的取景参数(多显示器拼起来很宽时,细节会被归一化缩放吃掉);
  • 附件存储会随画面数量增长(按内容去重,但没有自动清理策略);
  • 监视区只画 1–4 块、只判定"变没变",不会告诉模型"哪里变了"(可以看画面,或下一轮把监视区放小一点)。