← Back to home@Hope-Phenom

dsh-plugin-remote-use

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

Stars
0
Language
JavaScript
Created
Oct 14, 2026
Updated
Oct 16, 2026

Introduction

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 规则在任意深度都匹配 —— 新克隆里没有它。
npm install --prefix packages/dsh-plugin-remote-use/slave

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

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

插件管理器 → 安装 → @hope_phenom/dsh-plugin-remote-use
手动安装(插件管理器不好用时的退路)
# 装到某个 profile —— 这一步只把文件放进去
npm install --prefix "$env:USERPROFILE\.dsh\profiles\desktop" @hope_phenom/dsh-plugin-remote-use

⚠️ 光装不够:还必须把包名加进该 profile 的 dsh.profile.bundles ✓,否则不会被加载 ✗。 profile 的 package.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 里要加什么(配置即代码路线)
# ① 插件自身的配置
- 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):

.\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):

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. 在需要用到从机的那个会话里输入:
/remote-use on

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

  1. 让模型去做需要 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 文件的优先级

内建默认  <  profile 补丁  <  settings 文件

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


运维

虚拟显示器挂了怎么恢复

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

# 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 调整(负数表示不清理); 字节上限用 --keep-shot-bytes(默认 0 = 不限。0 绝不表示删除全部——否则一次升级就会清空从机缓存)。 设置页的「截图缓存」区可按上限自动清理最旧的,并分别显示主机与从机各占多少空间
  • 配对串里带的是从机当时的 IP——用主机名写配置才能在 IP 变化后幸存
  • 设置导航栏的图标由 DSH 硬编码,插件无法替换(插件提供的图标显示在插件管理页)

许可

MIT © Hope.Phenom