dsh-workbuddy-xdpool 0.4.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +220 -153
- package/LICENSE +1 -1
- package/README.en.md +163 -163
- package/README.md +181 -181
- package/lib/bin.js +110 -10
- package/lib/client.js +109 -13
- package/lib/index.d.ts +72 -8
- package/lib/index.js +186 -27
- package/package.json +126 -109
package/README.md
CHANGED
|
@@ -1,181 +1,181 @@
|
|
|
1
|
-
# DSH WorkBuddy XD Pool
|
|
2
|
-
|
|
3
|
-
<p align="center">
|
|
4
|
-
<a href="https://www.npmjs.com/package/dsh-workbuddy-xdpool"><img src="https://img.shields.io/npm/v/dsh-workbuddy-xdpool?style=flat-square&label=npm&color=cb3837" alt="npm version"></a>
|
|
5
|
-
<a href="https://www.npmjs.com/package/dsh-workbuddy-xdpool"><img src="https://img.shields.io/npm/d18m/dsh-workbuddy-xdpool?style=flat-square&label=downloads&color=cb3837" alt="npm downloads"></a>
|
|
6
|
-
<a href="LICENSE"><img src="https://img.shields.io/github/license/
|
|
7
|
-
<a href="https://github.com/
|
|
8
|
-
</p>
|
|
9
|
-
|
|
10
|
-
[English](./README.en.md) | 中文
|
|
11
|
-
|
|
12
|
-
将 WorkBuddy 桌面 App 里登录过的**所有账号**自动并入一个 **DeepSeek Harness 模型池**:无需任何手动配置,你在 WorkBuddy 桌面登录的每个账号都会成为一个池成员;某个账号被限流(429)时,请求会自动切换到下一个可用账号,实现多账号自动容错轮换。
|
|
13
|
-
|
|
14
|
-
> 与单账号连接插件(如 dsh-workbuddy-connect)的区别:**XD Pool 把多账号当成常态**——它不挑账号、不做手动导入,而是把本机 WorkBuddy 桌面 App 的所有历史登录快照全部纳入一个共享池,用一个 `workbuddy-xdpool` provider 分组对外暴露,模型请求在池内自动 failover。
|
|
15
|
-
|
|
16
|
-
**插件配置卡片(设置 → 插件 → DSH WorkBuddy XD Pool)**
|
|
17
|
-
|
|
18
|
-

|
|
19
|
-
|
|
20
|
-
**模型选择器(国内版 / 国际版 两个独立供应商分组;倍率直接拼进 model.name:DSH 0.1.2 composer 只读 name)**
|
|
21
|
-
|
|
22
|
-

|
|
23
|
-
|
|
24
|
-
**国内版 / 国际版 双供应商(各自独立账号、积分与模型,可同时使用)**
|
|
25
|
-
|
|
26
|
-

|
|
27
|
-
|
|
28
|
-
## 功能
|
|
29
|
-
|
|
30
|
-
- **零配置开箱即用**:安装并启用后,WorkBuddy 桌面 App 里每个已登录账号都会在第一次被请求时自动发现、进入池中轮换。无需在插件里手动录入账号。
|
|
31
|
-
|
|
32
|
-
- **自动容错轮换**:池维护每个账号的 `429` 冷却状态。当某个账号触发限流进入冷却,后续请求会跳过它、落到下一个健康账号;冷却结束自动恢复。所有账号同时冷却时请求才暂停。
|
|
33
|
-
|
|
34
|
-
- **账号健康一目了然**:插件设置卡片显示池健康状态(N 账号 / X 冷却、当前会轮到哪个账号)、每个账号的令牌有效期与冷却倒计时。
|
|
35
|
-
|
|
36
|
-
- **剩余积分实时可见**:卡片按账号展示积分包(`套餐名 · 剩余 / 总量`)与合计剩余(大字绿色高亮),跟随上游实时刷新。
|
|
37
|
-
|
|
38
|
-
- **模型目录直接标注**:卡片列出当前在池内可用的模型,并标注积分倍率(如 `GLM-5.2 · x0.79`)、免费 / 限时免费 / 夜间折扣标签、图片输入能力与上下文窗口,倍率与标签跟随上游 `credits` / `tags` 实时更新。
|
|
39
|
-
|
|
40
|
-
- **每日签到**:卡片在每个账号的积分区下方提供签到按钮,并显示连签天数、每日积分与里程碑额外奖励;一键领取该账号当日签到奖励。多账号可逐个领取,无需先切换账号。领取前会重新查询状态,**今日已领取的账号不会被重复领取**。CLI 亦提供 `checkin` 命令。
|
|
41
|
-
|
|
42
|
-
- **国内版 / 国际版自动适配**:按账号凭据里的登录域名自动选择上游域名——国际版登录(`workbuddy.ai`)走 `www.workbuddy.ai`;国内版(默认)走 `copilot.tencent.com` 与 `www.codebuddy.cn`。同一池内可混用两种区域的账号,各自请求各自区域的上游。
|
|
43
|
-
|
|
44
|
-
- **三种人工动作**:卡片与 CLI 都提供「重新检测账号」(重新扫描桌面登录快照,把新登录的账号并入池)、「清除所有冷却」(立即解除全部 429 冷却)与「每日签到」三个操作。
|
|
45
|
-
|
|
46
|
-
## 安装
|
|
47
|
-
|
|
48
|
-
前置:已安装并登录 WorkBuddy 桌面 App(插件复用 App 的登录状态;多账号 = 在桌面 App 里逐个登录/切换账号即可,每次登录都会被自动吸收进池)。已针对 DSH Desktop host `0.1.2` 兼容。
|
|
49
|
-
|
|
50
|
-
> 与 host 兼容 `0.1.1-rc.2` / `0.1.2` 系:设置节安装会按 host 能力自动选择 `settings.installSection`(0.1.2-rc.1+)或自由函数(更早)。
|
|
51
|
-
|
|
52
|
-
**方式一:从 npm 安装(推荐)**
|
|
53
|
-
|
|
54
|
-
```sh
|
|
55
|
-
# dsh 不在 PATH 时,用 node ~/.dsh/profiles/node_modules/@deepseek-ai/dsh/lib/bin.js 代替 dsh
|
|
56
|
-
dsh plugin --profile desktop add dsh-workbuddy-xdpool
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
> 推荐 npm 安装:pnpm / npm 需要拉取的依赖只有插件自身(**约 1 个包,数秒完成**)。
|
|
60
|
-
> 从 GitHub 源码安装会连带安装开发依赖(构建器、测试框架等数百个包),明显更慢。
|
|
61
|
-
|
|
62
|
-
**方式二:从 GitHub 源码安装**
|
|
63
|
-
|
|
64
|
-
```sh
|
|
65
|
-
dsh plugin --profile desktop add github:
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
**方式三:手动注册 bundle**
|
|
69
|
-
|
|
70
|
-
```sh
|
|
71
|
-
# 1) 安装包(npm 或 GitHub 任选其一)
|
|
72
|
-
dsh plugin --profile desktop add dsh-workbuddy-xdpool
|
|
73
|
-
|
|
74
|
-
# 2) 注册 bundle:编辑 ~/.dsh/profiles/desktop/package.json,
|
|
75
|
-
# 在 "dsh" → "profile" → "bundles" 数组末尾追加 "dsh-workbuddy-xdpool"
|
|
76
|
-
|
|
77
|
-
# 3) 重启 DSH Desktop
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
**本地构建**(开发者):
|
|
81
|
-
|
|
82
|
-
```sh
|
|
83
|
-
pnpm install
|
|
84
|
-
pnpm build # 产出 lib/index.js + lib/index.d.ts + lib/bin.js + lib/client.js
|
|
85
|
-
pnpm test # 26 项测试(自动容错轮换、按(账号,模型)冷却、签到守卫)
|
|
86
|
-
pnpm typecheck # 宿主侧
|
|
87
|
-
pnpm typecheck:client # 客户端
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
> **构建产物已随仓库提交**(`lib/` 不再 gitignore)。因此从 GitHub 安装时不需要任何安装期脚本,不会触发 pnpm 的「构建脚本被拦截,请放行」提示。**改动 `src/` 后请重新 `pnpm build` 并把 `lib/` 一并提交**,否则用户拿到的是旧产物。
|
|
91
|
-
|
|
92
|
-
> 注意:`pnpm install` 需用 pnpm 11(`npx pnpm@11`),必要时加 `--config.confirmModulesPurge=false --config.minimumReleaseAge=0`(pnpm 11 默认的 `minimumReleaseAge` 供应链年龄策略会拦截刚发布的 rc 包)。
|
|
93
|
-
|
|
94
|
-
装好后:模型选择器里会出现 **WorkBuddy XD Pool** 分组;设置 → 插件 → **DSH WorkBuddy XD Pool** 卡片可查看池健康、各账号令牌/积分/签到/冷却,以及「重新检测账号」「清除所有冷却」按钮与每个账号的签到按钮。
|
|
95
|
-
|
|
96
|
-
插件在 Web / TUI profile 下同样可用(`--profile web` / `--profile dsh-tui`)。
|
|
97
|
-
|
|
98
|
-
## 命令行
|
|
99
|
-
|
|
100
|
-
统一用 `dsh plugin --profile desktop exec dsh-workbuddy-xdpool <子命令>` 调用:
|
|
101
|
-
|
|
102
|
-
```sh
|
|
103
|
-
dsh plugin --profile desktop exec dsh-workbuddy-xdpool status # 池账号数/冷却 + shim 状态(--credits 查积分、--json 机器可读、--rates 看倍率)
|
|
104
|
-
dsh plugin --profile desktop exec dsh-workbuddy-xdpool accounts # 已发现账号(--json)
|
|
105
|
-
dsh plugin --profile desktop exec dsh-workbuddy-xdpool doctor # 诊断发现/冷却/上游连通性
|
|
106
|
-
dsh plugin --profile desktop exec dsh-workbuddy-xdpool reset # 立即清除所有 429 冷却
|
|
107
|
-
dsh plugin --profile desktop exec dsh-workbuddy-xdpool checkin # 查询每个账号今日签到状态(--json 机器可读)
|
|
108
|
-
dsh plugin --profile desktop exec dsh-workbuddy-xdpool checkin all
|
|
109
|
-
# 领取所有账号今日签到奖励;也可传账号标签只领一个
|
|
110
|
-
dsh plugin --profile desktop exec dsh-workbuddy-xdpool login # 引导如何在桌面再加一个账号入池
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
## 池里怎么多账号?
|
|
114
|
-
|
|
115
|
-
池走**自动发现**:WorkBuddy 桌面 App 每次登录都会在本机留下一个带令牌的历史快照,XD Pool 扫描这些快照,把每个账号都吸收入池。因此多账号 = 在 WorkBuddy 桌面 App 里逐个登录 / 切换账号即可,之后点卡片「重新检测账号」或重启 DSH,新账号自动成为池成员。
|
|
116
|
-
|
|
117
|
-
若你想对桌面 App 之外的某个登录做**显式快照**(例如临时固定某个账号再验证),也可手动导入:
|
|
118
|
-
|
|
119
|
-
```sh
|
|
120
|
-
# 在 WorkBuddy 桌面 App 登录账号后(key 自己起名):
|
|
121
|
-
dsh plugin --profile desktop exec dsh-workbuddy-xdpool import myKey
|
|
122
|
-
# 查看/删除已导入快照:
|
|
123
|
-
dsh plugin --profile desktop exec dsh-workbuddy-xdpool accounts
|
|
124
|
-
dsh plugin --profile desktop exec dsh-workbuddy-xdpool remove myKey
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
导入快照以 key 的 **MD5 前 8 位**命名落在 `~/.dsh/.workbuddy-xdpool/`(key 本身记在文件里),中文、带 `/`、带空格的 key 都安全;长期使用靠 refresh token 自动续期,失效则回到桌面重新登录后 `import <key> --force` 覆盖。
|
|
128
|
-
|
|
129
|
-
## 配置
|
|
130
|
-
|
|
131
|
-
池的有效配置经插件设置节(`settings.workbuddy-xdpool`)读取,模型设置页可改,改动即时生效:
|
|
132
|
-
|
|
133
|
-
| 字段 | 说明 | 默认 |
|
|
134
|
-
| --- | --- | --- |
|
|
135
|
-
| `authFile` | 覆盖 WorkBuddy 桌面 auth 文件路径(跨平台探测异常时用,等价于 `WORKBUDDY_AUTH_FILE`) | 自动探测 |
|
|
136
|
-
| `cooldownMs` | 单账号 429 冷却时长(毫秒) | `60000` |
|
|
137
|
-
|
|
138
|
-
也可直接写在 `~/.dsh/settings.yaml`:
|
|
139
|
-
|
|
140
|
-
```yaml
|
|
141
|
-
workbuddy-xdpool:
|
|
142
|
-
cooldownMs: 120000
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
## 架构
|
|
146
|
-
|
|
147
|
-
- **宿主侧**(`src/`,DSH 主进程内):
|
|
148
|
-
- `index.ts` —— 注册 `workbuddy-xdpool` provider、`workbuddy-xdpool` 设置节(`settings.installSection`)、4 条同源路由(状态 / 重新检测 / 清除冷却 / 签到)、账号发现与模型目录播种。
|
|
149
|
-
- `accounts.ts` —— `WorkBuddyAccountPool`:读本机 WorkBuddy 桌面 auth 快照、429 冷却、round-robin failover 与 token 刷新。
|
|
150
|
-
- `catalog.ts` / `upstream.ts` —— 上游模型目录(含每模型积分倍率、免费/图片能力标签)、积分查询与每日签到的上游客户端(按凭据域名自动切换国内 / 国际版域名)。
|
|
151
|
-
- `web-status.ts` / `status-paths.ts` —— 卡片消费的同源状态文档与路由;签到是本插件唯一的写操作,按「POST + 回环来源 + 显式 accountId + 领取前二次确认」四重守卫。
|
|
152
|
-
- `bin.ts` —— 上述 CLI。
|
|
153
|
-
- **客户端**(`src/client/`,浏览器卡片,经 `dsh.client` 由宿主加载):折叠卡片外壳沿用宿主内置卡的 `dsm-plugin-card*` 样式语言(`--dsw-alias-*` 主题变量),内容用 `dsm-workbuddy-xdpool-*` 前缀,绝不污染宿主其它卡片;命名空间 `settings.workbuddy-xdpool`。
|
|
154
|
-
- **构建**:`tsdown` 产出 `lib/index.js`(宿主入口)+ `lib/index.d.ts`(类型)+ `lib/bin.js`(CLI)+ `lib/client.js`(CJS,`window.__ModuleLoader__.load` 包裹的浏览器 bundle)。四个产物均随仓库提交,因此安装时不需要任何构建脚本。
|
|
155
|
-
|
|
156
|
-
## 已知限制
|
|
157
|
-
|
|
158
|
-
- **仅使用本机桌面 App 的账号**:池不会、也无法替你发起 WorkBuddy 的登录/扫码(token 由 WorkBuddy 桌面 App 自己的腾讯 SSO 登录铸造并设备绑定)。加池账号 = 在 WorkBuddy 桌面 App 里登录/切换,XD Pool 自动吸收。
|
|
159
|
-
- 依赖 WorkBuddy 客户端接口(非官方开放 API),WorkBuddy 更新后插件可能需要随之调整;若某账号 refresh token 失效,回到桌面重新登录即可。
|
|
160
|
-
- 若 Windows 与 Linux 用户名不同且 Windows 环境变量未传入 WSL,请用 `WORKBUDDY_AUTH_FILE` 或配置节的 `authFile` 指定实际位置。
|
|
161
|
-
|
|
162
|
-
## 免责声明
|
|
163
|
-
|
|
164
|
-
- 本项目**仅供个人学习和研究使用**,仅驱动使用者自己的 WorkBuddy 账号在本机调用,请勿用于商业用途或超出个人合理使用的场景。
|
|
165
|
-
- 使用者需遵守 WorkBuddy 的服务条款;因使用本项目产生的任何后果(包括但不限于账号被限制、额度被清空、服务中断),由使用者自行承担。
|
|
166
|
-
- 本项目作者不对任何因使用或滥用本项目产生的直接或间接损失负责。
|
|
167
|
-
- 本项目与腾讯、WorkBuddy、DeepSeek 均无关联,未获其授权或认可;文中出现的名称仅用于描述兼容关系,其商标权利归各自所有。
|
|
168
|
-
|
|
169
|
-
## 致谢
|
|
170
|
-
|
|
171
|
-
本项目的实现参考了以下已公开的项目,并按其许可证要求保留版权声明。参考方向为**设计思路与既有结论**,代码为独立实现;关键模块在源文件头部注释中也标注了所参考的项目与模式:
|
|
172
|
-
|
|
173
|
-
- [corrinehu/dsh-workbuddy-connect](https://github.com/corrinehu/dsh-workbuddy-connect)(MIT)—— 设置节注册(`settings.installSection`)与 DSH 插件结构、客户端卡片加载机制、桌面端凭据刷新与 loopback shim 加固的核心参照;本项目沿用其「宿主通过 installSection 挂卡片」的打通路径。
|
|
174
|
-
- [dingminhua/dsh-connect-workbuddy](https://github.com/dingminhua/dsh-connect-workbuddy)(MIT,Copyright (c) 2026 LaoDing)—— `dsm-plugin-card*` 卡片样式语言与 `--dsw-alias-*` 主题变量的参照实现;**每日签到**(`/v2/billing/meter/checkin-activity-status` 与 `/v2/billing/meter/daily-checkin`)、积分包聚合口径(月度周期套餐 / 一次性礼包区分)与国内 / 国际版按 `domain` 选择上游域名的做法,参考了该项目已验证的接口形态。
|
|
175
|
-
- [Sliverkiss/workbuddy2api](https://github.com/Sliverkiss/workbuddy2api)(MIT)—— WorkBuddy 上游协议(`copilot.tencent.com` 的 wire behavior)与积分接口的参照实现。
|
|
176
|
-
|
|
177
|
-
以上项目的版权归各自作者所有。本项目采用**参考设计思路 + 独立实现**的方式,未整体复制任何参考项目的源码。若标注有遗漏或不当之处,欢迎提交 issue 指正。
|
|
178
|
-
|
|
179
|
-
## 许可证
|
|
180
|
-
|
|
181
|
-
[MIT](./LICENSE)
|
|
1
|
+
# DSH WorkBuddy XD Pool
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<a href="https://www.npmjs.com/package/dsh-workbuddy-xdpool"><img src="https://img.shields.io/npm/v/dsh-workbuddy-xdpool?style=flat-square&label=npm&color=cb3837" alt="npm version"></a>
|
|
5
|
+
<a href="https://www.npmjs.com/package/dsh-workbuddy-xdpool"><img src="https://img.shields.io/npm/d18m/dsh-workbuddy-xdpool?style=flat-square&label=downloads&color=cb3837" alt="npm downloads"></a>
|
|
6
|
+
<a href="LICENSE"><img src="https://img.shields.io/github/license/XDTrees/dsh-workbuddy-xdpool?style=flat-square" alt="MIT license"></a>
|
|
7
|
+
<a href="https://github.com/XDTrees/dsh-workbuddy-xdpool/stargazers"><img src="https://img.shields.io/github/stars/XDTrees/dsh-workbuddy-xdpool?style=flat-square" alt="GitHub stars"></a>
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
[English](./README.en.md) | 中文
|
|
11
|
+
|
|
12
|
+
将 WorkBuddy 桌面 App 里登录过的**所有账号**自动并入一个 **DeepSeek Harness 模型池**:无需任何手动配置,你在 WorkBuddy 桌面登录的每个账号都会成为一个池成员;某个账号被限流(429)时,请求会自动切换到下一个可用账号,实现多账号自动容错轮换。
|
|
13
|
+
|
|
14
|
+
> 与单账号连接插件(如 dsh-workbuddy-connect)的区别:**XD Pool 把多账号当成常态**——它不挑账号、不做手动导入,而是把本机 WorkBuddy 桌面 App 的所有历史登录快照全部纳入一个共享池,用一个 `workbuddy-xdpool` provider 分组对外暴露,模型请求在池内自动 failover。
|
|
15
|
+
|
|
16
|
+
**插件配置卡片(设置 → 插件 → DSH WorkBuddy XD Pool)**
|
|
17
|
+
|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
**模型选择器(国内版 / 国际版 两个独立供应商分组;倍率直接拼进 model.name:DSH 0.1.2 composer 只读 name)**
|
|
21
|
+
|
|
22
|
+

|
|
23
|
+
|
|
24
|
+
**国内版 / 国际版 双供应商(各自独立账号、积分与模型,可同时使用)**
|
|
25
|
+
|
|
26
|
+

|
|
27
|
+
|
|
28
|
+
## 功能
|
|
29
|
+
|
|
30
|
+
- **零配置开箱即用**:安装并启用后,WorkBuddy 桌面 App 里每个已登录账号都会在第一次被请求时自动发现、进入池中轮换。无需在插件里手动录入账号。
|
|
31
|
+
|
|
32
|
+
- **自动容错轮换**:池维护每个账号的 `429` 冷却状态。当某个账号触发限流进入冷却,后续请求会跳过它、落到下一个健康账号;冷却结束自动恢复。所有账号同时冷却时请求才暂停。
|
|
33
|
+
|
|
34
|
+
- **账号健康一目了然**:插件设置卡片显示池健康状态(N 账号 / X 冷却、当前会轮到哪个账号)、每个账号的令牌有效期与冷却倒计时。
|
|
35
|
+
|
|
36
|
+
- **剩余积分实时可见**:卡片按账号展示积分包(`套餐名 · 剩余 / 总量`)与合计剩余(大字绿色高亮),跟随上游实时刷新。
|
|
37
|
+
|
|
38
|
+
- **模型目录直接标注**:卡片列出当前在池内可用的模型,并标注积分倍率(如 `GLM-5.2 · x0.79`)、免费 / 限时免费 / 夜间折扣标签、图片输入能力与上下文窗口,倍率与标签跟随上游 `credits` / `tags` 实时更新。
|
|
39
|
+
|
|
40
|
+
- **每日签到**:卡片在每个账号的积分区下方提供签到按钮,并显示连签天数、每日积分与里程碑额外奖励;一键领取该账号当日签到奖励。多账号可逐个领取,无需先切换账号。领取前会重新查询状态,**今日已领取的账号不会被重复领取**。CLI 亦提供 `checkin` 命令。
|
|
41
|
+
|
|
42
|
+
- **国内版 / 国际版自动适配**:按账号凭据里的登录域名自动选择上游域名——国际版登录(`workbuddy.ai`)走 `www.workbuddy.ai`;国内版(默认)走 `copilot.tencent.com` 与 `www.codebuddy.cn`。同一池内可混用两种区域的账号,各自请求各自区域的上游。
|
|
43
|
+
|
|
44
|
+
- **三种人工动作**:卡片与 CLI 都提供「重新检测账号」(重新扫描桌面登录快照,把新登录的账号并入池)、「清除所有冷却」(立即解除全部 429 冷却)与「每日签到」三个操作。
|
|
45
|
+
|
|
46
|
+
## 安装
|
|
47
|
+
|
|
48
|
+
前置:已安装并登录 WorkBuddy 桌面 App(插件复用 App 的登录状态;多账号 = 在桌面 App 里逐个登录/切换账号即可,每次登录都会被自动吸收进池)。已针对 DSH Desktop host `0.1.2` 兼容。
|
|
49
|
+
|
|
50
|
+
> 与 host 兼容 `0.1.1-rc.2` / `0.1.2` 系:设置节安装会按 host 能力自动选择 `settings.installSection`(0.1.2-rc.1+)或自由函数(更早)。
|
|
51
|
+
|
|
52
|
+
**方式一:从 npm 安装(推荐)**
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
# dsh 不在 PATH 时,用 node ~/.dsh/profiles/node_modules/@deepseek-ai/dsh/lib/bin.js 代替 dsh
|
|
56
|
+
dsh plugin --profile desktop add dsh-workbuddy-xdpool
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
> 推荐 npm 安装:pnpm / npm 需要拉取的依赖只有插件自身(**约 1 个包,数秒完成**)。
|
|
60
|
+
> 从 GitHub 源码安装会连带安装开发依赖(构建器、测试框架等数百个包),明显更慢。
|
|
61
|
+
|
|
62
|
+
**方式二:从 GitHub 源码安装**
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
dsh plugin --profile desktop add github:XDTrees/dsh-workbuddy-xdpool
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**方式三:手动注册 bundle**
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
# 1) 安装包(npm 或 GitHub 任选其一)
|
|
72
|
+
dsh plugin --profile desktop add dsh-workbuddy-xdpool
|
|
73
|
+
|
|
74
|
+
# 2) 注册 bundle:编辑 ~/.dsh/profiles/desktop/package.json,
|
|
75
|
+
# 在 "dsh" → "profile" → "bundles" 数组末尾追加 "dsh-workbuddy-xdpool"
|
|
76
|
+
|
|
77
|
+
# 3) 重启 DSH Desktop
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**本地构建**(开发者):
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
pnpm install
|
|
84
|
+
pnpm build # 产出 lib/index.js + lib/index.d.ts + lib/bin.js + lib/client.js
|
|
85
|
+
pnpm test # 26 项测试(自动容错轮换、按(账号,模型)冷却、签到守卫)
|
|
86
|
+
pnpm typecheck # 宿主侧
|
|
87
|
+
pnpm typecheck:client # 客户端
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
> **构建产物已随仓库提交**(`lib/` 不再 gitignore)。因此从 GitHub 安装时不需要任何安装期脚本,不会触发 pnpm 的「构建脚本被拦截,请放行」提示。**改动 `src/` 后请重新 `pnpm build` 并把 `lib/` 一并提交**,否则用户拿到的是旧产物。
|
|
91
|
+
|
|
92
|
+
> 注意:`pnpm install` 需用 pnpm 11(`npx pnpm@11`),必要时加 `--config.confirmModulesPurge=false --config.minimumReleaseAge=0`(pnpm 11 默认的 `minimumReleaseAge` 供应链年龄策略会拦截刚发布的 rc 包)。
|
|
93
|
+
|
|
94
|
+
装好后:模型选择器里会出现 **WorkBuddy XD Pool** 分组;设置 → 插件 → **DSH WorkBuddy XD Pool** 卡片可查看池健康、各账号令牌/积分/签到/冷却,以及「重新检测账号」「清除所有冷却」按钮与每个账号的签到按钮。
|
|
95
|
+
|
|
96
|
+
插件在 Web / TUI profile 下同样可用(`--profile web` / `--profile dsh-tui`)。
|
|
97
|
+
|
|
98
|
+
## 命令行
|
|
99
|
+
|
|
100
|
+
统一用 `dsh plugin --profile desktop exec dsh-workbuddy-xdpool <子命令>` 调用:
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
dsh plugin --profile desktop exec dsh-workbuddy-xdpool status # 池账号数/冷却 + shim 状态(--credits 查积分、--json 机器可读、--rates 看倍率)
|
|
104
|
+
dsh plugin --profile desktop exec dsh-workbuddy-xdpool accounts # 已发现账号(--json)
|
|
105
|
+
dsh plugin --profile desktop exec dsh-workbuddy-xdpool doctor # 诊断发现/冷却/上游连通性
|
|
106
|
+
dsh plugin --profile desktop exec dsh-workbuddy-xdpool reset # 立即清除所有 429 冷却
|
|
107
|
+
dsh plugin --profile desktop exec dsh-workbuddy-xdpool checkin # 查询每个账号今日签到状态(--json 机器可读)
|
|
108
|
+
dsh plugin --profile desktop exec dsh-workbuddy-xdpool checkin all
|
|
109
|
+
# 领取所有账号今日签到奖励;也可传账号标签只领一个
|
|
110
|
+
dsh plugin --profile desktop exec dsh-workbuddy-xdpool login # 引导如何在桌面再加一个账号入池
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## 池里怎么多账号?
|
|
114
|
+
|
|
115
|
+
池走**自动发现**:WorkBuddy 桌面 App 每次登录都会在本机留下一个带令牌的历史快照,XD Pool 扫描这些快照,把每个账号都吸收入池。因此多账号 = 在 WorkBuddy 桌面 App 里逐个登录 / 切换账号即可,之后点卡片「重新检测账号」或重启 DSH,新账号自动成为池成员。
|
|
116
|
+
|
|
117
|
+
若你想对桌面 App 之外的某个登录做**显式快照**(例如临时固定某个账号再验证),也可手动导入:
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
# 在 WorkBuddy 桌面 App 登录账号后(key 自己起名):
|
|
121
|
+
dsh plugin --profile desktop exec dsh-workbuddy-xdpool import myKey
|
|
122
|
+
# 查看/删除已导入快照:
|
|
123
|
+
dsh plugin --profile desktop exec dsh-workbuddy-xdpool accounts
|
|
124
|
+
dsh plugin --profile desktop exec dsh-workbuddy-xdpool remove myKey
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
导入快照以 key 的 **MD5 前 8 位**命名落在 `~/.dsh/.workbuddy-xdpool/`(key 本身记在文件里),中文、带 `/`、带空格的 key 都安全;长期使用靠 refresh token 自动续期,失效则回到桌面重新登录后 `import <key> --force` 覆盖。
|
|
128
|
+
|
|
129
|
+
## 配置
|
|
130
|
+
|
|
131
|
+
池的有效配置经插件设置节(`settings.workbuddy-xdpool`)读取,模型设置页可改,改动即时生效:
|
|
132
|
+
|
|
133
|
+
| 字段 | 说明 | 默认 |
|
|
134
|
+
| --- | --- | --- |
|
|
135
|
+
| `authFile` | 覆盖 WorkBuddy 桌面 auth 文件路径(跨平台探测异常时用,等价于 `WORKBUDDY_AUTH_FILE`) | 自动探测 |
|
|
136
|
+
| `cooldownMs` | 单账号 429 冷却时长(毫秒) | `60000` |
|
|
137
|
+
|
|
138
|
+
也可直接写在 `~/.dsh/settings.yaml`:
|
|
139
|
+
|
|
140
|
+
```yaml
|
|
141
|
+
workbuddy-xdpool:
|
|
142
|
+
cooldownMs: 120000
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## 架构
|
|
146
|
+
|
|
147
|
+
- **宿主侧**(`src/`,DSH 主进程内):
|
|
148
|
+
- `index.ts` —— 注册 `workbuddy-xdpool` provider、`workbuddy-xdpool` 设置节(`settings.installSection`)、4 条同源路由(状态 / 重新检测 / 清除冷却 / 签到)、账号发现与模型目录播种。
|
|
149
|
+
- `accounts.ts` —— `WorkBuddyAccountPool`:读本机 WorkBuddy 桌面 auth 快照、429 冷却、round-robin failover 与 token 刷新。
|
|
150
|
+
- `catalog.ts` / `upstream.ts` —— 上游模型目录(含每模型积分倍率、免费/图片能力标签)、积分查询与每日签到的上游客户端(按凭据域名自动切换国内 / 国际版域名)。
|
|
151
|
+
- `web-status.ts` / `status-paths.ts` —— 卡片消费的同源状态文档与路由;签到是本插件唯一的写操作,按「POST + 回环来源 + 显式 accountId + 领取前二次确认」四重守卫。
|
|
152
|
+
- `bin.ts` —— 上述 CLI。
|
|
153
|
+
- **客户端**(`src/client/`,浏览器卡片,经 `dsh.client` 由宿主加载):折叠卡片外壳沿用宿主内置卡的 `dsm-plugin-card*` 样式语言(`--dsw-alias-*` 主题变量),内容用 `dsm-workbuddy-xdpool-*` 前缀,绝不污染宿主其它卡片;命名空间 `settings.workbuddy-xdpool`。
|
|
154
|
+
- **构建**:`tsdown` 产出 `lib/index.js`(宿主入口)+ `lib/index.d.ts`(类型)+ `lib/bin.js`(CLI)+ `lib/client.js`(CJS,`window.__ModuleLoader__.load` 包裹的浏览器 bundle)。四个产物均随仓库提交,因此安装时不需要任何构建脚本。
|
|
155
|
+
|
|
156
|
+
## 已知限制
|
|
157
|
+
|
|
158
|
+
- **仅使用本机桌面 App 的账号**:池不会、也无法替你发起 WorkBuddy 的登录/扫码(token 由 WorkBuddy 桌面 App 自己的腾讯 SSO 登录铸造并设备绑定)。加池账号 = 在 WorkBuddy 桌面 App 里登录/切换,XD Pool 自动吸收。
|
|
159
|
+
- 依赖 WorkBuddy 客户端接口(非官方开放 API),WorkBuddy 更新后插件可能需要随之调整;若某账号 refresh token 失效,回到桌面重新登录即可。
|
|
160
|
+
- 若 Windows 与 Linux 用户名不同且 Windows 环境变量未传入 WSL,请用 `WORKBUDDY_AUTH_FILE` 或配置节的 `authFile` 指定实际位置。
|
|
161
|
+
|
|
162
|
+
## 免责声明
|
|
163
|
+
|
|
164
|
+
- 本项目**仅供个人学习和研究使用**,仅驱动使用者自己的 WorkBuddy 账号在本机调用,请勿用于商业用途或超出个人合理使用的场景。
|
|
165
|
+
- 使用者需遵守 WorkBuddy 的服务条款;因使用本项目产生的任何后果(包括但不限于账号被限制、额度被清空、服务中断),由使用者自行承担。
|
|
166
|
+
- 本项目作者不对任何因使用或滥用本项目产生的直接或间接损失负责。
|
|
167
|
+
- 本项目与腾讯、WorkBuddy、DeepSeek 均无关联,未获其授权或认可;文中出现的名称仅用于描述兼容关系,其商标权利归各自所有。
|
|
168
|
+
|
|
169
|
+
## 致谢
|
|
170
|
+
|
|
171
|
+
本项目的实现参考了以下已公开的项目,并按其许可证要求保留版权声明。参考方向为**设计思路与既有结论**,代码为独立实现;关键模块在源文件头部注释中也标注了所参考的项目与模式:
|
|
172
|
+
|
|
173
|
+
- [corrinehu/dsh-workbuddy-connect](https://github.com/corrinehu/dsh-workbuddy-connect)(MIT)—— 设置节注册(`settings.installSection`)与 DSH 插件结构、客户端卡片加载机制、桌面端凭据刷新与 loopback shim 加固的核心参照;本项目沿用其「宿主通过 installSection 挂卡片」的打通路径。
|
|
174
|
+
- [dingminhua/dsh-connect-workbuddy](https://github.com/dingminhua/dsh-connect-workbuddy)(MIT,Copyright (c) 2026 LaoDing)—— `dsm-plugin-card*` 卡片样式语言与 `--dsw-alias-*` 主题变量的参照实现;**每日签到**(`/v2/billing/meter/checkin-activity-status` 与 `/v2/billing/meter/daily-checkin`)、积分包聚合口径(月度周期套餐 / 一次性礼包区分)与国内 / 国际版按 `domain` 选择上游域名的做法,参考了该项目已验证的接口形态。
|
|
175
|
+
- [Sliverkiss/workbuddy2api](https://github.com/Sliverkiss/workbuddy2api)(MIT)—— WorkBuddy 上游协议(`copilot.tencent.com` 的 wire behavior)与积分接口的参照实现。
|
|
176
|
+
|
|
177
|
+
以上项目的版权归各自作者所有。本项目采用**参考设计思路 + 独立实现**的方式,未整体复制任何参考项目的源码。若标注有遗漏或不当之处,欢迎提交 issue 指正。
|
|
178
|
+
|
|
179
|
+
## 许可证
|
|
180
|
+
|
|
181
|
+
[MIT](./LICENSE)
|
package/lib/bin.js
CHANGED
|
@@ -758,6 +758,26 @@ function candidateAuthDirs(env = process.env) {
|
|
|
758
758
|
* Read-only pool of every discovered WorkBuddy account, with rate-limit
|
|
759
759
|
* cooldown and round-robin failover.
|
|
760
760
|
*/
|
|
761
|
+
/** Idle bonus per hour an account has been unused (reference-panel default). */
|
|
762
|
+
const IDLE_WEIGHT_PER_HOUR = .5;
|
|
763
|
+
/** Ceiling for the idle bonus, so an idle account cannot dominate forever. */
|
|
764
|
+
const IDLE_WEIGHT_MAX = 5;
|
|
765
|
+
/**
|
|
766
|
+
* Weight one account by how long it has been idle.
|
|
767
|
+
*
|
|
768
|
+
* The base of 1 keeps every eligible account in play: an account that served a
|
|
769
|
+
* moment ago still has a small chance, so a single unhealthy account cannot pin
|
|
770
|
+
* the pool to itself, and the weights never sum to zero.
|
|
771
|
+
*
|
|
772
|
+
* `lastUsedAt === undefined` means "never used in this process", which earns the
|
|
773
|
+
* full bonus: on a fresh start every account ties, and the weighted draw spreads
|
|
774
|
+
* the first requests instead of always picking the first entry.
|
|
775
|
+
*/
|
|
776
|
+
function idleWeight(lastUsedAt, now) {
|
|
777
|
+
if (lastUsedAt === void 0) return 6;
|
|
778
|
+
const hours = (now - lastUsedAt) / 36e5;
|
|
779
|
+
return 1 + Math.min(Math.max(hours, 0) * IDLE_WEIGHT_PER_HOUR, IDLE_WEIGHT_MAX);
|
|
780
|
+
}
|
|
761
781
|
var WorkBuddyAccountPool = class {
|
|
762
782
|
logger;
|
|
763
783
|
authDirs;
|
|
@@ -770,6 +790,24 @@ var WorkBuddyAccountPool = class {
|
|
|
770
790
|
cursor = 0;
|
|
771
791
|
lastScanAtMs = 0;
|
|
772
792
|
preferredId;
|
|
793
|
+
/**
|
|
794
|
+
* Account ids the user switched off on the card.
|
|
795
|
+
*
|
|
796
|
+
* Disabling is a user preference rather than a property of the credential:
|
|
797
|
+
* `scan()` rebuilds every account object from the auth files, so the set
|
|
798
|
+
* lives on the pool and is re-applied from settings after each scan.
|
|
799
|
+
*/
|
|
800
|
+
disabledIds = /* @__PURE__ */ new Set();
|
|
801
|
+
/**
|
|
802
|
+
* Last time each account served a request, epoch ms. Drives the idle term
|
|
803
|
+
* of the priority-mode weighting below: an account that just served loses to
|
|
804
|
+
* one that has been idle, so a small pool stops hammering a single account.
|
|
805
|
+
*
|
|
806
|
+
* In-memory on purpose: it only biases the next pick, so a cold start that
|
|
807
|
+
* treats every account as idle is the right default. Not keyed by id lookup
|
|
808
|
+
* misses because a removed account simply disappears from the map on re-scan.
|
|
809
|
+
*/
|
|
810
|
+
lastUsedAt = /* @__PURE__ */ new Map();
|
|
773
811
|
refreshInflight = /* @__PURE__ */ new Map();
|
|
774
812
|
constructor(options = {}) {
|
|
775
813
|
this.logger = options.logger;
|
|
@@ -788,6 +826,7 @@ var WorkBuddyAccountPool = class {
|
|
|
788
826
|
if (options.authDirs !== void 0 && options.authDirs.length > 0) this.authDirs = options.authDirs;
|
|
789
827
|
if (options.cooldownMs !== void 0 && options.cooldownMs >= 1e3) this.cooldownMs = options.cooldownMs;
|
|
790
828
|
if (options.distribution !== void 0) this.distribution = options.distribution;
|
|
829
|
+
if (options.disabledAccountIds !== void 0) this.disabledIds = new Set(options.disabledAccountIds);
|
|
791
830
|
}
|
|
792
831
|
/** Rescan the auth directories and merge newly discovered accounts. */
|
|
793
832
|
async scan() {
|
|
@@ -840,12 +879,47 @@ var WorkBuddyAccountPool = class {
|
|
|
840
879
|
*/
|
|
841
880
|
available(now, modelId, region) {
|
|
842
881
|
return this.accounts.filter((account) => {
|
|
882
|
+
if (this.disabledIds.has(account.id)) return false;
|
|
843
883
|
if (account.cooldownUntilMs > now) return false;
|
|
844
884
|
if (modelId !== void 0 && (account.modelCooldowns[modelId] ?? 0) > now) return false;
|
|
845
885
|
if (region !== void 0 && regionOf(account.credential.domain) !== region) return false;
|
|
846
886
|
return true;
|
|
847
887
|
});
|
|
848
888
|
}
|
|
889
|
+
/** Round-robin: the legacy cursor walk, kept for the distribution that asks for it. */
|
|
890
|
+
pickRoundRobin(pool) {
|
|
891
|
+
const index = this.cursor % pool.length;
|
|
892
|
+
const account = pool[index];
|
|
893
|
+
if (account === void 0) return void 0;
|
|
894
|
+
this.cursor = (index + 1) % pool.length;
|
|
895
|
+
return account;
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Priority mode: weighted random over the eligible accounts.
|
|
899
|
+
*
|
|
900
|
+
* The weight is an idle bonus — `1 + min(idleHours * perHour, max)` — so an
|
|
901
|
+
* account that has never served (or has been idle for a while) outranks one
|
|
902
|
+
* that just answered. Reference panel logic drops its success-rate term
|
|
903
|
+
* entirely because a lifetime error counter penalises an account forever;
|
|
904
|
+
* instantaneous health is already handled by cooldowns, which is why those
|
|
905
|
+
* accounts never reach this list.
|
|
906
|
+
*
|
|
907
|
+
* A pool with no idle history (fresh process) hashes to equal weights, which
|
|
908
|
+
* spreads the very first picks instead of always returning index 0.
|
|
909
|
+
*/
|
|
910
|
+
pickByWeight(pool) {
|
|
911
|
+
if (pool.length === 1) return pool[0];
|
|
912
|
+
const now = Date.now();
|
|
913
|
+
const weights = pool.map((account) => idleWeight(this.lastUsedAt.get(account.id), now));
|
|
914
|
+
const total = weights.reduce((sum, weight) => sum + weight, 0);
|
|
915
|
+
if (!Number.isFinite(total) || total <= 0) return pool[0];
|
|
916
|
+
let roll = Math.random() * total;
|
|
917
|
+
for (let index = 0; index < pool.length; index += 1) {
|
|
918
|
+
roll -= weights[index] ?? 0;
|
|
919
|
+
if (roll < 0) return pool[index];
|
|
920
|
+
}
|
|
921
|
+
return pool[pool.length - 1];
|
|
922
|
+
}
|
|
849
923
|
/**
|
|
850
924
|
* Pick the account to serve a request.
|
|
851
925
|
*
|
|
@@ -875,16 +949,16 @@ var WorkBuddyAccountPool = class {
|
|
|
875
949
|
}
|
|
876
950
|
if (pool.length === 0) return void 0;
|
|
877
951
|
if (this.preferredId !== void 0) {
|
|
878
|
-
const
|
|
879
|
-
if (
|
|
880
|
-
|
|
881
|
-
|
|
952
|
+
const preferred = pool.find((account) => account.id === this.preferredId);
|
|
953
|
+
if (preferred !== void 0) {
|
|
954
|
+
this.lastUsedAt.set(preferred.id, Date.now());
|
|
955
|
+
await this.ensureFresh(preferred);
|
|
956
|
+
return preferred;
|
|
882
957
|
}
|
|
883
958
|
}
|
|
884
|
-
const
|
|
885
|
-
const account = pool[index];
|
|
959
|
+
const account = this.distribution === "round-robin" ? this.pickRoundRobin(pool) : this.distribution === "balanced" ? this.pickByWeight(pool) : pool[0];
|
|
886
960
|
if (account === void 0) return void 0;
|
|
887
|
-
|
|
961
|
+
this.lastUsedAt.set(account.id, Date.now());
|
|
888
962
|
await this.ensureFresh(account);
|
|
889
963
|
return account;
|
|
890
964
|
}
|
|
@@ -896,6 +970,14 @@ var WorkBuddyAccountPool = class {
|
|
|
896
970
|
prefer(accountId) {
|
|
897
971
|
this.preferredId = accountId;
|
|
898
972
|
}
|
|
973
|
+
/** Whether the user switched this account off on the card. */
|
|
974
|
+
isDisabled(accountId) {
|
|
975
|
+
return this.disabledIds.has(accountId);
|
|
976
|
+
}
|
|
977
|
+
/** Every account id the user switched off, in discovery order. */
|
|
978
|
+
disabledIdsInOrder() {
|
|
979
|
+
return this.accounts.filter((account) => this.disabledIds.has(account.id)).map((account) => account.id);
|
|
980
|
+
}
|
|
899
981
|
/** Best-effort refresh of one account after a session-dead upstream answer. */
|
|
900
982
|
async refreshAccount(accountId) {
|
|
901
983
|
const account = this.accounts.find((item) => item.id === accountId);
|
|
@@ -1154,13 +1236,31 @@ function formatRates(status) {
|
|
|
1154
1236
|
}
|
|
1155
1237
|
return lines.join("\n");
|
|
1156
1238
|
}
|
|
1239
|
+
//#endregion
|
|
1240
|
+
//#region src/index.ts
|
|
1241
|
+
/**
|
|
1242
|
+
* One region's model-selection schema.
|
|
1243
|
+
*
|
|
1244
|
+
* Every field is optional on purpose: an absent field keeps its documented
|
|
1245
|
+
* meaning ("all enabled" / "follow the upstream image flag" / "no cap"), and a
|
|
1246
|
+
* region that has never been saved stays absent so `applyConfigFromSource` can
|
|
1247
|
+
* fall back to the legacy flat keys.
|
|
1248
|
+
*/
|
|
1249
|
+
const modelSelectionSchema = z.object({
|
|
1250
|
+
enabledModelIds: z.array(z.string()).description("Model ids enabled in this region's picker (absent = all)"),
|
|
1251
|
+
imageModelIds: z.array(z.string()).description("Model ids accepting image input in this region (absent = follow upstream)"),
|
|
1252
|
+
contextBudgets: z.dict(z.number().step(1).min(1)).description("Per-model context-window override for this region")
|
|
1253
|
+
});
|
|
1157
1254
|
z.object({
|
|
1158
1255
|
authFile: z.string().description("WorkBuddy desktop auth file (defaults to the app own location)"),
|
|
1159
1256
|
cooldownMs: z.number().step(1).min(1e3).default(6e4).description("Rate-limit cooldown per account, in milliseconds"),
|
|
1160
1257
|
distribution: z.union(["priority", "round-robin"]).default("priority").description("How requests are spread: priority (drain one) or round-robin"),
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1258
|
+
disabledAccountIds: z.array(z.string()).default([]).description("Account ids excluded from the pool (empty = every discovered account participates)"),
|
|
1259
|
+
enabledModelIds: z.array(z.string()).default([]).description("Legacy shared model-id list; used by a region that has no per-region selection yet"),
|
|
1260
|
+
imageModelIds: z.array(z.string()).default([]).description("Legacy shared image-id list; used by a region that has no per-region selection yet"),
|
|
1261
|
+
contextBudgets: z.dict(z.number().step(1).min(1)).default({}).description("Legacy shared context budgets; used by a region with no per-region selection yet"),
|
|
1262
|
+
modelSelectionCn: modelSelectionSchema.description("Model selection for the domestic gateway"),
|
|
1263
|
+
modelSelectionGlobal: modelSelectionSchema.description("Model selection for the international gateway")
|
|
1164
1264
|
});
|
|
1165
1265
|
/** Assemble the runtime objects without registering anything. */
|
|
1166
1266
|
/**
|