dsh-connect-qwenwork

modelinstall failsnpmMIT

注册千问办公(QwenWork)模型提供器,复用桌面客户端的本机登录态,在 127.0.0.1 起一个 OpenAI 兼容回环桥,把 DSH 的模型请求翻译成上游真正使用的 ACP 控制信封对话协议。

下载量 / Downloads
718
测试时间 / Tested
2026-11-07
DSH 版本 / Version
0.2.2-rc.1

Install

dsh plugin --profile web add dsh-connect-qwenwork

Sandbox test results

StageResult
1 install✗
2 list—
3 mount—
4 boot—
First error
dsh: initialized profile web at /home/node/.dsh/profiles/web

Four stages run in an isolated sandbox: install (network on) → list → mount → boot (last three with no network).

README

dsh-connect-qwenwork

把 千问办公(QwenWork) 接入 DeepSeek Harness(DSH)。

插件读取本机已登录的千问办公桌面客户端凭证,在 127.0.0.1 上起一个 OpenAI 兼容的

回环桥,把 DSH 的模型请求翻译成千问办公真正使用的会话协议。


这份文档的协议结论是实测出来的

本文写于 2026-10-22,当时机器上装着千问办公桌面客户端并已登录,因此下面的

协议与凭证格式都在真实上游上跑通过(不是从 bundle 推断的)。三个关键结论

与早期的猜测相反:

早期猜测 · 实测结果

会话凭证在 Chromium 的 Cookies 库里 · 不在。那个库里只有一个统计 Cookie(.mmstat.com / cna)。真凭证在 auth-v2.dat

上游用 Cookie: 鉴权 · 用 Authorization: Bearer <JWT>。同一 token 放 Cookie 里无效

会话协议是标准 ACP(initialize/session/new/session/prompt) · 是带信封的 JSON-RPC,方法名叫 join / new_prompt。标准 ACP 的方法名一律被拒:Unknown control method: initialize


一条真实的对话回合(实测时间线)

REST   POST /api/chat-sessions            Authorization: Bearer <jwt>   → 201 { id }
WS     wss://qwenwork.cn/api/chat-ws      Authorization: Bearer <jwt>   → 101
  →    {type:"control", payload:{method:"ping"}}                        → pong
  →    {type:"control", payload:{method:"join",  params:{sessionId}}}   → join_ack {accepted:true}
  →    {type:"control", payload:{method:"new_prompt", id:1,
                                 params:{sessionId, prompt:[{type:"text",text}]}}}
                                            ← result { promptId, turnId }   只是"接受了"
                                            ← new_prompt_ack {accepted:true}
                                            ← session_status: idle          **上一轮遗留,必须忽略**
                                            ← session_status: running       本轮真的开始
                                            ← session/update: agent_thought_chunk     思考增量
                                            ← session/update: agent_message_chunk     正文增量
                                            ← session_status: idle          **本轮结束**

一次「一句话 + 单独一行 DONE-OK」的回合实测约 15–17 秒(上游是 agent 型会话,

有固定的沙箱启动开销)。

信封很重要:裸 JSON-RPC 会被回

{"code":-32700,"message":"unknown envelope type"},必须包一层

{"type":"control"|"data", "payload":{...}}。带 seq 的 data 帧承载

session/update 通知,control 帧承载方法与响应。

结束判据(最容易写错的地方):idle 在本轮开始前也会来一条,单看"有没有

idle"会把回复截断成空。判据必须是「见过 running 或正文增量之后的 idle」。


三层结构

DSH / pi-ai                本插件                       千问办公
──────────────         ───────────────────         ──────────────
chat/completions  ──►   bridge.js   (回环 HTTP)
                          │  压成一条 prompt 文本
                          ▼
                        acp.js      (控制信封)   ──►  wss://qwenwork.cn/api/chat-ws
                          ▲                            join / new_prompt
                          │                            session/update …
                        SSE delta   ◄──  data 帧
desktop-auth.js   ◄──  读取本机 %APPDATA%\QwenWorkCN\auth-v2.dat

接缝的选择与参考插件 dsh-connect-workbuddy

一致:回环 HTTP + pi-ai 适配器。理由也一致——DSH 的 adapter 契约是内部 API,

而回环地址 + OpenAI 兼容协议是稳定的外部契约。


凭证从哪来

按优先级:

1. 插件设置里的 token —— 手填 Bearer 之后的那段 JWT,最高优先。

2. 环境变量 QWENWORK_TOKEN —— 同上,适合脚本化环境。

3. 千问办公桌面客户端的 auth-v2.dat(推荐,复用客户端的登录)。

auth-v2.dat 的解密链

实测的文件在 %APPDATA%\QwenWorkCN\auth-v2.dat(Electron 的 userData):

Local State → os_crypt.encrypted_key     base64("DPAPI" + DPAPI(<32 字节>))
                                        ──DPAPI──► 32 字节主密钥
auth-v2.dat  = "v10" + nonce(12) + AES-256-GCM 密文 + tag(16)
                                        ──AES-GCM──► 明文 JSON
明文 JSON    = { token, refreshToken, expiresAt, user: { name, planName, … } }

解出来的 token 是 JWT,iss: qwenwork.cn、aud: oauth_app、

app_id: qwenwork-desktop-app。同目录下还有一份更老的 auth.dat,它的 token

是 "placeholder-token-for-claudecode-sdk" 占位值——插件用"看起来像 JWT"这条

检查把它筛掉,避免把占位值发给上游(那会得到 401,然后用户被告知"没登录")。

同目录还有两个取值名(QwenWork / QwenWorkCN),插件都扫。

一个必须知道的限制

DPAPI 解密需要起一个 powershell 子进程(ProtectedData::Unprotect)。

在禁止派生子进程的执行环境里这一步会失败(实测到 spawnSync powershell EPERM)。

那时插件会明确报 `cannot spawn powershell (EPERM); this process is not allowed to

start child processes`——不是"登录态坏了",重新登录没有用。此时用方式 1 手填 token。

同理,客户端正在运行时它的 Cookie 库被独占打开,任何方式都读不到(实测

File.Copy / cmd copy / esentutl 全被系统拒绝)。不过 auth-v2.dat 不在

那条路径上,所以凭证读取不受影响。

失败分类(每条指向不同的修复动作):

reason · 含义 · 怎么办

missing · 该路径不存在 · 装客户端,或先登录一次

unreadable · 文件存在但打不开 · 退出占用它的程序

invalid · 解开后不是 JSON / 不是库 · 文件已损坏

encrypted · 容器解不开 · 见上文的 DPAPI 限制,或改手填 token

no-session · 能读,但 token 不是 JWT · 该文件是旧格式/未登录

unsupported-platform · 该平台的密钥取法未实现 · 手填 token


一个必须遵守的接入约束

本插件用静态 import 引宿主的 @earendil-works/pi-ai 等包。这不是风格选择:

那些包只在 DSH 的 app.asar 里,profile 的 node_modules 中不存在,只有宿主的

模块系统认得。改成动态 await import() 会 ERR_MODULE_NOT_FOUND,让 apply()

静默放弃注册——表现为「插件装了但模型列表里没有」,且没有报错。

细节与实测证据见 [docs/HOST_IMPORTS.md](docs/HOST_IMPORTS.md)。


安装

# 从 GitHub 安装(npm 发布前先走这条)
dsh plugin --profile <你的 profile> add github:tang-bugu/dsh-connect-qwenwork

# 或从本地目录安装
dsh plugin --profile <你的 profile> add "file:/path/to/dsh-connect-qwenwork"

dsh plugin add 会同时写 dependencies 与 dsh.profile.bundles——后者才是

Cordis 树真正读取的列表,手改 node_modules 不够。

本包自带的 cordis.patch.yml 会随 bundle 层一起生效,把插件挂进 Cordis 树,

不需要再手改 profile 的 cordis.patch.yml。装好重启 DSH 即可在模型选择器

里看到 qwenwork。

⚠️ 用 file: 安装时不要跑 dsh plugin remove:file: 是复制而非链接,
remove 会把源目录清空。详见 [docs/INSTALL.md](docs/INSTALL.md)。

设置卡片

插件自带一张卡片,注册在设置页的 Plugins → 本插件 行里

(plugins.row.config 槽位)。它显示:

  • 是否找到登录态、账号名与套餐、凭证文件在哪;
  • 桥是否就绪、它的回环地址;
  • 每一条读取尝试的失败分类,并给出对应的修复动作;
  • 可用的模型档位。

卡片上可以手填 Bearer token,写入 token 字段。留空并保存等于 unset

(恢复自动读取),而不是写空串——后者会存一个"存在但为空"的覆盖层。

卡片本身不接触任何 token:它读 Host 的只读路由

/plugins/dsh-connect-qwenwork/status,那份 JSON 只给来源、身份片段与字节数。

为什么是手写卡片:@deepseek-ai/dsh-settings 文档的原话是「autoGenerate
…供客户端按 schema 建页面;目前还没有客户端这样做」。只声明 schema 等于
用户没有界面可填,所以插件自带一个。schema 仍要声明——设置页的写入校验走
序列化后的 Config schema。

诊断

np

…(truncated)

Rendered from the README published on npm. Formatted only, not edited.

Versions

Published on npm::3,最后更新 2026-10-28

Links

GitHub · Homepage · npm

Same category