dsh-workbuddy-xdpool 1.8.1 → 1.8.2

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 CHANGED
@@ -4,6 +4,46 @@
4
4
 
5
5
  版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
6
6
 
7
+ ## 1.8.2 — 免费的那个被限流了,旁边那个收费的同款还能用
8
+
9
+ > 免费模型被限流的时候,插件以前只会说一句「所有账号都在冷却」。这句话没错,但它让用户以为自己只剩一条路:等。其实同一个模型往往还有一条**收费的**产品行,而且在限流器眼里它们是两个不同的模型——免费行被关小黑屋的时候,收费行通常毫发无伤。这个版本就是把这个「另一条路」写出来。
10
+
11
+ ### 同一个模型,凭什么有两行
12
+
13
+ 上游把一条产品线按**多行**卖,每行有自己的 id、自己的价格、自己的限流额度,但**显示名字是一样的**。实测两个网关:
14
+
15
+ | 网关 | 免费行 | 收费行 |
16
+ | --- | --- | --- |
17
+ | 国际版 | `deepseek-v4.1-flash`(x0.00) | `deepseek-v4.1-flash-sg`(x0.03) |
18
+ | 国内版 | `hy3`(x0.00) | `hy3-x`(x0.05) |
19
+
20
+ 注意 `-sg` 和 `-x` 跟免费行**没有任何前缀关系**,能把它们认成一家的只有那个相同的显示名。所以匹配是按名字来的,不是按 id 猜的。
21
+
22
+ ### 现在会怎么提示
23
+
24
+ - **模型列表**:免费行后面挂一个小灰标「付费同款 deepseek-v4.1-flash-sg(0.03x)」,鼠标悬停解释「免费档先被限流;同一个模型的付费行不受这个限制影响」。灰的、低调的——这一行现在仍然是免费的那个,标签只是告诉你退路在哪,不是催你花钱。
25
+ - **429 报错**:以前是「every account is rate-limited for model deepseek-v4.1-flash (4/4 cooling, tried 0)」,现在后面会接一句「deepseek-v4.1-flash-sg is the same model on a paid row (x0.03 credits) and is rate-limited separately; switch to it to keep going」。
26
+ - **账号卡片**:某个账号上「`hy4-preview` cooling to 10:14」这种小标后面,也会跟上「→ 收费行的 id(倍率)还能用」。
27
+
28
+ ### 刻意没做的事
29
+
30
+ - **收费行不标标签。** 只有倍率等于 0 的行才找孪生兄弟。否则「GLM-5.3」这种本身收费、又恰好有个更便宜的闪速版的模型,也会被挂上一个标签,最后满屏都是提示,等于没提示。
31
+ - **没有收费同款就不说话。** 独苗模型(整条线只有它自己)不会有任何多余文字,也不会留下一个孤零零的分隔符。
32
+ - **不把另一个免费行当退路。** 两个免费行是一起被限流的,切过去纯属换个人排队,说了等于骗人。
33
+ - **倍率未知的行不算数。** 上游没给价格的兄弟行不能被承诺成「那个收费的」——用户切过去以为知道价钱,结果未必。
34
+
35
+ ### 顺带说清楚:国际版没有「每天领 30 积分」
36
+
37
+ 上一版已经查过一遍,这次把话说完整。国际版(`www.workbuddy.ai`)**没有**签到活动,也**没有** `daily-checkin` 这条路由:
38
+
39
+ - `POST /v2/billing/meter/checkin-activity-status` 返回 200,但 `active: false`(字段在,活动没开);
40
+ - `GET /v2/billing/meter/get-user-resource` 直接 404(国内版才有这个接口);
41
+ - `/v2/billing/meter/checkin`、`/daily-checkin-activity`、`/checkin-activity` 全是 404。
42
+
43
+ 国际版真正有的是另一套东西:**成长任务板**(5 条:完成一次对话、安装技能、绑定微信、召唤专家、使用模板)和 **streak 连续打卡**(7 天 / 14 天 / 28 天档位,分别给 0 / 50 / 150 积分)。但它的字段形状跟国内版不一样(没有 credit / energy / target / current),插件目前解析不了,所以**没有**为国际版开自动化——没有可领的东西,开了也只是每天准时去撞 404。
44
+
45
+ 国内版的签到是正常的,多账号逐个领取的链路本来就有,不受本次改动影响。
46
+
7
47
  ## 1.8.1 — 一个过期的登录状态,不再拖着整个池子陪跑
8
48
 
9
49
  > 有人的卡片上,好几个账号下面顶着一大段红色英文,大意是「上游不认这个登录了,去桌面端重新登录」。话说得没错,可问题是:它只说了结论,没解决过程——那几个账号明明已经废了,每次请求还是要挨个撞一遍,撞得头破血流,旁边几个好端端的账号一次都轮不上。
package/README.md CHANGED
@@ -50,6 +50,8 @@
50
50
 
51
51
  - **模型目录带标注**:列出池里能用的模型,附带积分倍率(如 `GLM-5.2 · x0.79`)、免费 / 限时免费 / 夜间折扣标签、能否收图和上下文窗口大小,全部跟随上游实时更新。
52
52
 
53
+ - **被限流时告诉你退路**:免费档被 429 冷却时,卡片和报错都会点名**同名的付费行**(如国际版 `deepseek-v4.1-flash` 对 `deepseek-v4.1-flash-sg`、国内版 `hy3` 对 `hy3-x`)。上游按「行」分别限流,所以免费行被关小黑屋时,收费行通常还是通的。
54
+
53
55
  - **国内版 / 国际版自动适配**:按账号凭据里的登录域名自动选上游——国际版登录(`workbuddy.ai`)走 `www.workbuddy.ai`,国内版(默认)走 `copilot.tencent.com`。两种账号可以混在同一个池里。
54
56
 
55
57
  - **想手动做点啥也行**:卡片和命令行都提供「重新检测账号」「清除所有冷却」「每日签到」三个操作。
package/lib/client.js CHANGED
@@ -256,6 +256,10 @@ window.__ModuleLoader__.load({
256
256
  .dsm-workbuddy-xdpool-account-meta-dead{color:var(--dsw-alias-state-error-primary,#ef4444)}
257
257
  .dsm-workbuddy-xdpool-account-modelcool{display:flex;flex-wrap:wrap;gap:6px;margin-top:2px}
258
258
  .dsm-workbuddy-xdpool-account-modelcool-chip{display:inline-flex;align-items:center;gap:4px;padding:1px 8px;border-radius:999px;font-size:11px;line-height:18px;background:var(--dsw-alias-state-warning-subtle,rgba(217,119,6,.12));color:var(--dsw-alias-state-warning-primary,#d97706)}
259
+ /* "→ deepseek-v4.1-flash-sg (0.03x) still works" inside a cooling chip. Keeps
260
+ the warning tint of its parent but drops the weight: it is the way out, not
261
+ a second problem. */
262
+ .dsm-workbuddy-xdpool-account-modelcool-twin{color:var(--dsw-alias-label-secondary,#c6c9d0);cursor:help}
259
263
  .dsm-workbuddy-xdpool-account-error{margin:0;color:var(--dsw-alias-state-error-primary,#ef4444);font-size:13px;line-height:20px}
260
264
 
261
265
  /* Two-column stats: packages on the left, total + check-in on the right. */
@@ -307,6 +311,10 @@ window.__ModuleLoader__.load({
307
311
  /* "活动至 10-31": the campaign's end date. Muted — it is context for planning,
308
312
  not a claim about the current price. */
309
313
  .dsm-workbuddy-xdpool-model-meta-promo{color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:11px;line-height:16px;opacity:.85}
314
+ /* "付费同款 deepseek-v4.1-flash-sg (0.03x)": the way out when the free row is
315
+ rate-limited. Muted like the other meta chips — it is an alternative, not a
316
+ warning, and the row it sits on is still the free one. */
317
+ .dsm-workbuddy-xdpool-model-meta-twin{color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:11px;line-height:16px;opacity:.85;cursor:help}
310
318
  /* Unreadable credential files: a warning tint, since it explains a smaller pool
311
319
  and "encrypted" is actionable (start the app once). */
312
320
  .dsm-workbuddy-xdpool-skipped{margin:6px 0 0;padding:7px 10px;border-radius:8px;background:var(--dsw-alias-state-warning-subtle,rgba(217,119,6,.12));display:flex;flex-direction:column;gap:3px}
@@ -510,6 +518,43 @@ window.__ModuleLoader__.load({
510
518
  return status?.kind === "free" || status?.kind === "night";
511
519
  }
512
520
  //#endregion
521
+ //#region src/siblings.ts
522
+ /**
523
+ * The cheapest PAID model sharing `model`'s display name, or undefined.
524
+ *
525
+ * Cheapest rather than first, because the point of the hint is "there is a
526
+ * cheap way out": offering the most expensive twin would make switching look
527
+ * like a trap. A sibling whose price is unknown is skipped — an unpriced row
528
+ * cannot be promised as "the paid one".
529
+ */
530
+ function paidSiblingOf(model, models) {
531
+ let best;
532
+ for (const candidate of models) {
533
+ if (candidate.id === model.id) continue;
534
+ if (candidate.name !== model.name) continue;
535
+ const multiplier = candidate.multiplier;
536
+ if (multiplier === void 0 || multiplier <= 0) continue;
537
+ if (best === void 0 || multiplier < best.multiplier) best = {
538
+ id: candidate.id,
539
+ name: candidate.name,
540
+ multiplier
541
+ };
542
+ }
543
+ return best;
544
+ }
545
+ /**
546
+ * The paid sibling of a model, but ONLY when the model itself is free.
547
+ *
548
+ * The guard is the whole point: a `x0.79` row with a `x0.06` cousin is not a
549
+ * "free model with a paid twin", it is just a more expensive row, and labelling
550
+ * it would be noise on most of the list. The hint answers one question — "the
551
+ * free one is limited, what now".
552
+ */
553
+ function paidAlternativeFor(model, models) {
554
+ if (model.multiplier !== 0) return void 0;
555
+ return paidSiblingOf(model, models);
556
+ }
557
+ //#endregion
513
558
  //#region src/client/PoolCard.tsx
514
559
  /**
515
560
  * WorkBuddy XD Pool card contributed to DSH Plugin configuration.
@@ -1617,6 +1662,7 @@ window.__ModuleLoader__.load({
1617
1662
  })(),
1618
1663
  status?.accounts.map((account) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountBlock, {
1619
1664
  account,
1665
+ models: status?.models ?? [],
1620
1666
  ...checkinBusyId === void 0 ? {} : { checkinBusyId },
1621
1667
  onClaimCheckin: (accountId) => {
1622
1668
  claimCheckin(accountId);
@@ -1723,6 +1769,7 @@ window.__ModuleLoader__.load({
1723
1769
  children: [...status?.models ?? []].sort((a, b) => Number(isFreeNow(b, now, activeRegion)) - Number(isFreeNow(a, now, activeRegion))).map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelRow, {
1724
1770
  model,
1725
1771
  region: activeRegion,
1772
+ paidTwin: paidAlternativeFor(model, status?.models ?? []),
1726
1773
  t,
1727
1774
  draft: modelDraft[model.id] ?? {
1728
1775
  enabled: model.enabled,
@@ -1739,7 +1786,7 @@ window.__ModuleLoader__.load({
1739
1786
  });
1740
1787
  }
1741
1788
  /** One account block: label + status tag + meta + optional credit panels. */
1742
- function AccountBlock({ account, t, checkinBusyId, onClaimCheckin, accountBusyId, onToggleDisabled, onIgnoreAccount, onSaveCreditReserve, reserveBusyId }) {
1789
+ function AccountBlock({ account, models, t, checkinBusyId, onClaimCheckin, accountBusyId, onToggleDisabled, onIgnoreAccount, onSaveCreditReserve, reserveBusyId }) {
1743
1790
  const isDisabled = account.disabled === true;
1744
1791
  const isCooling = account.cooling === true;
1745
1792
  const isDead = account.credentialDead === true;
@@ -1812,13 +1859,24 @@ window.__ModuleLoader__.load({
1812
1859
  }) : null,
1813
1860
  modelCooldowns.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1814
1861
  className: "dsm-workbuddy-xdpool-account-modelcool",
1815
- children: modelCooldowns.map((mc) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1816
- className: "dsm-workbuddy-xdpool-account-modelcool-chip",
1817
- children: t?.("row.modelCooling", {
1818
- model: mc.modelId,
1819
- time: formatDateTime(mc.until)
1820
- }) ?? `${mc.modelId} cooling to ${formatDateTime(mc.until)}`
1821
- }, mc.modelId))
1862
+ children: modelCooldowns.map((mc) => {
1863
+ const coolingModel = models.find((model) => model.id === mc.modelId);
1864
+ const twin = coolingModel === void 0 ? void 0 : paidAlternativeFor(coolingModel, models);
1865
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1866
+ className: "dsm-workbuddy-xdpool-account-modelcool-chip",
1867
+ children: [t?.("row.modelCooling", {
1868
+ model: mc.modelId,
1869
+ time: formatDateTime(mc.until)
1870
+ }) ?? `${mc.modelId} cooling to ${formatDateTime(mc.until)}`, twin === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1871
+ className: "dsm-workbuddy-xdpool-account-modelcool-twin",
1872
+ title: t?.("row.paidTwinHint") ?? "Same model, paid row",
1873
+ children: t?.("row.rateLimitedPaidTwin", {
1874
+ id: twin.id,
1875
+ rate: twin.multiplier.toFixed(2)
1876
+ }) ?? `→ ${twin.id} (${twin.multiplier.toFixed(2)}x) still works`
1877
+ })]
1878
+ }, mc.modelId);
1879
+ })
1822
1880
  }) : null
1823
1881
  ]
1824
1882
  }),
@@ -2085,7 +2143,7 @@ window.__ModuleLoader__.load({
2085
2143
  * context radios stay disabled rather than pretending an edit took hold. The
2086
2144
  * draft lives in the parent, so this component only ever reports intent.
2087
2145
  */
2088
- function ModelRow({ model, region, t, draft, editable, onToggle, onToggleImage, onBudget }) {
2146
+ function ModelRow({ model, region, paidTwin, t, draft, editable, onToggle, onToggleImage, onBudget }) {
2089
2147
  const now = /* @__PURE__ */ new Date();
2090
2148
  const tag = tagFor(model, now, region);
2091
2149
  const promo = promoStatusFor(model, now, region);
@@ -2184,6 +2242,14 @@ window.__ModuleLoader__.load({
2184
2242
  className: "dsm-workbuddy-xdpool-model-meta-promo",
2185
2243
  children: promoUntil
2186
2244
  }),
2245
+ paidTwin === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2246
+ className: "dsm-workbuddy-xdpool-model-meta-twin",
2247
+ title: t?.("row.paidTwinHint") ?? "Same model, paid row",
2248
+ children: t?.("row.paidTwin", {
2249
+ id: paidTwin.id,
2250
+ rate: paidTwin.multiplier.toFixed(2)
2251
+ }) ?? `paid twin ${paidTwin.id} (${paidTwin.multiplier.toFixed(2)}x)`
2252
+ }),
2187
2253
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2188
2254
  className: "dsm-workbuddy-xdpool-model-cap",
2189
2255
  children: t?.("row.modelOutput", { size: formatCapacity(model.maxOutputTokens) }) ?? `out ${formatCapacity(model.maxOutputTokens)}`
@@ -2345,6 +2411,9 @@ window.__ModuleLoader__.load({
2345
2411
  "row.nightFreeNow": "free until {time}",
2346
2412
  "row.nightFreeLater": "free from {time}",
2347
2413
  "row.promoUntil": "promo to {date}",
2414
+ "row.paidTwin": "paid twin {id} ({rate}x)",
2415
+ "row.paidTwinHint": "The free tier is rate-limited first; this same model has a paid row that keeps working",
2416
+ "row.rateLimitedPaidTwin": "Tip: {id} is the same model on a paid row ({rate}x credits) and is limited separately — switch to it to keep going",
2348
2417
  "row.skippedFiles": "{count} credential file(s) could not be read",
2349
2418
  "row.skippedEncrypted": "encrypted — start WorkBuddy once so its key can be read",
2350
2419
  "row.skippedUnreadable": "file could not be read",
@@ -2503,6 +2572,9 @@ window.__ModuleLoader__.load({
2503
2572
  "row.nightFreeNow": "免费至 {time}",
2504
2573
  "row.nightFreeLater": "{time} 起免费",
2505
2574
  "row.promoUntil": "活动至 {date}",
2575
+ "row.paidTwin": "付费同款 {id}({rate}x)",
2576
+ "row.paidTwinHint": "免费档先被限流;同一个模型的付费行不受这个限制影响",
2577
+ "row.rateLimitedPaidTwin": "提示:{id} 是同一个模型的付费行({rate}x 积分),限流是分开算的 —— 切过去就能继续用",
2506
2578
  "row.skippedFiles": "有 {count} 个凭据文件读不出来",
2507
2579
  "row.skippedEncrypted": "已加密 —— 启动一次 WorkBuddy 才能取到密钥",
2508
2580
  "row.skippedUnreadable": "文件读不出来",
package/lib/index.js CHANGED
@@ -5687,6 +5687,43 @@ function injectSummary(compacted, summary) {
5687
5687
  ];
5688
5688
  }
5689
5689
  //#endregion
5690
+ //#region src/siblings.ts
5691
+ /**
5692
+ * The cheapest PAID model sharing `model`'s display name, or undefined.
5693
+ *
5694
+ * Cheapest rather than first, because the point of the hint is "there is a
5695
+ * cheap way out": offering the most expensive twin would make switching look
5696
+ * like a trap. A sibling whose price is unknown is skipped — an unpriced row
5697
+ * cannot be promised as "the paid one".
5698
+ */
5699
+ function paidSiblingOf(model, models) {
5700
+ let best;
5701
+ for (const candidate of models) {
5702
+ if (candidate.id === model.id) continue;
5703
+ if (candidate.name !== model.name) continue;
5704
+ const multiplier = candidate.multiplier;
5705
+ if (multiplier === void 0 || multiplier <= 0) continue;
5706
+ if (best === void 0 || multiplier < best.multiplier) best = {
5707
+ id: candidate.id,
5708
+ name: candidate.name,
5709
+ multiplier
5710
+ };
5711
+ }
5712
+ return best;
5713
+ }
5714
+ /**
5715
+ * The paid sibling of a model, but ONLY when the model itself is free.
5716
+ *
5717
+ * The guard is the whole point: a `x0.79` row with a `x0.06` cousin is not a
5718
+ * "free model with a paid twin", it is just a more expensive row, and labelling
5719
+ * it would be noise on most of the list. The hint answers one question — "the
5720
+ * free one is limited, what now".
5721
+ */
5722
+ function paidAlternativeFor(model, models) {
5723
+ if (model.multiplier !== 0) return void 0;
5724
+ return paidSiblingOf(model, models);
5725
+ }
5726
+ //#endregion
5690
5727
  //#region src/shim.ts
5691
5728
  /**
5692
5729
  * Loopback OpenAI-compatible endpoint with multi-account failover.
@@ -5872,6 +5909,22 @@ function createWorkBuddyShim(options) {
5872
5909
  const server = createServer((req, res) => {
5873
5910
  handle(req, res);
5874
5911
  });
5912
+ /**
5913
+ * "The same model has a paid row" sentence for a 429 body, or undefined.
5914
+ *
5915
+ * Reads the catalog the shim was built with, so the hint names a row this
5916
+ * gateway ACTUALLY serves — a CN-only id in a global 429 would be worse than
5917
+ * no hint at all. Absent when the cooling model is itself paid, or when the
5918
+ * family has no priced twin.
5919
+ */
5920
+ function paidTwinHint(modelId) {
5921
+ const models = catalog.current();
5922
+ const model = models.find((candidate) => candidate.id === modelId);
5923
+ if (model === void 0) return void 0;
5924
+ const twin = paidAlternativeFor(model, models);
5925
+ if (twin === void 0) return void 0;
5926
+ return `${twin.id} is the same model on a paid row (x${twin.multiplier.toFixed(2)} credits) and is rate-limited separately; switch to it to keep going`;
5927
+ }
5875
5928
  const ready = new Promise((resolve, reject) => {
5876
5929
  server.once("listening", () => resolve());
5877
5930
  server.once("error", reject);
@@ -5983,7 +6036,8 @@ function createWorkBuddyShim(options) {
5983
6036
  if (why.reason === "cooling") {
5984
6037
  const subject = modelId === void 0 ? "every WorkBuddy account is rate-limited" : `every account is rate-limited for model ${modelId}`;
5985
6038
  const upstream = last === void 0 ? "" : ` — ${last.message.slice(0, 200)}`;
5986
- writeOpenAIError(res, 429, "soft_rate", `${subject} (${why.cooling}/${why.total} cooling, tried ${tried.length})${tried.length === 0 ? "" : `: ${tried.join(" → ")}`}${upstream}`);
6039
+ const twin = modelId === void 0 ? void 0 : paidTwinHint(modelId);
6040
+ writeOpenAIError(res, 429, "soft_rate", `${subject} (${why.cooling}/${why.total} cooling, tried ${tried.length})${tried.length === 0 ? "" : `: ${tried.join(" → ")}`}${upstream}` + (twin === void 0 ? "" : ` — ${twin}`));
5987
6041
  return;
5988
6042
  }
5989
6043
  if (why.reason === "disabled") {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-workbuddy-xdpool",
3
3
  "displayName": "DSH WorkBuddy XD Pool",
4
4
  "description": "Merge every locally signed-in WorkBuddy account into DeepSeek Harness as one auto-failing-over model pool (multi-account rotation, live credits, daily check-in and model catalog).",
5
- "version": "1.8.1",
5
+ "version": "1.8.2",
6
6
  "license": "MIT",
7
7
  "author": "XDTrees",
8
8
  "homepage": "https://github.com/XDTrees/dsh-workbuddy-xdpool#readme",