dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
DSH environments plugin: remote/local environments all in one.
给 DeepSeek Harness(dsh)的环境管理插件。它让智能体可以使用别的设备,例如本机、自己部署的服务器、SSH 主机、Android 真机或模拟器,以及本机上的 Windows 受限测试账户。每个环境有两种用法:
read / write / edit / read_image / glob / grep / bash(Windows 环境为 pwsh)会直接在环境中执行,AGENTS.md 和项目技能也从环境中读取。不挂载时会话就在本机(运行 dsh 的电脑)上运行,这是默认情况,也是开销最小的情况。env_borrow,借到后获得一组以环境别名为前缀的工具(例如 pixel__exec、pixel__install_apk),用完调用 env_return 归还,这些工具随即消失。每次占用(挂载或借用)分为无界面(headless:文件、命令、进程、隧道,以及 install_apk、app、logcat 等不操作屏幕的工具)和 GUI(另外可以截图、键鼠输入、操作界面元素和窗口)两种方式。无界面占用可以多个会话同时进行,GUI 同一时间只属于一个会话,详见下文“借用”。
发布版本附带全部五个平台的 dsh-env-server(win32-x64、linux-x64、linux-ia32、linux-arm64、darwin-arm64),二进制以 Brotli 压缩存放,首次使用时解压到 ~/.dsh/cache/。任选一种方式安装:
# 1. 某个版本的发布包(GitHub Release 附件,推荐)
dsh plugin add https://github.com/std-microblock/dsh-plugin-environments/releases/download/vX.Y.Z/dsh-plugin-environments-X.Y.Z.tgz
# 2. release 分支:始终是最新正式版;也可以用 release-vX.Y.Z 标签固定版本
dsh plugin add github:std-microblock/dsh-plugin-environments#release
dsh plugin add github:std-microblock/dsh-plugin-environments#release-vX.Y.Z
需要指定配置时加 --profile desktop 等参数。每个 Release 还附带 SHA256SUMS,以及各平台的原始服务端二进制 dsh-env-server-X.Y.Z-<平台>.gz,用于在别的机器上运行 dsh-env-server serve。
pnpm install
pnpm run build # 构建 dsh-env-server(本机 + Linux musl 目标)、插件 dist/ 和 client.js
dsh plugin --profile desktop add link:G:/dsh-plugin-remote-environments/packages/plugin
插件包位于 packages/plugin(包名仍是 dsh-plugin-environments)。pnpm run build 会把服务端二进制放到 packages/plugin/bin/<platform>-<arch>/,把插件打包到 packages/plugin/dist/index.js,把界面打包到 packages/plugin/client.js;这些都是构建产物,不进入 git。pnpm run package 生成与发布版本相同的 .tgz(见 CONTRIBUTING.md)。
安装后会出现以下入口:
内置的 local 环境(界面上叫“本机(独立进程)”)是在本机上单独启动的 dsh-env-server 子进程,和其他环境一样可以借用或挂载;它和“本机”不同:“本机”是不挂载、直接在 dsh 所在电脑上运行。
| 类型 | 连接方式 | 能力 |
|---|---|---|
本机 local | 把随插件附带的 dsh-env-server 作为 stdio 子进程启动 | 全部能力:文件、进程/PTY、TCP/UDP 正反向隧道、glob/grep、截图、键鼠输入 |
环境服务器 server | 连接到目标机器上运行的 dsh-env-server serve,地址可以是 host:port、tcp://、ws://,或经 TLS 反向代理的 wss://。用共享密钥做双向认证并加密(见下文“网络连接”) | 同上;截图和输入目前仅限 Windows 目标 |
SSH ssh | 使用 ssh2 连接,支持密码、私钥或 ssh-agent。自动识别远端系统和架构(Linux x64/ia32/arm64、macOS arm64、Windows x64 OpenSSH),按内容哈希只上传一次对应的 dsh-env-server(~/.dsh-env/bin/,目录权限 0700);连接时才启动、断开即退出,数据走 SSH 端口转发。SSH 服务端禁止转发时退回 exec 通道的 stdio;没有合适的二进制时退回 SFTP + exec + TCP 转发 | 完整能力,或退回后的基础能力 |
反向连接 reverse | 目标机器运行 dsh-env-server connect 主动连到插件开启的 TCP / WebSocket 监听,适合目标机器没有公网地址的情况;断线后自动重连 | 与环境服务器相同 |
Android adb | 调用 adb CLI。adb devices 发现的设备会自动列出,可以一键加入列表 | 文件(push/pull/exec-out)、shell(含 PTY)、TCP forward/reverse、截图、输入,以及 install_apk、app、ui_dump、logcat |
Windows 账户 winuser | 创建一个本地标准账户(需要管理员确认),密码用 DPAPI 加密保存。连接时用 CreateProcessWithLogonW 以该账户身份在当前交互桌面上启动 dsh-env-server | 全部能力。该账户启动的图形界面程序会显示在当前桌面,智能体可以截图并操作 |
server 和 reverse 两种环境的连接可能经过公网,所以总是先建立一层安全通道:双方用每个环境独立的高熵共享密钥互相认证,再派生出两个方向各自的 AES-256-GCM 密钥加密全部流量。密钥本身从不在网络上传输,也不需要证书。细节见 docs/protocol.md。
| 谁有公网地址 | 做法 |
|---|---|
| 目标机器 | 在目标机器上运行 dsh-env-server serve --listen 0.0.0.0:7461 --token-file token.txt(或 --listen ws://0.0.0.0:7461/dsh-env),然后添加“环境服务器”并选择“直连”,填入地址和 token.txt 里的密钥 |
| dsh 这台电脑 | 添加“环境服务器”并选择“反向连接”。没有开启任何监听时,保存会自动开启 TCP 监听(也可以在“本机监听”里改用 WebSocket)。保存后会显示一次可以直接复制的命令(Linux/macOS 和 PowerShell 两种),在目标机器上运行即可 |
--token-file)或标准输入(--token-stdin)交给 dsh-env-server。--token 会出现在其他用户可见的进程列表里,不建议使用。ws:// 监听放在 nginx、Caddy 等反向代理之后,插件一侧直接填 wss:// 地址;dsh-env-server 本身不带 TLS。/dsh-env),默认关闭。也可以在插件配置里用 reverse: { tcp: { enabled, host, port }, ws: { enabled, host, port, path }, publicHost } 预设;在界面里改过之后以界面为准。在环境页面或环境卡片上选择“新建远程工作区”,在弹出的远程文件浏览器里选好目录。插件会在 ~/.dsh/env-mounts/ 下创建一个占位目录,并把它注册成普通工作区;在这个工作区里新建的会话(包括它的子智能体)都会自动挂载到该环境。挂载期间:
.agents/skills、.dsh/skills)从环境中发现和读取。mountMode: 'gui')可以让挂载同时占用 GUI:GUI 空闲时挂载会话直接获得截图等工具(<别名>__screenshot 等),GUI 被别人占用时挂载退回无界面。挂载会话随时可以用 env_borrow { environment: <挂载的环境>, gui: true } 拿 GUI(可排队),用 env_return 还回 GUI,挂载本身不受影响。会话恢复时按上次的状态重新拿 GUI(拿不到就保持无界面)。也可以不建工作区,在普通会话开始前通过输入框里的“环境”按钮选择一个环境和目录。会话一旦开始,运行环境就不能再更改。
挂载失败时对话不会继续。 远程工作区的会话、单独挂载了环境的会话以及它们的子智能体,如果挂载失败(环境不可用、目录不存在、独占环境被占用等),或挂载中途断开,插件不会让它退回宿主机上运行:
只有远程工作区绑定环境:创建时选择的环境和目录,不能更改,其中的会话都挂载到它上面。其他工作区里的会话默认在本机运行,可以在会话开始前通过“环境”按钮选择别的环境。一个会话最终在哪里运行,按以下顺序决定:
工作区的“默认可借用列表”(在“环境”按钮里点“设为此工作区默认”)只影响借用:会话自己的列表优先,其次是工作区的列表,都没有时可以借用全部环境。
侧边栏的状态点来自轻量检查,不会建立完整连接:本机始终可用;adb 设备看 adb devices 里是否在线;环境服务器和 SSH 尝试连接它的 TCP 端口;正在被会话使用的环境直接视为可用。结果缓存约 15 秒,侧边栏大约每 10 秒刷新一次。
| 工具 | 作用 |
|---|---|
env_list | 列出本会话可借用的环境、它们的状态(谁在用、谁持有 GUI、排队情况),以及本会话已经持有的环境和方式 |
env_borrow | 借用一个环境,默认无界面;gui: true 同时拿 GUI。对已持有的环境(包括挂载的环境)再调用一次:gui: true 升级为 GUI,gui: false 交回 GUI。wait: true 时环境或 GUI 忙会排队(可设超时) |
env_return | 归还环境,同时关闭通过它开启的进程和隧道;gui_only: true 只交回 GUI、保留无界面租约。对挂载的环境只交回 GUI,挂载保留 |
env_transfer | 在本会话工作区和已借环境之间、或两个已借环境之间复制文件或目录,位置写成 别名:/路径 |
本会话可借用的环境会写进 system prompt(每个环境一行,形如 - pixel: Pixel 8 [adb, Android 14 测试机],含名称、类型和描述),模型不必先调用 env_list 就知道有哪些可选;实时状态(谁在用、谁持有 GUI、排队情况)仍然只在 env_list 里给出,这样租约变化不会反复改写 system prompt。会话自己的可借用列表变化时(在工作区里改默认值或调整本会话的勾选)这段文字随之更新;没有可借用环境时不出现。
借到之后的工具(无界面和 GUI 都有):<别名>__exec、read_file、read_image、write_file、edit_file、list_dir、glob、grep、process_start / process_io / process_kill(可交互的长时间进程)、tunnel(to_env / from_env,TCP 或 UDP)。
其他工具按环境能力出现;标为 GUI 的只在持有 GUI 时出现,升级后出现、交回 GUI 后消失:
| 工具 | 方式 | 环境 | 作用 |
|---|---|---|---|
screenshot | GUI | Windows、Android | 截图并缩小到长边 ≤ max_size(默认 1280),之后所有坐标都按这张图的像素计算;region 放大局部,window 截单个窗口,display 选显示器,可选 JPEG 和鼠标指针 |
input | GUI | Windows、Android | 按顺序执行动作:点击(双击、右键、修饰键)、移动、拖动、长按、滑动、滚动、输入 Unicode 文本、组合键、按住键、等待;element: N 直接指向 ui 列出的元素;可附带操作后截图 |
ui | GUI | Windows、Android | 列出界面元素(Android uiautomator / Windows UI Automation):编号、类型、文字、id、中心坐标、大小、状态;可按文字查找并点击、设置文本、滚动、展开、切换等 |
windows | GUI | Windows | 列出顶层窗口,聚焦、最小化、最大化、还原、关闭、移动窗口 |
device | GUI | Android | 设备信息(型号、系统、屏幕尺寸/密度/方向、前台应用、输入法、亮屏/锁屏、电量),唤醒、熄屏、解锁(无密码时)、打开通知栏/快捷设置 |
app | 无界面 | Android | 启动、停止、清除数据、卸载、查看信息、列出应用、当前前台 Activity、打开 URL、am start 任意 Intent |
install_apk | 无界面 | Android | 从工作区或其他已借环境安装 APK |
logcat | 无界面 | Android | 读取最近的日志,可按文字和级别过滤 |
Android 上 ASCII 文本走 input text;非 ASCII(中文、emoji 等)走剪贴板粘贴:插件把几 KB 的 dsh-clipboard.jar 推到设备 /data/local/tmp,以 shell 身份用 app_process 运行它写入系统剪贴板,再发一次 KEYCODE_PASTE,输入法全程不动(input text 和 scrcpy 的文本注入都只能生成 KeyCharacterMap 认得的字符,中文会被直接丢掉)。粘贴后会把设备原来的剪贴板恢复回去。只有设备不允许 app_process(或 Android 7 以下)时才回退到 ADB Keyboard IME;两者都不可用时才明确报不支持。个别禁止粘贴的输入框仍需 ADB Keyboard。
每个环境有一个设置“无界面使用可并行”(headlessParallel,默认开启):
| 环境设置 | 新的无界面租约 | 新的 GUI 租约 / 升级为 GUI |
|---|---|---|
| 可并行(默认) | 总是立即获得,不受 GUI 持有者影响 | 只要有别的租约持有 GUI 就要等待(或 wait: false 时立即失败) |
| 不可并行(独占) | 只要有任何租约(任一方式)就要等待 | 同左;持有唯一租约的会话升级为 GUI 立即生效 |
每个会话能借哪些环境:默认是全部环境;工作区设置了默认列表时继承它;会话也可以随时单独修改。
从旧版本升级:旧版本的“独占使用”(exclusive)设置在加载时自动迁移。Android 和 Windows 账户环境以前默认独占,是为了保护屏幕,现在由 GUI 的独占来保证,所以它们迁移为“无界面可并行”;其他类型上手动开启过“独占使用”的环境迁移为“不可并行”。
dsh-env-server 在连接断开时会结束它启动的所有进程树(Windows 用 Job Object,Unix 用进程组),并关闭监听和套接字。--lifeline --exit-idle 启动,SSH 通道关闭(主动断开、插件卸载或网络中断)或它唯一的会话结束时立即退出,不会留下孤儿进程。每次启动都使用新的随机密钥,经标准输入传入。winuser)在环境页面创建账户后,插件会:
NetUserAdd 建一个注释为 dsh-env managed 的本地标准用户,随机密码用当前用户的 DPAPI 加密后存到插件数据目录的 winusers/<账户>.secret。可选地给若干目录授予该账户“修改”权限(icacls /grant <账户>:(OI)(CI)M,继承到已有的子文件)。%ProgramData%\dsh-env\dsh-env-server-<内容哈希>.exe(插件自带的二进制通常在当前用户的配置目录里,其他账户进不去;ProgramData 下的文件继承 “Users:读取和执行”)。然后以当前用户身份运行 dsh-env-server winuser launch --supervise,它用 CreateProcessWithLogonW(LOGON_WITH_PROFILE) 以该账户身份启动 serve --listen 127.0.0.1:<随机端口> --token <随机令牌> --once --cwd ~:
C:\Users\<账户>,如果同名目录已存在则是 C:\Users\<账户>.<计算机名>);USERPROFILE、APPDATA、TEMP、HKCU 都是该账户自己的。cwd 必须是该账户能进入的目录(例如授予过权限的工作区),否则启动会报“目录名称无效”。WinSta0\Default 桌面上运行(不指定桌面时由 Secondary Logon 服务授予访问权限),所以它启动的窗口程序会显示在当前桌面上,截图和键鼠输入作用于同一块屏幕(坐标是物理像素)。服务端自己没有控制台窗口。KILL_ON_JOB_CLOSE)。启动器作为插件的子进程一直运行,环境关闭、连接断开或 dsh 退出(启动器的 stdin 关闭)时,以该账户身份启动的整棵进程树都会被结束。DeleteProfileW 删除它的配置文件(配置文件卸载需要一点时间,会重试约 15 秒;删不掉时账户仍会删除,并返回警告)。环境设置里的“独立桌面 / 独立会话”决定该账户的窗口在哪里:
| 模式 | 窗口在哪 | 鼠标指针 | 输入方式 | 需要什么 |
|---|---|---|---|---|
| 共用(默认) | 你的桌面 | 你的指针 | SendInput | — |
| 独立桌面 | 同一会话内的 WinSta0\dsh-<账户> | 合成指针 | 窗口消息 | — |
| 独立会话 | 该账户自己的 Windows 会话 | 真正的指针 | 真实键鼠 | TermWrap |
winuser launch --desktop dsh-<账户> 由启动器创建并持有该桌面(桌面对象是引用计数的,最后一个句柄关闭就销毁),账户的一切进程都落在上面,窗口不会出现在你的屏幕里。因为非活动桌面没有屏幕表面(GetDC(NULL)+BitBlt 会是黑的),截图改成枚举该桌面的窗口、逐个 PrintWindow(PW_RENDERFULLCONTENT) 再按 z 序合成;输入走窗口消息(SendInput 在非输入桌面上被拒绝),指针位置由服务端记录并画进截图。%ProgramFiles%\RDP Wrapper\ 加入 Defender 排除项——微软对这个家族有官方特征 HackTool:Win64/RDPWrap!MTB)。装完不需要重启电脑:安装会自己重启 TermService/UmRdpService,服务启动时 SCM 会重新读 ServiceDll,补丁随即生效(只有服务停不下来/起不来时才回退到提示重启)。之后连接时由服务端自带的无界面 RDP 客户端(IronRDP,crates/dsh-env-server/src/rdp.rs)把该账户登进自己的会话,并把连接一直挂着(断开的会话不再渲染,截图会变黑);登录前会把该账户自己的 Winlogon\Shell 指向 <服务端> serve … --no-console,于是服务端本身充当那个会话的 shell,账户里不需要注册任何自启动,也不会在账户桌面上留下控制台窗口。不用 mstsc:它常常无视 .rdp 里的密码弹凭据框,窗口还会出现在你的桌面上,失败时也没有任何可读的错误。限制:
dsh-env-server elevate 完成:它用 ShellExecuteEx 的 runas 动词让 Windows 弹出 UAC 授权,再等被提权的进程结束。不用 gsudo 之类的第三方工具——它们靠替换调用进程的令牌来提权,而宿主机给子进程的是重定向控制台(没有自己的控制台),会以 “Failed to substitute token” 失败;需要管理员权限的每一步(建账户、授权目录、装 TermWrap)都会各自弹一次 UAC。Start-Process -Verb RunAs)会在当前桌面弹出索要管理员凭据的 UAC 窗口,需要坐在电脑前的人处理。127.0.0.1,但同一台机器上的其他本地用户也能连接这个端口,靠随机令牌认证;令牌出现在服务端的命令行里(只有该账户本身和管理员能读到)。C:\Users\Public、大多数程序目录),不能读写当前用户的配置目录,除非显式授权。seclogon);禁用该服务或组策略禁止该账户“本地登录”时会给出对应的错误说明。中文 Windows 的控制台默认代码页是 936(GBK),cmd、ping、net、Windows PowerShell 等写进管道的文字原本会被当成 UTF-8 解码成乱码。现在:
proc.spawn 默认(encoding: 'utf8')让子进程运行在 UTF-8 控制台上(服务端通过 dsh-env-server __utf8-console 包装一层,调用 SetConsoleCP/SetConsoleOutputCP(65001)),command 模式还会把 Windows PowerShell 的 $OutputEncoding 设为 UTF-8;仍不是 UTF-8 的输出行按 OEM 代码页转码。这样 GBK 以外的字符(emoji、韩文等)也不会变成 ?。副作用:在 UTF-8 控制台上,ping、net、Windows PowerShell 等系统工具会输出英文提示。encoding: 'auto' 保持控制台原来的代码页(提示保持中文),只把非 UTF-8 的输出行按 OEM 代码页转成 UTF-8;encoding: 'raw' 原样转发字节(二进制输出)。详见 docs/protocol.md 的 proc.spawn 一节。
文档见 docs/protocol.md:
这是一个 TypeScript(strict)pnpm monorepo,目录结构和约定见 CONTRIBUTING.md。
pnpm install
pnpm lint && pnpm typecheck # ESLint(type-aware)+ tsc
pnpm test # 协议单元测试(安全通道、WebSocket)+ 插件集成测试:env-server、ssh、反向连接、adb(模拟设备)、租约、挂载映射
pnpm build:server # cargo 构建 crates/dsh-env-server,并放到 packages/plugin/bin/
pnpm build:plugin # packages/plugin/dist/index.js
pnpm build:client # packages/plugin/client.js
pnpm run package # 发布包 out/dsh-plugin-environments-<版本>.tgz + SHA256SUMS(本地缺少 macOS 二进制时加 --allow-missing)
crates/dsh-env-server 是 Rust 写的 dsh-env-server,子命令有 serve、connect、stdio、elevate、session …、winuser create|delete|list|launch|grant|session。命令行用 clap,glob/grep 基于 ripgrep 的 ignore / globset / grep-* 库;WebSocket 和安全通道是手写的小实现,加密只依赖 RustCrypto 的 aes-gcm、hkdf、hmac、sha2 / sha1。Linux 静态二进制通过 rust-lld 交叉编译,不需要额外的工具链。
dsh-env-server 上暂不支持。Android 的截图和输入走 adb。dsh-env-server)时只有 TCP 隧道;远端是 Windows 时也不支持 grep。@ 文件补全仍然显示宿主机上的占位目录。data-row-key 属性插入到页面里的。DSH 改变侧边栏结构后,这些装饰可能不再显示,但不会影响其他功能。local 环境和 Windows 账户环境)可以各自被一个会话持有 GUI。SendInput/SetCursorPos 会被拒绝,所以那里的键鼠只能作为窗口消息投递(依赖原始输入、低层钩子或 GetAsyncKeyState 的程序可能不灵),指针位置由服务端自己记录并画进截图。HackTool,Windows 更新后可能需要重装;客户端 SKU 同时多会话也不在 Windows 客户端授权范围内,是否使用请自行判断。详见 docs/session-mode.md。proc.spawn 返回的 pid 是 __utf8-console 包装进程的 pid。