dsh-remote-access-cidr
Manifest validProvides a remote access entry point for DeepSeek Harness Desktop — restricted to specific network segments with an optional numeric password
dsh-remote-access-cidr
给 DeepSeek Harness(DSH)桌面版用的插件:把本机 DSH 的网页界面只开放给你指定的网段
(默认 Tailscale 的 100.64.0.0/10),于是手机或另一台电脑也能用上它。
- 不绑
0.0.0.0:只监听「允许网段」里属于本机的地址,网段外的对端一律403。 - 不写死地址:网段、监听地址、端口、上游、落地文件全是配置项。
- 两种进入方式:复制带令牌的网址;或者设一个密码,手机开裸地址输一次(之后 30 天免密)。
- 手机界面能看:「远程UI布局」可选手机 / 电脑 / 自动;手机布局只作用于远程页面,本机界面一个像素都不动。
- 在设置页里改:保存即热重载(原地关掉旧监听、按新参数重开),不用重启 DSH。
- 失败可见:无论成功失败都会写一份自诊断文件,看不到 DSH 日志时也能定位。
安装
三种方式,选一种即可;装完重启一次 DSH。
① 插件页(推荐) —— 打开 DSH →「设置 → 插件 → 添加插件」,填下面任一:
| 填什么 | 从哪里装 |
|---|---|
dsh-remote-access-cidr | npm(发布后可用) |
github:Mzy123l/dsh-remote-access-cidr | 直接从 GitHub |
| 本机某个目录的绝对路径 | 自己 clone / 下载下来时 |
② 命令行(等价于插件页那一步;DSH 正在运行时 profile 写锁会占住):
dsh plugin --profile desktop add dsh-remote-access-cidr
③ GitHub 一键取代码 —— 想把源码放到本机再装:
irm https://raw.githubusercontent.com/Mzy123l/dsh-remote-access-cidr/main/install.ps1 | iex
脚本只负责把代码取到一个固定目录(默认 %LOCALAPPDATA%\dsh-plugins\dsh-remote-access-cidr),
装的动作仍然交给 DSH 自己(插件页填那个目录)。需要代理时:
$s = irm https://raw.githubusercontent.com/Mzy123l/dsh-remote-access-cidr/main/install.ps1
& ([scriptblock]::Create($s)) -Proxy http://127.0.0.1:7890
为什么不一步装完:DSH 的插件安装(写 profile 的
package.json/cordis.patch.yml、跑包管理器) 只能由 DSH 自己做(插件页,或让 agent 用plugin_manager工具),手工改 profile 容易把它弄坏。
之后改参数都是热重载;但换了 index.js / client.js 的代码仍需重启一次才会加载。
使用
方式一:设访问密码(推荐,手机不用复制长串)
- 「设置 → 远程访问 → 访问密码」填 4–12 位数字或字母,保存(写别的字符会被就地拦下并说明规则)。
- 手机打开
http://<地址>:<端口>/—— 地址和端口见状态文件里的手机访问:那一行。 - 输一次密码即可进入,之后 30 天免密(cookie)。
- 密码输错一次,该地址就自动写进「排除的网段」(形如
100.x.y.z/32)、之后一律403; 解封 = 到「设置 → 远程访问 → 排除的网段」里把那一项删掉。
方式二:用带令牌的网址(不设密码)
打开状态文件里的 远程访问网址: http://…/?token=… 一次,DSH 会把 token 换成自己的签名
cookie(默认 30 天);之后直接访问 http://<地址>:<端口>/ 即可。没票就是 401。
每次启动 DSH 都会换一张 token(用另一个 Windows 账户跑 DSH,也有它自己的一张),所以旧网址、 别的账户/上一次运行留下的网址里那个
?token=…已经作废。拿它访问不会再把 DSH 那句英文甩给你了: 从当前版本起会看到插件自己的中文说明页(讲清「票作废」还是「没带票」,以及去哪儿拿当前那条)。 省事的做法:设了accessCode就直接打开裸地址 (http://<地址>:<端口>/)输一次密码;没设就复制状态文件里当前那条带票网址。 旧 cookie 同理不通用(DSH 的 cookie 绑hostname:port)。
状态文件
<DSH_HOME>\remote-access-url.txt(默认在 %USERPROFILE%\.dsh,权限 0600)里写着:带票网址、
手机访问地址、是否开启了解锁密码、已拉黑的地址与「待补写」的地址、放行密钥、当前生效的配置、
上游自检结果。打不开时先看它。
⚠️ 文件里有访问令牌,等于本机操作权限,不要外传(访问密码在里面打码成
***)。
配置
「设置 → 远程访问」里只有 8 项:启用 / 允许的网段 / 排除的网段 / 端口 / 并发上限 / 日志级别 / 访问密码 / 远程UI布局。
保存会写进 profile 的 cordis.patch.yml,插件随即热重载(端口会变,新网址以状态文件为准)。
访问密码只收 4–12 位数字或字母(大小写敏感)。写别的(空格、符号、太短太长)不会生效,
而且一定看得见:设置页点保存时就地拦下并说明规则,cordis.patch.yml 里手写的非法值会在
状态文件的诊断里留一条 「访问密码」没生效…。这条提示是故意做的 —— 静默失效会让人以为密码设好了,
然后在手机上撞见 DSH 那句英文 401(这曾经是最难查的一种「坏了」)。
手机上的同一页也能改:DSH 的客户端策略让 configForms 在非回环页面不可写,但 Remote 通道本身是通的,
所以那一页直接调 ctx.remote.settings.describe() / mutate(),写盘跑在 Host 网关自己的上下文里,
落盘位置与桌面完全相同,而且等写盘完成才回执。
其余参数只在 cordis.patch.yml 里配(也不建议常用):
| 键 | 默认 | 说明 | 设置页 |
|---|---|---|---|
enabled | true | 关掉它只需设 false,不必卸载 | ✅ |
allowCidrs | ['100.64.0.0/10'] | 允许来访的网段;也是 listen: auto 挑选本机监听地址的依据 | ✅ |
denyCidrs | [] | 白名单里的例外黑名单;输错访问密码的地址会自动加到这里,删掉该项即解封 | ✅ |
port | 0 | 监听端口,0 = 系统随机 | ✅ |
maxConnections | 64 | 并发连接上限(浏览器会开好几条 keep-alive,别设太小) | ✅ |
logLevel | 'info' | silent / info / debug | ✅ |
accessCode | '' | 解锁密码(4–12 位数字或字母)。设了就不用复制 token:手机开裸地址输一次即可;输错一次即把该地址写进「排除的网段」 | ✅ |
remoteLayout | 'auto' | 远程页面的界面布局:auto(按视口/指针自动判断)/ phone(强制手机布局,横竖屏自适应)/ desktop(与桌面一致)。只作用于远程页面 | ✅ |
listen | ['auto'] | auto = 只监听上面网段里的本机地址;也可写 ['100.x.y.z'] | — |
upstream | 'auto' | DSH 界面地址;auto 读 ctx.webServer.port | — |
rewriteHost | true | 把 Host/Origin 改写成上游 authority | — |
forwardClientHeaders | true | 转发 x-forwarded-for / x-forwarded-proto | — |
timeoutMs | 0 | 等上游响应头的超时(毫秒),0 = 不超时。响应头一到就交棒,正文/流式传输不会被它掐断 | — |
allowWebSocket | true | 透传 WebSocket(界面实时推送靠它) | — |
urlFile | '' → <DSH_HOME>/remote-access-url.txt | 状态文件写哪;off = 不写 | — |
printUrl | true | 同时把带票网址打到 DSH 日志 | — |
banFile | '' → 与状态文件同目录的 remote-access-bans.txt | 拉黑暂存文件:只有「写进排除的网段」失败时才用得上,平时不用碰 | — |
改密码不会把已经进来的设备踢下线:放行 cookie 的密钥与 accessCode 无关,单独存在
<DSH_HOME>\remote-access-secret(0600),DSH 重启后也照样有效。想让所有设备重新输一次密码,
删掉那个文件即可。
手机上的「新建工作区」选目录
DSH 默认用 directory-picker-auto:它判定「回环绑定 + 非 SSH + 有显示会话」时会挑原生后端,
而原生后端是在宿主屏幕上弹系统对话框 —— 手机那边点下去只会一直等(表现就是「打不开文件管理器」)。
本插件的 bundle patch 因此把选目录器固定成应用内浏览:关掉 auto,改挂
dsh-host-directory-picker-browse(列目录 / 建目录的后端)+ dsh-client-ui-directory-picker-browse
(「选择工作区目录」对话框)。手机上点新建工作区就会弹出这个应用内对话框,桌面上也一样。
- 桌面端代价:系统原生对话框换成应用内对话框(功能等价)。
- 安全说明:已授权的远程端因此可以列目录、建目录。它本来就能通过 DSH 跑命令,所以这不是新的权限等级;
不想让远程端看到文件系统,就把
cordis.patch.yml里disabled那段和两行-browse删掉 (代价:手机端不再有选目录能力)。
为什么只看到「主目录」,怎么去别的盘
对话框的面包屑在主目录以内会折叠:起点显示成「主目录」,C:\、Users 这些上层被有意隐掉,
所以「找不到上一级」是设计而不是坏了。跳出去很容易:
点面包屑右端的铅笔按钮(「编辑路径」),它会变成输入框并预填当前路径,改成你要的路径回车即可:
| 输入 | 效果 |
|---|---|
C:\ | C 盘顶层,所有顶层文件夹都出来了 |
D:\projects | 直接跳到目标目录 |
\\server\share | UNC 路径 |
宿主只接受完全限定路径(Windows 上必须是 C:\… 或完整的 \\server\share…;\foo、相对路径会被拒绝)。
跳到主目录以外之后,面包屑就会显示真实完整路径。目标文件夹不存在时,先用对话框里的「新建文件夹」,
或直接输入一个已存在的目录再点「打开」。
手机布局(远程UI布局)
DSH 的界面是为桌面三栏设计的。在 390px 宽的手机上,它的表现是:
- 侧栏一旦展开就吃掉 280px,正文只剩 110px;
- 设置页的左右两栏里,右侧只剩 101px —— 中文会一个字一行竖排下来;
- 输入区工具条把模型名截成
DeepSeek-V4…。
「远程UI布局」就是针对这些做的,选 手机 或 自动 后,只有远程页面会变成:
| 位置 | 手机布局 |
|---|---|
| 侧栏 | 收起时保持 56px 图标栏;展开时改成抽屉浮在正文上(min(86vw, 320px)),正文保持满宽,点遮罩即收起 |
| 设置页 | 改成上下两段:导航变成顶部横向滚动的胶囊条,内容占满整宽并自行滚动 |
| 输入区 | 工具条允许换行、左右内边距收窄,模型名不再被截断 |
| 触控 | 设置项在手机上单列排布、输入框 16px(iOS 聚焦不放大整页)、按钮 ≥40px |
| 安全区 | 刘海/圆角/手势条按 env(safe-area-inset-*) 留白;横屏高度紧张时压薄设置页导航 |
三条实现上的约定:
- 本机不动。判据是
ctx.remote.$host.isLoopback(页面生命周期内固定),不是窗口宽度 —— 本机窗口拖到 390px 也不会变成手机布局。所有样式都挂在html[data-ra-layout="phone"]之下, 本机页面拿不到这个属性,也就永远匹配不上任何一条规则。 - 不改别人的渲染树。只加
data-ra-*标记和一张<style>,靠 CSS 覆盖; 唯一挂在框架之外的是抽屉遮罩(<body>下、有固定 id)。定位用的是data-slot这类稳定契约, 再用[class*="_xxx"]兜住 DSH 会变的 CSS-module 哈希前缀。 - 改完立刻生效。这一项走 Remote 通道热更,手机上改完不用刷新(
settings/document-updated会重算)。
已知限制:手机宽度下 DSH 本身不给右侧栏分配轨道(available < 300 时右栏宽度算成 0),
所以文件/终端那类右侧面板在手机上仍然打不开 —— 这属于 DSH 布局自身的策略,
本插件只改样式、不动别人的布局计算,因此没有去绕它。
动态壁纸为什么常常是静态的
装了「壁纸引擎」这类插件时,手机端经常只看到一张静态首帧。根因在壁纸插件那边,不在代理:
它把动态载荷(scene.pkg、网页壁纸入口)的地址写死成自己另起的那个媒体服务
http://127.0.0.1:<随机端口>(只绑回环)。手机打开这个绝对地址,访问的是手机自己的 127.0.0.1,
必然连不上 → 渲染页等不到载荷 → 首帧预算(15s 起、封顶 90s)到期 → 判定实时渲染失败,
并把这次失败写进宿主共享的设置文件,于是手机和桌面一起退回静态帧,直到手动重开「场景实时渲染」。
静态预览图走的是相对路径、经代理可达,所以「有图但不动」正是这个组合的表现。
本插件这边确实有一个放大器,已修:默认 timeoutMs=0 时没有显式声明超时,
于是继承了 Node ≥19 默认 agent 的 5 秒空闲超时 —— 上游 5 秒没吐字节就把正在传输的响应掐断,
而且日志里打印的 cfg.timeoutMs 还是 0,看起来像「没设超时却超时了」。现在:总是显式声明超时,
且 timeoutMs 只作用于「等响应头」,头一到就交棒;上游半路断掉时下游会被一起收掉,不会让手机一直挂着。
彻底治好动态壁纸需要壁纸插件改用相对路径(或让它感知反代)。在它能改之前,两个可用做法: 把壁纸插件的「适配」目标设成浏览器(回到相对路径),或者接受手机端只有静态壁纸。
安全边界
- 只监听
allowCidrs里的本机地址,永远不会绑0.0.0.0。 - 每个连进来的对端地址也必须在
allowCidrs内,否则403;被拉黑(在denyCidrs/ 拉黑暂存里)的一律403。 - 没设
accessCode时:DSH 自身的令牌 / cookie 仍然生效(没票401),本插件不降低 DSH 的任何鉴权。 - 设了
accessCode就别再把它当「双因子」:那时「网段 + 数字密码」就是全部凭据 —— 解锁成功后由本插件 在服务端替请求补上 DSH 的令牌(浏览器始终看不到 token),token 在这条路径上不再是第二道门。 想回到「必须带票」的严格模式,把accessCode清空即可。 - 一次密码错误立即拉黑该地址(写进「排除的网段」
denyCidrs;万一写盘失败,它仍会被立刻拦住、 暂存到banFile并在状态文件里写明),这是故意的:可暴力猜的数字密码需要一道硬刹车。 - 建议再在 Tailscale ACL 里限定设备;
tailscale funnel(公网入口)不要开。 - 本插件把来自网段的请求改写
Host/Origin后转给回环上的 DSH,因此它就是这个边界的守门人:allowCidrs写宽了,等于把本机命令执行权限放开。
常见问题
显示 dsh web authentication required(DSH 自己那句英文) — 说明这次访问用的是已经作废的票:
每次启动 DSH 都会换一张 token,别的账户/上一次运行留下的那条 ?token=… 网址都不能再用了。
换个做法:设了 accessCode 就直接打开裸地址 http://<地址>:<端口>/ 输一次密码;
没设就复制状态文件里当前那条带票网址。
如果你之前明明能进、现在只看到这句:那是浏览器里那张 DSH cookie 失效了。 从 b0ec52a 起,插件遇到这种 401 会自动带当前票重试一次,DSH 顺手发一张新 cookie,你不用再手动清。
从当前版本起,这句英文不会再出现在「页面导航」上了。 地址栏打不开时看到的是插件自己的中文页,
它按情况说清两种可能:票作废了(每次启动 DSH 都会换一张)还是根本没带票/没带密码,
并告诉你下一步去哪儿拿当前那条网址(本机「设置 → 远程访问」或状态文件)。
两个刻意的设计:页面绝不回显当前 token(它发给的正是尚未通过校验的对端),
而且只接管导航请求 —— RPC/子资源的 401 仍原样透传,机器可读的错误语义不变。
设了 accessCode 但凭据过期时,回的是解锁页(重新输一次密码即可),也不是那句英文。
输错密码被挡在外面 — 该 IP 已被拉黑。到「设置 → 远程访问 → 排除的网段」里删掉对应那一项
(形如 100.x.y.z/32)即可。如果状态文件里「待补写」不为空,说明当时写盘失败、它还没进配置,
那就删掉 <DSH_HOME>\remote-access-bans.txt 里的那一行。
前几天还好,现在又要重新验证 — 换过端口或重启过 DSH。DSH 的 cookie 绑定 hostname:port,所以旧的不通用;
按「使用」里的步骤重来一次。
设置页点了保存没反应 / 报失败 — 跑 node tools/check-config-schema.mjs,它会检查写盘的两个硬前提
(Config 是原生 Schemastery schema 且字段都标了 .volatile();package.json 把
@deepseek-ai/schemastery 声明成了 peerDependency)。改完 package.json 要重启一次 DSH 才生效。
手机在选目录里只能看到主目录 — 见上面「为什么只看到主目录」。
手机上界面挤成一条、中文一个字一行 — 那是「远程UI布局」还留在 电脑(或 auto 没判定成手机)。
改成 手机 或 自动 即可。注意这一项是存服务端配置的:在手机上改完立刻生效,不用重装也不用刷新。
视频/大文件/动态壁纸传到一半卡住 — 老版本会在「上游 5 秒没吐字节」时掐断响应
(Node ≥19 的默认 agent 自带 5 秒空闲超时,而 timeoutMs=0 恰好没覆盖它)。已修:
现在总是显式声明超时,且 timeoutMs 只管「等响应头」,头一到就交棒。
若壁纸仍然只有静态画面,那是壁纸插件自己把载荷地址写死成 http://127.0.0.1:<端口> 造成的
(手机打开这个地址等于访问手机自己的回环),见下面「动态壁纸为什么常常是静态的」。
开发与测试
node --check index.js # 语法
node tools/test-remote-access.mjs # Host 功能测试(起一个假上游,不需要 DSH)
node tools/test-client.mjs # 客户端冒烟测试(极简 React / 宿主桩子真跑一遍设置页,不需要浏览器)
node tools/check-config-schema.mjs # 用安装包里的真 Schemastery 校验 Config(找不到安装包则 SKIP)
test-remote-access.mjs 覆盖转发、Host/Origin 改写、x-forwarded-for、WebSocket 101 透传、
网段外 403、解锁页与「错一次自动写进排除的网段」(含写盘失败退回暂存)、改密码不踢人、热重载换端口,
以及本轮补上的:访问密码规则(字母数字、非法值必须可见)、没有凭据时的中文 401 页
(且绝不回显 token、非导航请求仍原样透传)、timeoutMs=0 不再继承 agent 的 5 秒空闲超时、
timeoutMs 只作用于等响应头、上游半路断掉时下游不悬挂。
那条 5 秒超时的用例故意要跑 5.3 秒:只有真的等过 5 秒才能证明没继承那个默认值 (Node 18 上它恒过,因为那个默认值不存在)。
test-client.mjs 覆盖设置页注册与渲染、回环与手机两条读写通道、保存与失败提示
(Remote 通道返回的是 { ok, value } 信封,这是最容易踩的一处),以及手机布局最关键的那条不变式:
回环页面即使窗口只有 390px、配置又写着 phone,也不设 data-ra-layout、不注入任何样式;
远程页面则按 phone/desktop/auto(窄视口、触摸设备)分别给出正确判定。
check-config-schema.mjs 覆盖设置页写盘的两道硬前提,并顺带对表 client.js 的表单字段与 Config。
改手机布局时必须真机看一眼:这里用的调试回路是「无头浏览器 + CDP 仿真视口」
(Emulation.setDeviceMetricsOverride 到 390×844),因为 DSH 的布局计算读的是 window.innerWidth,
光把容器改窄看不到真实效果。
目录
| 路径 | 说明 |
|---|---|
index.js | 插件 Host 半区(零依赖,只用 node: 内置模块):反向代理、访问门、401 说明页、热重载 |
client.js | 浏览器半区:设置页参数表单 + 只作用于远程页面的手机布局引擎 |
cordis.patch.yml | bundle 的 patch:插入插件行、固定选目录器 |
package.json | 清单(dsh.bundle.patch / exports / icon / meta) |
locale/{zh,en}.json | 插件页显示用的标题与说明 |
icon.svg | 插件页图标 |
tools/ | 测试(Host / 客户端 / Config 校验)与从 DSH 安装包里取文件的工具 |
install.ps1 | GitHub 一键取代码(放到本机固定目录,安装仍由 DSH 自己做) |
LICENSE | MIT |
许可
MIT © 2026 Mzy123l
Comments
Loading…
Similar plugins
by zptalk0221-cpu
远程桌面移动化插件:为 DeepSeek Harness 提供手机横屏外壳与中文输入法
★ 0
MIT
JavaScript
Aug 29, 2026
dsh plugin --profile web add dsh-remote-desktopby hutao562
DeepSeek Harness plugin: a sidebar tab that takes over the whole page with another DSH instance's Web GUI, plus a remote session-state badge. 在侧边栏顶部加一个「远程」标签,整页切到另一台主机上的 DSH。
★ 0
↓ 289/wk
MIT
JavaScript
Sep 11, 2026
dsh plugin --profile web add dsh-remote-dshby saya-ch
DeepSeek Harness 的 Android App 与安全远程访问插件,支持局域网/远程连接和高度自定义的移动界面与扩展能力。
★ 355
↓ 3.7k/wk
Apache-2.0
TypeScript
Oct 2, 2026
dsh plugin --profile web add dsh-mobileby GDWhisper
DSH(DeepSeek Harness)远程 Web 启动 + 用户名/密码认证插件。 | DeepSeek Harness Remote‑Web‑Launch Plugin with Username/Password Auth
★ 50
↓ 502/wk
MIT
TypeScript
Oct 2, 2026
dsh plugin --profile web add dsh-web-startup-authby JUANWANG-BUAA
Auditable, token-gated DeepSeek Harness remote gateway: mobile QR access, per-device sessions, Host/Origin rewrite, settings/credentials/directory support.
★ 45
MIT
TypeScript
Sep 26, 2026
dsh plugin --profile web add dsh-full-remoteby summer1238
手机平板远程 DeepSeek Harness:扫码即可继续使用电脑上的 DSH,无需远程桌面 / SSH / 公网 IP,支持一次性配对、Github授权加密登录,独立设备授权与随时撤销,实现远程连接很简单,但安全才是我们所想要的。
★ 156
↓ 132/wk
MIT
TypeScript
Aug 28, 2026
dsh plugin --profile agent add dsh-remote-web-gateway