dsh-connect-workbuddy 设置卡:模型目录与倍率、图片与可关闭思考开关、活动标签(账号名已打码)
dsh-connect-workbuddy
把本机登录的 WorkBuddy 模型(国内版与国际版)接入 DeepSeek Harness —— 每次刷新自动读取每个模型的积分倍率,逐模型开关图片输入,多账号可切换,并带每日签到与只读积分概览(卡片与输入框里的胶囊读数)。
English ·
安装 ·
工作原理 ·
账号池 ·
更新日志 ·
问题反馈
一个独立的 DeepSeek Harness (DSH) bundle 插件。它把本机已登录的 WorkBuddy 账号(国内版与国际版 WorkBuddy AI 均支持)接到 DSH 的模型选择器,同时提供账号池、可勾选的模型管理,以及一套围绕积分的只读能力:每次刷新按上游目录自动读取每个模型的积分倍率(x0.79 这种,倍率完全由上游给出,插件不写死也不猜)、逐模型开关图片输入、多账号切换与账号池自动换号、每日签到、以及卡片与输入框里胶囊式的只读积分读数。国内版与国际版是两个并行的供应商(workbuddy / workbuddy-global),可同时使用;插件设置卡片以 tab 区分两者,方便统一管理。只需本机 WorkBuddy 的登录态,或在浏览器里登录一次即可。
风险提示
WorkBuddy 已开始限制非官方客户端请求。本项目不保证相关接口长期可用,上游一旦收紧限制,插件可能失效。请先阅读文末「免责声明」,自行评估账号风险。使用官方 WorkBuddy 客户端始终是最安全的选择。
需要说明的是,这个插件的目的很普通:省掉在 WorkBuddy 客户端和 DSH 之间来回切换的麻烦。你已经在自己的机器上登录了 WorkBuddy,插件只是把那份已有的登录态接到 DSH 的模型选择器上,让你在写代码的地方直接用到它——不提供账号、不共享凭据、不绕过计费。每一次调用都消耗你自己账号的额度,按上游给出的倍率真实扣费,插件从不修改账号状态,也不附带任何提权或刷额度的能力。
它依赖的是 WorkBuddy 客户端自身的接口(非官方开放 API),所以上游调整时插件可能需要跟着调整——这是这类工具的常态,也是这条提示存在的原因。请自行评估账号风险后再决定是否安装。
功能特性
-
双供应商并行接入(国内版 + 国际版) —— 国内版注册为 workbuddy provider(GLM-5.3、DeepSeek-V4-Pro、Kimi-K3、MiniMax-M3、Hy3 等),国际版注册为 workbuddy-global provider(GPT-5.6、Gemini-3.5-Flash、GLM-5.3、Kimi-K3 等)。有账号的区域其模型会出现在 DSH 模型选择器里:两边都有账号时两组同时可选,不同会话可以各选一边,互不干扰。本机没有账号的区域不列出任何模型(该区域一个请求也发不出去,列出来只会点到就失败);只要在该区域登录一次,「重新检测账号」或重启后模型就会自动出现——provider 本身始终注册着,因为它的设置卡片正是登录的入口(重新检测在账号池区块里)。模型名内嵌上游积分倍率(如 GLM-5.3 · x0.79),与 WorkBuddy 自身模型菜单一致;活动时段显示的是折后价——「夜间折扣」开着的时候,卡片与选择器上的数字一起变成 x0.06,活动一过自动回到原价,不需要重启(#120)。
-
插件卡片 tab 切换 —— 设置卡片顶部为「国内版 / 国际版」两个 tab,各含独立的账号池与模型管理;每个 tab 的账号、目录、勾选与未保存草稿完全隔离——在一个 tab 里切账号或刷新模型,不会触碰另一边的运行时目录与会话。
-
可单独关闭任一版本供应商 —— 每个 tab 右侧有一个勾选框(默认都勾选)。取消勾选即把该供应商从 DSH 模型选择器里彻底撤掉(不是只藏起 tab):它的路由与「设置 → 模型」页的目录条目一并摘除,启动时也不再为它发无用请求。账号、积分、目录与勾选全部保留,重新勾选即完整恢复。典型用法是「我根本用不到国际版」——关掉它,让选择器干净。注意:若某会话此前选中的正是被关闭供应商的模型,该会话再调用会报 NO_ADAPTER,卡片会在关闭态的 tab 上明确提示这一点,不会静默失败。
-
设置卡片分四栏:账号 / 模型 / 能力 / 诊断 —— 原来这张卡片是一列 3000 多像素的长页,把两类互不相干的事情混排在一起:运维账号(谁在服务、还剩多少、切换顺序、签到日志)与编辑目录(勾模型、上下文预算、推理档、关闭思考)。它们连提交语义都不同——账号改动点一下即生效,目录改动是草稿、必须点保存——所以「保存」按钮常常离它要保存的东西隔着三屏。现在分栏,一栏只做一件事;切栏既不会重新拉取,也不会丢掉未保存的目录草稿(非活动栏留在页面里,只是通过 hidden 不可见,因此也不进入无障碍树)。第三栏「能力」放的是授权类开关——允许调用本机 CodeBuddy CLI、允许模型自主生图、积分读数显示在哪——它们点一下即生效、从不经过保存,而且每一个都留着长自己子选项的位置(CLI 的工作目录、生图的尺寸或价格上限),所以不挤在账号表格上方那一小条里。第四栏「诊断」是只读的事实表:当前区域、上游域名、真正在服务的账号名、登录有效期、目录里共几个模型、启用了几个、账号池开关,以及上游最近一次错误的原文(原样引用,不翻译、不概括)——这一栏存在的唯一理由就是能被整段贴进问题反馈,所以里面没有任何会改设置的控件。
-
模型管理 —— 从上游刷新完整模型目录,逐项勾选启用或禁用;刷新是草稿操作,点保存才生效。上游同时给出积分倍率、上下文/输出上限与推理档位;每个模型行上有一个「默认推理强度」选择器(issue #179):请求未指定档位时该模型用哪一档——新对话、压缩摘要、会话标题都属于这类请求。选项就是该模型自己声明的那些档位(名册里 deepseek/glm-5.3 是 low/high/max,glm-5.2 是 high/xhigh,space-bunny 有五个,所以只能逐模型选、不能全局一个值),第一项是「默认(high)」——括号里写明上游为这个模型声明的档位。这一行同时也是原来那行只读「推理强度:…」的替代——选择器已经列全了档位,不必再多说一遍。「关闭思考」不在这里,它仍是下面那个 off 勾选框的职责。图片输入按核验表判定:刷新时只有核验表收录为原生多模态的模型才自动勾选,写明纯文本的不勾,没有资料的 id 一律按「未核实」不猜(上游的 supportsImages 是平台侧声明,不等于模型能看图,不参与判定)。核验表分两档证据——厂商文档(13 条)与(2 条:、,各自的描述里写着「原生多模态」)——但新增 id 仍要求厂商来源,详见 的「图片输入」一节。用户仍可随时手动勾选,勾选状态保存在本地。:决定要不要为该模型提供「完全关闭思考」这一档位。默认由内置复核名单给出(上游声称可关闭、实测却对它返回 HTTP 400 的 9 个模型默认不勾),你可以按模型改勾/取消——用于当前不需要该档位的模型;(行上标「拒绝 off」,悬停有说明):该档位被勾上后不只是多一个选项,pi-ai 会把它当作,压缩摘要请求正属于这一类,勾一次等于让该模型的每一次 都 400 / 11133(issue #83)。名单外的模型仍完全由你的勾选与自身声明决定;首次保存即写入你的勾选,之后不再重算。
工作原理
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 账号。账号池把这件事收成一次点击:把要用的账号勾进池子,然后「签到勾选的账号」或「测试勾选的账号」;再打开启用账号池,插件就会在请求失败时自动换号接着跑。
每个区域(国内版 / 国际版)各有独立的一池:成员与目标模型互不影响——两边的账号本来就属于不同的上游栈。签到只有国内版有:海外区域不显示签到按钮,也没有那一列。
自动换号(开启账号池即生效)
- 谁来服务由排序决定:开启账号池后,每一次请求都发给池子里排序最靠前的可用账号——所以「当前使用」显示的是排序的结果,不一定是你手动选的那个。排序规则见下一节。
- 失败时继续往下试:某个请求在上游失败时,插件会依次用池内其他可用账号重试,直到有一个接手、或全部不可用为止;全部失败才把错误报出来(并在错误里注明尝试过几个账号)。
- 哪些失败会换号:429 限流、402 无积分、401 凭据失效、502 网关/网络——这些都是某个账号的状态,换一个账号很可能就过了。
- 哪些不会:请求本身不合法(HTTP 400)。同一个请求体换哪个账号都是同样的 400,重试只是让用户多等几轮才看到同一个错误。流中途断开也不会重试——字节已经发出去了,重放会让客户端看到两段拼接的回答。
- 已知不可用的账号会被跳过:正在限流冷却期内、或凭据已被上游拒绝的成员不会参与(这也是「一键测试」的意义:测试结果直接决定谁能被选中)。从没测试过的账号按「默认可用」参与——刚发现的登录不该因为「还没测」就被排除。
- 手动选择不会被改写:池子通过运行时覆盖决定「谁服务」,从不写回你保存的选择;关闭账号池,你选定的账号立刻完全恢复生效。这也是为什么卡片要显示「当前使用」——它可能不是你在上面选的那个,而你应该看得出来。
- 失败会被记下来:某个请求撞上失败时,插件把这次失败写成该账号的测量(含上游报文里给出的重置时间),所以下一个请求直接从可用账号开始,不会每次都先撞一遍同一个限流账号。只有需要你手动「一键测试」时才不写测量这件事,从此不成立了。HTTP 400 例外——那是请求不合法,与账号无关,记下来会白白闲置一个好账号;未勾选进池的账号也不记(勾选才是「可以花它的积分」的同意)。
- 每次换号都会在宿主日志里留一行
warn,写明因何失败、换到了下一个账号。
这个开关到底控制什么
它只控制「插件要不要自己决定」,不控制「你能不能操作」。
| 账号池 开启 | 账号池 关闭 |
|---|
| 谁服务 | 由排序自动选出(可用优先 → 快过期先花 → 积分高 → 凭据新) | 你在账号池里手动选「使用哪个账号」 |
| 请求失败 | 自动换下一个可用账号重试 | 不换号,如实报错 |
| 一键签到 / 一键测试 | 可用 | 仍然可用 |
| 目标测试模型 | 可改 | 仍然可改 |
最后两行是刻意的:签到与测试是手动动作,它们作用于你勾选的成员,与「插件是否自动路由」是两件事。之前把按钮也挂在开关上,导致关掉账号池就再也没法签到或测试——而「关闭」本该是「我手动来」,不是「什么都不许做」。
手动选择账号在哪里:账号池区块里。关闭账号池时,该区块会显示一行「使用哪个账号」的下拉,选定即生效(写回本区域的账号槽,与凭据失效时的「切换到可用账号」走同一条已验证的写入路径);开启账号池时这一行不显示——那时由排序决定,一个改了不生效的下拉比没有更糟。账号的发现(「重新检测账号」)、勾选、签到与测试全部集中在这个区块里,所以账号管理只有一处。
唯一的硬约束:池里一个成员都没勾选时,批量动作一律拒绝。空的池绝不退化成「那就对全部账号执行」——那是唯一会花掉你没授权过的积分的解读。
排序规则(开启账号池后决定谁服务)
- 可用优先 —— 被测量判定为不可用(限流未到恢复时间、凭据被拒、连通性失败等)的排到最后,不参与选择;
- 快过期的积分先花 —— 3 天内到期的积分用不掉就作废,所以有临期积分的账号排在前面,先到期的先排;两个都在临期时,临期积分多的优先;
- 积分高者优先 —— 在没有临期积分的账号里,选积分最多的。积分只在有新鲜读数时参与(卡片轮询、批量测试或保存时读到的那份,10 分钟内有效):请求路径不会为了排序去打上游,否则一页对话就会变成 N 个额外请求;
- 凭据更晚过期的优先;
- 最后按账号 id 收尾,保证顺序是全序、可复现,不会因为账号表顺序变化而抖动。
第 2 条为什么排到第 3 条前面(issue #70):这两条此前是严格字典序——先比积分总量,只有总量完全相等才比到期时间。而总量几乎从不恰好相等,于是「快过期优先」实际上从未生效过:一条「不花就作废」的规则被另一条完全遮蔽,等于死代码。真实病例里,某账号有 196.4 分当天作废,只因为比另两个账号少约 1,200 分就永远排在最后,那 196 分必然烂掉。
代价也要说清:这条规则会让临期 1 分的账号排到余额 5 万的账号前面。这不是缺陷——积分是通用的,不花那 1 分它确定会损失;而余额 5 万的账号下一轮还在。唯一的真实代价是临期账号可能不够跑完一个请求,这时由既有的换号逻辑接手。
两个批量动作
- 签到勾选的账号 —— 逐个账号签到。签到前先查当天状态,已签到的直接标记为「已签到」而不发写入请求(幂等,不重复领取);任何单个账号失败只标记该行,不中断整批。
- 测试勾选的账号 —— 对每个成员发一次真实体积的探测请求(约 25k 输入 token;与模型行的「测试」是同一套
probeModel,因而同样继承两个实测结论:限流按请求体积触发、恢复时间写在响应体里而不是响应头),得出该账号此刻能否用这个模型。
测试目标是哪个模型:默认是该区域目录里倍率为 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 线被明确拒绝。详见下方受支持的宿主范围。
⚠️ 国内区的「渠道校验」400(code 11128)请确保插件 ≥ 5.14.0。 5.13.1 起会在发出请求之前按句改写已知的固定指纹,5.14.0 又补上了 Web GUI 段落的第二句(#167,5.13.1 上仍然必现的那一支);5.12.x 及更早只有「中性头 + 零宽扰动」三级救援,在同一份 body 上实测全部 400(对照矩阵见 #164)。停在旧版的人会反复撞上这个已经修好的 400,而表现是「每次必现、重启也没用」。
⚠️ 别把插件市场里那个「最新」当日程表——它读的是一份会落后的索引。 社区市场走定期构建的快照(按包各自收录),2026-10-30 15:00 那次观测里本插件还停在 5.13.1,而当时 npm latest 已是 5.14.1;桌面宿主另有「24 小时内发布的版本先标成「新版等待中」」这条规则。以 npm view dsh-connect-workbuddy version 与 Releases 为准,按下方「更新」一节做(写精确版本号),或者把 docs/PINNED-UPGRADE-LAG.md §3 那段话丢给 AI 让它替你跑。
另一个会让人升级不到的原因:pnpm 的 24 小时新版本等待期(minimumReleaseAge)会把浮动范围静默压回上一个版本。需要立刻修复时请写精确版本号:dsh plugin --profile desktop add dsh-connect-workbuddy@5.15.1(原因见下方「刚发布的版本请写精确版本号」那条)。
受支持的宿主范围
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* 命名空间(其余 peer 不参与),而它对这批 peer
的范围求值用的是 semver.satisfies(rt, range, { includePrerelease: true }),所以 rc 宿主照旧
放行——闸门确实校验了,只是用 includePrerelease 放过(归因订正见
docs/DSH-0.2.1-RC3-FOLLOWUP.md §3)。
这不是靠改范围能解决的问题——任何静态范围都覆盖不了「任意 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。由于两种写法都「安装成功」,表现是「升级命令跑完了、版本却没变」,很容易被误判成发布失败。已实测两轮(隔离目录、--lockfile-only,未触碰用户环境):
第一轮 pnpm 12.3.4(4.4.x 当时):
| 目标写法 | 解析到的版本 |
|---|
浮动 ^4.4.2 | 4.4.2 ⚠️ |
精确 4.4.3 | 4.4.3 |
浮动 + --config.minimum-release-age=0 | 4.4.3 |
第二轮 pnpm 10.34.5(2026-10-30,5.14.1 发布 11 小时,显式设 minimumReleaseAge=1440):
| 目标写法 | 结果 |
|---|
浮动 ^5.13.1 | 解析到 5.13.1,不报错 ⚠️ |
精确 5.14.1 | ERR_PNPM_NO_MATURE_MATCHING_VERSION,明确拒绝 |
精确 5.14.1 + profile pnpm-workspace.yaml 里 minimumReleaseAgeExclude: [dsh-connect-workbuddy] | 5.14.1 ✅ |
| 不设该项(pnpm 默认) | 浮动 ^5.13.1 → 5.14.1 ✅ |
想立刻用上新版,请写精确版本号:dsh plugin --profile desktop add dsh-connect-workbuddy@5.15.1;若被等待期拒绝,见 docs/PINNED-UPGRADE-LAG.md §2 的两条路(等窗口过去,或加那行 exclude)。过了等待期之后,浮动范围会自然跟上。
插件在 Web、Desktop、TUI 三种界面下均可运行,按你使用的 profile 选择对应命令:
dsh plugin --profile web add dsh-connect-workbuddy # Web
dsh plugin --profile dsh-tui add dsh-connect-workbuddy # TUI
更新
市场里显示几就是几,那是快照;npm view dsh-connect-workbuddy version 与 Releases 才是准的。
2026-10-30 15:00 实测:社区市场索引里本插件还只列到 5.13.1,而 npm latest 当时已是 5.14.1(现在是 5.15.1)。滞后是按包各自发生的——同一份索引里 @changfenhuang/dsh-genui 0.15.0、dsh-client-auto-continue 0.17.0、billion-context 0.1.209 都是新的,所以「别人那个包有新版」不能证明「你这个包也该有」。
手动更新(一条命令):
dsh plugin --profile desktop add dsh-connect-workbuddy@5.15.1
dsh plugin --profile desktop exec dsh-connect-workbuddy doctor # 第一行就是版本
让 AI 更新:把下面这段整段发给 DSH 里的 AI,它会把版本号、安装与核对都跑完,只在需要改 pnpm 配置时回来问你:
把 dsh-connect-workbuddy 更新到 npm 上的最新版,按下面的步骤来,不要跳步:
1) 先跑 `npm view dsh-connect-workbuddy version` 确认当前 latest 是多少;
2) 用精确版本号安装:`dsh plugin --profile desktop add dsh-connect-workbuddy@<上一步得到的版本>`;
3) 如果报 ERR_PNPM_NO_MATURE_MATCHING_VERSION,不要改用浮动范围硬装(那会静默装回旧版),
告诉我这个版本还要等多久,或者问我是否要在 profile 的 pnpm-workspace.yaml 里加
minimumReleaseAgeExclude: dsh-connect-workbuddy;
4) 装完跑 `dsh plugin --profile desktop exec dsh-connect-workbuddy doctor`,
把第一行的版本号念给我听;
5) 提醒我重启/重载 DSH(bundle 的 host 半边在启动时加载)。
我的 profile 是 desktop。
别让 AI「反复重试到成功」:等待期内重试只会拿到同一个拒绝,pnpm 没有一次性的绕过开关。
怎么确认真的升上去了:doctor 的第一行(WorkBuddy Connect 5.15.1 on …),或 status --json 顶层的 version。不要找 /workbuddy status(本插件注册的斜杠命令是 /codebuddy,用于转发本机 CodeBuddy CLI),也不要在卡片「诊断」栏找版本号(那一栏只有区域 / 域名 / 服务账号 / 登录有效期 / 目录模型数 / 已启用数 / 账号池 / 最近一次失败八行,没有版本行)。装完重启或重载再判断。
细节(市场索引与 pnpm 等待期分别怎么拦住你、两条绕过路径)见 docs/PINNED-UPGRADE-LAG.md,同一份正文也贴成了长期置顶 issue。
平台支持
三个平台都是受支持目标;下列差异是实现事实,不是「尚未支持」的委婉说法。要改这一节,请同时改 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 |
| 凭据库(vault)的文件权限 | mode: 0o600 不生效,权限由继承的 DACL 决定 → 写入前后用 icacls 显式加固(只允许 所有者 / SYSTEM / Administrators)。doctor 会回读并报告 protected / exposed / unverified | 由 mode 0600 生效 | 由 mode 0600 生效 |
| CI 覆盖 | windows-latest 跑完整套件 | 开发者本机(macOS) | ubuntu-latest 跑完整套件 |
Windows 上需要知道的四个具体点:
- 账号改动可能保存失败(profile 的配置文件
cordis.patch.yml 被占用)。插件会在写入后回读校验:确认落盘才显示「已清除」,失败则明确报错并保留原选择。遇到提示时关闭占用该文件的程序后重试。详见已知限制。
- 凭据路径已部分经真机验证。候选列表按平台约定推导并有单元测试钉住(含
LOCALAPPDATA / APPDATA 缺省与空白串两种回落)。已在真实 Windows 11 上确认:%LOCALAPPDATA%\CodeBuddyExtension\Data\Public\auth 与 %LOCALAPPDATA%\Programs\WorkBuddy\WorkBuddy.exe 命中,加密凭据的 keyId 与登记向量逐字一致。其余候选仍未经真机逐项验证——若你的凭据或 App 不在默认位置,用 WORKBUDDY_AUTH_FILE / WORKBUDDY_APP_EXECUTABLE 指定,并欢迎回报现场。
- 加密凭据(5.6.0 起)需要桌面 App 在场,Windows 与 macOS 同样如此;App 装在自定义目录(如
E:\WorkBuddy\WorkBuddy.exe)时用 WORKBUDDY_APP_EXECUTABLE 指定。
- 凭据库里存的是明文 token(issue #183)。插件的 vault 目录
<DSH home>/workbuddy-vault/<region>/<accountId>.json 保存解密后的 accessToken / refreshToken,未加密。Windows 上从 5.16.0 起会在写入前后用 icacls 把该目录限制为所有者 / SYSTEM / Administrators 可读,并用 doctor 的 Credential vault ACL 一行回读确认;但权限收紧不等于加密——管理员、SYSTEM 服务,或已取得你用户令牌的攻击者仍可读取。若你需要磁盘加密,请把 DSH home 放在 BitLocker / FileVault 卷上。POSIX 平台依靠 mode 0600。
在服务器上使用:搬运已登录的凭据
在无桌面环境的服务器(或任何不想安装 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」——这一层是真实的收益(渠道校验不会再因为提示词里有句读而拒你),但它不等于「免封」。两条路是互补的容错,不是替代。
前提
- 本机装了 CodeBuddy CLI(npm 包
@tencent-ai/codebuddy-code,codebuddy 与 cbc 是同一个入口)。装在非标准位置就用 cliBridge.binary 指绝对路径。
- 它已登录。插件按这个顺序找凭据:环境变量
CODEBUDDY_AUTH_TOKEN → 本插件已经维护的那份 vault($DSH_HOME/workbuddy-vault/<region>/<account>.json)。也就是说,你为了用这个插件登录过的账号,CLI 路线直接复用,不需要再登录一次。(真机验证:CLI 的 getAuthenticationAttributesSync 把 CODEBUDDY_AUTH_TOKEN 排在自己的登录文件之前。)
- 区域优先于新鲜度:国内账号的令牌对
copilot.tencent.com 有效,国际版的令牌在那边会 401(真机实测:401 Authentication required ... (target: https://copilot.tencent.com))。所以默认顺序是 cn,global,某个区域失败才换下一个。
打开
{
"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)序列化出去。
- 只在真有回合在跑时出现。 没有回合时组件渲染
null,那一行一个像素都不变——这条路由是无条件注册的(开关关着也在),客户端才能把「没有回合」和「没有这条路由」区分开,而不用知道开关状态。
- 只描述本会话的回合。 live board 是宿主级的(一条路由回答进程里所有会话),所以座位
conversation.composer.dock 声明成 scope: 'session'、inject 第一个参数就是 sessionId,胶囊只留下这一行的行。不这么过滤,A 会话输入框下面就会描述 B 会话(甚至另一个标签页)的回合——一条关于你没在看的目录的字,计数器还在动。宿主没给 sessionId 时(老宿主,或直接挂载组件的测试)不过滤:少一颗诊断读数比假装没有回合更糟;宿主拿不到 agent.id 而标成 sessionId: '' 的行也照样保留,那同样是「真有人在干活」的证据。
- 不按供应商门控。 额度胶囊只在会话的模型是 WorkBuddy 供应商时出现,因为它报的是那家供应商的额度;这颗胶囊报的是一个本地进程,而 CLI 路线刻意与模型无关——从 DeepSeek 会话里发起也应当看得见。
cliBridge.enabled 也不需要单独判断:关着时不会有回合开始,板子是空的,胶囊自然什么都不渲染。
- 秒数在本地走,不额外发请求。 5 秒轮询一次文档,秒数由 1 秒的本地 tick 从
startedAt 推出来。多个会话共享同一个在途请求(模块级缓存 + inflight 去重),N 个标签页不会变成 N 倍轮询。
- 时钟偏差按宿主时间校正。
startedAt 是宿主时钟;浏览器时钟慢一分钟就会算出负数。文档里带 serverTime,客户端据此算偏移——而且只在拿到新文档时重算(缓存的文档带的是旧 serverTime,每次都重算会把已经过去的时间从偏移里再减一遍,计数器就会冻在原地)。
- 工具次数是近似值。 从 CLI 的 NDJSON 里数
tool_use 帧得来,phase 是 starting / running / finishing 三态;lastTool 是最近一次工具名。这些是给人看的进度信号,不是计费依据。
- 它不会撒谎,也不会赖着不走。 文档里
serverTime 不是有限数字时整份作废(否则标签会印出 NaNs);路由连续三个周期(15s)失败就清空,不会在输入框上留一颗描述着已结束回合的幽灵。同一个会话里多条回合同时跑时它显示最新的那条,+N 表示还有几条,目录只进 tooltip——因为这条 dock 只有一行。
这颗胶囊走的是插件的客户端入口(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 对话。 = 自动续接(走 ), = 每次都是独立的新对话。显式传 参数时以参数为准。 |
边界
- 它看不到这段对话,但看得到自己的上一轮。 CodeBuddy 是另一个 agent,DSH 这边的对话历史它拿不到。不过从 4.14 起,同一个会话里连续两次调用默认是同一条 CodeBuddy 对话(
sessionContinuity=auto):第二次知道第一次说了什么、读了哪些文件,不用从零重读仓库。所以「接着刚才那个思路改」现在可行,但它依据的是 CodeBuddy 自己的上下文,不是你在这里说的话——真要它知道你说过什么,还是得写进指令。想回到「每次都独立」就设 sessionContinuity=off。
- 它有写权限的门槛,没有沙箱。 默认
dontAsk 只读;调成 acceptEdits 之后它真的会改工作区里的文件。允许根只约束它在哪个目录里跑,不约束它在这个目录里做什么。
- 两套账。 CodeBuddy CLI 的配额和 DSH 的模型额度是两回事。指令本身不花 DSH 模型 token,但默认的回注会让会话模型多跑一轮,那一次是真的 DSH token。描述里写了成本信号,也设了每小时上限,防止「回注 → 模型再问 → 再回注」这种循环。
- 失败就停,不换着账号试。 只有明确识别出凭据被拒(401 /
Authentication required / /login)才会换下一个账号;超时、崩溃、找不到 CLI 都直接报错——十几个 vault 账号逐个重试一遍 900 秒,比失败本身更糟。换号也不再看模型说的话:早期版本把 CLI 的正文也扫了一遍,导致「评审一段包含 HTTP 401 的代码」被误判成凭据失效。
- 超时会连子进程一起杀。 CLI 会派生
git / npm,只 kill 直接子进程会留下孤儿。POSIX 上按进程组发信号,Windows 上走 taskkill /T。
- 要重启。 新增的 bundle 与 JS 改动都要重启 DSH 宿主才生效。
命令行
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 输出机器可读格式。
设备身份相关的环境变量
出站身份档位(wireProfile)与设备头档位(deviceTier)都是卡片配置项,不是环境变量。下面这些是给"配置项够不着的场景"留的旁路:
| 变量 | 默认 | 说明 |
|---|
WORKBUDDY_DEVICE_TOKEN_FILE | 未设 | 「虚拟值」档外部 X-Device-Token 的文件源:指向别的工具维护的 token 文件(裸值或 {"token":…} 信封都吃),5 分钟重读、4 KB 上限;token 字节不进日志与 doctor。与卡片配置 deviceToken.file 同义,配置为空时读它 |
WORKBUDDY_HEADER_TRACE | 未设 | 设 1 后 doctor 多打一节:每条请求路径将要发出的头名清单(只有名字,永远没有值)——排查"这一档到底发了什么"用 |
WORKBUDDY_TURING_SDK_DIR | 未设 | 「真实值」档的 Turing Shield SDK 目录。不设置时插件按桌面端安装形状自动发现(/Applications、%LOCALAPPDATA%、卸载注册表等);装了桌面端却仍报「未找到」时,用它显式指路。卡片「SDK」分区也能直接填(不用改环境变量、保存即生效),本变量优先级更高。取不到真实设备时的行为见下一行 |
WORKBUDDY_TURING_CHANNEL_ID | 109144 | SDK 的渠道号,与桌面端 product.json 一致 |
WORKBUDDY_TURING_PRODUCT_NAME | WorkBuddy | SDK 初始化自称的产品名 |
WORKBUDDY_TURING_PRODUCT_VERSION | 插件版本 | SDK 初始化自称的产品版本 |
WORKBUDDY_TURING_KEYCHAIN_STORAGE | 未设(=关) | 设 1 允许 SDK 启用其自身的设备缓存存储;默认关闭——插件不把自己的设备状态写进用户机器的钥匙串 |
为什么这三条命令能在「什么都没装」的机器上跑(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
link: 安装必须先构建,否则 DSH 会拒绝加载
lib/ 是构建产物且被 gitignore,而 link: 安装不执行包的生命周期脚本(prepare 不会跑)。
于是从一份没构建过的源码检出安装时,package.json 里声明的 main / exports 指向的文件并不存在,
DSH 的插件市场据此判定:
声明的入口产物缺失(源码检出或构建被拦),下次启动会失败
这个判定来自 dshmarket 的 entryArtifactExists()——它只读安装目录里的 main 与 exports,
文件在不在就是全部依据。所以:
pnpm install # prepare 会构建一次(lib/ 出现)
# 或只构建
pnpm run build
改完源码也要再 pnpm run build 一次(或重跑 pnpm install),然后重启 DSH Desktop。
pnpm install 之后仍报同样的话,先跑这一条自查——它打印每个被声明的产物在不在,
并区分「从没构建过」和「manifest 与构建不一致」两种原因:
pnpm run verify:entries # 当前仓库
node scripts/verify-entry-artifacts.mjs <已安装的目录> # 具体某个安装副本
从 npm 安装(dsh plugin add dsh-connect-workbuddy)不需要这一步:发布包里 lib/ 已经在,
prepack 也只负责把它重新生成一遍。
写脚本调用 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 合并收录(2026-08-30)。
⚠️ 条目合并之后不会自己更新。 #3812 收录时插件还在 2.0.15,注册表上挂的描述因此一直停在当时的能力:没提国内版/国际版双供应商、没提每次刷新读取积分倍率、没提账号池自动换号,也没说明签到是手动的(签到会真实领取奖励,只在用户点按钮时执行)。插件每次有大的能力变化,条目也要另开一次 PR 跟上——注册表评审明确「描述必须属实、会对着代码核」。当前那条更新是 PR #10281。
收录目录规范(依据 contributing.md):
- 每个插件一个 YAML:
data/plugins/<owner>__<repo>.yml,字段为 url(与仓库地址完全一致)、name(owner/repo)、category(有效值之一,本项目为 model)、description.en(必填,以句号结尾)/ description.zh(可选);描述中含 : 必须加引号。
- 仓库必须声明
dsh.bundle manifest(本项目 dsh.bundle.patch: ./cordis.patch.yml),且被 dsh-plugin topic 标记;只声明 dsh.client 是最常见的被拒原因。仓库创建满 1 天由 CI 自动检查(没有提交数门槛,那条已废除)。
- 两个 README(
README.md / README.zh.md)由脚本生成,不得手工编辑;修改 YAML 后执行 node scripts/generate-readme.mjs 重新生成。
- 一个 PR 最多收录 3 条;只改自己的条目,不动其他插件。
市场卡片中的截图与 GitHub README 徽章是两套机制:
- GitHub 徽章由本 README 顶部的 Shields/dshfind 图片链接生成。
- 市场截图按当前约定由插件自己仓库根目录的
screenshots.json 声明(本项目声明了 screenshots.json,含使用界面截图),注册表 data/screenshots.json 是旧约定下的回退,不再添加新键(本项目已在提交前撤销该文件的改动)。
- 市场图标在注册表条目里用可选的
icon: 字段声明(本项目指向 icons/dsh-connect-workbuddy-128.png):必须是 GitHub 托管的位图(png/jpg/jpeg/webp),SVG 与第三方图床会被拒;不写则回退到作者头像。它并不是 npm package.json 的通用字段,也不是 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)。改一处就要同改三处。
已知限制
-
依赖 WorkBuddy 客户端接口(非官方开放 API),WorkBuddy 更新后插件可能需要随之调整。
-
账号切换基于 WorkBuddy 桌面端留下的历史 auth 文件(App 自身的备份产物),并非官方多账号 API。默认行为仍是跟随 App 当前登录;切换账号是显式选项,且历史凭据可能因 App 清理或退出登录而失效。
- 显式选择的账号若在本机消失(App 更换登录或清理备份文件),插件不会静默改选其他账号——那样会让账单落到另一个账号上。卡片会明确说明「保存的账号已不存在」,此时重新选择一个账号,或点「跟随 App 当前登录」清除该选择即可恢复。重新登录桌面端 App 无法修复这种状态:凭据本身是好的,失效的只是保存下来的账号标识。
- 从 2.0.0 之前的版本升级而来的用户,设置里可能仍留有旧版的顶层
accountId(迁移来源)。它只作用于其所属区域,且该区域被显式清除(accounts.<区域> = "")后即不再生效——否则清除掉的账号会在下次启动时被静默恢复。卡片会显示每个区域当前是「使用保存的账号」还是「跟随 App 当前登录」:清除后回到的默认账号往往与刚清掉的账号是同一个,没有这行提示就无法分辨清除是否生效。
-
Windows:profile 的配置文件 cordis.patch.yml 被占用时,账号改动可能无法保存。DSH 用「写临时文件 + 覆盖替换」的方式更新该文件,而杀毒软件、OneDrive 等同步盘或正在打开该文件的编辑器会短暂锁住它;重试耗尽后,写入失败不会让 set() 报错。插件因此会在写入后回读校验:确认落盘才显示「已清除」,失败则给出明确错误并保留原来的选择(不会显示一个旧选择默默反驳的假确认)。遇到该提示时,关闭占用该文件的程序后重试即可。
-
Windows / Linux 的凭据默认路径按平台约定推导。已在真实 Windows 11 上确认 %LOCALAPPDATA%\CodeBuddyExtension\Data\Public\auth 与 %LOCALAPPDATA%\Programs\WorkBuddy\WorkBuddy.exe 命中,其余候选与 Linux 路径仍未经真机验证;必要时可通过环境变量 WORKBUDDY_AUTH_FILE 指定实际位置。
-
独立 CLI(dsh plugin … exec)不带宿主提供的 peer 包,因此插件在宿主之外运行时会回落到自带的原子写实现(src/host-peers.ts)。它转写了宿主的行为契约(锁文件命名 <file>.lock、Windows 的 EACCES/EBUSY/EPERM 重试与节奏),并且只在这两个包解析不到时才启用——宿主内、以及仓库检出里都仍用宿主/依赖里的那份。doctor 会报告当前用的是哪一份。详见命令行一节的 issue #47 说明。
-
加密凭据依赖桌面 App 在场:新版桌面端(5.6.0 起,macOS 与 Windows 同样如此)加密了 auth 文件里的 token 字段,插件需要调用该 App 自身的原生绑定才能取回字段密钥。因此 App 若未安装、被卸载,或安装在探测路径之外(Windows 上会自动回退查安装注册表,仍找不到时用 WORKBUDDY_APP_EXECUTABLE 指定),加密文件就读不出来——此时账号会显示为未登录,而不会退化成读出一个残缺的 token。doctor 会报告这项能力是否可用。该密钥是构建期常量,随 App 版本可能轮换;插件每次向本机 App 现取,不缓存到磁盘,所以 App 升级后无需升级插件。
- App 可执行文件按 bundle 自身声明定位,不按 App 名猜:WorkBuddy 的 macOS bundle 里
CFBundleExecutable 是 Electron(不是 WorkBuddy),因此插件读取 bundle 的 Info.plist 来决定二进制名。国内版 WorkBuddy.app 与国际版 WorkBuddy AI.app 都在候选内;App 被归入 /Applications 子目录(如 /Applications/IDE/WorkBuddy.app)时,插件还会向下扫一层并用 bundle id 确认身份后才使用——因为同名的其他 Electron 应用而误启动它。
免责声明
请完整读完后自行决定是否安装和使用。(本文开头另有一条风险提示,两条说的是同一件事。)
- 第三方接入存在账号风险。 官方服务条款对未经授权的第三方接入设有限制。使用本项目可能导致账号或功能受限、账号封禁、服务暂停或终止。能够正常调用、正常扣费,不代表获得官方授权或不存在账号风险。
- 本项目仅供个人学习和研究使用,仅驱动使用者自己的 WorkBuddy/WorkBuddy AI 账号在本机调用,请勿用于商业用途或超出个人合理使用的场景。
- 使用本项目不保证服务持续可用、账号不受限制,或积分展示始终准确。开发者无法控制平台的限制、处罚或服务调整;上游一旦收紧限制,插件可能失效。
- 请充分了解上述风险,并自行决定是否安装和使用。 因使用本项目产生的账号限制、额度损失、服务中断等后果,由使用者自行承担;如不能接受,请使用官方 WorkBuddy 客户端——那始终是最安全的选择。
- 本项目作者不对任何因使用或滥用本项目产生的直接或间接损失负责。
- 本项目与腾讯、WorkBuddy、DeepSeek 均无关联,未获其授权或认可;文中出现的名称仅用于描述兼容关系,商标权利归各自所有。
致谢
本项目的实现建立在他人已公开的工作之上。以下内容如实标注来源与许可证,我们对此保持充分尊重:
连接内核的参照
- corrinehu/dsh-workbuddy-connect(MIT,Copyright (c) 2026 Corrine Hu)— 本项目的主要参照。WorkBuddy 接入 DeepSeek Harness 的完整可行方案由该项目首先验证:桌面端凭据的发现与刷新机制、上游协议与请求头约定、loopback shim 的入站加固、pi-ai provider 的装配方式、以及状态诊断 CLI,均以其为参照。本项目在保留这些已验证能力的基础上重写,着重改善使用体验。
- Sliverkiss/workbuddy2api(MIT)— WorkBuddy 上游协议(
copilot.tencent.com 的 wire behavior)的参照实现,经 dsh-workbuddy-connect 转引。
- hawklithm/workbuddy2api(MIT,Copyright (c) 2026 Mayer)— 本项目的致谢名单里唯一一项「代码移植」而非「思路参照」:其
src/codebuddy_proxy/dsml_parser.py 是一份代理同一 WorkBuddy/CodeBuddy 上游的 DSML 工具调用解析器,本项目把它移植为 src/dsml-recovery.ts(标签状态机、忽略区域判定、缺包裹修复、参数递归解析、流式缓冲),并在其上补了它没有的两道闸(工具名必须在本次请求声明的工具里、无声明工具完全不做恢复)。三处有意偏离逐条写在该文件头部,义务声明见 THIRD_PARTY_NOTICES.md。
自动续跑的语义参照
插件呈现与结构的基线
说明
以上项目的版权归各自作者所有。本项目采用借鉴设计思路 + 独立实现的方式,未整体复制任何参考项目的源码;关键模块均为独立编写,并在源文件头部注释中标注了所参考的具体项目与模式。若你发现本项目的标注有遗漏或不当之处,请提交 issue,我们会立即更正。
第三方开源依赖
本项目参考的开源项目、其许可证与合规说明,完整记录见 THIRD_PARTY_NOTICES.md。引入新的与 WorkBuddy 接入相关的外部依赖或复用其他项目代码时,请同步更新该文件并遵守对应许可证要求。
许可证
本项目采用 MIT 许可证,版权归属:Copyright (c) 2026 LaoDing。