结论先行:DeepSeek Harness 0.1.0-rc.8 下,远程访问(dsh-full-remote)和手机端组合插件中的 dsh-host-history-lite、dsh-client-ui-voice-input 都能稳定安装;但手机端组合里的 dsh-ui-mobile 与 rc.8 不兼容,装上会卡死页面,正确做法是禁用而不是强上。下面把完整过程、七个踩坑和正确解法写清楚。
一、环境与目标
| 项 | 值 |
|---|---|
| DeepSeek Harness | 0.1.0-rc.8(dsh --version 确认) |
| web profile | ~/.dsh/profiles/web/ |
| 运行方式 | systemd 服务 dsh-web.service,监听 127.0.0.1:3080 |
| 包管理 | pnpm(nodeLinker: hoisted) |
目标:装上四个插件,让手机/远程能访问 Harness,并在手机上获得更好的体验:
dsh-full-remote^0.3.7 —— 远程访问(令牌门 + 每设备独立会话 + 可选审批)dsh-ui-mobile^0.1.8 —— 手机响应式布局 / 抽屉 / PWAdsh-host-history-lite^0.1.1 —— 长会话历史回放优化(纯宿主端,无 client bundle)dsh-client-ui-voice-inputv0.1.0 —— 语音输入 + 提示词优化(源码来自 GitHubzjzqs/dsh-client-ui-voice-input)
二、正确安装步骤(最终可用版本)
1. 安装依赖
dsh-client-ui-voice-input 没有发布到 npm registry,用源码克隆 + file: 依赖注册:
cd ~/.dsh/profiles/web
git clone https://github.com/zjzqs/dsh-client-ui-voice-input.git voice-input-src
# package.json 依赖里加:
# "dsh-client-ui-voice-input": "file:./voice-input-src"
# 其他三个直接进 dependencies 即可:
# "dsh-full-remote": "^0.3.7",
# "dsh-host-history-lite": "^0.1.1",
# "dsh-ui-mobile": "^0.1.8"
pnpm install
2. 配置 patch 层(cordis.patch.yml)
profile 的插件组合由 cordis.patch.yml 组装,先配 dsh-full-remote(本机反向代理 + 新设备需审批),再插入 voice-input:
# dsh-full-remote:本机反向代理,新设备登录需本机批准
- id: reverse-proxy
name: dsh-full-remote
config:
listenHost: 127.0.0.1
listenPort: 3081
approvalMode: true
# voice-input 挂进 composer 输入区
- insert:
- id: voice-input
name: dsh-client-ui-voice-input
⚠️ 关键:不要在这里启用
dsh-ui-mobile。见坑一。
3. 启动 & 验证
sudo systemctl restart dsh-web.service
# 验证:页面能加载出 composer 输入框(而非卡在 "Loading plugins…")
# 验证:控制面可用(见坑四)
curl -H "x-dsh-reverse-proxy-control: 1" \
-H "Origin: http://127.0.0.1:3080" \
http://127.0.0.1:3080/dsh-reverse-proxy/status
三、踩坑记录(重点)
坑一:dsh-ui-mobile 装上后页面卡死 “Loading plugins…”
症状:http://127.0.0.1:3080/ 打开后永远停在 “Loading plugins…”,composer 不出来,curl 却返回 200。
排查:curl 200 只能证明服务活着,页面是否真的加载完必须用真实浏览器引擎验证。我用 headless Chrome(playwright-core + 系统自带 /usr/bin/google-chrome)逐秒采样页面文本,确认 “Loading plugins…” 确实不消失。
然后做插件隔离:把三个新插件一个个禁用,用临时 patch 覆盖 + 不同端口起测试实例,验证到底是谁卡住的:
# 临时禁用 dsh-ui-mobile 的 patch 文件
cat > /tmp/patch-nouimobile.yml <<'EOF'
- id: dsh-ui-mobile
disabled: true
EOF
# 用覆盖方式起一个测试实例(不改任何持久配置)
dsh --profile web --patch /tmp/patch-nouimobile.yml --host 127.0.0.1 --port 3084
结果:禁用 dsh-ui-mobile 后页面秒开,另外两个插件不影响加载。
根因:dsh-ui-mobile v0.1.8 的 client 初始化在 rc.8 上直接挂起;而且它的所有版本(0.1.0 ~ 0.1.8)都 peer 依赖 dsh-client-ui-primitives / dsh-client-ui-slots,而 rc.8 把这两个包内联进 main.js,并不提供独立包——装到 node_modules 也没用(/plugins/<pkg>/client.js 路由只提供 CLI 打包的 bundle,不提供任意 node_modules 包)。
正确做法:在 cordis.patch.yml 显式禁用,不要强上:
- id: dsh-ui-mobile
disabled: true
教训:插件报错时,先确认兼容性(peer 依赖是否被宿主内联),别用”把依赖装进 node_modules”这种绕法——绕不过去,还容易污染 profile。修完记得 dsh --profile web --dump-config 确认最终生效状态。
坑二:dsh web --patch 报 “web takes none of parent —profile, —patch…”
症状:想给测试实例加 patch 覆盖,dsh web --patch x.yml --host ... --port ... 直接报错。
原因:web 子命令不接受父级的 --profile / --patch flag。
正确写法:flag 放在子命令之后作为尾部参数:
dsh --profile web --patch /tmp/x.yml --host 127.0.0.1 --port 3084
坑三:headless 探测找不到 voice-input 的 🎤 / ✨ 按钮
症状:写脚本遍历 button.textContent 找 🎤 / ✨ / 语音,一个都找不到,以为插件没生效。
原因:voice-input 的按钮是 SVG 图标(无 emoji 文本),只有 className 和 aria-label。
正确做法:按 class 或 aria-label 探测:
document.querySelectorAll('.dsh-vi-button') // 2 个:✨ 优化 + 🎤 语音
Array.from(document.querySelectorAll('.dsh-vi-button'))
.map(b => b.getAttribute('aria-label')) // ["Optimize prompt", "Voice input"]
坑四:reverse-proxy 控制面到底在哪?
症状:想直接 curl 反向代理的会话/审批接口,试 http://127.0.0.1:3081/dsh-reverse-proxy/sessions 返回 forbidden。
原因:控制面不在反向代理监听端口(3081)上,而是挂在 **DeepSeek Harness 宿主 Web 服务(3080)**上:http://127.0.0.1:3080/dsh-reverse-proxy/...。且需要两个条件才放行:
- 请求头
x-dsh-reverse-proxy-control: 1 Origin是 loopback(如http://127.0.0.1:3080)
curl -s -H "x-dsh-reverse-proxy-control: 1" \
-H "Origin: http://127.0.0.1:3080" \
http://127.0.0.1:3080/dsh-reverse-proxy/sessions
附带知识点:访问令牌存在 ~/.dsh/reverse-proxy.json(权限 600),默认 allowTokenRead: false 不通过 HTTP 暴露,只能本机读文件或在 Harness 控制台看。审计事件(登录/审批/轮换/令牌读取)追加在 ~/.dsh/reverse-proxy.audit.jsonl。
坑五:approvalMode 审批在哪个”审批页面”?
症状:手机用令牌登录,一直停在 “Waiting for approval…”(等待页每 2 秒轮询自己的状态),却在本机找不到审批入口。
原因:没有独立的审批页面。审批入口在 DSH 宿主界面里:设置(Settings)→ Reverse proxy → Connected devices(已连接设备)。新设备会以 Pending approval 状态列在已批准设备的下方,旁边是 Approve / Reject 按钮。而且只能在这台宿主机上操作(错误文案:只能从本机操作控制面板。请回到这台电脑上的 Harness 窗口再试。),手机端无法自我审批。
正确做法:
- 在宿主机浏览器打开
http://127.0.0.1:3080 - 设置 → Reverse proxy → 找到设备(如 “Chrome on Android | Pending approval”)
- 点 Approve——设备端等待页轮询到状态变化后自动进入
排查辅助:审批前先确认设备确实是 pending:
# 控制面 API 确认
curl -s -H "x-dsh-reverse-proxy-control: 1" -H "Origin: http://127.0.0.1:3080" \
http://127.0.0.1:3080/dsh-reverse-proxy/sessions
# → {"sessions":[{"label":"Chrome on Android","status":"pending",...}]}
# 直接批准(等效于点界面 Approve)
curl -s -X POST -H "x-dsh-reverse-proxy-control: 1" -H "Origin: http://127.0.0.1:3080" \
-H "Content-Type: application/json" \
-d '{"id":"<sessionId>"}' \
http://127.0.0.1:3080/dsh-reverse-proxy/sessions/approve
一个小陷阱:控制面的设备列表在界面上要往下滚动一点才看得到 pending 设备(按最近活动排序,pending 通常排后面),别以为列表里只有已批准设备就没问题。
坑六:voice-input 的 ✨ 优化提示词返回 502
症状:点 ✨ 按钮,接口 POST /dsh-voice-input/optimize 返回 502 model call finished with error。
原因:✨ 优化依赖 agent 默认模型的 LLM 调用,Harness 未配置模型时必然 502。🎤 语音输入不受影响——它走浏览器 Web Speech API,无需任何后端配置。
正确做法:🎤 可直接用;✨ 需要在 Harness 配置好可用的 LLM 后才会生效。别把”优化挂了”当成”插件坏了”。
坑七:systemd 环境下看不到审批/控制台提示
症状:插件文档说”审批提示显示在本地 Harness 窗口”,但在 systemd 服务下跑,根本没有交互终端,登录请求来了却什么提示都没看到。
原因:服务 stdout 进了 journal,不会弹到任何”窗口”。
正确做法:
- 需要看提示:
sudo journalctl -u dsh-web.service翻日志 - 需要操作(审批/看令牌/改监听):走 Web 控制面板(
Settings → Reverse proxy),别等终端 - 排查时保持 systemd 启动方式不变,只重启 DSH 服务,不要重启 Nginx / 数据库等无关服务
四、调试工具箱(遇到问题先查这些)
| 工具 | 用途 |
|---|---|
playwright-core + 系统 Chrome headless | 验证页面是否真正加载完(curl 200 ≠ 页面可用) |
| patch 覆盖 + 不同端口起测试实例 | 插件隔离,定位”是谁卡死页面” |
dsh --profile web --dump-config | 看最终生效配置(含 disabled 状态) |
控制面 API(x-dsh-reverse-proxy-control: 1 + loopback Origin) | 查会话 / 审批 / 轮换令牌 |
~/.dsh/reverse-proxy.audit.jsonl | 审计日志:登录、审批、令牌读取时间线 |
~/.dsh/reverse-proxy.json | 令牌与设备会话的持久状态(权限 600) |
五、结语与可复现清单
- 先查兼容性再强上:插件 client 的 peer 依赖若被宿主内联,装 node_modules 无效,该禁用就禁用。
- curl 200 ≠ 页面可用:加载问题要用真实浏览器引擎验证。
- 隔离永远安全:临时 patch + 新端口测实例,不动持久配置,出问题可回滚。
- 控制面在宿主 Web,不在代理端口:
x-dsh-reverse-proxy-control: 1+ loopback Origin。 - 审批只有宿主机能做:Settings → Reverse proxy → Connected devices。
- 保持启动方式不变:只重启 DSH,不动其他服务;改任何配置前留备份。
最终可用的插件组合是 dsh-full-remote + dsh-host-history-lite + dsh-client-ui-voice-input,dsh-ui-mobile 等待 DSH 升级到兼容版本后再考虑重新启用。