Skip to content
dsh.fish
Bundle

dsh-rotakit

Merge every locally signed-in WorkBuddy account into DeepSeek Harness as one auto-failing-over model pool, serving OpenAI Chat, OpenAI Responses and Anthropic Messages from the same pool (multi-account rotation, live credits, daily check-in and model catalog).

Source
2861292267
stars
12 stars
License
MIT
Updated
Updated 11 days ago

Readme

# RotaKit · DSH 官方版 WorkBuddy 积分池

把本机登录过的 **WorkBuddy / CodeBuddy 账号**合并成一个自动故障转移的 DeepSeek Harness 模型池,附带一整套积分自动化。

你在桌面端登过几个号,池里就有几个号 —— 不用手动录入,不用一个个切换。哪个号限流了、积分用完了、令牌失效了,请求自动落到下一个能用的号上。

池子同时说 **OpenAI Chat / OpenAI Responses / Anthropic Messages** 三种协议,所以 DSH、Codex、Claude Code 以及任何 OpenAI / Anthropic SDK 客户端都能直接接进来,共用同一个账号池。

> **来源与署名。** RotaKit 是独立维护的改编版,基于 [`XDTrees/dsh-workbuddy-xdpool`](https://github.com/XDTrees/dsh-workbuddy-xdpool)(MIT,作者 **XDTrees**)。账号凭据解密移植自 [`dingminhua/dsh-connect-workbuddy`](https://github.com/dingminhua/dsh-connect-workbuddy),shim 的加固与错误映射参照 [`corrinehu/dsh-workbuddy-connect`](https://github.com/corrinehu/dsh-workbuddy-connect),两者均为 MIT。原版文档见 [`UPSTREAM-README.md`](UPSTREAM-README.md)。

---

## 功能总览

### 一、多账号池

| 能力 | 说明 |
|---|---|
| **自动发现账号** | 读本机桌面端登录快照(国内 / 国际两套),一个文件一个号,无需手动导入 |
| **按权重选号** | 空闲越久的账号权重越高;取权重最高的前 5 个再洗牌,避免长尾账号被反复抽中 |
| **会话粘性** | 同一对话固定用同一个账号,缓存不失效;账号不可用时自动换绑并固定新号 |
| **在途租约** | 同一账号并发超过 2 个请求时让路,避免把限流风险堆在一个号上 |
| **冷却分级** | 429(限流)与 5xx(网关故障)分开计数 —— 网关抖动不再污染限流历史 |
| **按模型冷却** | 某个模型被限流只影响该模型,同账号其他模型照常可用 |
| **凭据熔断** | 连续 3 次被拒的账号移出池 30 分钟,并标「需重新登录」,不再无声消失 |
| **额度门槛** | 余额低于可服务门槛(默认 50)的账号自动跳过 —— **余额恢复后自动回到池中**,无需任何操作 |
| **积分保留** | 可给单账号设保留线,低于该线不再派活 |
| **手动停用** | 可停用指定账号,与自动跳过分开管理 |
| **选号预算** | 单次请求最多尝试 8 个**不同**账号;同一请求内不会重复选到同一账号 |

### 二、模型目录

- 从上游实时拉取,带**积分倍率、免费 / 夜间折扣标签、图片能力、思考档位、上下文窗口**
- 标记「**无可用账号**」的模型:某模型没人能服务时一眼可见
- 标记「**成本未知**」:上游未公布倍率时不再默默留空
- **目录刷新带重试**:网络抖动不再把正常列表打回兜底
- **实测输出上限**:可发现网关对 `max_tokens` 的静默钳制

### 三、积分自动化

每项任务可单独配置执行时刻,默认:

| 任务 | 默认时刻 | 内容 |
|---|---|---|
| 每日签到 | 09:00 | 领取当日签到积分 |
| 活跃上报 | 10:00 | 上报活跃状态 |
| 任务奖励 | 11:00 | 报名并领取任务奖励 |
| 连登奖励 | 12:00 | 连登档位兑换与抽奖 |
| 猫猫旅行 | 09:00 / 21:00 | 出发与领取 |

**细节处理:**

- **漏跑补跑**:某个时刻在应用关闭期间过去了,启动后立即补跑,不用等到明天
- **签到结果分开呈现**:`今日已签 21/21 —— 本次新领 16 个`。「已签到」与「本次领到积分」是两件事,混在一起会让人误以为有账号失败
- **每账号签到时间**:区分「**本次领到**」与「**发现今天已领过**」两个时刻 —— 上游不提供签到时间,所以只报插件能作证的时刻
- **收益账本落盘**:跨天自动清零,重启不丢

### 四、请求可观测

| 能力 | 说明 |
|---|---|
| **请求记录** | 时间、模型、账号、结果、总耗时、排队、首字、token 数、轮换次数 |
| **排队时长统计** | 按模型给出 p50 / p95 / 平均排队时长 —— 用数据选模型,而不是凭感觉 |
| **输出速度** | 每次请求的 tok/s 与 p50 / 最小 / 最大,按模型聚合 |
| **消耗账本** | 今日 / 近 7 天 / 本月消耗(由插件累积观测;上游无时间窗接口) |
| **请求形状诊断** | 上游 400 时记录请求形状(参数名、消息条数、工具数、字符数) |

### 五、界面

- **双区域标签**:国内版 / 国际版各自独立 —— 账号、积分、模型目录、未保存草稿全部隔离,两边同时生效
- **可折叠列表**:池中账号 / 模型 / 请求记录三块**默认收起**,卡片不占地方
- **响应式布局**:窄窗口(≤640px)自动堆叠
- **主题三态**:跟随系统 / 浅色 / 深色,写在宿主声明主题变量的那一层,切换立即生效
- **状态标记**:冷却中、需重新登录、额度不足、已保留 —— 每种都有自己的原因与对策
- **一键清除**:只要有任何可清除状态(冷却 / 熔断 / 按模型缓存)按钮就出现,点了真的清干净

### 六、协议兼容

池对外同时说**三种协议**,三条路由共用同一套账号轮换、冷却、熔断、积分门槛与上下文压缩:

| 协议 | 路径 | 谁在用 |
|---|---|---|
| **OpenAI Chat** | `/v1/chat/completions` | DSH 本体、普通客户端 |
| **OpenAI Responses** | `/v1/responses` | **Codex**、任何 Responses 客户端 |
| **Anthropic Messages** | `/v1/messages` | **Claude Code**、Anthropic SDK |

翻译层与调度层是分开的:新协议只负责把请求转成 chat 体、把回答转回去,**调度逻辑一行都不重复**。
好处是改轮换策略时三条路由同时生效,不会有人掉队;协议出 bug 也只会是翻译错,不会串到调度。

几个不显然的实现点(都是实测踩出来的):

- **工具调用在 `response.output` 里出现两次** —— Codex 靠读最终响应判断「这轮要不要继续」,
  只在流事件里发 `output_item.done` 而 `output` 数组里没有,Codex 会认为模型只是说了句话,
  于是**结束回合等人输入**,表现为「任务做到一半停了」
- **工具调用的「调用」与「结果」先配对再发送** —— 上游拒绝「结果早于调用」和「调用没有结果」,
  而客户端传来的历史里两种都真实存在(会话被中断、被裁剪过)
- **对话不以工具轮开头** —— 裁剪过的历史第一条可能就是助手工具调用,上游直接拒;
  补一条用户轮在最前面,而不是删掉工具调用(后面的结果是真的)
- **Anthropic 的 `stop_reason` 不是装饰** —— 模型要用工具时必须报 `tool_use`,
  报成 `end_turn` 客户端会认为回合结束,刚拿到的 `tool_use` 块永远不会被执行
- **`max_tokens` 有地板** —— 池里是**推理模型**,思考计入 `max_tokens`。
  实测(同一提示词,各 6 次):`max_tokens=16` 时只有 1/6 能产出内容、`64` 时 3/6、`256` 以上 6/6 ——
  预算被思考吃光时上游返回 `finish_reason: length` 且 **`content` 完全为空**,
  客户端收到一个语法完整、内容为空、无法分辨原因的回复。所以低于 512 的预算会被抬高
  (预算仍是上限不是配额,要 64 token 照样得到简短回复,只是**至少能得到**)

### 七、请求链路

- **固定端口**:国内 8120 / 国际 8121 —— 重载不会换端口,宿主不再「Connection error」
- **只绑回环**、强制 loopback `Host` / `Origin`、常量时间比较令牌、请求体上限 64MB
- **写操作要求 JSON body** —— 这道校验才是真正的防线:跨站的 JSON POST 会被浏览器预检拦下
  (本服务不批准预检),而跨站的**表单** POST 虽然发得出去、也带环回 `Host`,
  却**无法声明 `application/json`**。仅靠 `Origin` 挡不住表单,因为 `file://` 加载的卡片
  和沙箱 iframe 发送的都是 `Origin: null`
- **上下文超限自动压缩重试**,压缩目标取「按窗口」与「按原文一半」的较小值
- **错误归因**:池为空时如实报原因(限流 429 / 额度耗尽 402 / 全部停用 403 / 需重登 401),不再一律 401
- **冷却跨重载存活**:冷却窗口落盘(`.workbuddy-xdpool/cooldowns.json`),
  插件重载不再清空限流惩罚;显式「清除冷却」会同步清掉账本

---

## 用数据选模型(实测结论)

来自真实请求记录,不是推测:

| 模型 | 样本 | 排队 p50 | 排队 p95 | 输出速度 p50 | 倍率 |
|---|---|---|---|---|---|
| `deepseek-v4.1-flash` | 57 | **4.2s** | 6.0s | **263 tok/s** | 0.11 |
| `glm-5.3-flash` | 2 | 6.7s | 23.2s | 40 tok/s | 0.06 |

**小样本提醒**:`glm-5.3-flash` 只有 2 个样本,排队数字分歧极大(1.6–23s),**速度数字则一致**。倍率便宜但速度慢约 6 倍。

卡片里的「按模型排队时长」会持续累积样本,样本多了结论才可靠。

---

## 目录结构

```
.
├── lib/
│   ├── index.js        宿主端:账号池、shim、模型目录、调度器(源码,直接编辑)
│   ├── client.js       浏览器端卡片(源码,直接编辑)
│   ├── protocol-compat.js  Anthropic / OpenAI Responses 协议互译
│   ├── bin.js          CLI:doctor 等诊断命令
│   └── index.d.ts      类型
├── .github/workflows/  打 tag 即发布到 npm(OIDC,无需 token)
├── tools/              发布前检查与回归套件
│   ├── run-suite.mjs           全量回归(332 断言)
│   ├── check-publish.mjs       本机发布前检查(需已安装副本)
│   └── check-package-files.mjs CI 可跑的打包完整性检查
├── cordis.patch.yml    插件注册补丁
├── package.json        清单(dsh.bundle / dsh.client 都在这里)
├── screenshots.json    卡片截图清单
├── assets/             截图
├── README.md           本文件
├── README.en.md        英文版
├── UPSTREAM-README.md  上游原版文档(致谢与出处)
├── AGENTS.md           开发约定(**改代码前先读**)
└── CHANGELOG.md        变更记录
```

**`lib/` 下的文件是源码,不是构建产物** —— 本仓库没有 `src/`、没有构建步骤,直接改 `lib/` 即可。

`tools/` 是开发期脚本,安装和运行都用不到。**日常使用只看 `lib/`。**

---

## 与上游的关系

本仓库是 [`XDTrees/dsh-workbuddy-xdpool`](https://github.com/XDTrees/dsh-workbuddy-xdpool) 的**二次开发**,
按 MIT 许可保留原作者版权。上游是 TypeScript 项目(`src/` → 构建出 `lib/`),本仓库不是。

**两条版本线互相独立**,不对齐、不互认:

| | 上游 | 本仓库 |
|---|---|---|
| 包名 | `dsh-workbuddy-xdpool` | **`dsh-rotakit`** |
| 版本 | 1.8.4 | 2.3.23 |
| 形态 | TS 源码 + 构建 | `lib/` 即源码 |

> **不要往 `dsh-workbuddy-xdpool` 发布。** 那个包名属于上游作者,不是我们的。
> 参见 `AGENTS.md` §零。

---

## 安装

```bash
# 1. 装进 DSH profile(仓库根目录就是插件本体)
#    默认 profile 是 desktop,也可换成你自己的
git clone https://github.com/2861292267/DSH-Official-WorkBuddy-Credit-Proxy.git \
  ~/.dsh/profiles/desktop/node_modules/dsh-rotakit

# 2.(可选)二维码兜底依赖,纯 JS、无原生编译
#    登录链接已能自动打开;只有上游不返回 authUrl 时才回退二维码,此时才需要它
cd ~/.dsh/profiles/desktop/node_modules/dsh-rotakit && npm i qrcode

# 3. 确认 profile 的 bundles 里有它
#    ~/.dsh/profiles/<你的profile>/package.json → dsh.profile.bundles 应含 "dsh-rotakit"
```

**然后重启 DeepSeek Harness。** 模型选择器里会出现 **WorkBuddy XD Pool** 分组。

> **宿主插件不会热重载。** 改了 `lib/` 下的文件必须**完全退出应用再启动**,否则运行中的实例仍在用内存里的旧代码。

---

## 自动更新

卡片的「设置」页有**检查更新**与**更新**按钮。整条链路是:

```
检查更新   拉取本仓库 main 分支的 tarball → 读 package.json 的版本号
           (12 秒超时;只读开头一小段,不下载整个包)
更新       下载整个 tarball → 校验 → 逐个文件替换
回滚       把某次更新前的备份原样放回
```

### 几条设计上的取舍

**只更新运行时真正加载的文件。** `TRACKED_FILES` 列了六个:三个 `.js` 模块、
`bin.js`(`doctor` 命令的全部逻辑)、`cordis.patch.yml`(DSH 据此注册 provider)、
以及 `package.json`。`index.d.ts` 是纯类型、运行时不加载,因此**刻意不在列表里** ——
带上它只会让每次更新多传 94 KB 而不改变任何行为。

**先备份全部,再写入任何一个。** 早先的版本是"备份一个、立刻覆盖一个",
于是更新失败时留下的备份集是**残缺的**;而回滚点又只接受完整的备份集,
结果**最需要回滚的那次更新恰恰没有回滚点**。现在两个阶段分开:
六个文件全部备份完,才开始写第一个。

**写入失败会停下并说清楚是哪个文件**,而不是抛异常。
临时文件 + 同卷 `rename`,所以任何单个文件要么是旧内容、要么是新内容,不会有半个。

**回滚是可以反悔的。** 覆盖前会先把"当前版本"另存为 `.pre-rollback-<时间戳>`。
备份集本身只按它**实际覆盖到的文件**恢复 —— 早于某个文件加入的备份集仍然可用,
恢复它等于"回到那个文件还不存在的版本"。

**备份只保留最近 3 组。** 每次更新会把六个文件复制一份(约 1.1 MB);
成功更新之后自动清理更旧的,否则 `lib/` 会一直长下去。

### 关于「环境不匹配」

| 情况 | 行为 |
|---|---|
| **没装桌面客户端 / 只用了网页版** | 检测不到账号,卡片给出各区域(国内版 / 国际版)的登录指引。**插件读的是桌面客户端留在本机的凭据,网页版登录不会被读到。** |
| **非 Windows** | 跳过注册表探测,改走 macOS bundle 与配置文件路径;两者都没有时安静返回空,不抛错 |
| **GitHub 不可达** | 版本号照常显示(显示本机版本),更新按钮可用,失败信息可读;不会卡在"正在检查更新" |
| **安装目录只读** | 更新失败并明确告知,不留下半写状态 |

---

## 验证方式

每个结论都有对应的可复现验证,核心几类:

| 验证 | 检查什么 |
|---|---|
| **选号逻辑** | 额度门槛、冷却分级、按模型冷却、熔断、租约、粘性、同请求不重复选号 |
| **错误归因** | 每种失败类型映射到正确的状态码与原因 |
| **签到语义** | 「已签到」与「本次领到」分开;已签到标记不虚报积分 |
| **计数口径** | `available` 与实际可选账号一致,不虚高 |
| **界面** | 中英文案 1:1、CSS 类完整、运行时探针、渲染输出 |
| **协议翻译** | 三条路由的事件序列、工具调用往返、非流式与流式分支、`stop_reason` 映射 |
| **写接口鉴权** | JSON 才放行;表单 / 无 content-type / 外域 Origin / 伪造 Host 全部拒绝 |
| **冷却持久化** | 跨重载存活、模型级窗口、过期不复活、reset 同步清账本、多实例合并 |
| **推理预算地板** | 小预算被抬高但**不越过模型上限**(上限是硬界,地板是软界) |
| **打包完整性** | 每个运行时加载的文件都在 `files` 白名单里(`tools/check-package-files.mjs`) |

**回归套件 17 套、332 条断言,全部通过。**

其中协议与安全相关的几套是**实际跑真实 HTTP** 的:起真实的 shim、发真实的请求、
打到真实的 WorkBuddy 上游,而不是对着 fixture 断言。

改完代码后跑:

```bash
node tools/run-suite.mjs          # 全量回归
node tools/check-publish.mjs      # 本机发布前检查(需已安装副本)
node tools/check-package-files.mjs  # 打包完整性(CI 也跑这个)
```

---

## 安全须知

- 插件只**读取**本机桌面端已经存在的登录态,**不发起**任何登录流程。卡片内扫码是**可选操作**,只在明确点击后打开,走的是桌面客户端自己的接口,**没有绕过任何鉴权**,消耗的是你自己的账号与积分。
- 令牌只在**本机内存与回环 shim** 之间传递,卡片上显示的账号一律脱敏。
- shim 只绑 `127.0.0.1`,强制回环来源校验。
- **管理接口的写操作要求 JSON 请求体**:跨站页面发不出 JSON POST(预检不被批准),
  而它发得出的表单 POST 无法声明 `application/json` —— 所以本机的账号启停、冷却清除、
  插件自更新等操作不会被网页越权调用。
- **二维码内容经过净化**:上游返回的登录二维码先按 SVG 解析并剥掉 `script` / `on*` /
  `href` 等可执行部分才注入 DOM,不直接信任上游返回的标记。

---

## 免责声明

本项目**仅供个人学习和研究使用**,仅驱动使用者自己的账号在本机调用。请遵守 WorkBuddy 的服务条款;因使用本项目产生的任何后果由使用者自行承担。

---

## 许可

MIT。上游版权归 **XDTrees** 所有,另有部分代码来自 **dingminhua**、**corrinehu**(均为 MIT),详见 [`LICENSE`](LICENSE) 与文件内的署名注释。

Install

dsh plugin --profile web add github:2861292267/DSH-Official-WorkBuddy-Credit-Proxy

Profile: web

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source