dsh-connect-workbuddy README

The project's README from github.com/dingminhua/dsh-connect-workbuddy. As one markdown file: README.md.

dsh-connect-workbuddy 设置卡:模型目录与倍率、图片与可关闭思考开关、活动标签(账号名已打码)

dsh-connect-workbuddy

把本机登录的 WorkBuddy 模型(国内版与国际版)接入 DeepSeek Harness —— 每次刷新自动读取每个模型的积分倍率,逐模型开关图片输入,多账号可切换,并带每日签到与只读积分概览(卡片与输入框里的胶囊读数)。

English · 安装 · 工作原理 · 账号池 · 更新日志 · 问题反馈

npm version npm downloads test status MIT license GitHub stars dshfind plugin

一个独立的 DeepSeek Harness (DSH) bundle 插件。它把本机已登录的 WorkBuddy 账号(国内版与国际版 WorkBuddy AI 均支持)接到 DSH 的模型选择器,同时提供账号池、可勾选的模型管理,以及一套围绕积分的只读能力:每次刷新按上游目录自动读取每个模型的积分倍率(x0.79 这种,倍率完全由上游给出,插件不写死也不猜)、逐模型开关图片输入、多账号切换与账号池自动换号、每日签到、以及卡片与输入框里胶囊式的只读积分读数。国内版与国际版是两个并行的供应商(workbuddy / workbuddy-global),可同时使用;插件设置卡片以 tab 区分两者,方便统一管理。只需本机 WorkBuddy 的登录态,或在浏览器里登录一次即可。

风险提示

WorkBuddy 已开始限制非官方客户端请求。本项目不保证相关接口长期可用,上游一旦收紧限制,插件可能失效。请先阅读文末「免责声明」,自行评估账号风险。使用官方 WorkBuddy 客户端始终是最安全的选择。

需要说明的是,这个插件的目的很普通:省掉在 WorkBuddy 客户端和 DSH 之间来回切换的麻烦。你已经在自己的机器上登录了 WorkBuddy,插件只是把那份已有的登录态接到 DSH 的模型选择器上,让你在写代码的地方直接用到它——不提供账号、不共享凭据、不绕过计费。每一次调用都消耗你自己账号的额度,按上游给出的倍率真实扣费,插件从不修改账号状态,也不附带任何提权或刷额度的能力。

它依赖的是 WorkBuddy 客户端自身的接口(非官方开放 API),所以上游调整时插件可能需要跟着调整——这是这类工具的常态,也是这条提示存在的原因。请自行评估账号风险后再决定是否安装。

功能特性

工作原理

DSH PiAiAdapter(每个 provider 一套)
  -> 安全 loopback shim(每区域一个随机端口 + 进程内随机 secret)
  -> WorkBuddyUpstreamClient
  -> 国内版 https://copilot.tencent.com/v2/chat/completions
  -> 国际版 https://www.workbuddy.ai/v2/chat/completions
  -> WorkBuddy SSE
  -> DSH 本地执行工具并回传结果

国内版与国际版各持一套完整的运行时栈——凭据 store、模型 catalog、回环 shim、adapter——按凭据域名(workbuddy.ai / codebuddy.ai → 国际版,其余 → 国内版)隔离可见账号,所以两个区域的账号可以同时在线、同时被不同会话使用。国际版有两个品牌域名:桌面端登录在 workbuddy.ai,CodeBuddy CLI 登录在 codebuddy.ai,两者都是国际版。

模型目录按区域取自各自的正确来源,两者都是 App 自己读的那份 /v3/config,且都以 App 自己的客户端身份去读(WorkBuddy/<app> WorkBuddy/<app> CLI/<cli>,即桌面端 buildVlmGatewayHeaders 拼出的那个头)。这一步是必需的:/v3/config 按客户端通道下发不同的产品配置,CLI 通道的答复是 App 通道的严格子集,既不发布 modelPromotions 也不发布 modelTiers——用 CLI 身份读,国内版的活动徽标就会一个都画不出来。聊天与探针仍以 CLI 身份发送(会话身份三元组绑在聊天通道上),只有目录读取换成 App 身份。国内版若 /v3/config 取不到,会退回旧的 /v2/enterprises/personal/models 并记一条日志,所以只在降级时使用。积分概览走 https://www.codebuddy.cn/v2/billing/meter/get-user-resource(国际版账号自动路由到其凭据所属的国际网关,workbuddy.ai 或 codebuddy.ai——两个品牌域名的凭据互不通用),均为只读接口。

凭据读取自 WorkBuddy 桌面 App 自身的 auth 文件(只读);刷新得到的 token 按区域存放在 $DSH_HOME/.workbuddy-auth.cn.json 与 $DSH_HOME/.workbuddy-auth.global.json(双账号同时在线互不覆盖;旧的单文件 .workbuddy-auth.json 作为迁移来源保留读取),桌面端文件永不被写入。

加密凭据字段:新版桌面端(Windows 先行)把 auth 文件里的 accessToken / refreshToken 从纯字符串改为 {"$wbEncrypted":1,"envelope":"…"} 的 AES-256-GCM 信封,账号昵称 nickname 同样被加密(不解密就会退回成一串 uin 数字)。信封用的是 App 自身构建期内置的字段密钥,并非用户密钥,因此插件不内置任何密钥副本,而是在需要时用 ELECTRON_RUN_AS_NODE 调用已安装 App 的原生绑定(electron_browser_workbuddy_storage.loggerGet())取回同一份载荷并就地派生密钥——也就是「向写下这个文件的那个构建本身询问」。密钥只在本进程内存中缓存,不落盘、不进日志;纯字符串文件(macOS 与旧版 Windows)走原路径,一次子进程都不会启动。其中的 account.phoneNumber 虽同样加密,插件不读取也不显示。若 App 安装在非常规位置,用 WORKBUDDY_APP_EXECUTABLE 指定其可执行文件。

账号池

dsh-connect-workbuddy 账号池:签到勾选的账号、测试勾选的账号、成员排序与签到状态(账号名已打码)

一台机器上往往登录过多个 WorkBuddy 账号。账号池把这件事收成一次点击:把要用的账号勾进池子,然后「签到勾选的账号」或「测试勾选的账号」;再打开启用账号池,插件就会在请求失败时自动换号接着跑。

每个区域(国内版 / 国际版)各有独立的一池:成员与目标模型互不影响——两边的账号本来就属于不同的上游栈。签到只有国内版有:海外区域不显示签到按钮,也没有那一列。

自动换号(开启账号池即生效)

这个开关到底控制什么

它只控制「插件要不要自己决定」,不控制「你能不能操作」。

账号池 开启 账号池 关闭
谁服务 由排序自动选出(可用优先 → 快过期先花 → 积分高 → 凭据新) 你在账号池里手动选「使用哪个账号」
请求失败 自动换下一个可用账号重试 不换号,如实报错
一键签到 / 一键测试 可用 仍然可用
目标测试模型 可改 仍然可改

最后两行是刻意的:签到与测试是手动动作,它们作用于你勾选的成员,与「插件是否自动路由」是两件事。之前把按钮也挂在开关上,导致关掉账号池就再也没法签到或测试——而「关闭」本该是「我手动来」,不是「什么都不许做」。

手动选择账号在哪里:账号池区块里。关闭账号池时,该区块会显示一行「使用哪个账号」的下拉,选定即生效(写回本区域的账号槽,与凭据失效时的「切换到可用账号」走同一条已验证的写入路径);开启账号池时这一行不显示——那时由排序决定,一个改了不生效的下拉比没有更糟。账号的发现(「重新检测账号」)、勾选、签到与测试全部集中在这个区块里,所以账号管理只有一处。

唯一的硬约束:池里一个成员都没勾选时,批量动作一律拒绝。空的池绝不退化成「那就对全部账号执行」——那是唯一会花掉你没授权过的积分的解读。

排序规则(开启账号池后决定谁服务)

  1. 可用优先 —— 被测量判定为不可用(限流未到恢复时间、凭据被拒、连通性失败等)的排到最后,不参与选择;
  2. 快过期的积分先花 —— 3 天内到期的积分用不掉就作废,所以有临期积分的账号排在前面,先到期的先排;两个都在临期时,临期积分多的优先;
  3. 积分高者优先 —— 在没有临期积分的账号里,选积分最多的。积分只在有新鲜读数时参与(卡片轮询、批量测试或保存时读到的那份,10 分钟内有效):请求路径不会为了排序去打上游,否则一页对话就会变成 N 个额外请求;
  4. 凭据更晚过期的优先;
  5. 最后按账号 id 收尾,保证顺序是全序、可复现,不会因为账号表顺序变化而抖动。

第 2 条为什么排到第 3 条前面(issue #70):这两条此前是严格字典序——先比积分总量,只有总量完全相等才比到期时间。而总量几乎从不恰好相等,于是「快过期优先」实际上从未生效过:一条「不花就作废」的规则被另一条完全遮蔽,等于死代码。真实病例里,某账号有 196.4 分当天作废,只因为比另两个账号少约 1,200 分就永远排在最后,那 196 分必然烂掉。

代价也要说清:这条规则会让临期 1 分的账号排到余额 5 万的账号前面。这不是缺陷——积分是通用的,不花那 1 分它确定会损失;而余额 5 万的账号下一轮还在。唯一的真实代价是临期账号可能不够跑完一个请求,这时由既有的换号逻辑接手。

两个批量动作

测试目标是哪个模型:默认是该区域目录里倍率为 0 的免费模型(实测国内版当前只有 hy3 一个;不写死名字,因为上游会改倍率——deepseek-v4.1-flash 就已从 0 变成 0.11),也可以在下拉里指定一个具体模型。指定的模型若已离开目录,手动动作会明确拒绝(target-model-stale),而不是偷偷换一个——你点的是「测这个模型」,换掉就是答非所问。

成本与边界:测试是真实请求,指向免费模型时(倍率 0)几乎不花钱,但它不是零成本的空转;签到则会真实领取当日奖励。两个动作都只在你点按钮时执行——插件不提供自动签到,卡片上也不放一个永远点不动的开关。

池成员是显式勾选,默认一个都不含

「本机有登录」不等于「要拿它去签到和花积分」。成员默认空,必须逐个勾选(或点「全选」);空池不会退化成「全部账号」——没勾选时两个批量动作直接拒绝,而不是替你把所有账号跑一遍。

勾选是一次草稿编辑:点「保存」才落盘,未保存时保存按钮带 dirty 指示;切走或关掉卡片,草稿仍在。

手动选择:卡片上没有账号下拉了

账号一律在账号池里管:成员勾选就是现在的账号选择面,「重新检测账号」也在那一块。原先那个「账号选择」下拉在有账号池之后是自相矛盾的——池开启时由排序决定谁服务,它改什么都不生效,却看起来像权威;顶部状态行还可能显示成 A、下拉显示成 B。

所以区域状态行也跟着改:池开启时不再报出某一个账号名(多个账号同时登录是常态,用单数说「已登录:A」本身就是错的),只陈述本区域可用、由池决定用谁;池关闭时才报出那个账号——那时它确实是唯一答案。状态行旁边的圆点与 token 过期时间讲的是「这个区域能不能用」,和「谁在计费」是两件事,后者由账号池区块负责显示。

账号为什么出池,以及什么时候回来

探测结果 对轮换的意义
可用 可入选
被限流 暂时出池,按上游给出的重置时间自动回池(上游没说时间就按 30 分钟兜底,不编造倒计时)
积分耗尽 出池,按月周期包的刷新时间回池
连不上上游 / 失败 暂时出池,30 分钟后自动回池——这类结果说的往往不是账号,而是一次网络抖动或上游 5xx
凭据被拒 / 模型不存在 出池,只能靠手动「测试」回池:一个需要重新登录,一个是本区域不再提供该模型,等待都修不好
账号被上游限制使用 停权,等待修不好(5.8.0)。上游的话是「账号存在异常行为,已被限制使用」——它点名的是这个账号,所以自动换号会绕开它去用下一个。这与「渠道校验失败」正好相反:那条说的是这个请求,换号只会白烧一次额度,因此不换号。回池的实际出路只有手动「测试」:账号 id 是 uin/uid 的哈希,重新登录后仍是同一个 id,插件也不会因为重登清掉这条测量(「重新授权就回池」不成立);上游解除限制后测一次通过,它才会重新成为候选。插件不给它编一个倒计时,但停权只作用于轮换候选资格——全池没有可用账号时,请求仍会落到你保存的那个账号上

关键一条:「被限流」「连不上上游」都不等于不可用。它们恰恰是最值得等的那两类状态——当成永久失效剔除,正好丢掉池子里本来最能用的账号,而且当池内每个成员都被这样剔除时,池子就没有任何候选,自动换号会无处可去,你只会看到上游原始报错。

为什么只有限流/欠费/瞬时故障会自己回池:这三类要么由上游写明了恢复时间,要么由插件按 30 分钟兜底;而「凭据被拒」「模型不存在」等待都无意义,只能由你手动重测或重新登录来刷新。插件不再有自动复测定时器(见下),所以后两类的唯一出路就是手动动作。

保存与草稿:为什么卡片上有两个「保存」

模型管理一个、账号池一个,这是刻意保留而非漏合并:两者的草稿域不同,合并后一次写入失败会连累另一边(模型目录写不进去,就连池偏好也存不了);一个共享的「放弃」还会在一次误点里同时丢掉两份草稿。两边都是验证写入——写不成功不丢弃草稿,否则用户的编辑就永久丢了。「保存中」那个转圈只覆盖这次已校验的写入(5.8.0,issue #131):写入一落地就解除忙碌、丢弃草稿,随后的用量刷新只是显示——刷新慢或失败都不会把已经保存成功的设置说成「没保存上」,只会另附一行「已保存,但面板未能重新读取。设置已写入;下次刷新后会显示。」(role="status",与「保存失败」的 role="alert" 是两个独立节点)。此前两者共用同一个 loading,于是一次慢刷新能让按钮永远转下去,而数据其实早就已经落盘。

池里还有一类状态不走草稿:探测结果、冷却时间、当前轮换到的账号。它们是观测事实,由插件自己高频写入。原因很实际——草稿是覆盖式的,若观测结果也进草稿,你手工点一次「保存」就可能把插件刚写进去的结果回退成几分钟前的旧值。

安装

⚠️ 版本要求:DSH 0.1.7-rc.1 及以上(当前窗口:0.1.7-rc.1 ~ 0.2.x 正式版)。 自 v2.1.0 起本插件只支持 DSH 0.1.7-rc.1 及以上的宿主(旧版宿主无法通过依赖解析安装本版);0.1.5 及更早的宿主请停留在 v2.0.15。自 v2.1.2 起上界放宽为 <0.3.0-0,0.3.0 线被明确拒绝。详见下方受支持的宿主范围。

受支持的宿主范围

DSH 的随包门禁(packages/boot/app-boot/src/plugin-compatibility.ts 的 evaluatePluginCompatibility)会逐条核对本插件声明的 dsh-* peer 范围;只要有一条不满足,整个组合包会被跳过(进入 skippedBundles,仅打印到 stderr)——provider 不注册、卡片不出现、模型列表清空,而页面上没有任何报错。

宿主版本 判定
0.1.7-rc.1 ~ 0.1.7-rc.2 ✅ 支持
0.2.x 正式版(0.2.0、0.2.1 …) ✅ 支持(2.1.2 起实测)
0.2.x 预发布(0.2.0-rc.1、0.2.1-rc.1 …) ⚠️ 不落在声明范围内,见下方说明
0.3.0-0 及以上 ❌ 明确拒绝(该线尚未验证)
0.1.6 及更早 ❌ 明确拒绝(0.1.5 线请停留在 v2.0.15)

关于 0.2.x 预发布(v4.7.0 更正):此前本表把 0.2.0-rc.1 写作「✅ 支持」,那是错的。 npm semver 规定:预发布版本只有当一个比较器在同一条 major.minor.patch 元组上带预发布标签时才被放行。 现声明 >=0.1.7-rc.1 <0.3.0-0 只对 0.1.7-x 开口,<0.3.0-0 又排除了 0.3.0 元组,因此 0.2.1-rc.1 这类版本两条比较器都不满足(satisfies('0.2.1-rc.1', …) === false)。 实际影响很小:宿主门禁只校验 @deepseek-ai/dsh* 命名空间,且 rc 宿主上本插件按 link: / 手工安装照常加载。 这不是靠改范围能解决的问题——任何静态范围都覆盖不了「任意 0.2.x 预发布」,已逐一实算否证五种写法 (见 CHANGELOG 4.7.0)。等宿主发 0.2.1 正式版即自然成立。

上界写作 <0.3.0-0 而非 <0.3.0:DSH 至今全部 tag 都是预发布(-alpha.N / -rc.N),只写 <0.3.0 会放行整条 0.3.0-alpha.1 / 0.3.0-rc.1 这条从未验证过的线,围栏形同虚设——这正是 2.1.2 修掉的缺陷形状。该口径由 tests/dsh-line.spec.ts 守卫(含预发布悬崖的用例)。

🪟 平台支持:Windows、macOS、Linux 三者并列支持。 Windows 是一等目标平台,不是「顺带能跑」:CI 在 windows-latest 上跑完整套件,凭据路径、进程心跳、App 可执行文件定位、设置写入四处都有 Windows 专属分支与测试,并且修复只对 Windows 生效的缺陷时,以 Windows 用户回报的现场与真机取证的加密向量为准(见 CHANGELOG 的 2.0.6 / 2.0.11 / 2.0.12 各节)。见下方平台支持一节了解各平台的具体差异与已知限制。

前置:已安装并登录 WorkBuddy 桌面 App(插件复用 App 的登录状态)。

推荐使用 DSH 插件命令安装 npm 已发布版本:

dsh plugin --profile desktop add dsh-connect-workbuddy

或直接通过 npm 安装:

npm install dsh-connect-workbuddy

安装、更新或卸载 bundle 后,需要重启对应的 DSH 进程。

⚠️ 刚发布的版本请写「精确版本号」,不要依赖浮动范围。 pnpm 有新版本等待期(minimumReleaseAge,默认 24 小时),所以一个版本刚发布不久时,浮动范围会被静默压回上一个版本:声明 ^4.4.2 时它仍解析到 4.4.2,而写精确的 4.4.3 则立即拿到 4.4.3。由于两种写法都「安装成功」,表现是「升级命令跑完了、版本却没变」,很容易被误判成发布失败。已实测(隔离目录、pnpm 12.3.4、--lockfile-only,未触碰用户环境):

目标写法 解析到的版本
浮动 ^4.4.2 4.4.2 ⚠️
精确 4.4.3 4.4.3
浮动 + --config.minimum-release-age=0 4.4.3

想立刻用上新版,请写精确版本号:dsh plugin --profile desktop add [email protected]。过了等待期之后,浮动范围会自然跟上。

插件在 Web、Desktop、TUI 三种界面下均可运行,按你使用的 profile 选择对应命令:

dsh plugin --profile web add dsh-connect-workbuddy     # Web
dsh plugin --profile dsh-tui add dsh-connect-workbuddy # TUI

平台支持

三个平台都是受支持目标;下列差异是实现事实,不是「尚未支持」的委婉说法。要改这一节,请同时改 docs/WINDOWS.md——那份文件是平台相关约束的登记处。

方面 Windows macOS Linux
凭据默认目录 %LOCALAPPDATA% / %APPDATA% 优先,未设时回落 <home>\AppData(重定向配置文件仍可解析) ~/Library/Application Support/CodeBuddyExtension/Data/Public/auth $XDG_CONFIG_HOME 优先,未设时回落 ~/.config
桌面 App 可执行文件 %LOCALAPPDATA%\Programs\WorkBuddy、%LOCALAPPDATA%\WorkBuddy、两个 Program Files(按 WorkBuddy.exe 定位) 读 bundle 的 Info.plist 取 CFBundleExecutable(真实值 Electron,不是按 App 名猜);含国际版 WorkBuddy AI.app 与 /Applications 下的子目录 不适用(加密凭据场景下 Linux 无对应 App;用 WORKBUDDY_AUTH_FILE 指定凭据)
进程心跳的时间戳来源 PowerShell Get-Process 的 StartTime(POSIX 无 ps -lstart 等价物) ps -o lstart= ps -o lstart=
子进程的窗口 必须 windowsHide:宿主本身没有控制台(GUI 进程),漏设会让每次探测都在屏幕上闪一个可见的黑框 不适用(该选项仅 Windows 生效) 不适用
设置写入的瞬时占用 会踩到:profile 的配置文件 cordis.patch.yml 被杀毒软件 / OneDrive / 编辑器短暂锁定会拒绝 rename 覆盖,@deepseek-ai/dsh-atomic-write 的重试只在 win32 生效 不踩:POSIX 的 rename 直接替换,同一段代码不进重试分支 同 macOS
CI 覆盖 windows-latest 跑完整套件 开发者本机(macOS) ubuntu-latest 跑完整套件

Windows 上需要知道的三个具体点:

  1. 账号改动可能保存失败(profile 的配置文件 cordis.patch.yml 被占用)。插件会在写入后回读校验:确认落盘才显示「已清除」,失败则明确报错并保留原选择。遇到提示时关闭占用该文件的程序后重试。详见已知限制。
  2. 凭据路径已部分经真机验证。候选列表按平台约定推导并有单元测试钉住(含 LOCALAPPDATA / APPDATA 缺省与空白串两种回落)。已在真实 Windows 11 上确认:%LOCALAPPDATA%\CodeBuddyExtension\Data\Public\auth 与 %LOCALAPPDATA%\Programs\WorkBuddy\WorkBuddy.exe 命中,加密凭据的 keyId 与登记向量逐字一致。其余候选仍未经真机逐项验证——若你的凭据或 App 不在默认位置,用 WORKBUDDY_AUTH_FILE / WORKBUDDY_APP_EXECUTABLE 指定,并欢迎回报现场。
  3. 加密凭据(5.6.0 起)需要桌面 App 在场,Windows 与 macOS 同样如此;App 装在自定义目录(如 E:\WorkBuddy\WorkBuddy.exe)时用 WORKBUDDY_APP_EXECUTABLE 指定。

在服务器上使用:搬运已登录的凭据

在无桌面环境的服务器(或任何不想安装 WorkBuddy 桌面 App 的机器)上,插件读不到本机凭据。办法是从一台已经登录过 WorkBuddy 的机器上,把凭据目录整体拷过来——插件只读这份文件,不要求 App 在场(前提是凭据本身未加密,见下):

# 1) 在「已登录 WorkBuddy 的机器」上,找到并打包凭据目录
#    macOS:  ~/Library/Application Support/CodeBuddyExtension
#    Linux:  ${XDG_CONFIG_HOME:-~/.config}/CodeBuddyExtension
#    Windows:%LOCALAPPDATA%\CodeBuddyExtension  以及  %APPDATA%\CodeBuddyExtension
tar czf codebuddy-cred.tgz -C <上面的父目录> CodeBuddyExtension

# 2) 把 codebuddy-cred.tgz 传到服务器,解到「插件会读的那个目录」
#    Linux:
mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}"
tar xzf codebuddy-cred.tgz -C "${XDG_CONFIG_HOME:-$HOME/.config}"

# 3) 确认插件读到了
dsh plugin --profile web exec dsh-connect-workbuddy status

⚠️ Linux 上没有 ~/.local/share 这条路。 插件的候选目录由 src/auth.ts 的 defaultDesktopAuthDirs() 决定,Linux 只读 $XDG_CONFIG_HOME(缺省 ~/.config):

平台 插件实际读取的凭据目录
macOS ~/Library/Application Support/CodeBuddyExtension/Data/Public/auth
Linux ${XDG_CONFIG_HOME:-~/.config}/CodeBuddyExtension/Data/Public/auth
Windows %LOCALAPPDATA% 与 %APPDATA% 下的 CodeBuddyExtension\Data\Public\auth

所以只把凭据解到 ~/.local/share 是不会被读到的——要么解到上面列出的目录,要么用 WORKBUDDY_AUTH_FILE 直接指向那份 auth 文件(显式指定时该目录下的同级备份文件仍会一并扫描,账号切换照常可用)。

🔐 加密凭据(WorkBuddy 5.6.0 起)不能靠搬运解决。 新版桌面端把 auth 文件里的 token 字段加密了,密钥由 App 的原生绑定在运行时现取、不落盘。因此一台没有 App 的服务器上,即使把加密后的 auth 文件拷过去也读不出来(插件会如实报告未登录,而不会退化成读出一个残缺 token)。可行的做法是从一台仍在运行 5.6.0 之前版本、或凭据未加密的机器上搬运,或者在该服务器上安装 App 以取得密钥。

判断当前处于哪种情况,看 doctor 的两行输出即可:

Desktop auth file: present (…/CodeBuddyExtension/Data/Public/auth/.workbuddy-auth.json)
Encrypted-credential support: unavailable (…)

Desktop auth file: present 说明文件找到了;此时若 status 仍报未登录、而 Encrypted-credential support 为 unavailable(服务器上没有 App),那就是加密凭据 + 缺密钥的场景——搬运救不了它。

🔒 凭据等同于账号:这份文件里是可直接使用的登录态。传输时请走加密通道(scp / 私网),落到服务器后用 chmod 600 收紧权限,别放进任何会被打包或提交的目录。

CLI 路线(可选):让 CodeBuddy 直接干活

默认关闭(cliBridge.enabled: false)。关闭时不注册工具、不注册指令,provider 那条路一行都没变。唯一的例外是下面那颗实时胶囊:它的只读路由与客户端组件无条件注册(客户端要靠它区分「没有回合」和「没有这条路由」),所以关着时每个打开的页面仍会每 5 秒问一次那条本地路由——不碰上游、不花额度,返回一个空数组。

这条路不把 CodeBuddy 当模型提供方,而是当工具:插件提供一个 codebuddy_ask 工具和一条 /codebuddy 指令,由本机已登录的 CodeBuddy CLI 在自己的进程里跑一轮真实任务——读文件、跑 git、改代码——再把结论交回来。

这条路到底绕开了什么,说清楚。 提供方那条路要让请求看起来像 CodeBuddy 客户端才能过网关的渠道校验,4.15.x 起那条校验已经升级成「按提示词中段做精确子串匹配」,靠句读处插零宽空格躲开——这是军备竞赛,而且不再免费:issue #98 里已经出现账号级限制(code 11140,「账号存在异常行为,已被限制使用」)。

2026-10-29 的 #163 复测更新:零宽这条路已经被网关堵死——CN 网关在匹配前会规范化 U+200B,system 开头每个词边界都插零宽仍然 11128。现在改为按句改词(rewriteChannelFingerprintPhrases,落在 neutralizeChannelFingerprints 之前),只改命中句、其余逐字节不动,也不再需要把提示词伪装成第三方 agent。细节见 CHANGELOG 的 Unreleased。

CLI 路线逃掉的是客户端指纹层,不是账号层。它用 CODEBUDDY_AUTH_TOKEN 把本插件 vault 里的同一个 WorkBuddy 账号交给 CLI,所以「跟网关说话的是真的 CodeBuddy CLI 进程、没有身份要伪造、因此账号不会被限制」这句话不成立:身份仍然是同一批账号,11140 是账号级的,同一批账号在 CLI 路线上一样受限。真正被绕开的只有「这条请求的客户端指纹像不像 CodeBuddy」——这一层是真实的收益(渠道校验不会再因为提示词里有句读而拒你),但它不等于「免封」。两条路是互补的容错,不是替代。

前提

打开

{
  "cliBridge": {
    "enabled": true
  }
}

其余键都有默认值,不写就是默认。完整的键见下表。改这些键不需要重启宿主:cliBridge 是 volatile 字段,改动只重建这一条路由,不会重载两个 provider 栈。

两个入口

入口 谁触发 工作目录 花谁的额度
codebuddy_ask 工具 模型自己决定调用 会话工作区;或调用方传的 working_dir;或 cliBridge.workingDir CodeBuddy 的配额 + 那一次回注唤醒的 DSH token(injectResult 默认开)
/codebuddy 指令 用户敲 自动取当前会话的工作区 同上——指令本身不经过模型,但默认会把结论回注给模型

/codebuddy 后面留空 = 让 CodeBuddy 评审当前工作区里未提交的改动(这种调用强制只读的 dontAsk 权限模式,并且要求它自己跑 git diff、只报确实看见的问题);写了字 = 原样把这句话发过去;以 --edit 开头 = 这一轮允许它改文件(acceptEdits,只影响这一次,不改变默认值,例如 /codebuddy --edit 修一下这个空指针;--edit 单独用 = 评审并顺手修掉找到的问题)。

跑完之后结论会回注给会话模型(cliBridge.injectResult,默认开):会话空闲时直接唤醒它起一轮,正在跑就排进它的下一步。这一步真的花 DSH token——把 injectResult 设成 false 才是「完全不碰模型」,那时 /codebuddy 的结论只显示给你看。回注的文本以一行 [dsh-connect-workbuddy · 由 /codebuddy 自动回注,不是用户输入] 开头,免得你(或者模型)把它当成自己打的字。

跑的时候看得见:输入框下方那颗胶囊

工具调用在 DSH 里是一次性的:execute() 什么时候返回,界面才知道发生了什么。中间那几分钟(读文件、跑 git、反复试)本来是一片空白,用户分不清「还在干活」和「已经卡死」。

所以插件在 conversation.composer.dock(输入框下方那一行,和额度胶囊同排)挂了一颗实时胶囊:CodeBuddy · 7 tools · 42s · last: Bash。它由插件自己的只读路由 /plugins/dsh-connect-workbuddy/cli-live 供数,那个路由把宿主侧的 live board(src/cli-bridge/live.ts)序列化出去。

这颗胶囊走的是插件的客户端入口(dsh.client),所以改它需要重新构建 lib/client.js 并重启宿主,和宿主侧代码一样。

权限:默认只读

permissionMode 默认 dontAsk:只读工具(Read/Glob/Grep)自动放行,写文件与执行命令被拒,且不卡住——CLI 会自己结束这一轮并告诉你哪些工具被拒了。

为什么不是 plan(plan 是更保守的候选,也是本次评审建议的默认值):在 -p(headless)下 plan 会发一条 control_request 等 stdin 回授权,而无人值守的调用没有人在终端上点「同意」。不处理这条请求,CLI 会一直等到 60 秒的协议超时,插件则会一路等到 timeoutSecs(默认 900 秒)才失败——实测两次都是这样挂死的。所以默认取 dontAsk,同时在 runner 里把每一条 control_request 明确拒掉。拒绝时要按工具区分:只有 ExitPlanMode 带 interrupt: true(不带则 plan 不结束,实测挂过 70 秒);其它工具(Bash、Write…)不能带——带了会让整轮直接中止(实测 acceptEdits 下拒绝 Bash:is_error、空回复、4.2 秒收场),不带则 CLI 记下这次拒绝、换一个工具继续,整轮正常出结论(实测改用 PowerShell 后 12 秒成功)。想让它真的能改文件,显式设成 acceptEdits。

工作目录:四级阶梯 + 允许根

codebuddy_ask 的工作目录按顺序取:调用方传的 working_dir → 本会话的工作区(session.header.cwd)→ cliBridge.workingDir → (只有 allow_unattended 打开时才)临时目录 → 以上都没有就拒绝并说明三条出路。

不管来源是哪一级,选中的目录都要过三关:必须是绝对路径、必须存在且是一个目录、必须落在允许根之内(本会话工作区、cliBridge.workingDir、cliBridge.allowedDirs)。allowedDirs 是逗号分隔的绝对路径列表,用来放行工作区之外的目录。这样「模型自己编一个 / 或 ~/.ssh 传进来」不会被执行,.. 也走不出去(比较的是 realpath 解析后的路径,并按路径分隔符对齐边界,/repo-evil 不算 /repo 的子目录)。

配置

键 默认 说明
enabled false 唯一的开关。false 时这个路由什么都不注册。
binary '' CLI 可执行文件的绝对路径。留空时只按已知安装位置探测,找不到就拒绝运行——不会回退到 PATH 上的 codebuddy(那个二进制会拿到 vault 令牌,不能随便认)。
model '' 传给 --model;留空用 CLI 自己的默认模型。
permissionMode dontAsk dontAsk(默认,只读)/ plan / default / auto / acceptEdits / bypassPermissions。后两个有写权限。
effort '' --effort:minimal / low / medium / high / xhigh / max;留空用 CLI 默认。
maxTurns 10 单次任务的 CLI 轮数上限(硬上限 100)。
timeoutSecs 900 单次任务的墙钟超时,秒(硬上限 1800)。
regionOrder cn,global 凭据区域的优先顺序。
workingDir '' 工具入口的默认工作目录,同时也是允许根之一。
allowedDirs '' 额外允许的根,逗号分隔的绝对路径。
appendSystemPrompt '' 追加到 CLI 系统提示末尾;留空不追加。
allowUnattended false 允许在没有任何工作目录时退回一个临时目录。默认关:CLI 有读写权限,不该在没人指定的目录里跑。
maxReplyChars 60000 交回给会话模型的最大字符数,超出截断并注明(硬上限 400000)。
injectResult true 跑完后是否把结论回注给会话模型。设为 false 才完全不碰模型。
sessionContinuity auto 同一个 DSH 会话里,下一次调用是否续接上一次的 CodeBuddy 对话。auto = 自动续接(走 --session-id),off = 每次都是独立的新对话。显式传 session_id 参数时以参数为准。
maxTurnsPerSessionHour 6 每个会话每小时最多几次。0 = 不限。
maxTurnsPerHour 20 全部会话每小时合计最多几次。0 = 不限。

边界

命令行

dsh plugin --profile <web|desktop|dsh-tui> exec dsh-connect-workbuddy status   # 登录状态与剩余积分
dsh plugin --profile <web|desktop|dsh-tui> exec dsh-connect-workbuddy doctor   # 凭据路径与宿主诊断
dsh plugin --profile <web|desktop|dsh-tui> exec dsh-connect-workbuddy logout   # 清理插件持有的凭据副本

status 与 doctor 支持 --json 输出机器可读格式。

为什么这三条命令能在「什么都没装」的机器上跑(issue #47)

该插件把 @deepseek-ai/dsh-atomic-write、@deepseek-ai/dsh-home-paths 等一整套 DSH 包声明为 peer:由宿主提供,插件不自己带一份。这在已启动的 DSH 进程内工作正常(宿主有运行期拦截层负责按宿主安装位置解析这些名字),但 dsh plugin … exec 不是那种环境——它就是在 profile 目录里跑一次 pnpm exec,子进程用普通 Node 解析规则,而 profile 是按 autoInstallPeers: false 建的,peer 根本不会装进去。于是这两个包解析不到。

4.3.1 及更早版本会因此在加载期直接崩溃(ERR_MODULE_NOT_FOUND),且报错文件看起来和命令无关:

Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@deepseek-ai/dsh-atomic-write'
imported from …/profiles/dsh-tui/node_modules/dsh-connect-workbuddy/lib/host-heartbeat-*.js

原因是打包把 auth / host-heartbeat / at-rest / pi-ai-runtime 合并进了同一个 chunk,lib/bin.js 一 import 它,auth.ts 里那句顶层 import 就被求值了——连只读的 status 也一样。4.3.2 起这两个包改为「宿主优先、按需加载」:宿主在场时仍用宿主的那份(保持与宿主共享同一套跨进程写协议),不在场时回落到插件自带的小实现。所以现在 exec 在干净机器上可用,doctor 也会把当前用的是哪一份打印出来:

Atomic writes: built-in copy (host peer not resolvable here; normal outside a booted DSH)

在仓库里跑(peer 齐全)则显示 host-provided (@deepseek-ai/dsh-atomic-write)。这条守卫写在 tests/host-peers.spec.ts:它直接扫描构建产物,一旦 lib/bin.js 的依赖图里再出现裸包名就变红。

开发

pnpm install
pnpm run check   # typecheck + test + build

本地开发用 link: 安装到 desktop profile(改码后重启 DSH Desktop 生效):

dsh plugin --profile desktop add /Users/dmh2002/DshProject/dsh-connect-workbuddy

写脚本调用 chatStream() 时注意:它返回的是信封,不是可迭代流

const envelope = await client.chatStream(credential, bodyJson)
if (!envelope.ok) { /* 失败:envelope.status / .kind / .message / .detail */ }
else { await envelope.response.text() /* 或者按 SSE 消费 */ }

先查 envelope.ok 再消费 —— 它返回 { ok: true, response } 或 { ok: false, status, kind, message, detail },而不是一个可以直接 for await 的流。跳过这一步的典型后果是把失败全报成成功(response 是 undefined,读它会抛错或被吞掉,于是「每个模型都成功」)。这条来自 issue #56 报告者的实测提醒,写在这里是为了让下一个人少踩一次同样的坑。

失败信封里的 kind 就是本文提到的失败分类(soft_rate / model_busy / hard_credit / session_dead / policy_reject / not_found / server / client),detail 携带上游的 upstreamCode / requestId / displayMsg。

市场收录与展示

插件已包含可安装的 dsh.bundle manifest,并发布到 npm。社区市场通常从 Awesome DSH Plugin 注册表同步条目;仓库内的提交草稿位于 awesome-dsh-plugin-submission/dingminhua__dsh-connect-workbuddy.yml,正式提交为 PR #3812(data/plugins/dingminhua__dsh-connect-workbuddy.yml,已通过 CI 与 Submission gate,等待维护者合并)。

收录目录规范(依据 contributing.md):

市场卡片中的截图与 GitHub README 徽章是两套机制:

插件卡上那句标题与简介不是本插件渲染的——那是 DSH 插件管理器读的包元数据:它按 locale/<lang>.json 的 meta.title / meta.description 取词(locale/en.json 是锚点,缺了它 zh.json 永远不会被读;exports 里缺 "./locale/*.json" 通配则整个目录被静默跳过),英文回退到 package.json 的 description。因此同一句话在本仓库写在三处,并由 tests/locale-meta.spec.ts 钉死:卡面字典(locale/en.json / locale/zh.json)、英文回退(package.json.description)、市场条目草稿(awesome-dsh-plugin-submission/dingminhua__dsh-connect-workbuddy.yml 的 description.en / description.zh)。改一处就要同改三处。

已知限制

免责声明

请完整读完后自行决定是否安装和使用。(本文开头另有一条风险提示,两条说的是同一件事。)

致谢

本项目的实现建立在他人已公开的工作之上。以下内容如实标注来源与许可证,我们对此保持充分尊重:

连接内核的参照

自动续跑的语义参照

插件呈现与结构的基线

说明

以上项目的版权归各自作者所有。本项目采用借鉴设计思路 + 独立实现的方式,未整体复制任何参考项目的源码;关键模块均为独立编写,并在源文件头部注释中标注了所参考的具体项目与模式。若你发现本项目的标注有遗漏或不当之处,请提交 issue,我们会立即更正。

第三方开源依赖

本项目参考的开源项目、其许可证与合规说明,完整记录见 THIRD_PARTY_NOTICES.md。引入新的与 WorkBuddy 接入相关的外部依赖或复用其他项目代码时,请同步更新该文件并遵守对应许可证要求。

许可证

本项目采用 MIT 许可证,版权归属:Copyright (c) 2026 LaoDing。

dsh-connect-workbuddy (dingminhua/dsh-connect-workbuddy)

dsh-bundle
Searching Database…

What is this?dsh-connect-workbuddy is a plugin for DeepSeek Harness (DSH) that connects your locally signed-in WorkBuddy AI accounts (both the China and international editions) to DSH's model picker, adding a read-only credit overview, per-model toggles, and account management.
Is this a standalone app or does it plug into something else?It is a bundle plugin for DeepSeek Harness (DSH) — it doesn't run on its own, it registers WorkBuddy as a model provider inside DSH's existing model selector.
How do I connect it to my WorkBuddy account?Install it as a DSH bundle plugin, then it picks up your machine's already-signed-in WorkBuddy login, or you can click 'Scan QR code' (or 'Web login' for the international edition) in the plugin's settings card to authorize an account without the WorkBuddy desktop app.
Do I need the official WorkBuddy app installed first?No — you need a WorkBuddy account (China or international edition), but you can authorize it directly inside the plugin via QR code or web login without the desktop client.
Is this guaranteed to keep working?No. It depends on WorkBuddy's unofficial client interface, and WorkBuddy has already started restricting non-official clients, so the plugin could stop working if the upstream tightens restrictions further.
Does using the image-generation tool cost credits?Yes, and it is off by default. It calls WorkBuddy's own text-to-image models (hunyuan-image-alpha domestically, hy-image-3.5 internationally), spending about 5.71 credits per image in the China region versus 0.03 in the international region, so you must explicitly enable 'allow the model to generate images' per region.
Can it generate video too?Yes, via a separate 'workbuddydrawvideo' tool (also off by default) that submits an async job, polls until it finishes, and downloads the resulting mp4 locally; it supports both text-to-video and image-to-video modes using models like kling-video-v3 and minimax-video-h3.
What happens when I hit WorkBuddy's rate limit?The plugin parses the exact reset time out of the upstream's 429 response body and shows it directly on the model row (e.g. rate-limited until a specific date/time), instead of making you guess by retrying blindly.
Can I manage multiple WorkBuddy accounts at once?Yes — the account pool lets you batch check-in and batch-test accounts, and enable automatic failover: if a request fails on one account, the plugin retries it with another available account in the pool before reporting an error.
Creator
dingminhua
Size
9.3 MB
Stars
983
Age
2mo
Commits · 90 Days