🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

远程桌面委托 Hope-Phenom/dsh-plugin-remote-use

DeepSeek Harness
Hope-Phenom17 天前有提交已提供中文文档扫描:含敏感能力

把需要真实桌面的任务交给局域网工作节点执行

30/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/15 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add Hope-Phenom/dsh-plugin-remote-use
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:需留意
是什么
dsh 原生插件 · dev
装得上吗
实装验证未通过(unknown),装前请到仓库确认最近更新与 issue
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 17 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
⚠︎ 实装验证未通过(unknown · 2026/10/15) ——可能是验证环境差异,装前建议到 GitHub 仓库确认最近更新与 issue。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 22 处,展示前 22 条)
  • 发起网络请求
    packages/dsh-plugin-remote-use/lib/client.js:1412
    const response = await fetch(ROUTE, {
  • 发起网络请求
    packages/dsh-plugin-remote-use/lib/client.js:1581
    const response = await fetch(ROUTE, {
  • 发起网络请求
    packages/dsh-plugin-remote-use/lib/client.js:2426
    const response = await fetch(ROUTE, { cache: 'no-store' })
  • 发起网络请求
    packages/dsh-plugin-remote-use/lib/client.js:2431
    const response = await fetch(ROUTE, {
  • 发起网络请求
    packages/dsh-plugin-remote-use/lib/index.js:1065
    const waiting = queue.request({
  • 发起网络请求
    prototype/host/client.mjs:134
    const init = await ctx.request(acp.methods.agent.initialize, {
  • 发起网络请求
    prototype/host/client.mjs:142
    const session = await ctx.request(acp.methods.agent.session.new, { cwd: CWD, mcpServers: [] })
  • 发起网络请求
    prototype/host/client.mjs:146
    const result = await ctx.request(acp.methods.agent.session.prompt, {
  • 发起网络请求
    prototype/host/client.mjs:154
    await ctx.request(acp.methods.agent.session.close, { sessionId: session.sessionId })
  • 删除本地文件递归
    prototype/host/plugin-live.mjs:71
    process.on('exit', () => { try { rmSync(TEST_HOME, { recursive: true, force: true }) } catch { } })
  • 删除本地文件递归
    prototype/host/plugin-smoke.mjs:39
    process.on('exit', () => { try { rmSync(TEST_HOME, { recursive: true, force: true }) } catch { } })
  • 发起网络请求
    prototype/host/probe-image-return.mjs:47
    await ctx.request(acp.methods.agent.initialize, {
  • 发起网络请求
    prototype/host/probe-image-return.mjs:50
    const session = await ctx.request(acp.methods.agent.session.new, { cwd: CWD, mcpServers: [] })
  • 发起网络请求
    prototype/host/probe-image-return.mjs:52
    const result = await ctx.request(acp.methods.agent.session.prompt, {
  • 发起网络请求
    prototype/host/probe-plugin-rpc.mjs:18
    const init = await ctx.request(acp.methods.agent.initialize, {
  • 发起网络请求
    prototype/host/probe-plugin-rpc.mjs:25
    const pong = await ctx.request('dsh-remote/ping', {})
  • 发起网络请求
    prototype/host/probe-plugin-rpc.mjs:33
    const rep = await ctx.request('dsh-remote/selfcheck', { maxAgeMs: 0 })
  • 执行外部命令来自配置/变量
    prototype/relay/argv-probe.mjs:12
    const c = spawn(cmd, args, { windowsHide: true, ...opts })
  • 读取本地文件
    prototype/relay/make-launch-spec.mjs:43
    if (existsSync(join(c, 'DeepSeek Harness.exe'))) { installDir = c; break }
  • 写入本地文件
    prototype/relay/make-launch-spec.mjs:69
    writeFileSync(OUT, JSON.stringify(spec, null, 2), 'utf8')
  • 读取本地文件
    prototype/relay/relay.mjs:51
    const raw = readFileSync(CMD_FILE, 'utf8').replace(/^ FEFF/, '')
  • 执行外部命令来自配置/变量
    prototype/relay/relay.mjs:123
    const child = spawn(LAUNCH.command, LAUNCH.args, {
扫描于 2026/10/31(1 天前) · 范围 全包扫描 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
展开完整介绍(166 字)↓

把 DSH 里需要真实桌面的任务(GUI 自动化测试、只有看屏幕才能验证的结果)交给局域网里已配对的工作节点执行,像调用子代理一样驱动它——自动化不再和操作者抢鼠标、键盘和焦点。任务以指令经 ACP over WebSocket 下发,由工作节点本地的 DSH 执行;提权一律进人工审批,沙箱无法被证明时拒绝启用,凭据不回传浏览器。

README

由 DeepSeek 最新模型翻译生成
dsh-plugin-remote-use 把 DSH 的任务委托给局域网里另一台已配对的工作节点执行,像调用子 Agent 一样驱动它。 主机 DSH ── ACP over WebSocket ──▶ 从机中继 ──▶ 从机 DSH(在从机本地跑任务、截图、看 GUI) │ ▲ └── 审批 / 审计 / 会话级开关 ────────┘ 为什么需要它 GUI 自动化测试和「看屏幕才知道结果」的验证,必须在一台真的能操作鼠标键盘的机器上跑。 如果直接在本机做,就会和操作者抢电脑:抢焦点、抢光标、抢剪贴板。 这个插件的做法是把那部分工作推到一台专门的工作节点上: - 任务的核心逻辑仍然在主机——规划、改代码、看结果都在这里 - 只有需要 GUI 的那一段被委托到从机执行 - 从机的屏幕、鼠标、键盘与操作者本人完全隔离 边界(它不做什么) | 不做 | 原因 | | 远程桌面 / 原始鼠标键盘转发 | 委托的是指令,不是输入事件。从机上的 DSH 自己决定怎么做 | | 工作区同步 | GUI 验证在从机本地跑,主机源码不需要同步过去 | | 后台常驻子会话 | 委托是一次性的(backgroundMode: one-shot)。
展开完整文档(README 剩余 1 节)
dsh-plugin-remote-use

把 DSH 的任务委托给局域网里另一台已配对的工作节点执行,像调用子 Agent 一样驱动它。

主机 DSH ── ACP over WebSocket ──▶ 从机中继 ──▶ 从机 DSH(在从机本地跑任务、截图、看 GUI) │ ▲ └── 审批 / 审计 / 会话级开关 ────────┘

为什么需要它

GUI 自动化测试和「看屏幕才知道结果」的验证,必须在一台真的能操作鼠标键盘的机器上跑。
如果直接在本机做,就会和操作者抢电脑:抢焦点、抢光标、抢剪贴板。

这个插件的做法是把那部分工作推到一台专门的工作节点上:

- 任务的核心逻辑仍然在主机——规划、改代码、看结果都在这里
- 只有需要 GUI 的那一段被委托到从机执行
- 从机的屏幕、鼠标、键盘与操作者本人完全隔离

边界(它不做什么)

| 不做 | 原因 |
| 远程桌面 / 原始鼠标键盘转发 | 委托的是指令,不是输入事件。从机上的 DSH 自己决定怎么做 |
| 工作区同步 | GUI 验证在从机本地跑,主机源码不需要同步过去 |
| 后台常驻子会话 | 委托是一次性的(backgroundMode: one-shot)。要看进度用 remote_agents 轮询 |
| danger-full-access 自动放行 | 提权一律进人工审批队列,永远不自动批准 |

快速开始

1. 前置条件

| 位置 | 需要 |
| 主机 | DSH(本项目在 0.2.0-rc.2 上开发)、Node ≥ 20 |
| 从机 | DSH 已装并已登录、Node ≥ 20、一个已解锁的交互式桌面会话 |
| 从机 | 沙箱 mediator 生效(见下方「安全模型」)——沙箱不可用时会拒绝启用 |
| 从机 | 一个虚拟显示器(见下)——不满足时自检的 S2 阻断派单 |

从机必须真的登录进桌面。锁屏或无人登录时无法注入输入,自检会明确报出来。

为什么从机需要一个虚拟显示器

不是为了「多一块屏」,而是为了让显示固定下来。勘察记录(.research/07-slave-recon.md)里的原始问题:

物理控制台 1920×1080 vs RDP 2560×1440 ⇒ 同一个测试在两种会话下看到的布局不同

再加上一条运维上的矛盾:从机若要独占物理显示器,操作者自己就没屏用了。虚拟显示器一次解决两件事:

| 好处 | 说明 |
| 坐标固定 | 1920×1080@60、主屏在 (0,0) —— GUI 断言的坐标才有意义 |
| 不占用物理屏 | 物理显示器可随时还给主机;也不需要 RDP |
| 重启后保持 | Windows 记住布局,无人值守 |

参考实现是 Virtual-Display-Driver (VDD)(MIT)。自检里与之相关的检查:

| 检查 | 判据 | 级别 |
| S1 | 至少一个 ATTACHED_TO_DESKTOP 的适配器 | 🔴 阻断 |
| S2 | 虚拟显示器存在且有 EDID —— 不能是 WinDisc 兜底 | 🔴 阻断 |
| S3 | 主屏分辨率 1920×1080 | 🟡 警告 |
| S4 | 主屏必须在 (0,0)(是不是虚拟屏写进报告,不阻断) | 🔴 阻断 |

S2 与 WinDisc:虚拟显示器被禁用或驱动加载失败时,Windows 会回落到 WinDisc(Microsoft 基本显示适配器)——通常是 1024x768。它能让 S1 通过,但不是约定的显示,所以 S2 单独把它判为阻断,报错文案就是 virtual display present (not WinDisc fallback)。

S4 阻断的是什么:主屏不在 (0,0)。不在原点时,GUI 自动化的绝对坐标会整体偏移,而症状是「点了但点偏了」——不会报错。这是这个项目里最危险的失败模式。

S4 不阻断「主屏是物理屏」。 这条要求一度被写成「必须是虚拟屏」(设计文档原本就是这么措辞的),但在真机上发现它会让从机无法登录:

Windows 11 的登录界面只画在主显示器上。 虚拟显示器是主屏时,登录界面就渲染在一块没有物理输出的屏上 —— 操作者的显示器全程黑屏,根本没法登录。

那条要求想保证的是「坐标空间独立于操作者的显示器」。而在 KVM 保持 EDID 的接法下, Windows 从不认为显示器消失过 ✓ —— 这个保证已经由硬件给出了 ✓。坚持「必须是虚拟屏」只会把登录界面挪到没人看得见的地方。

所以:原点是阻断项 ✓;主屏是不是虚拟屏写进报告 ✓(actual 里的 virtual=True/False),让你看得见,但不拦你。

⚠️ 切换物理显示器不会让虚拟屏自动接任主屏。 KVM 让物理显示器继续呈现 EDID, Windows 因此仍然认为它接着,于是它继续是主屏 —— 实测如此。「切给主机后虚拟屏自动变成默认」这个预期不成立。

想让从机被派单,主屏在 (0,0) 即可 ✓ —— 物理屏当主屏完全可行 ✓,而且登录界面看得见 ✓。
坐标空间由那块主屏决定,所以从机的工作分辨率应当是约定值 1920×1080(S3 会就分辨率发警告)。

⚠️ 运维警告(来自实测):大版本 GPU / 芯片组驱动更新可能与 VDD 冲突,症状是开机黑屏(虚拟屏被排到物理屏前面)。动显示相关配置前先确认 SSH 可用,它是唯一的逃生通道。

2. 主机侧:装插件

从零克隆之后先装依赖(宿主侧与从机侧是两套,别漏第二条):

宿主侧:插件自身的依赖(ws、ACP SDK)
npm install --prefix packages/dsh-plugin-remote-use

从机侧:relay 的依赖(ws)。它必须随文件一起被拷到从机,而 .gitignore 的 node_modules 规则在任意深度都匹配 —— 新克隆里没有它。powershell
npm install --prefix packages/dsh-plugin-remote-use/slave

漏掉第二条时 tools/deploy-slave.ps1 会拒绝执行并给出修复命令 —— 它不会拷一个跑不起来的中继过去。

通过 DSH 的插件管理器安装(它一次完成装包、注册进 profile 的 bundles、启用):

插件管理器 → 安装 → @hope_phenom/dsh-plugin-remote-use

手动安装(插件管理器不好用时的退路) powershell

装到某个 profile —— 这一步只把文件放进去

npm install --prefix "$env:USERPROFILE\.dsh\profiles\desktop" @hope_phenom/dsh-plugin-remote-use

⚠️ 光装不够:还必须把包名加进该 profile 的 dsh.profile.bundles ✓,否则不会被加载 ✗。
profile 的 package.json 里那一节长这样(你已有的插件就是这么注册的 ✓): json
"dsh": { "profile": { "bundles": [ "@deepseek-ai/dsh-base", "@hope_phenom/dsh-plugin-remote-use" ] } }

开发时也可以直接在 profile 的 node_modules 里放一个指向本仓库的链接 ✓。

装完重启 DSH,设置页里就会出现「远程工作节点」。

优先走设置页:粘贴配对串 + 图形化配置,写入 %USERPROFILE%\.dsh\storages\dsh-plugin-remote-use\settings.json(该文件存在时,profile 补丁里的同名配置不再生效)。下面那个补丁层入口是等价的配置即代码路线——两种方式写的是同一批键,选一种即可。

%USERPROFILE%\.dsh\profiles\desktop\cordis.patch.yml 里要加什么(配置即代码路线) yaml

① 插件自身的配置

- id: dsh-plugin-remote-use
config:
workers:
- id: worker-pc # 也决定 provider 名:remote-worker-pc name: worker-pc url: 'ws://worker-pc:7331' # 建议用主机名,见下方"为什么用主机名" token: '<从机上 run-relay.ps1 里的同一个 token>' cwd: 'C:\dsh-remote\p0work' # 从机上的绝对路径;也是沙箱允许写入的范围 enabled: true serialQueue: 1 # GUI 是独占资源:一个节点同时只跑一个任务 approvalTimeoutMs: 90000 requiredTier: workspace-write injectTaskSummary: true

就这些。0.2.0 起不需要再为每台从机写"工具行"

关于工具行:0.2.0 起不再需要手写。

早期版本要求在 profile 补丁里为每台从机加一条 dsh-tool-subagent 行(provider: remote-<worker id>)。
那条路即使写对了也不生效:Web 会话的模型工具目录由 agent preset 枚举(preset-standard.plugins),不在表里的 loader 行挂了也不会出现在模型面前 —— 详见踩坑与教训 F11。

0.2.0 改为由插件自己注册委派工具 remote_subagent,与它已有的四个 remote_ 工具同一条路(ctx.tools.register,按 agent 作用域)。所以:

- 装完插件就能用,profile 补丁里只需要上面 ① 那段 worker 列表;
- 如果你按旧文档写过 tool-subagent-remote- 那一块,可以整块删掉;
- 委派入口的工具名从 subagent_remote 改为 remote_subagent。

为什么插件不能沿用 dsh-tool-subagent 那条现成的路(记在这里,免得以后有人又去试): provider 确实是插件在运行时注册的,但注册 provider 与把工具暴露给模型是两件事——后者由 Web 会话的 agent preset 枚举(preset-standard.plugins),而那个列表里只有官方自己的四行(subagent / subagent_fork / subagent_codex / subagent_claude_code)。
即便把那条 dsh-tool-subagent 行正确地写进 profile 补丁,模型也看不到它。
唯一不需要改 preset 的暴露方式,就是插件自己用 ctx.tools.register() 注册 —— 它已有的四个 remote_* 工具正是这么工作的。完整推导见 踩坑与教训 F11。

改完重启 DSH。

3. 从机侧:部署

一条命令(在主机上跑,走 SSH): powershell .\tools\deploy-slave.ps1 -Remote worker-pc -Destination C:\dsh-remote\plugin-slave

它会 scp 必需文件(含 node_modules/ws),然后在从机上验证:逐个 node --check、运行 relay.mjs --print-pairing、检查 .ps1 是不是纯 ASCII 且 param() 在第一条语句。任何一步失败都以非零退出。
这一步不能只靠「清点文件」。从机中继有三个相对导入模块和一个裸包(ws),漏掉任何一个的症状都是「从机连不上」——和网络故障完全一样。所以脚本的验收是运行,不是核对清单。

从机上用计划任务拉起中继(参考 run-relay.ps1): powershell
node relay.mjs --port 7331 --token <secret>
--cmd-file agent-spec.json
--selfcheck-script selfcheck.ps1
--screenshot-script screenshot.ps1

agent-spec.json 由 slave/make-launch-spec.mjs 生成——它复刻 DSH 的 dsh.cmd,用 argv 数组避开 Windows 引号问题。

4. 第一次使用

1. 打开 设置 → 远程工作节点,确认 worker 卡片显示已连接、自检没有阻断项
2. 在需要用到从机的那个会话里输入: text /remote-use on

默认是关闭的。 新会话里远程工具不可见,策略提示词也不注入。开关是按会话的,且不持久化——重启后回到 off。

3. 让模型去做需要 GUI 的事即可。它会先调 remote_selfcheck 确认从机可用,再委托。

日常使用

会话开关:/remote-use

| 输入 | 效果 |
| /remote-use on | 本会话:工具可用 + 注入委派策略 |
| /remote-use off | 本会话:工具重新隐藏、策略撤销 |
| /remote-use status | 看当前状态 |

只影响当前会话。 另一个会话即使同时开着,也各自独立。

模型可见的远程工具

| 工具 | 用途 |
| remote_subagent | 把一个任务派到工作节点上执行,像调用子 Agent 一样。run_in_background: true 则不占住当前对话,答案稍后用 remote_agents 取回 |
| remote_selfcheck | 从机就绪报告(阻断项 / 警告 / 会话段)。委托前先调它——未就绪的从机会被派单门禁拒绝 |
| remote_agents | 看任务进度与在途任务;action: status / wait 盯某一个任务,任务落定后把它的答案正文一并给你 |
| remote_screenshot | 抓从机屏幕,落盘到主机,然后 read_image 看 |
| remote_interrupt | 停止任务(协作式:任务在下一个断点才真正结束) |

后台派单怎么拿结果(这是后台模式唯一的路):

remote_subagent { prompt: "…", run_in_background: true } → taskId: rtask_xxx
remote_agents { action: "status", taskId: "rtask_xxx" } → 状态 + 完整答案

⚠️ 两个容易踩的点:

- run_in_background: true 不是「立刻返回」。一台从机同时只跑一个任务(GUI 是独占资源),所以 worker 忙时这个调用会先排队等轮到自己(实测等过 32 秒)。派单前可以先 remote_agents action="list" 看看谁在忙。
- status 读回的是留存下来的答案,任务落定前没有答案可给(那时它只报进度)。答案上限 64 000 字符,超长时会在标题里写明「first N of M chars」,不会静默截断。

取证:两条规矩,否则会以为「任务没执行」

规矩一:任务结束后,它启动的 GUI 进程会被一起清理。

所以「任务执行过」和「事后能看到它留下的界面」是两件事。如果提示词写「打开某程序,不要关掉窗口,留给主机稍后查看」,那条指令在任务结束的一瞬间就失效了——从机无权决定任务之后进程树是否存活。

规矩二:按窗口需要活多久,选一种取证方式。

| 需求 | 做法 |
| 想知道界面长什么样 | 让任务自己在运行期间截图/断言,把结论写进返回值 |
| 窗口必须活到主机来看 | run_in_background: true 派一个明确保持存活的任务(「启动后 Start-Sleep,期间不要关闭任何东西」),主机在它 running 时调 remote_screenshot,看完再 remote_interrupt |

并且:从机的自述不能当证据。 实测中它给过自相矛盾的报告 —— 同一份回复里既说「窗口存在:否」,又说「标题含 Windows 的窗口出现了:是」。真正定案的是主机自己截的图,以及从机操作系统留下的记录(事件日志里那次 Application Error id=1000 证明任务确实跑过)。实测对照表见踩坑与教训 F14。

设置页

| 区块 | 内容 |
| 本机角色 | 主机/从机两种身份。下方是布防面板:接口下拉、端口、任务目录、先检查、布防为工作节点 |
| 工作节点 | 每个节点的卡片:连接状态、自检报告(可展开 17 项)、重新自检 / 截图 / 连接测试 / 编辑 / 删除。配置有问题时卡片上直接标出来(见下方「任务目录」) |
| 添加工作节点 / 粘贴配对串 | 不用手抄 url+token。配对串里带着从机的任务目录,所以通常什么都不用填 |
| 审批与连接 | 默认档位、审批超时、心跳间隔、每个节点保留的截图数、委派策略文案 |
| 等待你批准的请求 | 提权请求在这里等你批准/拒绝;超时按拒绝处理 |
| 最近的裁定 | 审计留痕(谁、何时、哪个工具、什么档位、结果) |
| 急停 | 全部急停:打断所有在途任务 |

任务目录:为什么必须由从机给出

cwd 是从机上的一个绝对路径:任务在这里运行,同时也是从机沙箱允许它写入的范围。因此:

- 它只能由从机提供——主机不可能猜出另一台机器上的路径,而猜错了不会报错,只会让任务写到没人选过的目录;
- 布防时在「任务目录」里填一次,它会被写进配对串(0.2.1 起),主机粘贴后自动带上,不必手抄;
- 0.2.0 及更早的从机产生的配对串不带这一项,那时主机的配对卡片会让你补一个 —— 或者先把从机更新到 0.2.1;
- 主机侧拒绝保存没有可用任务目录的 worker(400,且什么都不写)。理由很直接:这种配置永远不可能派单成功,早一点拒绝远好过第一次派单时才炸 [REMOTE_BAD_CWD];
- 从机布防时留空也可以(就是「还没决定」),只是那样配对串里不带这一项,主机的「粘贴配对串」卡片会让你补一个;
- 从机侧若填了相对路径,先检查 / 布防会直接拒绝——它会被当成合法路径写进配对串,而主机没有理由怀疑它。

为什么不是「警告后照样保存」:一个连任务目录都没有的 worker,无论链路多健康、自检多绿,都接不了任何单。
把它存下来只是把一次必然的失败从「粘贴的那一刻」推迟到「下一次委派」,而那正是这个缺陷当初被发现的路径。

对称配对:把主机也变成从机

反过来也成立——本机可以布防为工作节点:

1. 设置页 →「本机角色」→ 选接口、填「任务目录」(本机上一个绝对路径,任务在那里运行并受沙箱约束)
2. 先检查 —— 跑沙箱门禁,并校验任务目录
3. 检查通过后 布防为工作节点 —— 凭据和配对串只显示这一次,配对串里已带上任务目录
4. 把配对串给对方,或让对方的设置页用「粘贴配对串」——正常情况下不需要再填任何东西

布防会一直保持到手动关闭(没有定时器)。立即停止 是本地动作,不联系对端。

安全模型

默认关闭

新会话不注入策略、远程工具在该会话内不可见。这不是「隐藏按钮」——ctx.tools.restrict() 是按 agent 作用域的(DSH 强制:全局限制会屏蔽所有 agent),外加一个执行守卫兜底,即使限制失败也拒绝执行。

沙箱必须真实生效

从机的 DSH 跑在受限沙箱里。沙箱无法被证明生效时,布防会被拒绝——不是警告,是拒绝。

自检里的六项行为检查会实际执行验证:工作区内写入成功(对照)、工作区外写入被拒、只读档无法写、只读档仍可读。

Windows 注意:dsh-sandbox-local 用 process.execPath 构造 mediator 调用,而在 GUI 子系统镜像下(无控制台启动)它会在 DLL 初始化阶段死掉(STATUS_DLL_INIT_FAILED)。修法是在 profile 里把 runnerCommand 指向一个控制台子系统的 mediator(本项目用 C:\dsh-sandbox-mediator\shim.mjs),并用 runnerFailureSignatures 声明失败特征。

人工审批

needsHuman / danger-full-access 提权永远不自动批准:

- 请求进入主机的审批队列,页面上出现批准 / 拒绝
- 超时按拒绝处理,且不打断任务
- 决定送达从机、任务继续,审计留痕(谁决定的、什么工具、什么档位)

凭据纪律

- worker token 绝不回传浏览器——页面只拿到 tokenSet 和一个掩码
- worker 角色的凭据在布防时生成一次,明文只回传一次;之后只留掩码
- 配对串携带凭据,当作密码对待
- 从机角色的 token 持久化在 settings 文件里,重启后保持;但再次布防会轮换

设置页的写操作拒绝跨站请求

DSH 会认证应用外壳(直接 GET / 返回 401),但不认证插件自己注册的路由 ——所以这道门必须插件自己把。0.2.2 起:

- POST 且 Origin 头存在且不是本机同源(含 null)→ 403,而且是在任何动作被分发之前,所以被拒的请求不会写配置、不会开监听、不会打断任务;
- 判据用的是请求自己的 Host,所以用 127.0.0.1:19387 还是 localhost:19387 打开页面都正常;
- 没有 Origin 头则放行:浏览器对 POST 一律带这个头(同源也带),所以「没有它」不可能来自恶意页面,只可能是 curl、脚本或本仓库的真机 harness;
- 读取不加门:响应不带 Access-Control-Allow-Origin,跨站页面本来就看不到内容。

为什么非要有这道门:路由不检查 Content-Type,所以 text/plain 的 POST 照样被当 JSON 解析 ——而 text/plain 是浏览器可以跨站盲发、不触发 preflight 的那种请求。详见踩坑与教训 F17。

settings 文件的优先级 text 内建默认 < profile 补丁 < settings 文件

workers 是替换语义。文件存在时,profile 补丁里的同名配置不再生效——插件会把这件事记进日志(settings file applies: workers, ...)。

运维

虚拟显示器挂了怎么恢复

症状:自检 S2 阻断、报 virtual display present (not WinDisc fallback),派单被门禁拒绝。
在从机上(需要管理员): powershell

1. 看设备状态。Error + CM_PROB_DISABLED(Code 22) 就是它

Get-PnpDevice -Class Display | Select-Object Status,Problem,FriendlyName,InstanceId | Format-List

2. 看当前实际生效的是哪块屏 —— 出现 WinDisc 就说明已经回落到兜底了

Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Screen]::AllScreens | ForEach-Object { "$($_.DeviceName) $($_.Bounds.Width)x$($_.Bounds.Height) at $($_.Bounds.X),$($_.Bounds.Y)" }

3. 重新启用

Enable-PnpDevice -InstanceId 'ROOT\DISPLAY\0000' -Confirm:$false

4. 让它生效并复核 Start-Sleep 5 Get-PnpDevice -Class Display | Select-Object Status,FriendlyName

如果启用后马上又变回 Code 22,那就不是「被谁禁用」,而是驱动框架没起来。VDD 是用户态(UMDF)驱动,依赖 \Driver\WUDFRd:

开机时是否报过这个 —— 事件 219「驱动程序 \Driver\WUDFRd 加载失败」就是它 Get-WinEvent -FilterHashtable @{LogName='System'; Id=219} -MaxEvents 5 | Select-Object TimeCreated,Message | Format-List

这种情况重新启用可能不持久,会每次开机复发。排查方向:WUDFRd 服务的启动类型、VDD 驱动包是否完整(pnputil /enum-drivers)、以及最近是否动过 GPU/芯片组驱动。

⚠️ 动显示相关配置前先确认 SSH 可用 —— 它是唯一的逃生通道(设计文档 §7.8 的实测警告)。

排查顺序

1. 沙箱是否真的生效(主机/从机角色的前提)
node tools\check-sandbox-ready.mjs --profile %USERPROFILE%\.dsh\profiles\desktop\cordis.patch.yml

2. 从机在哪(IP 会变时) .\tools\find-slave.ps1 # 扫网段 → 用从机自己的密钥认领 → 更新 hosts

3. 端到端真机测试(不需要第二台机器也能跑,见下)
node prototype\host\plugin-live.mjs --url 'ws://worker-pc:7331' --token <secret> --cwd 'C:\dsh-remote\p0work'

从机离线时的自查

从机自检报告在卡片的「显示全部检查」里。常见阻断项:

| 检查 | 含义 |
| S9 睡眠/休眠 | 从机会睡着掉线。powercfg /change standby-timeout-ac 0(交流+直流) |
| S12 控制台 QuickEdit | 在控制台里选中文本会阻塞正在运行的程序——GUI 自动化里很致命 |
| S8 合成输入 | SetCursorPos 失败。注意:从受限 shell(如 agent 的沙箱内)启动中继时这一项必然假失败 |
| S14/S15 DSH 已装 / 已登录 | 路径从环境派生;报告里会写出找了哪些位置 |
| S18 目标应用 | 未配置时是「未配置」而非「缺失」。用 -App 或 DSH_SELFCHECK_APP 指定 |

更新部署

.\tools\deploy-slave.ps1 -Remote <host> -Destination <dir> # 拷贝 + 在目标机上验证 schtasks /end /tn DshRelay ; schtasks /run /tn DshRelay # 重启中继

备份

python tools\dsh-env-backup.py backup --to D:\dsh-backups --name before-upgrade --yes
python tools\dsh-env-backup.py verify --from D:\dsh-backups\before-upgrade
python tools\dsh-env-backup.py restore --from D:\dsh-backups\before-upgrade

退出码:0 通过 · 2 有差异(工具本身正常) · 3 选项未实现 · 1 操作失败 · 130 中断。

测试

三套 harness:单元(node --test)、冒烟(假 ctx 挂载真 apply())、真机(对着真 worker 跑完整协议)。
发布前都必须通过。命令、判据、以及「每条守卫为什么写成那样」,见 docs/TESTING.md。

真机 harness 不传 --entry 时测的是安装件,并且会打印它究竟从哪个文件加载。
那行必须留着:它曾经一边打印读起来像安装件的路径,一边加载源码检出(踩坑 D12)。

架构速览

主机侧一个 apply():每个 worker 一个 RemoteProvider(注册进 ctx.subagents)+ 一个 PeerConnection (AcpBridge + 心跳 + 截图),后者经 ACP over WebSocket 连到从机 relay,relay 再拉起从机的 dsh --profile acp 子进程。审批队列与审计日志在主机侧。

完整的组件图、各关注点落在哪个模块、以及从机形态,见 docs/DESIGN.md §3 架构总览。

文档地图

根 README 只讲这是什么、怎么用;知识都在 docs/。索引页:docs/README.md。

| 文档 | 内容 | 什么时候看 |
| docs/DESIGN.md | 设计与全部决策记录(按轮次,含被推翻的结论) | 想知道为什么这么设计 |
| docs/PITFALLS.md | 踩坑与教训总集(A–G 七类,75 条,每条附当时的原始输出与发现方式) | 先看这个通常最省时间 |
| docs/INTERFACES.md | 上游契约与集成事实(provider 接缝、ACP、ctx.tools.register、Cordis 补丁语义) | 要改代码或写别的插件 |
| docs/TESTING.md | 三套 harness 的命令、判据与每条守卫的理由 | 跑测试、加守卫 |
| docs/research/ | 当初的源码级调研原文与两轮验证发现(归档,有些结论已被推翻) | 考古、查「当时为什么这么想」 |
| tools/README.md | 运维工具说明(备份/还原、从机查找、沙箱检查) | 用那些脚本 |

已知限制

- 已验证:从发布包(registry)安装的路径 —— 主机与从机各装 0.2.2 跑过完整真机回归;装好的件与仓库源码逐文件哈希一致
- 未验证:两个会话同时向同一从机大量派单时的资源表现(串行队列本身已验证:实测第二个任务等前一个结束才开始)
- 从机 run-relay.ps1 默认保留 20 张截图(约 190 MB 上限);用 --keep-shots 调整(负数表示不清理)
- 配对串里带的是从机当时的 IP——用主机名写配置才能在 IP 变化后幸存
- 设置导航栏的图标由 DSH 硬编码,插件无法替换(插件提供的图标显示在插件管理页)

许可

MIT © Hope.Phenom
预览分享卡片长什么样 ↓Hope-Phenom/dsh-plugin-remote-use
分享 · DeepSeek Harness Hub
Hope-Phenom/dsh-plugin-remote-use

把需要真实桌面的任务交给局域网工作节点执行

回到顶部