← 返回列表
未验证
通过 SSH 从 DeepSeek Harness 控制远程 Windows 桌面。与本地 dsh-click…
尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/9/14 · 已提供中文文档
从 DeepSeek Harness 通过 SSH 控制远程 Windows 桌面 # 从 DeepSeek Harness 通过 SSH 控制远程 Windows 桌面 ## 概述 本指南介绍如何从 DeepSeek Harness 通过 SSH 建立到远程 Windows 桌面的连接,从而能够以编程方式控制远程桌面。 ## 先决条件 - 已安装并配置 DeepSeek Harness - 一台启用了 SSH 的远程 Windows 机器 - 网络访问权限和有效凭据 ## 步骤 1. 在远程 Windows 机器上启用 SSH 2. 从 DeepSeek Harness 配置 SSH 连接
综合分
29.7
GitHub 分
29.7
用户评分
—
★ Stars
0
周下载量
—
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add pureexe/dsh-windows-remote-ssh该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
数据截至 2026/9/16(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-agent@deepseek-ai/dsh-attachment@deepseek-ai/dsh-llm@deepseek-ai/dsh-session@deepseek-ai/dsh-tools@deepseek-ai/dsh-user-approval@deepseek-ai/schemastery用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动
README
dsh-windows-remote-ssh
通过 SSH 从 DeepSeek Harness 控制远程 Windows 桌面。与本地 dsh-click 插件具有相同的工具界面和安全模型——截图、UI Automation 读取、点击/输入/滚动/按键、应用列表/启动——但每个操作都在通过普通 SSH 连接的 Windows 机器上运行,而不是本地子进程。harness 本身可以运行在 Linux、macOS 或 Windows 上;只有目标需要是 Windows。
工作原理
- 目标上无需预装代理。 此插件在远程主机上唯一假设的是可用的 SSH 服务器(Win32-OpenSSH,现代 Windows 内置)。其他一切——窗口枚举、UI Automation、截图、输入传递——都由本包附带的一个 PowerShell 脚本(native/win32/dsh-windows-remote-ssh-helper.ps1)完成,首次需要时通过 SFTP 暂存到目标上,如果它发生变化则自动重新暂存(内容哈希与标记文件比较)。它只使用 Windows 自带的类(.NET 的 System.Windows.Automation、System.Drawing 以及 user32.dll/kernel32.dll P/Invoke)——无需 Python、无第三方模块、无单独安装程序。
- 基于文件的请求/响应,而非 stdin/stdout。 每次调用通过 SFTP 写入一个小型 JSON 请求文件,运行一次辅助脚本,然后通过 SFTP 读回一个 JSON 响应文件。通过 SFTP 传输字节而不是通过 SSH exec 通道的 stdin/stdout 管道传输,完全绕开了 Windows 控制台代码页和 UTF-8/UTF-16 编码陷阱。
- 在用户真实的桌面会话中运行,而非隐藏会话。 这是让通过普通 SSH 进行 GUI 自动化真正可行的关键细节:Win32-OpenSSH 在非交互式 Session 0 / 窗口站中运行普通的 ssh user@host command,与交互式桌面完全隔离——在那里,即使进程运行正常,EnumWindows、UI Automation 和屏幕捕获也都会返回空。因此,此插件不直接执行辅助脚本,而是将其调度为绑定到已连接用户的一次性计划任务,并带有“仅在登录时”标志(将其附加到用户的交互式会话),立即触发它,然后轮询响应文件——这是通过 SSH 驱动 Windows GUI 的标准变通方法。关于这对目标账户意味着什么,请参阅下面的要求。计划进程通过一个微小的 VBScript 包装器(WScript.Shell.Run 使用隐藏窗口样式)启动,而不是直接使用 powershell.exe -WindowStyle Hidden——仅该标志仍会让控制台在生效前在屏幕上闪现片刻。经验证:在持续轮询的情况下,重复调用中零窗口出现。
- 默认情况下绝不抢占用户的鼠标/键盘焦点。 操作优先使用 UI Automation 的 invoke/value/scroll 模式;当元素不暴露任何模式时,它们回退到投递窗口消息
(WM_LBUTTONDOWN/WM_KEYDOWN/等),而不是移动真实光标或将窗口带到前台。将窗口带到前台仅作为显式的、受配置门控的回退方案提供(focusFallback: 'allow',默认关闭)。另一个更具侵入性的可选启用项(allowHardwareInput: true,同样默认关闭)允许调用传入 hardware: true,改为通过真实的 SendInput 事件投递——这是触达那些完全忽略投递消息队列的应用(游戏、DirectX/UWP 界面、原始输入读取程序)的唯一方式;参见下方的 hardware: true。
要求
- 目标账户必须是本地管理员,并且在工具调用运行时必须已经以交互方式登录(已解锁的控制台/RDP 会话)——计划任务会附加到该现有会话;它不会创建会话。如果无人登录,调用会以明确的 SCHEDULE_FAILED / 超时错误失败,而不是挂起。
- 目标上运行着 Win32-OpenSSH(sshd)且可访问。
- 无论 harness 本身在哪里运行,都需要 Node.js ^22.19.0 || >=24.0.0。
安装
dsh plugin --profile web add "github:pureexe/dsh-windows-remote-ssh#main"
然后通过 profile 补丁挂载它(每个字段见 cordis.patch.yml,或在 bundle 层级覆盖):
- insert:
- id: dsh-windows-remote-ssh
config:
ssh:
host:
user:
Prefer a private key over a password where you can:
privateKeyPath: /path/to/id_ed25519
autoApproveWindows: ['^Notepad']
连接详情也可以来自环境变量而非 profile(便于 CI 使用,也便于让凭据不进入已检入的补丁):SSH_HOST、SSH_PORT(默认 22)、SSH_USER、SSH_PASSWORD、SSH_KEY_PATH、SSH_KEY_PASSPHRASE。当两者都设置时,显式的 config.ssh. 字段优先于匹配的环境变量。
提供 SSH 目标:两种选项
有两种方式告诉此插件要控制哪台 Windows 机器,而且它们可以混用——一个配置好的默认值,加上针对个别例外的每次调用覆盖。
1. 一次性配置 ssh.host/ssh.user/ssh.password(或 ssh.privateKeyPath),如上所示——推荐。 目标及其凭据永远不会出现在模型的上下文中,也永远不会被写入 harness 自身的工具调用会话日志。每次工具调用只需完全省略 ssh 并使用此默认值。对于固定、已知的目标(你自己的桌面、实验 VM、CI runner),这是正确的选择。
2. 不设置 config.ssh,让模型每次调用时提供目标。 每个入口工具(screen_shot、screen_read、app_list、app_launch)都接受一个可选的 ssh 参数——host、port、user,以及 password 或 privateKeyPath 之一——模型会根据你在聊天中告诉它的内容填写(“以 admin 身份连接到 10.0.0.12,密码 hunter2”)。无需编辑配置,当目标变化时这很方便
对话到对话,或者事先未知。权衡如下:
该主机/用户/密码现在成了对话的一部分——它会像任何其他工具参数一样流经模型的上下文,并被执行框架自身的工具调用日志捕获,就像任何其他工具调用的参数一样。如果可以,优先选择方案 1,或者至少在聊天中使用 privateKeyPath 而不是原始 password,这样密钥本身留在磁盘上,而不是留在记录中。
如果给出了 host/user(在聊天中,或在 config.ssh 中),但既没有 password 也没有 privateKeyPath,插件会回退到运行执行框架的机器上的默认 SSH 身份——~/.ssh/id_ed25519、id_ecdsa,然后是 id_rsa,与普通 ssh CLI 使用的查找顺序相同——之后才会以“requires either a password or a privateKeyPath”失败。这正是让“以 alice 身份连接到 10.0.0.12”在调用中完全没有凭据的情况下也能工作的原因,只要该机器的默认密钥已在目标上获得授权。
无论哪种方式,click/type/scroll/key 都不需要 ssh 参数——它们会针对其所引用的 basedOn 观察来源的主机进行重放,因此单个对话可以安全地同时处理多台远程机器,而不会让某个操作落到错误的机器上。
如果没有 config.ssh 默认值,也没有每次调用的 ssh 参数,调用会立即失败,并给出明确的错误,要求提供其中之一——它绝不会静默猜测或挂起。
工具
在开启 lazyToolLoading(默认)时,启动时只注册 pc_control;调用它一次就会为对话的其余部分注册下面所有其他工具,因此一个从不需要远程控制的会话永远不会为其他约 20 个 schema 付出提示词 token 成本。设置 lazyToolLoading: false 可改为立即注册所有内容。
| 工具 | 只读 | 审批 | 用途 |
|------|-----------|----------|---------|
| pc_control | ✅ | — | 加载此插件的其余工具(仅在 lazyToolLoading 开启时存在) |
| screen_shot | ✅ | — | 将窗口/屏幕(或 region/display)捕获为图像附件(或使用 imageMode: 'text' 仅捕获文本描述) |
| screen_read | ✅ | — | UI Automation 辅助功能树 + 像素位置提示 |
| app_list | ✅ | — | 枚举正在运行的应用程序及其窗口(现在包括 minimized/maximized) |
| display_list | ✅ | — | 枚举每台显示器:索引、矩形、是否为主显示器 |
| cursor_location | ✅ | — | 当前鼠标光标位置,使用与窗口矩形相同的屏幕坐标 |
| wait_for | ✅ | — | 轮询(约 500ms)直到满足条件或超时,然后返回新的观察结果 |
| clipboard(获取) | ✅ | — | 读取远程剪贴板文本 |
| process(列出) | ✅ | — | 列出正在运行的进程 |
| click | | 是 | 点击元素(按 id)或坐标(hardware: true 表示真实 SendInput) |
| type | | 是 | 将文本输入可编辑元素,失败时回滚(hardware: true 表示真实 SendInput) |
| scroll | | 是 | 滚动元素或窗口(hardware: true 时使用真实的 SendInput) |
| key | | 是 | 发送按键组合(例如 Ctrl+S)(hardware: true 时使用真实的 SendInput) |
| move | | 是 | 移动鼠标或拖拽,默认通过投递窗口消息实现(hardware: true 时使用真实的 SendInput) |
| multi_action | | 是 | 针对一次观察结果运行一批 click/type 子操作(可选列表 selectionMode) |
| window_control | | 是 | 最小化/最大化/还原/移动/调整大小/关闭窗口 |
| app_launch | | 是 | 按名称或路径启动应用程序 |
| filesystem_pull | | 是 | 从远程主机下载文件(图片 → 附件,除非 imageMode: 'text';小文本 → 内联;否则 → 文件附件) |
| filesystem_push | | 是 | 从字面内容或重新提供的附件引用上传文件到远程主机 |
| clipboard(设置) | | 是 | 替换远程剪贴板文本 |
| process(终止) | | 是 | 按 pid 或名称终止一个或多个进程 |
| notify | | 是 | 显示真实的 Windows 操作中心 toast 通知 |
| invoke | | 是 | 直接在元素上调用 UIA 控件模式方法(Invoke/Toggle/ExpandCollapse/SelectionItem/ScrollItem/Value/RangeValue) |
| read_text | ✅ | — | 通过 UIA Text 模式读取元素的完整内容 + 当前选区 |
| read_table | ✅ | — | 通过 Grid/Table 模式读取网格/表格元素的结构化单元格数据 |
| powershell | | 是 | 以完整用户权限运行任意脚本——默认关闭,见下文 |
screen_shot — 图像可能小于实际捕获区域
click/move/key 等操作都采用真实屏幕坐标——与 window.rect 和 display_list 相同的坐标空间——绝不是从截图图像上读取的原始像素位置。图像本身可能因两个独立原因而从该真实尺寸缩小:本插件自身的 maxSide 上限,和/或 harness 的附件存储在保存时进一步缩小它。每当返回的图像小于实际捕获区域时,工具结果都会附带一个 scaleNote,说明真实尺寸/原点以及精确的像素到屏幕坐标转换,这样通过视觉估算点击目标的调用方就能正确转换,而不是点击错误的位置。cursor_location 在这里也很有用——它以相同的真实屏幕坐标报告真实光标的实际位置,用于校验计算出的目标或确认上一个操作实际落点。
filesystem_pull / filesystem_push — 在两台机器之间移动文件
从远程 Windows 主机下载文件以便在此处检查,编辑后再上传回去——对配置文件、脚本或图像很有用。
- filesystem_pull(remotePath) 下载文件,并根据其类型:图片会作为真实图像附件返回(与 screen_shot 使用的机制相同——实际可见,而不仅仅是字节);小
text 会以内联方式作为纯字符串返回,模型可以直接读取并对其推理;其他任何内容都会变成通用的 file 附件。每种情况都会返回一个引用(或文本本身),filesystem_push 可以消费它,将其直接写回。
- filesystem_push(remotePath, ...) 只接受以下之一:content(一个字面字符串——写回编辑后的文本/配置文件最自然的方式),或 image/file(一个附件引用,完全按照之前 filesystem_pull 返回它的方式重新提供,以便将这些确切的字节原样写回——例如,在其他某个工具生成了所拉取图像的编辑版本之后)。默认会创建缺失的父目录。
- 两者都像变更操作一样受审批门控——与 screen_shot/screen_read 不同,读取(或写入)任意路径不会被当作免费的“观察者”:它可能暴露操作员从未放到屏幕上的内容。
- maxFilesystemTransferBytes(默认 10 MB)限制任一方向的一次传输;超过它会直接拒绝,而不是截断(截断只会损坏二进制内容)。maxInlineFilesystemBytes(默认 100 KB)是更小的阈值,超过该阈值时,拉取的文本文件会作为文件附件存储,而不是内联,这样仅仅较大的文件就不会使模型的上下文膨胀。
hardware: true — 为忽略投递消息的应用提供真实的 SendInput 投递
click、type、key、scroll 和 move 都接受一个可选的 hardware: true 参数。当设置该参数时(并且配置 allowHardwareInput: true,默认关闭),该操作会作为真实的 SendInput 事件投递——实际的 OS 鼠标光标会移动并点击,真实的键盘级按键事件会被注入——而不是使用 UI Automation invoke 或投递窗口消息(WM_LBUTTONDOWN/WM_KEYDOWN/等)。之所以存在这一点,是因为投递消息虽然对普通桌面应用很可靠,却根本无法到达某些目标:游戏、DirectX/UWP 渲染的表面,以及任何其他读取原始输入(GetAsyncKeyState、RAWINPUT)而完全不处理窗口消息队列的东西。对于接收应用来说,SendInput 与物理鼠标/键盘无法区分,因此它也能到达这些目标。
这比 focusFallback: 'allow'(它只是把窗口带到前台,但仍然投递消息)在类别上更具侵入性:hardware: true 总是把目标窗口带到前台,并移动真实光标 / 抢占真实键盘焦点,任何观看远程桌面的人都可见,并且会干扰那台机器前的人正在做的事情。它默认关闭,必须通过 allowHardwareInput: true 有意开启;如果调用传入 hardware: true 但没有设置该配置,会以清晰的错误失败,而不是静默回退。一旦启用,它仍然像其他所有变更操作一样受审批门控。当实际使用了这条路径时,操作结果会报告 delivered: "hardware"。
有一个值得了解的副作用:由于它是真实的硬件事件,而不是发布到某个窗口的消息,move 的 drag 上的 hardware: true 也会触达真正的 OLE/Shell 拖放(例如在两个资源管理器窗口之间拖动文件)——这正是 README 中关于发布消息式拖放的诚实限制(见下文)所说普通 move/drag 无法做到的那件事。
move — 鼠标移动和拖拽(仅发布消息,诚实限制)
move 发布 WM_MOUSEMOVE 以到达被观察窗口内的某个点,并且当给出 drag: { toX, toY }(或 toElementId)时,还会在源位置发布 WM_LBUTTONDOWN,向目标位置发布若干插值的 WM_MOUSEMOVE 步骤,并在那里发布 WM_LBUTTONUP——与本插件中的其他所有操作完全一样,真实操作系统光标从不移动,除非 focusFallback: 'allow',否则不会将任何窗口带到前台。
要诚实说明发布消息式拖拽能做什么、不能做什么。 它对于响应简单鼠标移动/按钮事件的控件是可靠的——滑块、画布、自行跟踪鼠标的自绘控件。它不是真正的 OLE/Shell 拖放:在两个资源管理器窗口之间拖动文件(或任何其他依赖 Windows 自身拖拽检测启发式以及 IDropTarget/IDataObject 协商的操作)通常不会通过发布消息生效——那需要实际的 SendInput 驱动的物理鼠标事件,而本插件默认不会生成这些事件。请将 move/drag 用于单个控件内的 UI 操作,而不是跨应用拖拽操作——除非设置了 hardware: true(见下文),它会生成真实的 SendInput 事件,并且确实能触达真正的拖放,代价是移动真实光标并抢占焦点。
wait_for — 轮询条件,而不是猜测固定延迟
wait_for 大约每 500ms 轮询一次远程主机,直到条件满足或 timeoutMs 过去(默认 waitForTimeoutMs,范围 500..120000),完全在 harness 侧进行(重复的 screen_read 等价/app_list 等价调用)——这一个不需要新的原生辅助操作。三种条件类型:
- kind: 'element' — target 指定一个窗口(就像 screen_shot/screen_read 那样),match(name/automationId/controlType,至少一个,不区分大小写的子串)指定要在其无障碍树中查找的内容。
- kind: 'window' — match.title 指定一个子串,用于在每个顶层窗口的标题中查找。
- kind: 'foreground' — target 指定一个必须存在且为当前前台窗口的窗口。
超时不是错误。 结果始终作为普通工具结果返回,携带 met(超时时为 false)、timedOut,以及最后观察到的任何内容(observationId、window、elements、pixels)——当等待超时时,模型需要看到屏幕上实际有什么,而不仅仅是一个通用失败。
clipboard — 获取/设置远程剪贴板文本
由 Get-Clipboard/Set-Clipboard 提供支持(内置于 Windows 上的 PowerShell 5.1,无需额外模块),与其他所有操作一样在同一个交互式计划任务会话中运行——剪贴板是按会话隔离的,因此普通的非交互式 SSH exec 会看到一个完全不同的、空的剪贴板。action: 'get' 是只读的,永远不需要批准(就像 app_list 一样);action: 'set' 是变更操作,除非 requireApproval 关闭,否则需要批准,其门控方式与 powershell 相同(没有窗口主体——剪贴板不限定于任何单个窗口)。
process — 列出或终止远程进程
由 Get-Process/Stop-Process 提供支持。action: 'list' 返回每个正在运行的进程的 pid、名称、可执行文件路径(当可解析时)以及主窗口标题(当存在时)——只读,永远不需要批准。action: 'kill' 接受 pid 或 name(二者恰好其一)以及可选的 force,并且像 powershell/clipboard 的 set 一样受批准门控(没有窗口主体)。
display_list 与多显示器 / 区域 screen_shot
display_list 通过 [System.Windows.Forms.Screen]::AllScreens 枚举每个显示器——索引、屏幕空间矩形,以及它是否为主显示器。只读,永远不需要批准。
screen_shot 新增了两个可选的、向后兼容的参数:display: N(配合 wholeScreen: true)捕获某个特定显示器的边界,而不是主屏幕;region: { left, top, right, bottom } 精确捕获该屏幕空间矩形,无论 target/wholeScreen/display 如何。这两个参数都不会改变任何未提供它们的现有调用——普通的 screen_shot() 或 screen_shot({ wholeScreen: true }) 的行为与之前完全一致。与 wholeScreen 一样,region/display 捕获的 windowId 是 0 哨兵值,不能用作后续操作的 basedOn 目标。
notify — 真正的 Windows 操作中心 Toast 通知
notify 显示一个实际的 Windows 11(以及 10)操作中心 Toast 通知——不是旧式的气泡提示/NotifyIcon 弹出窗口——通过 WinRT 的 Windows.UI.Notifications.ToastNotificationManager API 实现,从 PowerShell 5.1 通过标准反射加载技术([Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime])加载。它默认使用众所周知的内置 Windows PowerShell AUMID({1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\WindowsPowerShell\v1.0\powershell.exe,可通过 notifyAppId 配置,并可在每次调用时用 appId 覆盖),因此它在原版 Windows 10/11 上开箱即用,无需应用注册——这是从 PowerShell 发送 Toast 的标准社区技术。变更操作:像 powershell 一样受批准门控(没有窗口主体)。
multi_action — 一批点击/输入子操作,外加列表选择
multi_action 在单次工具调用和单次批准请求中,针对一个引用的 basedOn 观察结果运行一系列点击/输入子操作
— 而不是每个步骤一次 click/type 调用(以及一次审批提示)。
每个步骤都复用 click/type 自身已有的完全相同寻址与重新验证机制
(elementId 步骤会在执行前通过 UIA RuntimeId 重新解析该确切元素;
坐标步骤则不会)。默认情况下,批处理会在第一个失败的步骤处停止;
continueOnError: true 会无论失败与否都运行每个步骤,并为每个步骤返回一个结果(或错误)。
对于列表/网格多选,click 类步骤可以设置 selectionMode
('select'/'add'/'remove'/'toggle'):当所寻址的元素
支持 UIA 的 SelectionItem 模式时,这会直接调用 Select() /
AddToSelection() / RemoveFromSelection(),而不是发送
点击——比合成按住修饰键的点击更可靠,而且它
从不需要真正按住修饰键。当元素不支持该模式时,
回退为普通的发送点击。
window_control — 最小化/最大化/还原/移动/调整大小/关闭
window_control 通过 ShowWindow
(最小化/最大化/还原)和 MoveWindow(移动/调整大小)Win32 调用,或
发送 WM_CLOSE(关闭)来改变一个已观察窗口的状态。需要 basedOn 和审批,完全像
click/scroll/key 一样(参与 autoApproveWindows)。由于
成功的 move/resize/minimize/maximize 会改变窗口自身的
矩形/状态,针对同一窗口的后续操作需要先进行一次新的
screen_shot/screen_read——这与已在其他所有地方适用的新鲜度规则相同。
app_list 的窗口条目(以及底层的
WindowInfo 结构)现在也携带 minimized/maximized 布尔值
(IsIconic/IsZoomed),且无额外开销。
invoke — 基于 UIA 模式的直接控件交互
invoke 不发送合成点击或按键,而是直接调用目标
元素自身的 UI Automation 模式方法——对于会对真实模式方法作出反应
但忽略发送输入的控件来说更可靠。
始终通过 elementId 寻址(绝不使用坐标:此工具始终针对
特定元素)。pattern 为以下之一:
- 'invoke' — InvokePattern.Invoke()
- 'toggle' — TogglePattern.Toggle()
- 'expand' / 'collapse' — ExpandCollapsePattern.Expand()/.Collapse()
- 'select' / 'addToSelection' / 'removeFromSelection' —
SelectionItemPattern.Select()/.AddToSelection()/.RemoveFromSelection()
- 'scrollIntoView' — ScrollItemPattern.ScrollIntoView()
- 'setValue' — ValuePattern.SetValue(string),需要一个字符串 value
- 'setRangeValue' — RangeValuePattern.SetValue(double),需要一个
数值 value
如果所寻址的元素不支持所请求的模式,invoke
会失败,并给出一个明确错误,指明该模式以及元素的控件类型——
它绝不会静默地无操作。与通过 elementId 寻址的 click/type 一样,
该辅助程序会在执行前立即通过元素的 UIA RuntimeId 重新解析该元素,
因此整窗口树哈希在这里不会增加安全性。会改变状态:受门控
通过审批,例如 click/type。
read_text / read_table — 通过 Text/Grid/Table 模式获取结构化内容
两个只读工具,用于暴露比 screen_read 已返回的普通 Name/Value 更丰富的 UI Automation 内容:
- read_text(basedOn, elementId) 通过 TextPattern.DocumentRange.GetText(-1) 读取单个文本/文档/编辑元素的完整内容,并通过 TextPattern.GetSelection() 读取其当前选区的文本(如果有)。返回的文本会在 maxReadTextLength 处截断(默认 20000,与短得多的 maxTextLength 不同,后者用于标题等经过清理的短标签——而非文档正文),并由 truncated 报告是否发生了截断。如果元素不支持 Text 模式,则失败并给出指明控件类型的清晰错误(它绝不会静默回退到已可通过 screen_read 获得的普通 Name/Value)。
- read_table(basedOn, elementId) 通过 GridPattern/GridPattern.GetItem(row, col) 读取单个网格/列表/表格元素的结构化单元格数据:rowCount/columnCount,以及单元格文本(优先使用每个单元格的 ValuePattern.Value,回退到其 Name)。当元素还支持 TablePattern 时,列标题来自 TablePattern.GetColumnHeaders()——当它仅支持 GridPattern 时则省略(而非报错)。单元格数量上限为 maxTableCells(默认 500,最大 5000——这是总单元格上限,而非每个维度);即使 cells 被截断而未达到 rowCount/columnCount,这两个值也始终如实报告,并用 truncated: true 标记该情况。如果元素两种模式都不支持,则失败并给出指明控件类型的清晰错误。
两者都是纯观察者:只读,从不受审批门控——与 screen_read/app_list 处于同一策略层级。它们仍会引用一个 basedOn 观察,并在读取前确认窗口的身份在其之下未发生变化,这与每个接受 basedOn 的工具所采用的同一套新鲜度推理一致,只是没有审批请求。
powershell — 逃生舱(默认关闭)
powershell 以已连接用户的完整权限在远程主机上运行任意脚本:不限定于某个窗口或元素,也不在该账户已有能力之外做沙箱隔离。它的存在是为了处理上述结构化工具无法触及的任何事情(读写文件、查询系统状态、管理服务、访问注册表……)。它的能力在本质上强于此插件注册的所有其他工具,因此:
- 它默认禁用。请有意地开启它:
config:
enablePowerShellTool: true
- 它仍与其他所有变更操作一样受审批门控(requireApproval / autoApproveWindows)——启用它并不会绕过这一点。
- 它完全不参与新鲜度/过期机制(basedOn、staleCheckTree 等)——没有窗口会过期。
- 输出上限为 maxPowerShellOutputLength(默认 20000 个字符
每个用于 stdout/stderr,独立截断)并以与其他所有模型可见字符串相同的方式进行脱敏
(在到达模型或日志之前剥离凭据形式的文本)。
- powerShellTimeoutMs(默认 30000,独立于 helperTimeoutMs)
限制单次调用;如果远程进程运行时间过长,则将其终止。
在模型未被完全信任可操作目标机器的任何部署中启用此功能之前,请仔细考虑
——按设计,这等同于给模型一个终端。
screen_shot/screen_read/app_list/app_launch/filesystem_pull/filesystem_push/display_list/wait_for/clipboard/process/notify/powershell
每个都接受一个可选的 ssh 参数(参见上文 提供 SSH 目标),用于没有配置默认值的部署。
click/type/scroll/key/move/multi_action/window_control/invoke/
read_text/read_table 从不接受该参数——它们会针对其引用的 basedOn 观察所来自的主机进行重放。
每个变更操作(click/type/scroll/key/app_launch)都必须引用
由 screen_shot/screen_read 返回的 basedOn 观察。在
执行操作之前,插件会通过 SSH 重新观察窗口,并在窗口
身份、(当 staleCheckTree 开启时)其无障碍树、或(当
staleCheckPixels 开启时)其像素自该观察以来发生变化时拒绝执行——因此
模型无法对它已不再拥有准确画面的屏幕执行操作。它还会
在操作前后捕获目标进程的身份,并在操作过程中发生变化时拒绝执行。
staleCheckTree(默认 true)比较整个窗口的树,而
不仅仅是所寻址的元素。对于 type 以及
通过 elementId 寻址的 click/scroll,它会自动跳过——这些操作已经
在执行前立即通过其 UIA RuntimeId 重新解析该确切元素,并在其消失时明确失败,
因此更粗粒度的整树哈希在那里不会增加真正的安全性,只会在窗口中
其他无关的实时内容(时钟、状态指示器、“页面加载中”
旋转图标、自动完成、滚动条位置)上产生误报。它仍然适用于
基于坐标的 click 以及 key(两者都没有其他机制
重新验证目标),对于这些情况,如果目标窗口的内容漂移过快,即使这样也不适用,
你可以在部署范围内设置 staleCheckTree: false。
身份和观察时效检查始终适用,无论何种情况。
staleCheckPixels(默认 true)对于像素自身永不停歇变化的窗口
具有相同的权衡——例如实时 3D 视口、视频播放器、
游戏——在这些窗口中,无论在执行前多近重新观察,都永远无法产生
匹配的哈希,因此它会永久拒绝针对该窗口的每个基于坐标的 click/key。
与其在所有地方禁用该检查,不如通过
staleCheckPixelsExemptWindows(默认 [])中的标题/可执行文件正则表达式将该窗口加入允许列表;
身份(以及树检查,除非也被禁用)仍然
适用于它。
配置
所有字段都位于一个 Config 对象下(由 Schemastery 校验;无效值会在配置文件加载时大声失败,而不是在调用时)。请参阅带有完整注释的 cordis.patch.yml 以获取完整列表和默认值:
ssh.、lazyToolLoading、requireApproval、autoApproveWindows、auditSessionEvents、
focusFallback、allowHardwareInput、imageMode、connectTimeoutMs、helperTimeoutMs、
maxScreenshotSide、staleCheckTree、staleCheckPixels、staleCheckPixelsExemptWindows、maxObservationAgeMs、
maxCachedObservations、maxElements、maxTreeDepth、maxTextLength、
rollbackEnabled、enablePowerShellTool、powerShellTimeoutMs、
maxPowerShellOutputLength、maxFilesystemTransferBytes、
maxInlineFilesystemBytes、waitForTimeoutMs、notifyAppId、
maxMultiActionSteps、maxReadTextLength、maxTableCells。
开发
npm install
npm run typecheck
npm run build # tsdown -> lib/
npm run test:unit # pure logic, no network
npm run test:e2e # live, against a real remote Windows host - see below
运行实时集成测试套件
SSH_HOST= SSH_USER= SSH_PASSWORD= \
npm run test:e2e
当 SSH_HOST/SSH_USER 以及凭据未设置时,该测试套件会被跳过(而不是失败)。它直接针对真实目标演练 SSH + PowerShell 辅助程序路径:
1. 建立 SSH 连接并枚举窗口。
2. 列出正在运行的应用程序并启动 notepad.exe。
3. 读取新 Notepad 窗口的 UI Automation 元素树。
4. 向 Notepad 的编辑控件中输入文本。
5. 捕获该窗口的截图。
6. 在完全没有配置默认值的情况下连接,将 host/user/
password 直接传递给 resolveSshTarget —— 这与模型提供的
每次调用 ssh 参数所走的路径相同。
7. 推送一个小型 WinForms 测试夹具脚本(一个 Button、一个 CheckBox、
一个带有已知文本/选区的多行 TextBox,以及一个两列
三行的 ListView),并通过 powershell -File 启动它,然后
针对它演练 invoke(toggle/invoke/setValue)、read_text
(已知内容 + 选区)和 read_table(行/列数、
表头、单元格值)—— 外加每个工具一个负面用例
(一个不支持所请求模式的元素)—— 之后
终止夹具进程并删除推送的脚本。
它会在 afterAll 中清理其启动的 Notepad 进程。
设计说明 / 为什么不用 paramiko 或 asyncssh
DeepSeek Harness 的参考插件格式(参见 dsh-click)是一个
在 harness 自己的 Node/cordis 进程内运行的 TypeScript 模块 ——
在这种情形下没有 Python 运行时来托管 paramiko/asyncssh。
本插件使用 ssh2,这是 Node 的等效
成熟且广泛使用的 SSH2 客户端:持久连接、exec
通道和 SFTP,全部都在插件其余部分所运行的同一进程内。
安全边界
1. 新鲜度 —— 每个操作都引用一个 basedOn 观察结果;身份、
树,以及(可选地)像素哈希比较会拒绝过期操作。
2. 审批 —— 变更类操作默认通过
@deepseek-ai/dsh-user-approval 请求审批;autoApproveWindows 正则表达式
会跳过匹配窗口的询问,但仍会进行新鲜度检查并记录审计。
注意 harness 自身的会话级审批策略(ask /
never)—— never 并不意味着“从不询问,始终允许”;根据
dsh-user-approval 自身的文档,它的意思是“从不向任何人提示:每个询问
都会确定性地解析为 rejected”,即 CI/无人值守的锁定姿态。
若期望以此实现无摩擦自动化,反而会拒绝每一个
操作,并报错 action denied by approval: rejected by the approval
answerer。如果你不想要逐操作提示,请使用本插件自身的
requireApproval: false(或一个限定范围的 autoApproveWindows 条目),
而不是 harness 全局的策略开关。
3. 进程身份 —— 在每个操作前后立即验证 PID 和可执行文件路径。
4. 审计 —— 每个观察和操作都会记录为
dsh-windows-remote-ssh/observed / dsh-windows-remote-ssh/action
会话事件(已脱敏:凭据形态的文本在进入日志或模型之前就会被遮蔽)。
许可证
MIT同作者(pureexe)的其他插件
扫码进群