dsh-remote-kit
v1.0.0
Published
Remote-access kit for a DSH (DeepSeek Harness) web instance running in a headless container: mobile UI polish, old-browser polyfills, loopback-equivalent trust for public entries, and headless directory picking. One `dsh plugin add` registers all four.
Maintainers
Readme
dsh-remote-kit
给无桌面容器里跑的 DSH(DeepSeek Harness)Web 实例用的一套远程访问插件。一个包、四个插件、一条安装命令。
dsh plugin --profile web add dsh-remote-kit装完自动并入 profile 的插件层(dsh.profile.bundles),重启 DSH 即全部生效。不改任何官方文件,DSH 升级后不丢。
它解决四个具体问题
| # | 症状 | 插件 | 根因 |
|---|---|---|---|
| 1 | 老浏览器打开 DSH 报 Iterator is not defined,界面显示 "Failed to load plugins" | dsh-remote-kit/browser-compat | 官方 documentpreview 直接取 Iterator.prototype(ES2025,Chrome 122+),老浏览器上抛 ReferenceError 导致整个插件模块导入失败。垫片补齐 35 项新 API |
| 2 | 用公网 IP / 局域网 IP 访问时,设置页报 settings are unavailable in this browser | dsh-remote-kit/loopback-trust | 客户端 isLoopback 只看 location.hostname,不认 --trusted-host → 设置持久化降级为 memory,一个请求都不发。本插件走官方预留的 __DSH_TRANSPORT__.ownsHost 修正判据 |
| 3 | 无桌面容器里工作区创建不了,点「添加工作区…」没反应 | dsh-remote-kit/headless-picker | 容器 entrypoint 导出了 DISPLAY=:0 指向不存在的 X 服务器 → 官方选了 native 对话框后端 → spawn zenity 失败(exit 1)→ 被当成「用户取消」→ 前端静默无反应。本插件做能力探测,不可达就摘掉该变量 |
| 4 | 手机上用 DSH 界面难用(头部重叠、按钮错位、设置页层级混乱) | dsh-remote-kit(包根) | 官方 UI 按桌面设计。本插件是移动端深度适配,所有 CSS 挂在 [data-mobile] 下,桌面端逐字节不受影响 |
包结构(为什么长这样)
dsh-remote-kit
├── package.json dsh.client + dsh.bundle.patch
├── cordis.patch.yml 注册全部四个插件
└── lib/
├── index.js ← 包根 = mobile-polish 宿主半
├── client.js ← 包根 = mobile-polish client 半
├── browser-compat.js
├── polyfill.js
├── loopback-trust.js
├── snippet.js
└── headless-picker.js为什么 mobile-polish 必须占包根? 两个硬约束,都有源码出处:
dsh-client-modules的exactPackageSpecifier()只接受不含/的 specifier。含/的(如dsh-remote-kit/mobile-polish)会被判为「不是 client 包」→ client 半静默不注册(不报错,最难查)。- client bundle 里的
id必须等于包名 —— 入口图的每一行按包名校验factories.has(id),对不上就报bundle loaded without registering "..."。所以lib/client.js的id与data-plugin-css已同步为dsh-remote-kit。
其余三个是纯宿主半,走子路径导出 —— 这是官方惯用法,官方自己的补丁层里就有 @deepseek-ai/dsh-agent/invariant 等 9 处同样写法。
⚠️ 纯宿主半插件绝不能声明
dsh.client:声明了就必须提供./client导出,否则 DSH 直接起不来(报declares dsh.client but exports no "./client" bundle,且日志可能是 0 字节)。
给维护者的提醒(本包是从已有插件改名的,别踩)
本包由四个独立插件合并改名而来。改动前请先读这三条,否则会出现「不报错但不生效」:
lib/client.js里的id与data-plugin-css必须等于包名(现为dsh-remote-kit,共 4 处字面量)。 改包名时必须同步改这 4 处 —— 入口图每行按包名校验factories.has(id),对不上会报bundle ... loaded without registering "..."。lib/client.js里MP_PLUGIN_DESCS描述表的键要跟cordis.patch.yml的name:对齐 —— 插件列表卡片的title就是这个name,查不到就没有中文描述(不报错)。 本包已写全 4 个键:dsh-remote-kit、dsh-remote-kit/browser-compat、dsh-remote-kit/loopback-trust、dsh-remote-kit/headless-picker。- 宿主半
lib/index.js里export const name只是「显示名」(cordisFiber#name, 仅用于日志/诊断),改不改都不影响功能 —— 官方自己的子路径插件也各不相同 (如@deepseek-ai/dsh-agent/invariant导出"agent-invariant")。
安装
前置:DSH 侧的信任栅栏
用非回环地址访问时,必须给 DSH 加 --trusted-host,否则界面能加载但 /api/* 全 403(不是 404):
dsh web --port 3080 --no-open --trusted-host <公网IP>:<端口>判据:
curl -sS http://<入口>/→ 200,而/api/boot→ 404(栅栏已过)。 若是 403,就是缺--trusted-host。
前置:pnpm
dsh plugin 是 pnpm 的薄转发层,机器上必须有 pnpm。没有就装:
corepack enable pnpm # 或 npm i -g pnpm安装
dsh plugin --profile web add dsh-remote-kit命令做的事:在 profile 目录跑 pnpm add,然后按已安装状态重整 dsh.profile.bundles —— 凡是声明了 dsh.bundle 的依赖会被自动追加进插件层列表。所以不用手改任何 yml。
装完重启 DSH。
验证
装完后逐条对判据(都在浏览器 console 里跑):
// ① mobile-polish —— data-mobile 只有本插件会设置
document.documentElement.hasAttribute('data-mobile')
// 手机视口 → true 桌面视口 → false(桌面必须 false,这是设计红线)
// ② browser-compat —— 垫片已生效
window.__dshBrowserCompatApplied
// 现代浏览器 → ['Iterator helpers'] 之类的一两项;老浏览器 → 一长串
// ③ loopback-trust —— 仅非回环入口才干预
window.__DSH_TRANSPORT__.ownsHost
// 公网/局域网入口 → true 真回环 127.0.0.1 → undefined(正确,不干预)服务端判据(headless-picker):
curl -sS http://127.0.0.1:3080/ | grep -oE "directory-picker-[a-z]+"
# 无桌面容器 → browse(正确) 有桌面 → native(正确)四个插件是否都注册了:
dsh --profile web --dump-config | grep -A5 'dsh-remote-kit'设计红线(这些行为是刻意的,不是 bug)
| 插件 | 红线 |
|---|---|
| browser-compat | 全部 typeof 能力探测 → 现代浏览器上是 no-op,零行为改变。垫片全程 try/catch,读不到文件只记日志,失败绝不拖垮页面 |
| loopback-trust | 真回环页面一字不改(本机行为 100% 不变);不覆盖 __DSH_TRANSPORT__ 既有字段;幂等 |
| headless-picker | 真有显示服务器时一字不改;DISPLAY=host:0 这类无法本地验证的一律放行;X11 与 Wayland 各判各的 |
| mobile-polish | 全部 CSS 作用域在 [data-mobile] 下,且 data-mobile 要求「窄屏 且 非 hover+fine 指针」→ 桌面浏览器无论窗口多窄都保持官方 UI |
安全提示(请务必读)
loopback-trust 会让非回环页面获得与回环页面相同的设置读写能力。DSH 原本把非回环页面降级是有意的保守设计,本插件是在明确知情下放开。
仅在入口本身已被信任时使用 —— 即服务端已 --trusted-host,且该入口有别的门卫(认证/内网/隧道)。若入口是公网裸奔的,本插件不会引入新的漏洞面(能访问的人本来就能控制 DSH),但请清楚这一点。
常见问题
装完没生效?
- 重启 DSH(改
package.json/cordis.patch.yml必须重启;改lib/*.js刷新页面即可)。 dsh --profile web --dump-config | grep dsh-remote-kit确认四个条目都在。- 看 DSH 启动日志有没有
declares no dsh.bundle警告 —— 有就说明包版本太旧。
client 半没注册(首页 boot 清单里没有 dsh-remote-kit/client.js)?
包名里带了 / 就会这样(见上文「为什么包根必须是 mobile-polish」)。必须是包根。
dsh plugin add 报 pnpm not found on PATH?
装 pnpm(见前置)。
/api/* 返回 403?
缺 --trusted-host(见前置)。
dsh plugin add ./本地目录 报 Package name ... is invalid, it should have a @scope?
这是 pnpm 12.1.0 对本地路径的已知问题。从 npm 安装不受影响。本地调试可手写 "dsh-remote-kit": "link:/abs/path" 到 profile 的 package.json,再跑 dsh plugin --profile web install。
卸载
dsh plugin --profile web remove dsh-remote-kitdsh.profile.bundles 里的条目会被自动清掉(reconcile 的反向逻辑)。
License
MIT
