メインコンテンツへスキップ
C

dsh-account-pool

cliii-one/dsh-account-pool

把多个 WorkBuddy / Trae 账号汇成账号池接入 DeepSeek Harness:自动切换、限流熔断退避、凭证安全落盘

インストール

dsh plugin --profile web add github:cliii-one/dsh-account-pool

README

dsh-account-pool

多个 WorkBuddy / Trae 账号汇成账号池接入 DeepSeek Harness,并在账号之间自动切换

某个账号被限流、积分耗尽或掉线时,自动换下一个账号继续,无需手动干预。 两家上游(腾讯 CodeBuddy 系的 WorkBuddy、字节的 Trae)共用同一套选号、 失败换号、冷却熔断与用量统计。

它解决什么问题

单个 WorkBuddy 账号会限流、会耗尽积分,一个请求被卡住整轮对话就断。 本项目在插件内部维护一个账号池:

  • 多账号并行登录——插件内走 OAuth 设备流,扫码/点链接即可添加,不依赖 WorkBuddy 桌面端 App
  • 自动切换——按三因子加权选号(积分比例、闲置补偿、成功率),429 限流 / 402 欠费 / 会话失效时自动换号重试
  • 冷却与熔断——限流的账号按指数退避冷却(600 秒起,封顶 2 小时),连续失败达阈值熔断
  • 会话粘性——同一会话尽量复用同一账号,多轮对话不跳号

架构

插件自带「网关」,不依赖任何外部服务:

DSH LLM seam
  → pi-ai adapter(本插件注册的 provider 路由)
  → loopback shim(127.0.0.1:随机端口,本插件内置)
      ├─ 选号器:三因子加权 + 冷却 + 熔断 + 会话粘性
      └─ 失败自动换号重试
  → copilot.tencent.com / www.workbuddy.ai

为什么需要 shim:pi-ai provider 只认「一个 baseURL + 一个 apiKey」, 而切换必须发生在每次请求。所以在回环地址起一个 OpenAI 兼容端点, pi-ai 指向它,由它决定本次请求用哪个账号。

安全:shim 只绑 127.0.0.1,用进程内随机 secret 做鉴权,真实的 WorkBuddy token 不交给 pi-ai。入站请求校验 Host/Origin 必须回环 (防 DNS rebinding 与跨站请求),secret 用常量时间比较。

安装

dsh plugin --profile web add dsh-account-pool

安装后需要重启 DSH 进程(bundle 在启动期加载)。

使用

  1. 重启后打开 设置 → WorkBuddy 账号池
  2. 点「添加账号」,在浏览器中打开给出的链接完成登录
  3. 登录完成后账号自动加入池子,模型出现在 DSH 的模型选择器里
  4. 重复第 2 步可以添加多个账号;它们会被自动调度

账号列表里可以看到每个账号的状态(可用 / 冷却 / 熔断)、积分、成功率和凭证到期时间。 可以手动「解冻」某个账号,或「移除」它。

导入已有 auth 文件

已有凭证(例如 workbuddy2api 网关的 auths/*.json,或桌面端导出的文件)不必重新登录, 设置页点「导入 auth」即可,支持两种格式:

嵌套格式(网关与官方客户端落盘的形态):

{
  "auth": {
    "accessToken": "eyJ...",
    "refreshToken": "eyJ...",
    "expiresAt": 1794467101,
    "domain": "www.codebuddy.cn",
    "realm": "cn"
  },
  "account": { "uid": "...", "nickname": "..." }
}

扁平格式(手写方便,字段平铺在顶层):

{
  "accessToken": "eyJ...",
  "refreshToken": "eyJ...",
  "expiresAt": 1794467101,
  "domain": "www.codebuddy.cn",
  "uid": "...",
  "nickname": "..."
}

导入方式有两种:

  • 粘贴 JSON:把文件内容贴进对话框
  • 服务器路径:填文件路径,或填目录(会自动扫描里面所有 .json 逐个尝试)

行为说明:

  • expiresAt 兼容 Unix 秒(auth 文件)与毫秒(插件内部),自动识别
  • realm 时按 domain 推断区域
  • 区域不匹配的会被跳过并报明原因,不会把国际版凭证塞进国内版池子
  • 目录里损坏的文件不会中断整批导入,失败的会逐个列出

每日任务

设置页有两个页签:账号(账号池与运维)与每日任务

任务清单

页面按任务列出,每行显示完成数量已完成账号数 / 总账号数):

任务判定依据频率
签到调签到的返回(幂等)每天一次
猫猫旅行daily_limit_reached 或在途每天一次
连登兑换上游给的档位解锁状态满 7/14/28 天
抽奖当前抽奖次数次数来自兑换

全完成显示绿色 3/3 已完成,部分完成显示黄色 2/3,没做显示灰色 0/3

账号明细表里还有一列「今日获得」,按账号显示本次到手的积分:

情况显示
还没执行过未执行
算出收益+150(有收益时绿色)
收益为 0+0——「确实没加」与「不知道」是两回事
算不出,不编造数字

数值来自执行前后的余额差:签到接口只回成功/失败、不回奖励数量, Trae 的任务流也没有计费字段,余额差是唯一可靠的真值。只把正差值 算作获得(负差值可能是期间用了额度)。

只读与执行分离

打开页面只做只读查询,不会替你签到或派出。 点「全部执行」才真正执行。

一个例外要说明:签到没有只读状态接口,只能靠调用签到本身来判断(幂等,不会重复扣费)。 所以清单里的签到完成数来自上次执行结果,从没执行过时显示「未执行」。

执行顺序

签到 → 连登兑换 → 抽奖 → 猫猫旅行

顺序有意义:

  • 签到放最前——它会让积分冷却的账号恢复可用
  • 兑换必须在抽奖之前——抽奖次数只能从连登兑换获得
  • 账号间串行执行,避免密集请求触发风控

其他

  • 国际版账号自动跳过(签到/旅行/连登都是国内版活动),不发任何上游请求
  • 重复执行是幂等的:上游返回「今天已签到」,不算失败

两个区域

国内版与国际版是两个并行的 provider:

provider区域上游
workbuddy国内版copilot.tencent.com
workbuddy-global国际版www.workbuddy.ai

两者的账号不通用。 上游按登录域签发 token,送到另一个区域的网关会被直接拒绝 (openresty 层返回 HTML 401)。一个账号只属于一个区域,想两边都用必须分别登录。

cordis.patch.ymlregions 里配置启用哪些区域,默认只开国内版。 没有国际版账号时请不要开 global——否则 workbuddy-global 会出现在模型选择器里 却没有任何可用账号。

两边账号各存一份凭据文件,互不覆盖;同时启用时不同会话可以各选一边。

配置

cordis.patch.yml 支持以下字段:

字段默认说明
regions['cn']启用哪些区域,可选 cn / global
softCooldownSeconds600限流后的冷却基数,指数退避封顶 2 小时
breakerThreshold3连续失败多少次后熔断该账号

用量统计

设置页底部有「用量统计」卡片,按 (时间片, 账号, 模型) 分桶记录每一次请求:

指标说明
请求数 / 失败数失败的尝试也计入请求数,这样「重试放大」才看得出来
输入 / 输出 / 总 tokens取自上游 SSE 末尾的 usage
积分消耗上游 usage.credit只有逐次上报消耗的上游才有这一格(WorkBuddy);Trae 的对话流没有计费字段,该指标整块隐藏,而不是显示恒为 0
平均延迟按样本数加权,不是对每桶均值再取平均
按模型明细每个模型的请求、失败、token、积分

Trae 为什么没有积分消耗:它的对话流只有 6 种事件 (metadata / timing_cost / output / extra_info / token_usage / done), 没有任何计费字段。积分接口虽然能给「账号累计已消耗」,但那是账号总量、 不是本页消耗,混着展示会误导,所以不提供。

保留策略:按小时分桶;当桶数超过上限(20 万)时,把超过 90 天的小时桶 折叠为日桶(日桶长期保留)。折叠由桶数上限触发,不是按时间定时执行—— 所以在流量不大的情况下,历史小时桶会原样保留,不会自动精简。

数据落盘到 $DSH_HOME/.account-pool.usage.<区域>.json,防抖 30 秒写入,重启不丢。

凭证自动续期

不需要手动管凭证,插件会自己续。

两个触发点

时机行为
每次请求前取凭据时若发现剩不足 5 分钟,立刻刷新
后台每 6 小时检查所有账号,剩余不足 7 天的提前续上

后台保活是为了应对闲置:只在有请求时刷新的话,长期没人用会导致 access token 过期——这还能靠 refresh token 救回;但如果闲置到 refresh token 也过期,账号就彻底作废、只能重新登录。保活把两个到期时间都往后推。

上游的两个有效期

实测上游返回:

字段时长含义
expiresIn30 天access token 有效期
refreshExpiresIn60 天refresh token 有效期,即「最晚还能换新 token」的时间

refresh 比 access 长一倍,所以保活窗口是充足的。

失败处理

  • 刷新失败但 token 未过期 → 继续用旧的,不因一次网络抖动踢掉账号
  • 并发刷新合并 → 同一账号的并发请求只发一次刷新,不会重复打上游
  • refresh token 轮换 → 上游返回新 refresh token 时会一并保存

凭据存放

凭证落在 $DSH_HOME/.account-pool.<区域>.json,权限 0600。 刷新得到的 token 写回这里,不写入 DSH settings

登出(设置页「全部登出」)会删除对应区域的文件。

环境要求

  • DeepSeek Harness(web profile)
  • Node ^22.19.0 || >=24.0.0
  • 能访问 copilot.tencent.com(国内版)或 www.workbuddy.ai(国际版)

设置界面

插件在 DSH 设置里注册两个独立菜单

菜单内容
WorkBuddyWorkBuddy 国内版 / 国际版的账号、每日任务(签到·旅行·兑换·抽奖)、用量统计
TraeTrae 账号、每日任务(签到·积分)、用量统计

两者是平级的独立分区,互不混杂——各自只显示自己的区域与账号。

Trae 支持

插件同时支持 Trae(TRAE SOLO),与 WorkBuddy 并列成为独立的上游:

WorkBuddyTrae
上游copilot.tencent.comtrae-api-cn.mchost.guru
认证标准 Bearer自定义 Cloud-IDE-JWT
设备标识需要 machine/device id
登录方式浏览器授权,自动回调需粘贴回调链接
每日任务签到/旅行/兑换/抽奖签到 + 积分

两边共用同一套选号、失败换号、冷却、用量统计与安全 shim, 只有「上游协议」这一层不同(适配器在 lib/trae-*.js)。

Trae 登录

登录链接的参数必须与官方客户端完全一致——缺一个都可能走错登录流程: 少了 auth_type / login_channel / login_version 等字段时,Trae 授权页 返回的内容明显不同(实测差约 4.5KB),表现为登录完成后不跳转回调, 用户永远看不到带 refreshToken 的地址。

对齐后的参数:login_version auth_from login_channel plugin_version auth_type client_id redirect login_trace_id auth_callback_url machine_id device_id x_device_id x_machine_id x_device_brand x_device_type x_os_version x_app_version x_app_type

Trae 凭证:导入 storage.json

不做网页登录,改为导入 Trae 桌面端已登录的凭证。

原因是网页登录在当前部署下走不通:Trae 授权页把 token fetch 投递到 127.0.0.1:18080(不是导航跳转)。DSH 在 NAS、浏览器在另一台电脑时, 那个 127.0.0.1用户自己的电脑

  • 请求被拒 → 页面提示「网络错误」
  • 因为是 fetch 而非导航 → 地址栏不会出现带 token 的链接
  • 所以「复制地址栏链接」这条路也拿不到东西
flowchart LR
    A["Trae 授权页"] -->|"fetch 投递 token"| B["127.0.0.1:18080"]
    B -->|"NAS 部署时<br/>指向用户自己的电脑"| C["无服务 → 拒绝"]
    C --> D["页面报网络错误<br/>地址栏无变化 ✗"]
操作步骤
  1. 已登录 Trae 的电脑(Windows/macOS)上找到凭证文件:

    Windows: %APPDATA%\Trae CN\User\globalStorage\storage.json
    macOS:   ~/Library/Application Support/Trae CN/User/globalStorage/storage.json
    
  2. 插件设置里点「导入凭证」,对话框默认就是选择文件

    • 直接把文件拖进虚线框,或点「选择文件」按钮选它
    • 文件在你本地被浏览器读取,随表单一起提交——不需要先把文件传到 NAS
    • 也可以切到「粘贴内容」手动粘贴 JSON,或切到「服务器路径」填 NAS 上的路径

三种方式等价,按你的习惯选。文件读取用浏览器标准 FileReader, 不引第三方库;超过 2MB 会提示「可能选错了」(storage.json 通常几十 KB)。

为什么换个机器也能用

解密只用硬编码盐值 + 密文自带的随机数

salt    = SALT_A xor SALT_B
first   = sha512(random)      ← random 取自密文
derived = sha512(first + salt)
key = derived[0..16]   iv = derived[16..32]

没有机器码、没有 DPAPI、没有系统密钥——任何机器上都能解。 明文前 64 字节是 sha512 校验值,用来确认解密正确。

区域由凭证自带的 userRegion 判定(CN / SG),大小写不敏感。

开发自检

npm run check

17 项检查,覆盖静态、运行时与交互三层:

类别检查项
静态语法、import/export 一致性、跨作用域引用、props 完整性、死代码
Host模块加载、路由注册/注销配对、路径唯一、同源校验、定时器 unref
前端client.js 加载、两个页签渲染、所有按钮点击、边界状态
行为并发写入、用量统计口径、选号策略、打包内容

这些检查项都是从实际出过的问题里来的,不是凭空设计的:

检查项对应的问题
跨作用域引用DailyTasksView 直接调主组件的 setLastRun,报 not defined
props 完整性RegionPanel 没收到 onImport,按钮点了没反应
并发写入多个 save 共用 .tmp 文件名,rename ENOENT 丢数据
死代码streamBody 零引用
定时器 unref粘性清理定时器漏 unref,拖住宿主退出

改动后跑一遍,能提前挡住这几类问题。scripts/ 不进发布包。

已知限制

  • 依赖 WorkBuddy 客户端接口(非官方开放 API),上游变更后插件可能需要跟进
  • 账号池状态(冷却、熔断计数)是进程内状态,重启后重置
  • 本插件只负责「接入与调度」,不做自动签到、成长任务等上游活动

参考与致谢

本项目的上游协议细节(端点、请求头、字段名、刷新契约、解密方式) 不是猜出来的,而是在这四个项目的实测结论基础上做的。没有它们, 就没有这个插件——尤其是那些"看起来奇怪但必须这样写"的地方, 每一处背后都有人踩过坑并记录下来。

workbuddy2api-panel

WorkBuddy(CodeBuddy)网关面板,Go 实现。

用到的部分在哪个文件
请求头形态(X-CodeBuddy-Request、设备标识派生规则)lib/headers.js
选号策略(三因子加权 + Top-5 抽签)lib/selector.js
用量统计口径lib/usage.js
积分包解析(CapacityRemain / 周期包三档判定)lib/upstream.js

dsh-connect-workbuddy

同样把 WorkBuddy 接进 DSH 的插件,安全设计上做了正确的示范。

用到的部分在哪个文件
shim 安全模型(回环绑定、随机端口、进程内 secret、Host 校验)lib/shim.js

安全代码不做"改善"——这是它验证过的形态,照做。

dsh-connect-trae

Trae 接入 DSH 的插件,带完整取证文档(docs/ 下有实测记录)。

用到的部分在哪个文件
上游常量(host、APP_IDIDE_VERSIONlib/trae-accounts.js
刷新契约(按 region 分流的 ClientID 与端点)lib/trae-accounts.js
SSE 协议转换(Trae 命名事件 → OpenAI chunk)lib/trae-bridge.js
目录与通道不一致的取证(glm-5.3 为何不可调用)lib/trae-upstream.js
设备身份策略(导入时生成一次、复用而非每请求重新生成)lib/trae-accounts.js

traework2api

Trae 网关,Go 实现,登录脚本带有最完整的实测结论。

用到的部分在哪个文件
凭据解密(storage.json 的 AES-CBC 盐值与布局)lib/trae-storage.js
签到流程与必需的 X-Device-Idlib/trae-accounts.js

这些参考带来的具体判断

有些"为什么不那样写"的答案来自这些项目:

  • glm-5.3 在目录里但不能用 —— Trae 的目录(get_detail_param)与调用通道 (llm_utils_chat)是两个面,前者是全量、后者只服务子集
  • Trae 必须做协议转换 —— 它的 SSE 是命名事件格式,pi-ai 只认 OpenAI 格式, 直通会撑到超时(表现为"很慢、几乎都失败")
  • 签到要带 X-Device-Id —— 缺了回业务码 9004,HTTP 却是 200
  • 周期积分包不能靠 CapacityType 枚举判定 —— 要用三档数据驱动, 否则"刚好用完"的包会算错

免责声明

本项目仅供个人学习和研究使用,仅驱动使用者自己的账号。

使用者需遵守所接入服务(WorkBuddy / CodeBuddy、Trae)的服务条款; 因使用本项目产生的任何后果由使用者自行承担。

本项目与腾讯、字节跳动、WorkBuddy、CodeBuddy、Trae、DeepSeek 均无关联, 未获得上述任何一方的授权或认可。

许可

MIT © 2026 cliii-one

関連プラグイン