DeepSeek Harness 桌面端外部 CLI 接入:能力边界与 dsh web 替代架构
桌面端确实在跑一个带认证的 API,端口也是活的——但你拿不到进入它的钥匙。 桌面 Host 以 --no-open --host 127.0.0.1 --port 0 启动,带 token 的 URL 只通过 Node IPC 交给 Electron 壳,外部客户端没有受支持的配对流程(#10956)。先判宿主,再选架构。
DeepSeek Harness 第一步:确认你要接的是哪种宿主
桌面端与 dsh web 共享同一份会话数据,但对外授权方式完全不同;判断错宿主,后面所有命令都白试。
- 看进程:桌面端是 Electron 壳 + 内嵌 Host;
dsh web是 Node 进程直接起 Host。 - 看启动参数:桌面端固定
--no-open --host 127.0.0.1 --port 0,端口是随机的。 - 看终端输出:
dsh web会打印dsh web: http://127.0.0.1:3080/?token=…这一行。 - 看 CLI 能力:当前 CLI 只有 profile 启动、
--dump-config*、dsh plugin,没有 attach/session/send。 - 一个直观验证:
dsh session --help在 rc.3 上会报profile "session" does not exist,因为它把 session 当成 profile 名。
DSH插件 第二步:桌面端为什么接不进去
三条硬边界叠加:令牌只走 IPC、没有配对流程、会话锁不允许第二个写入者。
- 令牌边界:带 token 的 URL 只发给 Electron 壳(
desktop-host里的 L30、L103-L104),外部客户端无从获取。 - 配对边界:远程访问只提供「非 owner 主体」的策略位,但没有任何流程去注册这类主体,所以唯一调用者类型就是 owner,靠进程的启动令牌准入。
- 锁边界:活跃会话在
session.lock上持有跨进程 flock,第二个 Host 即使能列出会话,提交时也会返回session/writer-held。 - 源码层面还有一条:Desktop Host 其实也会打印
dsh web: …?token=那行,但 Electron 把它接到应用自己的 stdout(host-process.ts),只有从终端启动 app 二进制才看得到。 - 端口与 token 每次启动都变,而且这条路径对桌面端没有文档承诺。
DeepSeek Harness 第三步:受支持的替代架构
换成 dsh web Host,用启动 URL 换 cookie,再按 RPC 操作会话。
- 启动:
dsh web --host 127.0.0.1 --port 3080,记下终端打印的带 token URL。 - 换取 cookie:
B=http://127.0.0.1:3080
curl -s -c jar.txt -o /dev/null \
"$B/connect?token=<launch-token>&interactions=none"
# 期望 302,并拿到 API cookie
- 定义一个 RPC 调用函数(参数键就是 TypeScript 参数名):
rpc() { curl -s -b jar.txt -H 'content-type: application/json' \
-d "{\"type\":\"client-request\",\"rpcId\":\"$RANDOM\",\"method\":\"$1\",\"payload\":{\"args\":$2}}" \
"$B/api/$1"; }
rpc session/list '{"_request":{}}'
rpc session/prompt '{"request":{"requestId":"req-1","sessionId":"session-…","mode":"queue","content":[{"type":"text","text":"follow-up"}]}}'
- 读历史用
session/page与session/follow;管理队列用session/updateQueue与session/cancel。 - 如果客户端要代为回答审批,把
interactions改成answer。 - 没有 cookie 时
session/list返回 401,这是验证链路是否打通的最快信号。
社区实测:/connect 返回 302 指向 ./api/remote.mux,随后 session/create 与 session/prompt 返回 {"accepted":true};在同一个 home 上再起一个 dsh web,能列出该会话,但提交提示词返回 session/writer-held。
DeepSeek插件 桌面端已有会话怎么迁过去
两者共享 $DSH_HOME 下的会话文件,所以退出桌面端后用 dsh web 打开同一份数据即可继续——代价就是你想避免的那次重启。
- 退出桌面端所有进程。
- 用同一个
DSH_HOME启动dsh web。 - 在会话列表里找到原会话,确认历史完整。
- 再按上面的 RPC 序列提交后续提示。
- 整个过程不需要复制会话文件,也不需要重建工作区。
DeepSeek Harness 已知限制
这套 RPC 是内部线格式,不是对外文档 API。
args的键是 TypeScript 参数名,列表接口的字面量就是_request,看起来有点怪但确实如此。- 参数形状可能随 rc 版本变化,升级前需要回归。
- 远程访问策略目前只覆盖非 owner 主体,别指望靠它给外部客户端开权限。
- 桌面端的 attach 或配对入口目前不存在,rc.4 与 0.2.2-alpha.2 的发布说明也没有新增。
- 想要这条能力,只能走 Ideas 提需求,等上游决定。
DSH插件 注意事项
下面这些做法会加重问题或掩盖真因,先避开再谈修复。
- 不要试图从 Electron 进程里抠 token。那既不受支持,也超出本文范围。
- 不要用第二个 Host 硬写同一会话。
session/writer-held是锁在正常工作。 - 不要把 RPC 当稳定 API 写死。版本升级后先跑一次
session/list验证。 - 注意 cookie 的作用域。换端口或换主机名都要重新
/connect。 - 记录启动参数。端口随机时,靠终端输出里的 URL 才能确定地址。
如果你的目标只是「脚本里跑一次任务」,用 headless 比接桌面端更简单;只有需要操作已有会话时,才值得走 dsh web 这条路。
选型时可以按一句话判断:需要跨进程操作已有会话,走 dsh web 的 RPC;只需要一次性执行,走 headless;两者都不需要,就别开外部接入。
如果你已经在用桌面端且不能重启,那么外部接入这条路在当前版本无解,等上游提供配对入口是唯一方向。相关排查见 dsh web authentication required 报错?DeepSeek Harness 认证排查指引。
常见问题
DeepSeek Harness 桌面端在 0.2.1-rc.3 不支持外部接入,rc.4 与 0.2.2-alpha.2 的发布说明也没有新增。桌面 Host 服务的 API 是同一套,但 token 只通过 Node IPC 交给 Electron 壳,外部客户端没有受支持的授权入口。
不是,dsh session 并不是 DeepSeek Harness 的合法命令。CLI 目前只有 profile 启动、--dump-config* 与 dsh plugin 这几类命令,没有 attach/session/send;dsh session 会被当成 profile 名,所以报 profile 不存在。
因为 DeepSeek Harness 的活跃会话在 session.lock 上持有跨进程 flock。第二个 Host 能列出会话,但提交提示词会返回 session/writer-held。
受支持的做法是另起一个 DeepSeek Harness 的 dsh web Host:它会打印带 token 的启动 URL,用 /connect 换到 API cookie,然后按 RPC 调 session/list、session/prompt 等方法。
能,DeepSeek Harness 桌面端的会话可以迁到 dsh web 继续,但需要退出桌面端。两者共享 $DSH_HOME 下的会话,所以关掉桌面端后用 dsh web 打开同一份数据即可继续,代价正是你原本想避免的重启。
相关术语
- Desktop Host
- Desktop Host 是桌面端内嵌的宿主进程,以 --no-open --host 127.0.0.1 --port 0 启动,提供与 dsh web 相同的认证 API,但启动令牌只交给 Electron 壳。— https://github.com/deepseek-ai/deepseek-harness/discussions/10956
- /connect
- /connect 是 dsh web 的授权入口,用带 token 的 URL 换取 API cookie。请求会返回 302,随后携带 cookie 才能调用 session 相关 RPC。— https://github.com/deepseek-ai/deepseek-harness/discussions/10956
- session/writer-held
- session/writer-held 是提交提示词时返回的错误,表示该会话的写入权被另一个进程持有。它由 session.lock 上的跨进程 flock 保证。— https://github.com/deepseek-ai/deepseek-harness/discussions/10956
- RPC 方法
- RPC 方法是 dsh web 暴露的会话操作接口,如 session/list、session/prompt、session/page。参数键按 TypeScript 参数名直传,属于可能随版本变化的内部格式。— https://github.com/deepseek-ai/deepseek-harness/discussions/10956
来源
- #10956 — Native CLI/API attachment to an existing Desktop session· deepseek-ai(GitHub Discussions)
- deepseek-ai/deepseek-harness(源码仓库)· GitHub