🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

千问办公桥接 tang-bugu/dsh-connect-qwenwork

DeepSeek Harness
tang-bugu2 天前有提交已提供中文文档扫描:含敏感能力

在本机桥接千问办公会话,让 DSH 直接调用其模型

30.7/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/30 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add tang-bugu/dsh-connect-qwenwork
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:仅索引
是什么
dsh 原生插件 · model
装得上吗
本站尚未做安装检查
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 2 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 51 处,展示前 48 条)
  • 写入本地文件
    src/index.js:63
    mkdirSync(dir, { recursive: true })
  • 写入本地文件
    src/index.js:64
    appendFileSync(join(dir, 'module.log'),
  • 写入本地文件
    src/index.js:238
    mkdirSync(traceDir, { recursive: true })
  • 写入本地文件
    src/index.js:239
    appendFileSync(join(traceDir, 'apply.log'), line)
  • 写入本地文件
    src/index.js:891
    mkdirSync(dir, { recursive: true })
  • 写入本地文件
    src/index.js:892
    appendFileSync(join(dir, 'module.log'),
  • 读取本地文件
    src/auth.js:256
    entries = readdirSync(dir, { withFileTypes: true })
  • 执行外部命令固定命令
    src/auth.js:304
    const out = execFileSync(
  • 执行外部命令固定命令
    src/auth.js:326
    const out = execFileSync(
  • 读取本地文件
    src/auth.js:584
    desktopDirs: desktopUserDataDirs(platform, home, env).map((dir) => ({ dir, exists: existsSync(dir) })),
  • 读取本地文件
    src/auth.js:585
    browserDirs: browserUserDataDirs(platform, home, env).map((dir) => ({ dir, exists: existsSync(dir) })),
  • 读取本地文件
    src/auth.js:600
    return statSync(path)
  • 读取本地文件
    src/auth.js:607
    return readFileSync(path)
  • 发起网络请求
    src/acp.js:663
    const response = await fetch(new URL(QWENWORK_SESSIONS_PATH, this.restOrigin), {
  • 发起网络请求
    src/acp.js:869
    const settled = this.request(
  • 发起网络请求
    src/acp.js:941
    const response = await fetch(target, {
  • 发起网络请求
    src/wsclient.js:150
    const request = transport.request(requestOptions)
  • 读取本地文件
    src/desktop-auth.js:99
    parsed = JSON.parse(readFileSync(localStatePath, 'utf8'))
  • 写入本地文件
    src/desktop-auth.js:155
    writeFileSync(inPath, ciphertext)
  • 写入本地文件
    src/desktop-auth.js:156
    writeFileSync(scriptPath, [
  • 执行外部命令固定命令
    src/desktop-auth.js:167
    const result = execFileSync(
  • 读取本地文件
    src/desktop-auth.js:176
    return readFileSync(outPath)
  • 删除本地文件
    src/desktop-auth.js:199
    unlinkSync(path)
  • 写入本地文件
    src/desktop-auth.js:213
    writeFileSync: (path, data, enc) => writeFileSync(path, data, enc),
  • 删除本地文件
    src/desktop-auth.js:214
    unlinkSync: (path) => unlinkSync(path),
  • 读取本地文件
    src/desktop-auth.js:371
    if (!existsSync(userDataDir)) {
  • 读取本地文件
    src/desktop-auth.js:393
    if (!existsSync(filePath)) continue
  • 读取本地文件
    src/desktop-auth.js:396
    container = readFileSync(filePath)
  • 读取本地文件
    src/desktop-auth.js:478
    userDataDirs: desktopUserDataDirs(platform, home, env).map((dir) => ({ dir, exists: existsSync(dir) })),
  • 发起网络请求
    src/bridge.test.mjs:44
    return fetch(`${bridge.baseUrl}/chat/completions`, {
  • 发起网络请求
    src/bridge.test.mjs:58
    const response = await fetch(`${bridge.baseUrl}/models`, {
  • 发起网络请求
    src/bridge.test.mjs:72
    const response = await fetch(`${bridge.baseUrl}/models`)
  • 发起网络请求
    src/bridge.test.mjs:79
    const response = await fetch(`${bridge.baseUrl}/models`, {
  • 发起网络请求
    src/bridge.test.mjs:88
    const response = await fetch(`http://127.0.0.1:${bridge.port}/healthz`)
  • 发起网络请求
    src/bridge.test.mjs:96
    const response = await fetch(`${bridge.baseUrl}/secret-path-xyz`, {
  • 执行外部命令来自配置/变量
    src/card.test.mjs:32
    execFileSync(process.execPath, [linkScript], { stdio: 'ignore' })
  • 读取本地文件
    src/card.test.mjs:107
    const source = readFileSync(join(pluginRoot, 'lib', 'client.js'), 'utf8')
  • 读取本地文件
    src/card.test.mjs:115
    const source = readFileSync(join(pluginRoot, 'lib', 'client.js'), 'utf8')
  • 读取本地文件
    src/card.test.mjs:282
    const source = readFileSync(join(pluginRoot, 'lib', 'client.js'), 'utf8')
  • 读取本地文件
    src/card.test.mjs:415
    const source = readFileSync(join(pluginRoot, 'lib', 'client.js'), 'utf8')
  • 读取本地文件
    src/card.test.mjs:437
    const source = readFileSync(join(pluginRoot, 'lib', 'client.js'), 'utf8')
  • 执行外部命令来自配置/变量
    src/card.test.mjs:455
    execFileSync(process.execPath, [linkScript, '--clean'], { stdio: 'ignore' })
  • 读取本地文件
    src/cli.test.mjs:109
    const source = readFileSync(binPath, 'utf8')
  • 发起网络请求
    src/e2e.test.mjs:291
    return fetch(`${bridge.baseUrl}/chat/completions`, {
  • 执行外部命令来自配置/变量
    src/host.test.mjs:27
    execFileSync(process.execPath, [linkScript], { stdio: 'ignore' })
  • 执行外部命令来自配置/变量
    src/host.test.mjs:33
    execFileSync(process.execPath, [linkScript, '--clean'], { stdio: 'ignore' })
  • 发起网络请求
    src/host.test.mjs:293
    const response = await fetch(`http://127.0.0.1:${status.bridge.port}/healthz`)
  • 发起网络请求
    src/host.test.mjs:409
    const health = await fetch(`http://127.0.0.1:${after.port}/healthz`)
扫描于 2026/11/1(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/schemastery@deepseek-ai/cordis@deepseek-ai/dsh-llm@deepseek-ai/dsh-llm-pi-ai
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

把千问办公(QwenWork)接入 DeepSeek Harness:读取本机已登录的会话,在 127.0.0.1 起一个 OpenAI 兼容回环桥,把 DSH 的模型请求翻译成千问办公的 ACP 会话协议。

README

由 DeepSeek 最新模型翻译生成
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。
展开完整文档(README 剩余 1 节)
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」。

还有一条硬限制:一条消息最多 32768 字节。 2026-10-29 实测,发一条 33000 字节的 new_prompt,660 ms 后连接被对端以 1009 关闭,reason 是 read limited at 32769 bytes(1009 = message too big)。上限按未压缩的消息字节数算——同一轮探测的载荷是几万个重复字符,permessage-deflate 能压到几百字节,照样被拒,所以压缩救不了。

DSH 每轮都把整段对话发过来,而这个插件必须把它压成一条 new_prompt,于是对话一长就会撞上限。处理办法在 messagesToPrompt(messages, { maxBytes }):超预算时保住系统提示与最新的消息,从最老的一侧开始整条丢弃,并在正文里留下 [上游单条消息上限 32 KB:已省略较早的 N 条消息…] 的标记;最新那条如果自己就超了,则保留头尾各一段(结论常在尾部)。acp.js 发送前还有一道
fitPromptForUpstream() 兜底,保证不会把 1009 变成用户看到的错误。

三层结构

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。

安装 bash

从 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。

设置卡片

插件自带一张卡片,注册在设置页的 Plugins → 本插件 行里(plugins.row.config 槽位)。它显示:

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

卡片上可以手填 Bearer token,写入 token 字段。留空并保存等于 unset (恢复自动读取),而不是写空串——后者会存一个「存在但为空」的覆盖层。

卡片上还有两个会改变运行状态的按钮,分工是刻意分开的:

| 按钮 | 走哪条路由 | 做什么 | 用在哪 |
| 快捷重启 | POST /plugins/dsh-connect-qwenwork/reload | 让 DSH 重新加载本插件(插件代码、版本、桥全部重来) | 升级了插件、改了插件代码、刚更新过 node_modules |
| 重启桥 | POST /plugins/dsh-connect-qwenwork/restart | 只关掉回环桥与上游会话再重开(重新读登录态、重连上游) | 客户端重新登录了、上游断了、桥卡住了 |

「快捷重启」靠的是 DSH 自带的 HMR(@deepseek-ai/dsh-hmr):它的 profile 重算会用磁盘上已改动的文件替换正在运行的插件模块,不需要重启进程——也就是以前「退出应用再打开」的那个动作。所以它是先回 202 再异步触发重载:重载会把这条路由所在的 Loader 条目一起停掉,等重载完成再写响应就没有人接了。宿主没有 hmr 服务(headless、没开 profileContext)时这条路明确回 503,并告诉用户去重启,而不是假装成功。

两条路由都只收 POST,都要求回环对端,并且都要求 Origin 与 Host 同源——它们能停掉正在跑的会话里的请求,不该被任意网页触发(CSRF:同机任意网页都能向回环地址发 POST)。

卡片本身不接触任何 token:它读 Host 的只读路由 /plugins/dsh-connect-qwenwork/status,那份 JSON 只给来源、身份片段与字节数。

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

诊断 bash
npx dsh-connect-qwenwork doctor # 本机状态 + 上游验证
npx dsh-connect-qwenwork doctor --json # 机器可读
npx dsh-connect-qwenwork status # 只读,不联网

doctor 分两段报告:本地能看见什么(不联网,永远有答案),以及上游认不认(联网,可能因网络本身失败)。混在一起会让「网络不通」看起来像「没登录」。

输出绝不包含 token:只给路径、来源与身份片段。身份信息也脱敏(邮箱保留首字母与域,用户 id 只留前 6 位)。

模型目录

千问办公的模型选择器提供四个档位(官方文档):高级 / 基础 / 经济 / Qwen3.8-Max。文档明确说模型列表会持续更新、旧模型会被替换,所以上游真正接受的 id 无法从文档静态推出。

因此:

- 目录里每个模型带 source 字段,标明来自 documented-tier 还是上游实测;
- 拿不到上游目录时保留这四个档位作为回落,而不是给空列表;
- 绝不编造看起来像真模型 id 的字符串——那会让用户选到一个永远失败的模型。

(建会话时上游回的 extra_data.mode 实测是 "flash",模型档位由上游自己按账号与套餐决定,new_prompt 不接受逐轮指定模型。)

安全边界

- 桥只 listen('127.0.0.1'),不接受外部连接;
- 每次进程启动生成随机 bearer token,pi-ai 侧用它作 apiKey,桥做常量时间校验(绑定回环不够:同机进程能猜端口,但猜不到 token);
- /healthz 不需要 token,且不泄露任何信息;
- 未知路径回 404 不回显路径;
- 客户端断开时立即 abort 上游,避免继续消耗用户的额度;
- 插件不写上游的登录态,也不启动任何 OAuth 流程。

已知限制

- 每条 DSH 请求开一个新的上游会话。 复用会话会把 DSH 已经组装好的上下文重复跑一遍(上游会话是有状态的智能体会话),等于重复计费;代价是上游侧会留下较多短会话。
- 只发送文本。 图片与文件标注为 [image omitted: …] 占位符而不是静默丢弃,但也不会上传。
- new_prompt 不接受逐轮指定模型。 模型档位由账号/套餐决定;插件目录里的四个档位只作为 DSH 模型选择器的可选项。
- 空回合由超时兜底。 上游若只回一条 idle 而没有正文,本轮要等到 idle 超时才收尾(这是「上一轮遗留的 idle」与「这一轮真的空回复」在消息层面无法区分所致)。

模块地图

| 文件 | 职责 |
| src/index.js | 插件入口:配置 schema、provider 注册、状态路由、生命周期 |
| src/desktop-auth.js | 读 auth-v2.dat(DPAPI → os_crypt → AES-GCM → JWT) |
| src/auth.js | 旧的 Cookie 发现/解密层,保留供诊断对照 |
| src/acp.js | 会话客户端:控制信封、join/new_prompt、事件归一化 |
| src/bridge.js | 回环 HTTP 桥(OpenAI 兼容 → 会话协议) |
| src/wsclient.js | 无依赖的 RFC 6455 WebSocket 客户端(需要自定义握手头) |
| src/sqlite.js | 无依赖的只读 SQLite 读取器(供 auth.js 用) |
| src/bin.js | doctor / status 诊断命令行 |
| lib/client.js | 浏览器侧设置卡片(window.__ModuleLoader__ 自注册脚本,不需打包) |

发布

发布由 tag 驱动:推一个 v* 标签,GitHub Actions 自动跑测试、发布 npm 并建 Release。
bash
npm run release # 测试 -> bump patch -> 打 tag -> 推送

一次性的 secret 配置与完整说明见 docs/RELEASE-CI.md。
合并代码不会发布——npm 的版本号一旦发布不可覆盖,所以「发布」必须是一次显式声明。

开发 bash
cd dsh-connect-qwenwork
npm test

九个套件、161 个用例,全部不联网、不依赖 npm 包。

| 套件 | 用例 | 覆盖 |
| wsclient.test.mjs | 13 | 握手、分片、控制帧、掩码、超大帧、UTF-8 校验 |
| sqlite.test.mjs | 32 | 头部、序列类型、rowid、溢出页、损坏输入 |
| bridge.test.mjs | 15 | token 鉴权、SSE 顺序、finish_reason、错误路径 |
| acp.test.mjs | 30 | 控制信封、事件归一化、流式时序、遗留 idle、断线、32 KB 上限裁剪 |
| e2e.test.mjs | 6 | 假会话上游 → 真客户端 → 真桥 → OpenAI 客户端 |
| auth.test.mjs | 19 | Cookie 组头、主机匹配、路径、失败分类 |
| host.test.mjs | 24 | apply() 的 provider 注册、pi-ai profile 契约、重启桥/快捷重启路由、目录、模型发现 |
| card.test.mjs | 20 | 状态路由脱敏;lib/client.js 真实执行;字段名/路径对账 |
| cli.test.mjs | 2 | CLI 模块顶层求值、help 里的子命令与实现对账 |

两个值得单独说的:

- host.test.mjs 需要 @earendil-works/pi-ai 等宿主依赖才能真正注册。
测试用 test/fixtures/stubs 里形状一致的桩包,运行前后自动链接/拆除(tools/link-test-stubs.mjs),跑完不留会骗过 pnpm install 的 node_modules。
- cli.test.mjs 覆盖 bin.js ——那是用户跑的第一条命令,且它引用的函数名没有编译期保障。
本会话里真的发生过:Host 把 readSession/verifySession 换成 readCredential/ verifyCredential 后,bin.js 还在调旧名字,doctor 一跑就抛;没有测试覆盖到它,因为其它套件根本不碰 CLI。
- card.test.mjs 里有两条字段名对账测试:把 lib/client.js 当文本读,取出它引用的配置字段与状态字段,跟 Host 的 schema/状态文档逐个比。这类 bug(Host 改名、卡片没跟上)会静默渲染成"未登录",没有任何报错——值得钉死。

许可

MIT
预览分享卡片长什么样 ↓tang-bugu/dsh-connect-qwenwork
分享 · DeepSeek Harness Hub
tang-bugu/dsh-connect-qwenwork

在本机桥接千问办公会话,让 DSH 直接调用其模型

回到顶部