DeepSeek Harness 插件

wwwort/dsh-win-computer-use

Star 数 ★ 0 分类 工具与能力 收录于 2026-09-21

DSH 的 Windows 原生电脑操控:两个工具即可驱动并读取任意桌面应用。一次批量调用把找控件、点击、输入、按键、读取、等待、截图放进同一次引擎请求,步骤可事前确定的整件事只花一轮模型往返;读取返回窗口文本而非图像,输入与抓取优先走 UIA 模式、Win32 消息与 PrintWindow 离屏抓取,用户的焦点与鼠标全程不受打扰。

安装

# Release 预构建包

dsh plugin --profile web add "https://github.com/wwwort/dsh-win-computer-use/releases/latest/download/dsh-win-computer-use.tgz"

# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)

dsh plugin --profile web add github:wwwort/dsh-win-computer-use

装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本——pnpm 默认拦截,所以安装可能停在 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWEDERR_PNPM_IGNORED_BUILDS;dsh 会打印出需要添加的确切键名,把它加进该 profile 的 pnpm-workspace.yamlallowBuilds 下,重跑一次即可装上。放行构建本身就是一次信任判断:请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。

README

状态:✅ 已交付 · 发布件就绪(尚未进社区目录)

English →

Windows 原生的 DSH 电脑操控插件。一个批量工具把「找控件 → 点击 → 输入 → 等条件 → 截图」 整串放进一次引擎请求里跑完,并且默认走免聚焦通道 —— agent 在这台机器上干活, 不占用你正在用的桌面

在 ChatGPT 桌面端连跑两轮:工具调用打字发送,离屏抓取回复

省多少(实测)

下面这些是读数,不是估算。DeepSeek Harness 会把每次 assistant 消息的 usage 记进会话日志, 所以数字来自它自己的记录。

一个动作一次工具调用 本插件
8 个动作(启动 → 等窗口 → 找输入框 → 输入 → 点击 → 读回 → 截图) 8+ 轮模型请求 1 轮
整个对话上下文被重发 8 次 1 次
实测:单次请求重发了多少 那个 8 步批次调用是 185,404 token;同一长会话里每轮平均 437,542 token(峰值 473,134)
同一件事的上下文 token 量 8 × 437k ≈ 3.5M ≈ 437k → 少 ~3.06M(88%)
引擎启动 每次 ~800 ms(8 次≈6.4 s) 只付一次 ~800 ms,之后 40–100 ms/动作
看到结果 截图,再发一次 read_image 请求 截图在同一次调用里内联返回
8 步任务的墙钟 1 次调用、引擎内 1.4 s,目标窗口全程不在前台

关键在于轮数,不在 schema。每一次请求都要重发整个对话上下文 —— 那才是每轮按输入计费的东西, 也是为什么「八次调用并成一次」远比「把工具定义压掉几百字节」重要。

schema 也如实说: 把 8 个单动词工具并成 2 个,这一块反而变大了 —— 4,149 → 6,977 字符,因为 computer 的 step schema 文档了 41 个字段。它是每轮都在的固定前缀、 走提示词缓存命中;把它写大是刻意的取舍:字段说明换的是模型第一次就把整个 steps 写对。 省掉一次重试的价值远大于这点前缀 —— 一次重试就是又一次完整上下文重发,本次实测里最贵的单次请求 是 386,879 个未命中输入 token

该不该派子代理(两组实测)

同一件事既可以主代理自己做,也可以派子代理去做。省不省只取决于一件事 —— 主代理本来要几轮。 两种情形都做了对照,两边的 token 都用会话日志量出来:

① 不看屏幕就能写全的任务(同机同任务):

做法 代价
主代理直接做(一次 computer 批量调用) 522,925 —— 1 轮
派子代理做 525,007 + 99,092(子代理 5 轮)= 624,099

派出去贵 19%:一次批量调用一轮就做完了,而新起的子代理要五轮。所以「操作就派子代理」是错的。

② 冷启动的多轮任务(窗口随机显示一个取件码,不读屏幕就没法知道该填什么,主代理天然一轮做不完):

做法 代价
主代理直接做 559,092 + 560,636 + 562,535 = 1,682,263 —— 3 轮
派子代理做 565,459(1 轮)+ 98,535(子代理 5 轮)= 663,994

派出去省 61%。两组都自证通过(status: OK 2776 / status: OK 7152),且两边全程没抢前台。

起作用的就是两个常数:主代理一轮 ≈ 56 万 token,子代理一轮 ≈ 2 万 —— 差 28 倍。 子代理那一侧几乎不随复杂度增长,主代理那一侧是线性涨的,所以任务越复杂省得越多

主代理本来的轮数 自己做 派子代理
1(能一次写全) 0.56M 0.66M −15%(别派)
2 1.12M 0.64M 43%
3 1.68M 0.66M 61%(实测)
5 2.80M 0.70M 75%
10 5.60M 0.80M 86%
20 11.2M 0.99M 91%

越复杂省得越多,上限约 96%。

路由规则

  1. 不看屏幕就能把整个 steps 写全?(打开 X → 在 Y 输入 Z → 点确定 → 截图确认)→ 自己做,一次调用。派出去更贵。
  2. 否则 —— 下一步取决于屏幕上读到什么、或者要多阶段推进 → 整件事交给子代理,而且别自己先探一遍:先探一遍等于把探索成本又付一次,还是按主代理那侧的贵价付。

复用同一个子代理(实测)

后台子代理是 durable 的 —— 同一个操作代理被 send_message 派了第二个任务,照样做成(mirror=reuse-arm-ok,6 步,未抢前台)。 两个任务在这一个子代理里合计 120,803 token,而它每轮的上下文只从 18,496 涨到 21,554:复用不会把它撑大

但它仍然省不出钱来。后台子代理每交回一个结果,父代理就多一轮;而父代理一轮 ≈ 52.5 万 token —— 约等于那两个任务全部子代理开销的 14 倍。复用值得做,但理由是别的(操作代理积累了这台机器的界面知识,父上下文也不必再吸收第二份简报),不是省 token。

这套协议随插件以 computer-operator skill 形式提供:不加载时只占目录里一行。

为什么不一样

不抢你的操作 输入默认走 UIA 模式与 Win32 消息,窗口不在前台也能生效;截图走 PrintWindow,被遮挡也能拍。万不得已要物理输入时,结束会把前台窗口和光标位置还回去。mode:"background" 做不到就报错,绝不偷偷接管你的屏幕。
步骤按控件定位,不算坐标 find 找到控件、as:"ref" 记住它,click / type 直接点名控件,不用你从截图里换算像素坐标。
常驻引擎 后台引擎把 PowerShell/UIA 的初始化留热,Add-Type 只付一次。空闲(默认 10 分钟)自行退出;带内容指纹,改了引擎下一次调用自动换新,不用重启。

两个工具

刻意只有两个:一个动作一个工具,等于每轮请求都要为那些 schema 付一遍 token。

工具 作用
computer steps: [{op, …}, …]find / click / type / key / wait / shot / windows / uia / mouse / focus / clipboard / process / display / sleep。步骤可以用 as:"ref" 引用前面找到的控件,按控件点击,不必换算坐标shot 放最后一步会把图片直接返回。
computer_shot 只截图。给 window/pidPrintWindow 离屏抓取、不激活窗口;也支持 region / display / scale
// 一次调用:新开会话 → 打字 → 发送 → 等回复 → 截图
{"steps": [
  {"op": "click", "window": "ChatGPT", "name": "新聊天"},
  {"op": "wait",  "window": "ChatGPT", "type": "Edit", "timeout_ms": 8000},
  {"op": "type",  "window": "ChatGPT", "type": "Edit", "text": "你好", "mode": "background"},
  {"op": "key",   "keys": "enter", "window": "ChatGPT"},
  {"op": "sleep", "ms": 7000},
  {"op": "shot",  "window": "ChatGPT"}
]}

读内容比看屏幕便宜

内容是文本时(聊天回复、日志、状态行),就把窗口读成文本,别截图:

{"op": "read", "window": "ChatGPT", "tail": 2000}                                  // 最新 2000 字符
{"op": "wait", "window": "ChatGPT", "state": "text_stable", "stable_ms": 2500, "tail": 2000}

在 ChatGPT 桌面端实测:一轮长回复读回来是 2,200 字符可逐字引用的文本,一次调用约 280 ms; 而截图要付一张图、引不出准确文字、还会抓到当时恰好压在上面的那个窗口。 页面阅读顺序里新内容在尾部、应用自身的外壳在开头,所以 tail 读法天然干净。

它比在浏览器里找元素更可靠。有那么一刻,ChatGPT 窗口的 UIA 元素树塌成了 13 个节点 (只剩标题栏按钮和空 Pane,整个网页都不见了),而同一时刻 read 仍返回 25,722 字符的页面文本。 所以 Chromium 应用里 find 返回 0 命中不等于控件不存在 —— 可能只是那棵树没被建起来。

wait state:"text_stable" 取代猜的 sleep:它轮询文本,连续 stable_ms 不再变化就把文本返回 (实测:静态页面 4 次轮询、2.6 秒返回)。元素树没了也能写 —— focus + 物理 type 用真实按键照样送进输入框。

写入侧也不依赖元素树

同一个问题的另一半:树塌了就没有元素可寻址;而激活窗口并不会把键盘焦点给输入框 —— 光激活就打字会被丢掉,Ctrl+A 选不到东西,Enter 会落到应用自己当前的焦点上。 最后这一条,正是「发送了却没发出去」的成因。

所以 typekey 都接受 x/y,并先点一下——这才是真正移交键盘焦点的动作:

{"op": "type", "window": "ChatGPT", "mode": "physical", "x": 1436, "y": 1205,
 "text": "...", "verify": true}                       // 点一下拿焦点 → 真实按键 → 读回自证
{"op": "key", "keys": "enter", "window": "ChatGPT", "x": 1436, "y": 1205}

在元素树只剩 13 个节点的 ChatGPT 上实测:strategy: "physical.keystrokes"focusClick: trueverified: true,随后在输入框里读到了那段文字。长文本可以走 "via": "clipboard" —— 一次剪贴板写入 + Ctrl+V,取代上千条 SendInput(会保存并还原原剪贴板文本)。

两者合起来,一轮对话完全不需要元素树:点一下并打字 → Enter → wait text_stableread

还有两件事只有真跑起来才会暴露:

  • 前台是「每次调用」归还一次,不是「每一步」归还。 在同一批 steps 之间归还前台,等于把前台交给用户窗口、让目标应用丢掉控件级焦点 —— 下一步的 Enter / Ctrl+A 就无处可落。「发送了却没发出去」正是这么来的。 现在:每个物理步骤后只还原光标,整次调用结束时统一归还前台(结果里报 foregroundRestored);某一步写 restore:false 可以退出该行为,用来表达「把焦点留在这个应用上」。
  • 纯「文本不再变化」会停在「正在处理」上。 实测 wait state:"text_stable" 曾在 ChatGPT 的 「正在回应」 这个稳定短串上判定完成,返回了一个还没到的回复。要给它护栏: {"op":"wait","state":"text_stable","absent":"正在回应","contains":"ChatGPT 说"}

后台优先的三层策略

mode 缺省 "auto":先试免聚焦层,不成才退回物理输入。mode:"background" 做不到就报错, 绝不偷偷接管你的屏幕;mode:"physical" 才真的抢焦点,而且会还回去

动作 第 1 层(免聚焦) 第 2 层(免聚焦,老控件) 第 3 层(借桌面)
点击 UIA Invoke / SelectionItem / Toggle / ExpandCollapse BM_CLICKWM_LBUTTONDOWN+UP 直发控件 SetCursorPos + mouse_event
输入 UIA ValuePattern.SetValue WM_SETTEXT写后读回校验才认成功 SendInput 逐字符(任意 Unicode,含中文)
按键 —(组合键必须过 OS 输入队列,没有诚实的后台形式 keybd_event
截图 指定窗口时 PrintWindow(PW_RENDERFULLCONTENT) CopyFromScreen

后台层每次都要自证WM_SETTEXT 写完会读回比对,不相等就不算成功。

环境要求

  • 仅 Windowsapply()process.platform 有硬校验)。
  • 引擎用 Windows PowerShell 5.1powershell.exe)—— 它自带 UIAutomationClientSystem.Drawing,PowerShell 7 没有。
  • dsh web 0.1.x

安装

dsh plugin --profile web add github:wwwort/dsh-win-computer-use

发布到 npm 后,dsh plugin --profile web add dsh-win-computer-use 也可用。

构建

lib/预构建产物且已提交,安装过程不会构建任何东西。要自己重建:

DSH_CHECKOUT=<dsh 源码 checkout> bash scripts/build.sh
node scripts/preflight.mjs        # 校验 dsh.bundle 清单与真实入包清单

实测(2026-09-19,2560×1600,Windows 11)

结果
一件事一次调用 8 步(启动应用 → 等窗口 → 找输入框 → 输入 → 点击 → 读回标签 → 截图):1 次调用、1.4 s
后台输入 uia.valuePattern / win32.wm_settext,读回 bg-typed 中文 OK目标窗口全程不在前台,前台始终是用户自己的窗口
后台点击 win32.bm_click,应用自己的点击计数变成 1
离屏截图 被别的应用遮挡的窗口完整抓到(source: "printwindow"
冷/热调用 ~800 ms(一次性进程)→ 42–104 ms(常驻引擎)
改引擎 win.ps1 后下一次调用自动退休旧引擎并起新的;1 行日志,无重试风暴
上面的第 2、3 轮 追问引用了上一轮的回答 —— 同一个会话,全程由工具调用驱动

本仓库里的截图就是那次运行的真实抓取,裁掉了无关的桌面内容(侧栏、账号、用量等)。

已知边界

  • 仅 Windows。
  • key 没有后台实现 —— 要零打扰就用 type(写值)或 click(Invoke)。
  • 老控件(WinForms 等)在 UIA 下常常只暴露 Pane、无任何 Pattern,走 Win32 消息层; Chromium 渲染区不接受 WM_SETTEXT,浏览器页面只能靠 UIA ValuePattern 或物理输入。
  • contenteditable 输入框可能接受了 SetValue 却仍报告占位符,因此 type 返回 verified: true|false 加一句说明,而不是暗示一定成功。
  • 人机并发:「设坐标 → 点击」不是原子操作,指针没到位时引擎拒绝点击并报出指针真实位置。
  • UIA 遍历在复杂窗口上可能数百毫秒;uia 会在 max_nodes 处截断并说明。
  • 窗口标题是子串匹配,零宽字符会让匹配失败 —— Edge 自己的标题里就有一个。
  • windows restore 会激活窗口(SW_RESTORE);最小化的窗口不恢复就没法有意义地抓取。

许可

BSD-3-Clause。

内容来自项目 README(GitHub)↗

链接

同类插件

查看整个分类 →

社区评论

评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。