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

远程窗口 yeerock/deepseek-harness-remote-control

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

用浏览器远程打开并操作桌面端 Harness 界面

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

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

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

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:仅索引
是什么[目录]—
dsh 原生插件 · tool
暂无时间戳·判据 →
装得上吗[实装验证]—
本站尚未做安装检查
暂无时间戳·判据 →
安全吗[自动扫描]—
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
数据截至 2026/11/04·判据 →
还在维护吗[仓库]✓
活跃:最近一次提交在 4 天前
暂无时间戳·判据 →
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成

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

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

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

可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 79 处,展示前 73 条)
  • 读取本地文件
    apps/agent/src/dsh-session.ts:27
    text = readFileSync(filePath, 'utf8');
  • 读取本地文件
    apps/agent/src/dsh-session.ts:32
    const mode = statSync(filePath).mode & 0o777;
  • 读取本地文件
    apps/agent/src/identity.ts:34
    if (!existsSync(stateDir)) {
  • 写入本地文件
    apps/agent/src/identity.ts:35
    mkdirSync(stateDir, { recursive: true, mode: 0o700 });
  • 读取本地文件
    apps/agent/src/identity.ts:39
    const mode = statSync(stateDir).mode & 0o777;
  • 写入本地文件
    apps/agent/src/identity.ts:41
    chmodSync(stateDir, 0o700);
  • 读取本地文件
    apps/agent/src/identity.ts:90
    text = readFileSync(path, 'utf8');
  • 读取本地文件
    apps/agent/src/identity.ts:100
    const mode = statSync(path).mode & 0o777;
  • 写入本地文件
    apps/agent/src/identity.ts:102
    chmodSync(path, 0o600);
  • 写入本地文件
    apps/agent/src/identity.ts:118
    renameSync(path, `${path}.rejected-${String(Date.now())}`);
  • 删除本地文件
    apps/agent/src/identity.ts:121
    unlinkSync(path);
  • 写入本地文件
    apps/agent/src/identity.ts:133
    writeFileSync(temporary, `${JSON.stringify(identity, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
  • 写入本地文件
    apps/agent/src/identity.ts:134
    chmodSync(temporary, 0o600);
  • 删除本地文件
    apps/agent/src/identity.ts:139
    unlinkSync(temporary);
  • 读取本地文件
    apps/agent/src/lock.ts:78
    text = readFileSync(path, 'utf8');
  • 读取本地文件
    apps/agent/src/lock.ts:79
    mtimeMs = statSync(path).mtimeMs;
  • 读取本地文件
    apps/agent/src/lock.ts:90
    text = readFileSync(path, 'utf8');
  • 写入本地文件
    apps/agent/src/lock.ts:115
    mkdirSync(stateDir, { recursive: true, mode: 0o700 });
  • 写入本地文件
    apps/agent/src/lock.ts:126
    writeFileSync(descriptor, payload, { encoding: 'utf8' });
  • 删除本地文件
    apps/agent/src/lock.ts:165
    unlinkSync(path);
  • 读取本地文件
    apps/agent/src/lock.ts:194
    text = readFileSync(path, 'utf8');
  • 删除本地文件
    apps/agent/src/lock.ts:205
    unlinkSync(path);
  • 写入本地文件
    apps/agent/src/status.ts:72
    mkdirSync(directory, { recursive: true, mode: 0o700 });
  • 写入本地文件
    apps/agent/src/status.ts:76
    writeFileSync(temporary, `${JSON.stringify(status, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
  • 写入本地文件
    apps/agent/src/status.ts:77
    renameSync(temporary, filePath);
  • 删除本地文件
    apps/agent/src/status.ts:80
    unlinkSync(temporary);
  • 写入本地文件
    apps/edge/src/db.ts:332
    mkdirSync(dataDir, { recursive: true, mode: 0o700 });
  • 写入本地文件
    apps/edge/src/db.ts:336
    chmodSync(this.file, 0o600);
  • 删除本地文件
    packages/plugin/src/agent-supervisor.ts:285
    await rm(this.statusFile, { force: true }).catch(() => undefined);
  • 执行外部命令来自配置/变量
    packages/plugin/src/agent-supervisor.ts:289
    child = spawn(target.command, [...target.prefixArgs, ...this.buildArgs()], {
  • 执行外部命令固定命令
    packages/plugin/src/agent-supervisor.ts:351
    const guard = spawn('/usr/bin/caffeinate', ['-i', '-w', String(child.pid)], { stdio: 'ignore', detached: true });
  • 读取本地文件
    packages/plugin/src/agent-supervisor.ts:475
    const info = statSync(this.statusFile);
  • 读取本地文件
    packages/plugin/src/agent-supervisor.ts:477
    const text = await readFile(this.statusFile, 'utf8');
  • 写入本地文件
    packages/plugin/src/agent-supervisor.ts:555
    await mkdir(this.options.stateDir, { recursive: true, mode: 0o700 });
  • 写入本地文件
    packages/plugin/src/agent-supervisor.ts:558
    await writeFile(temporary, body, { mode: 0o600, encoding: 'utf8' });
  • 写入本地文件
    packages/plugin/src/agent-supervisor.ts:559
    await rename(temporary, this.upstreamFile);
  • 写入本地文件
    packages/plugin/src/agent-supervisor.ts:560
    await chmod(this.upstreamFile, 0o600).catch(() => undefined);
  • 删除本地文件
    packages/plugin/src/agent-supervisor.ts:564
    await rm(this.upstreamFile, { force: true }).catch(() => undefined);
  • 读取本地文件
    packages/plugin/src/agent-supervisor.ts:682
    return statSync(path).isFile();
  • 读取本地文件
    packages/plugin/src/agent-supervisor.ts:690
    return statSync(path).isDirectory();
  • 读取本地文件
    packages/plugin/src/config.ts:168
    if (existsSync(join(modern, 'device.json'))) return modern;
  • 读取本地文件
    packages/plugin/src/config.ts:170
    if (existsSync(join(legacy, 'device.json'))) return legacy;
  • 发起网络请求
    packages/plugin/src/host.ts:239
    const response = await fetch(`${this.edgeUrl}/v1/edge`, { headers: { accept: 'application/json' } });
  • 写入本地文件
    packages/plugin/src/host.ts:607
    void mkdir(this.stateDir, { recursive: true, mode: 0o700 })
  • 写入本地文件
    packages/plugin/src/host.ts:608
    .then(() => writeFile(join(this.stateDir, 'plugin-error.json'), `${JSON.stringify({ at: Date.now(), message: this.setupError }, null, 2)}\n`, { mode:
  • 读取本地文件
    packages/plugin/src/host.ts:1266
    const parsed = JSON.parse(readFileSync(join(stateDir, 'host.json'), 'utf8')) as { enabled?: unknown; name?: unknown; remoteFullAccess?: unknown };
  • 写入本地文件
    packages/plugin/src/host.ts:1278
    await mkdir(stateDir, { recursive: true, mode: 0o700 });
  • 写入本地文件
    packages/plugin/src/host.ts:1279
    await writeFile(join(stateDir, 'host.json'), `${JSON.stringify(settings)}\n`, { mode: 0o600 });
  • 发起网络请求
    packages/plugin/src/remote-view.ts:76
    const response = await fetch(`${edge.origin}/d/${encodeURIComponent(deviceId)}/dsh-rc/${encodeURIComponent(endpoint)}`, {
  • 发起网络请求
    packages/plugin/src/remote-view.ts:130
    const response = await fetch(ticket, { redirect: 'manual' });
  • 读取本地文件
    packages/plugin/src/sharing.ts:141
    text = readFileSync(file, 'utf8');
  • 读取本地文件
    packages/plugin/src/sharing.ts:150
    const mode = statSync(file).mode & 0o777;
  • 发起网络请求
    packages/plugin/src/sharing.ts:238
    const body = await this.request('GET', '/v1/devices/me/sharing');
  • 发起网络请求
    packages/plugin/src/sharing.ts:244
    const body = await this.request('POST', `/v1/devices/${encodeURIComponent(deviceId)}/rename`, { name });
  • 发起网络请求
    packages/plugin/src/sharing.ts:251
    const body = await this.request('POST', '/v1/devices/me/embed-ticket', { deviceId });
  • 发起网络请求
    packages/plugin/src/sharing.ts:257
    const body = await this.request('GET', '/v1/devices/me/peers');
  • 发起网络请求
    packages/plugin/src/sharing.ts:280
    const body = await this.request('PUT', '/v1/devices/me/sharing', { enabled });
  • 发起网络请求
    packages/plugin/src/sharing.ts:303
    const response = await this.request('POST', '/v1/devices/me/invites', body);
  • 读取本地文件
    packages/plugin/src/update.ts:83
    if (!existsSync(join(repoRoot, '.git'))) return false;
  • 执行外部命令固定命令
    packages/plugin/src/update.ts:85
    execFileSync('git', ['symbolic-ref', '-q', 'HEAD'], { cwd: repoRoot, stdio: 'ignore', windowsHide: true, timeout: 5_000 });
  • 读取本地文件
    packages/plugin/src/update.ts:260
    if (this.options.spawnInstaller === undefined && !existsSync(script)) {
  • 写入本地文件
    packages/plugin/src/update.ts:286
    mkdirSync(this.stateDir, { recursive: true });
  • 执行外部命令来自配置/变量
    packages/plugin/src/update.ts:289
    const child = spawn(node.command, [script, '--tag', tag], {
  • 读取本地文件
    packages/plugin/src/update.ts:398
    const lines = readFileSync(this.restartLogPath, 'utf8').split(/ ? /).map((line) => line.trim()).filter((line) => line !== '');
  • 读取本地文件
    packages/plugin/src/update.ts:418
    const value = JSON.parse(readFileSync(this.statusPath, 'utf8')) as unknown;
  • 读取本地文件
    packages/plugin/src/update.ts:427
    const value = JSON.parse(readFileSync(this.restartsPath, 'utf8')) as { version?: unknown; attempts?: unknown };
  • 写入本地文件
    packages/plugin/src/update.ts:436
    mkdirSync(this.stateDir, { recursive: true });
  • 写入本地文件
    packages/plugin/src/update.ts:437
    writeFileSync(this.restartsPath, `${JSON.stringify({ version: latest, attempts: this.restartAttempts(latest) + 1, at: Date.now() })}\n`, { mode: 0o600
  • 执行外部命令来自配置/变量
    packages/plugin/src/update.ts:468
    const version = execFileSync(candidate, ['-p', 'process.versions.node'], { encoding: 'utf8', timeout: 5_000, windowsHide: true, env }).trim();
  • 执行外部命令固定命令
    packages/plugin/src/update.ts:485
    spawn('/bin/sh', ['-c', `exec >>${log} 2>&1; echo "restart requested $(date)"; sleep 3; osascript -e "tell application \\"DeepSeek Harness\\" to quit"
  • 写入本地文件
    packages/plugin/src/update.ts:493
    writeFileSync(script, windowsRestartScript(logFile, fallbackExe), { encoding: 'utf8', mode: 0o600 });
  • 执行外部命令固定命令
    packages/plugin/src/update.ts:498
    spawn('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', launch], { detached: true, stdio: 'ignore', windowsHide: true }).unre
  • 执行外部命令固定命令
    packages/plugin/src/update.ts:513
    spawn('/bin/sh', ['-c', `exec >>${log} 2>&1; echo "restart requested $(date)"; sleep 3; p=$(${pids}); [ -n "$p" ] && kill -TERM $p; i=0; while [ -n "$
扫描于 2026/11/4(0 天前) · 范围 全包扫描 · 来源 GitHub 仓库

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

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

DeepSeek Harness 远程控制:仅靠提示词完成 Harness 配对,原生远程窗口,自托管边缘节点

README

由 DeepSeek 最新模型翻译生成
DeepSeek Harness 远程控制(DSH RC) 一条 TLS WebSocket,把你正在用的浏览器变成 DeepSeek Harness 桌面版的远程窗口。 创作者:yeerock DSH RC 通过一条由桌面端主动外连的长连接,把未经修改的 DeepSeek Harness Web UI 提供到任意浏览器:桌面里的 agent 主动连到 edge,edge 把 HTTP 与 WebSocket 流转发给它,浏览器打开的就是真正的 DSH 界面。没有 WebRTC,没有 UDP,不需要公网入站端口,不需要端口映射,也不用在面前的设备上再装一个客户端。 通过 DSH RC edge 打开的、未经修改的 DeepSeek Harness 界面 这张截图不是效果图:它是真实的 Harness 0.2.1 会话,由 tools/e2e/real-dsh.mjs 启动真实的 dsh --profile web、 edge、agent,完成 owner 初始化与配对后,用无头 Chromium 从 http://<edge>/d/<device>/ 加载出来的。 状态:v0.4.1。 协议、多路复用器、edge、agent、插件、测试与文档均已实现。
展开完整文档(README 剩余 1 节)
DeepSeek Harness 远程控制(DSH RC)

一条 TLS WebSocket,把你正在用的浏览器变成 DeepSeek Harness 桌面版的远程窗口。

创作者:yeerock

DSH RC 通过一条由桌面端主动外连的长连接,把未经修改的 DeepSeek Harness Web UI 提供到任意浏览器:桌面里的 agent 主动连到 edge,edge 把 HTTP 与 WebSocket 流转发给它,浏览器打开的就是真正的 DSH 界面。没有 WebRTC,没有 UDP,不需要公网入站端口,不需要端口映射,也不用在面前的设备上再装一个客户端。

通过 DSH RC edge 打开的、未经修改的 DeepSeek Harness 界面

这张截图不是效果图:它是真实的 Harness 0.2.1 会话,由 tools/e2e/real-dsh.mjs 启动真实的 dsh --profile web、 edge、agent,完成 owner 初始化与配对后,用无头 Chromium 从 http://<edge>/d/<device>/ 加载出来的。

状态:v0.4.1。 协议、多路复用器、edge、agent、插件、测试与文档均已实现。
321 个自动化测试全绿;新电脑只需一条命令即可安装并连接,连接断开(休眠唤醒、网络切换、edge 重置)后自动恢复,每台电脑自动检查、安装并在空闲时重启以保持最新版本,真实 DSH 端到端验证通过,下面的基准数字是实测值。
仍是预发布软件:请把插件当 beta,并在暴露桌面之前阅读 THREAT-MODEL.md。

两步开始

两个步骤都支持 macOS、Windows 和 Linux。每台电脑先安装并打开一次 DeepSeek Harness,然后把提示粘贴给 DeepSeek Harness 的 agent,下载、安装、设置、连接、重启全部自动完成;也可以自己在终端运行对应系统的一条命令。

第 1 步,第一台电脑(主机,任何系统),粘贴给 DeepSeek Harness:

Set up this computer as my DSH RC host, by yourself. Run the command for this system:
Windows (PowerShell): $env:DSHRC_MODE='host'; $env:DSHRC_TAG='v0.4.1'; irm https://raw.githubusercontent.com/yeerock/deepseek-harness-remote-control/v0.4.1/scripts/install.ps1 | iex
macOS or Linux: curl -fsSL https://raw.githubusercontent.com/yeerock/deepseek-harness-remote-control/v0.4.1/scripts/install.sh | sh -s -- host v0.4.1
If it prints ACTION NEEDED with a link, show me the link and keep waiting; it continues after I approve.
If it fails, fix the cause and run it again until it prints DONE. Never print or share the owner password.

只需批准一次 Tailscale 登录链接(以及 Funnel,如有提示);Windows 上安装 Tailscale 时可能还会出现一次系统授权提示。主人密码保存在 ~/.dsh-rc/owner-password.txt(仅你可读)。

第 2 步,其他每台电脑(Mac、Windows、Linux):在主机上打开 Remote Control,点击 Build Setup Prompt,把生成的提示粘贴到那台电脑的 DeepSeek Harness 中。约一分钟后它会出现在 Remote Workspaces,之后自动保持连接并自动更新。

更新时,Remote Workspaces 标题右侧显示与 DeepSeek Harness 自身相同的更新按钮:更新、安装进度百分比、重启以更新、正在准备重启、重试更新。

在远程电脑上新建会话时,点击 + 打开“新建远程会话”:和本地新建会话一样,可以选择项目文件夹(包括还没有会话的文件夹)、模型和推理强度以及权限(只读、工作区可写、完全访问,以及那台电脑提供的自动审查),默认使用那台电脑自己的设置;开启完全访问前会先确认。打开的远程会话中,输入框下方有与本地相同的两个控件:左侧是权限,右侧是模型和推理强度,修改会作用于该会话的下一条消息。

上一代远程插件的问题

参考实现(liguobao/ds-harness-remote)是一个认真的项目,它也做到了本 v1 还没做的部分:Android 与 VS Code 客户端,以及针对中继的应用层端到端加密。它的问题在于拓扑与表面面积,这正是中国境外用户感受到的"先卡住,然后掉线"。

| # | 上一代的做法 | 为什么境外用户受伤 |
| 1 | 优先尝试 WebRTC,ICE 要等 8 秒(DEFAULT_NEGOTIATE_TIMEOUT_MS)才放弃,控制握手最长 15 秒 | 在封 UDP 的网络上,每次连接都先卡几秒,然后才开始回退 |
| 2 | STUN/TURN 依赖 UDP;TURN 还要多一跳中继和带宽成本 | 酒店、企业、校园与 CGNAT 网络会丢弃 UDP;回退更慢,而且仍然失败 |
| 3 | 文档/默认服务器是 dsh.r2049.cn,托管在中国大陆 | 境外用户的每个会话都要跨一条很长、丢包严重的链路;仅 RTT 就足以让输入感觉卡死 |
| 4 | Android App + VS Code 客户端 + client-core 各自重新实现 Harness RPC | 两套协议面要同步;投影一卡,用户看到的就是"应用冻住",桌面功能也滞后 |
| 5 | 重连时拆掉连接,重新走 membership、固定密钥信任、选路与 Noise | 一次 Wi-Fi 抖动就丢几秒,并丢失在途 UI 状态 |
| 6 | Node 侧 WebRTC 是可选原生依赖(@roamhq/wrtc) | 安装/ABI 的脆弱性只在部分机器上出现,然后静默降级到中继 |

单独看,这些都不是 bug;合在一起,就解释了用户的抱怨:系统先用几秒钟尝试一条通常走不通的路,再经过一台很远的中继,抖动之后又从零开始。

Remote Control 如何解决

Remote Control 反过来下注:一种可靠传输、真实 UI、把 edge 放在离自己近的地方。

| 上一代的问题 | Remote Control 的做法 | 代码位置 |
| 8 秒 ICE 尝试、15 秒握手 | 只建立一条 443 出站 WSS;没有 ICE/STUN/TURN 需要协商或等待超时 | packages/mux、apps/agent |
| 依赖 UDP 的路径 | 只用 TCP/TLS;可穿过企业代理、TLS 中间人、CGNAT 与纯 IPv6 网络 | ARCHITECTURE.md |
| 默认使用中国大陆中继 | 自托管 edge;中国 ↔ 境外可放在香港、新加坡、东京或首尔--它就是唯一一跳 | DEPLOY.md §0 |
| 第二个客户端会漂移 | 浏览器直接加载 /d/<device>/ 下未经修改的 DSH Web UI,功能对齐是默认结果 | ARCHITECTURE.md §2 |
| 重连慢、丢状态 | 15 秒心跳、45 秒空闲上限、250 ms-20 s full-jitter、30 秒 presence 宽限;实测被 4004 强制替换后 49 ms 恢复;不重放 mutation | packages/mux、apps/agent |
| 批量传输拖垮交互 | 每流 256 KiB、每隧道 8 MiB 信用,按消费归还;high/normal/low 调度;4 × 16 MiB 下交互 p95 3.1 ms | packages/mux |
| 原生 wrtc 的脆弱性 | 没有原生插件;只用 Node 内置模块与 ws | package.json |
| 崩溃后留下两个 agent | 每个 stateDir 一把 agent.lock,拒绝第二个实例;4004 替换按可重试处理,而不是互相抢 | apps/agent/src/lock.ts |

安全与运维的形态也随之改变:DSH 启动 token 不离开桌面(0600 交接、只在内存中的 cookie),浏览器永远拿不到 DSH 凭证,设备密钥只存 SHA-256,配对码一次性且必须由 owner 配合,edge 可以完全自托管。代价仍然明确:edge 终止 TLS,因此能看到代理流量的明文。它是受信任的中转;THREAT-MODEL.md 写清了这份信任的收益与成本。如果你需要"连中继都无法解密"的端到端加密,这个 v1 不适合你--ADR-0001 解释了为什么仍然这样选。

架构

┌─────────────┐ HTTPS + WSS (443) ┌──────────────┐ one multiplexed WSS ┌───────────┐ loopback HTTP/WS ┌─────────────┐
│ browser │ ────────────────────▶ │ edge │ ◀────────────────────── │ agent │ ───────────────────▶ │ DSH desktop │
│ (anywhere) │ ◀──────────────────── │ (self-host) │ ──────────────────────▶ │ (desktop) │ ◀─────────────────── │ web server │
└─────────────┘ └──────────────┘ └───────────┘ └─────────────┘
auth · pairing mints the DSH unchanged presence · audit cookie, proxies

- 浏览器 → edge: 普通的 443 端口 HTTPS/WSS。挂在 /d/<deviceId>/ 子路径下,应用自身的相对 URL 与 完成其余工作。
- edge → agent: 每台设备一条 WebSocket,多路复用成相互独立的 HTTP 与 WebSocket 流,带每流流控和 high/normal/low 加权调度,批量传输抢不走交互流量。
- agent → DSH: 只连 127.0.0.1:<port>,使用由 DSH 启动 token 换来的 cookie。
token 和 cookie 都不离开桌面。

细节见 ARCHITECTURE.md、PROTOCOL.md、 ADR-0001。

环境要求

| 组件 | 要求 |
| 桌面端 | DeepSeek Harness 0.2.1-rc.3;插件声明 >=0.2.1-rc.3 <0.3.0 |
| Edge | Node.js ≥ 24.0(或 Docker 镜像),生产环境需要一个域名做 TLS |
| 浏览器 | 任意较新的 Chromium、Firefox 或 Safari |
| 网络 | 桌面端能出站访问 443;不需要入站端口,不需要 UDP |

快速开始

1. Edge

在一台小 VPS(或本机做演示)上:

git clone https://github.com/yeerock/deepseek-harness-remote-control && cd deepseek-harness-remote-control
npm ci && npm run build

node apps/edge/dist/main.js \
--host 0.0.0.0 --port 8787 \
--data-dir ~/.dsh-rc --public-url https://link.example.com

启动时会打印一次性 setup code(在完成初始化前每次启动都会轮换)。
用仓库自带的 Caddy + Docker 配置 在前面挡 TLS,或者在负载均衡处终止 TLS 并以 --trust-proxy 运行。

2. Agent(也可以交给插件)

node apps/agent/dist/main.js \
--edge https://link.example.com \
--dsh-url-file ~/.dsh-rc/upstream.json \
--state-dir ~/.dsh-rc/agent \
--name my-desktop \
--status-file ~/.dsh-rc/agent/status.json

upstream.json 的内容是 { "url": "http://127.0.0.1:<port>/?token=…" },权限 0600。token 故意不接受放在命令行参数里。首次运行会打印 DSHRC_PAIR_URL=…;以 owner 身份登录后打开它,即可批准设备并配对浏览器。
之后 DSH 界面就在 https://link.example.com/d/<deviceId>/。

3. 桌面插件(推荐)

插件从运行中的 DSH 桌面取得已认证 URL,写入 0600 文件,启动并监管 agent。
Remote Workspaces 行右侧的齿轮打开 Remote Control 弹窗:版本、两列状态、 Start/Restart/Stop 与 setup prompt。配对只使用一段可复制的提示词,不再有 pair URL、邀请链接或二维码。

点击 Remote Workspaces 中的主机,会在 DSH 的原生 Electron 子窗口中打开完整远程界面(会话、聊天、文件、终端),通过一次性 embed ticket 自动认证,不会出现 edge 登录页。真正的“侧边栏原生会话”设计见 ADR-0002。

当前英文 README 是事实来源;本文件保留为中文说明。

dsh plugin --profile <profile> add link:"$PWD/packages/plugin"

在 profile 的用户层 $DSH_HOME/profiles/<profile>/cordis.patch.yml 中启用:

- id: dsh-rc
config:
enabled: true
edgeUrl: https://link.example.com
deviceName: my-desktop
autoStart: true

然后以 dsh --profile <profile> --port 0 --no-open 启动; --profile <name> 已替代 dsh web 简写,不要再追加 web。

安装、配置与排错见 packages/plugin/README.md;部署形态、参数与 systemd 见 DEPLOY.md。

安全摘要

- DSH 启动 token 不离开桌面。 agent 用它一次性换成本地回环 cookie,只放在内存里;浏览器永远拿不到 DSH 凭证。
- 设备密钥是 32 字节随机数,edge 只保存 SHA-256,隧道上用 Bearer token 发送。
- 配对码是 8 字符、一次性、10 分钟有效,且必须由 owner 会话或 owner 密码配合才能兑换;它不是独立的 bearer 凭证。owner 会话 cookie 为 HttpOnly、 Secure、SameSite=Lax,30 天。
- edge 在终止 TLS 后能看到明文。 这是设计,不是缺陷,见 THREAT-MODEL.md 第 7.1 节。
- 即使 edge 被攻破,agent 也拒绝非回环上游、绝对 URL、.. 以及编码后的路径穿越。
- 没有 WebRTC、没有 UDP、没有入站端口,也不做应用层 mutation 重放。

漏洞报告流程见 SECURITY.md。

Edge 仪表盘:设备、在线状态与审计

验证

npm run typecheck # 全 workspace 的 tsc -b
npm test # 168 个测试:单元 + 集成
npm run bench:quick # 基准测试,JSON + markdown 写入 tools/bench/results
npm run e2e # 启动真实 DSH web、edge、agent,再用 Chromium 验证

npm run e2e 就是上面截图的来源:独立 DSH_HOME、真实 dsh --profile web、真实 edge、真实 agent,完成 owner 初始化、配对、取回 index、升级 /api/remote.mux,最后用 Chromium 渲染。它会断言代理出的 HTML 是 Harness 外壳、不含回环 origin,并且 UI 真的渲染出来。

实测环境:Apple M4 Pro(24 GiB,macOS 26,Node 24.18.0,2026-10-28), edge 在本机回环--这些数字衡量隧道开销,不含公网 RTT:

| 指标 | 数值 |
| 经 edge 的 TTFB,中位数 / p95 | 2.1 ms / 2.9 ms |
| 吞吐,1 MiB / 16 MiB | 157 / 299 MiB/s |
| 200 个串行请求 | 505 req/s(p95 2.1 ms) |
| 50 个并发请求 | 总计 16.6 ms(p95 约 19 ms) |
| 4 × 16 MiB 批量传输期间的交互 p95 | 3.1 ms |
| agent 重启后的隧道建立 | 2.7 ms |
| 被 4004 强制替换后的重连 | 49 ms |

真实 DSH 冒烟:index 37,907 B 字节一致,主 JS bundle 941,436 B 经隧道后 SHA-256 一致,remote.mux 升级成功。

明确不做的事

- 没有手机 App。 浏览器就是客户端。
- v1 没有 WebRTC 数据通道。 取舍与重启条件见 ADR-0001。
- 不在结果未知时重放 Harness mutation。
- 不做第二套聊天/文件/终端 UI。 这正是重点。
- 不是多租户 SaaS。 先做好自托管;托管账号与 OAuth 在路线图上。

仓库结构

| 路径 | 内容 |
| packages/protocol | 线格式、帧校验、路径与请求头策略 |
| packages/mux | 流、流控、优先级调度、心跳 |
| packages/shared | WebSocket 适配、令牌、Cookie、日志 |
| apps/edge | 公网 HTTPS/WSS 中转、认证、配对、SQLite、仪表盘 |
| apps/agent | 桌面隧道客户端与回环代理 |
| packages/plugin | DeepSeek Harness 插件(宿主 + 侧边栏 UI) |
| tests、tools/bench | 集成、故障注入、基准 |
| docs | 架构、协议、ADR、威胁模型、部署、运维手册 |

常见问题

DSH token 会离开本机吗? 不会。它在进程内被换成本地回环 cookie,浏览器看不到,也不会写入磁盘或命令行参数。

为什么不用 P2P? 因为用户报告的正是 P2P 无法工作的网络;而且浏览器无法在没有第二个客户端的情况下用 WebRTC 访问一个 HTTP origin。完整论证见 ADR-0001。

网络抖动时会怎样? 15 秒心跳、45 秒空闲上限、250 ms 到 20 s 的 full-jitter 重连、30 秒 presence 宽限,且不重放消息。桌面崩溃也不会留下两个 agent 抢同一条隧道:每个 stateDir 有单实例锁。

可以自托管 edge 吗? 这是主要部署方式。一个 Docker Compose 文件、Caddy 负责 TLS、一个 SQLite 文件负责备份。

更多:FAQ.md · OPERATIONS.md。

创作者

yeerock 是 DSH RC / Remote Control 的创作者与维护者。

- GitHub:@yeerock
- 项目:github.com/yeerock/deepseek-harness-remote-control

许可证

MIT,见 LICENSE。
预览分享卡片长什么样 ↓yeerock/deepseek-harness-remote-control
分享 · DeepSeek Harness Hub
yeerock/deepseek-harness-remote-control

用浏览器远程打开并操作桌面端 Harness 界面

这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。
认领条目 →
回到顶部