跳到正文
SL Blog 技术探索 · 工程实践 · AI 时代思考
返回

DeepSeek Harness 远程访问 + 手机端插件安装实战:七个踩坑与正确解法

文章目录
  1. 一、环境与目标
  2. 二、正确安装步骤(最终可用版本)
  3. 1. 安装依赖
  4. 2. 配置 patch 层(cordis.patch.yml)
  5. 3. 启动 & 验证
  6. 三、踩坑记录(重点)
  7. 坑一:dsh-ui-mobile 装上后页面卡死 “Loading plugins…”
  8. 坑二:dsh web --patch 报 “web takes none of parent —profile, —patch…”
  9. 坑三:headless 探测找不到 voice-input 的 🎤 / ✨ 按钮
  10. 坑四:reverse-proxy 控制面到底在哪?
  11. 坑五:approvalMode 审批在哪个”审批页面”?
  12. 坑六:voice-input 的 ✨ 优化提示词返回 502
  13. 坑七:systemd 环境下看不到审批/控制台提示
  14. 四、调试工具箱(遇到问题先查这些)
  15. 五、结语与可复现清单

结论先行: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 Harness0.1.0-rc.8(dsh --version 确认)
web profile~/.dsh/profiles/web/
运行方式systemd 服务 dsh-web.service,监听 127.0.0.1:3080
包管理pnpm(nodeLinker: hoisted)

目标:装上四个插件,让手机/远程能访问 Harness,并在手机上获得更好的体验:

  1. dsh-full-remote ^0.3.7 —— 远程访问(令牌门 + 每设备独立会话 + 可选审批)
  2. dsh-ui-mobile ^0.1.8 —— 手机响应式布局 / 抽屉 / PWA
  3. dsh-host-history-lite ^0.1.1 —— 长会话历史回放优化(纯宿主端,无 client bundle)
  4. dsh-client-ui-voice-input v0.1.0 —— 语音输入 + 提示词优化(源码来自 GitHub zjzqs/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/...。且需要两个条件才放行:

  1. 请求头 x-dsh-reverse-proxy-control: 1
  2. 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 窗口再试。),手机端无法自我审批。

正确做法:

  1. 在宿主机浏览器打开 http://127.0.0.1:3080
  2. 设置 → Reverse proxy → 找到设备(如 “Chrome on Android | Pending approval”)
  3. 点 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)

五、结语与可复现清单

  1. 先查兼容性再强上:插件 client 的 peer 依赖若被宿主内联,装 node_modules 无效,该禁用就禁用。
  2. curl 200 ≠ 页面可用:加载问题要用真实浏览器引擎验证。
  3. 隔离永远安全:临时 patch + 新端口测实例,不动持久配置,出问题可回滚。
  4. 控制面在宿主 Web,不在代理端口:x-dsh-reverse-proxy-control: 1 + loopback Origin。
  5. 审批只有宿主机能做:Settings → Reverse proxy → Connected devices。
  6. 保持启动方式不变:只重启 DSH,不动其他服务;改任何配置前留备份。

最终可用的插件组合是 dsh-full-remote + dsh-host-history-lite + dsh-client-ui-voice-input,dsh-ui-mobile 等待 DSH 升级到兼容版本后再考虑重新启用。


RAG 智能问答

针对本文继续提问:《DeepSeek Harness 远程访问 + 手机端插件安装实战:七个踩坑与正确解法》