dsh-connect-workbuddy 5.6.0 → 5.6.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
@@ -1,5 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.6.2 (2026-10-23)
4
+
5
+ > 补丁版本:修 5.6.0/5.6.1 里「折后价读不出来」的原因。**请求路径、provider 路由、计费与凭据一律未变。**
6
+
7
+ ### Fixes
8
+
9
+ - **真实促销的 `displayMode` 是 `strikethrough`,只认 `replace` 等于把折扣全丢了(#120,第二次报告)**
10
+ - **症状**:徽标亮着,价格不动。模型行显示 `DeepSeek-V4.1-Flash · x0.11` 配「夜间折扣」。
11
+ - **查证方式**:直接把国内区 `/v3/config` 的 8 条 `modelPromotions` 原文 dump 出来逐条比对,而不是继续照着此前文档里的样例推断(样例只出现过 `replace`)。
12
+ - **真实数据**(2026-10-23 实测,8 条):
13
+ | id | 标签 | `discount` | `displayMode` |
14
+ | --- | --- | --- | --- |
15
+ | `glm-52-night-discount-202607` | 夜间折扣 | `"0.50x"` | **`strikethrough`** |
16
+ | `hy4-night-discount-daytime-badge-202609` | 夜间免费 | `"0.00x"` | **`strikethrough`** |
17
+ | `step-5-discount-daytime-badge-202610` | 独家限免 | `"0.00x"` | **`strikethrough`** |
18
+ | `hy3-free-trial-202608` | 限时免费 | `"0x"` | `replace` |
19
+ | `glm-52-…-daytime-badge`、`hy4-…-badge`、`ds-discount-daytime-badge-202608`、`space-bunny-discount-202610` | — | 无 | — |
20
+ - **根因**:原实现把 `displayMode` 硬写成 `'replace'`(唯一的样例来源),解析时把 `strikethrough` 当成未知值丢弃,判定时也只放行 `replace`。于是**八条里三条带价格的折扣一条都不生效**,只有 hy3 的 `replace` 能过。
21
+ - **修法**:两种观测到的拼写都认。语义上它们都是「折后价才是实付价」,区别只在客户端是否同时把原价划掉——本插件每行只画一个数字,所以两者等价处理。**仍不认没标 `displayMode` 的 `discount`**:字段缺失不说明该怎么读那个数,猜成 `replace` 就是凭猜改价。
22
+ - **顺带确认不是 bug 的一条**:`ds-discount-daytime-badge-202608`(`deepseek-v4.1-flash` / `deepseek-v4-pro`)的 `discount` 就是 **`null`**,时段 `0:00→23:59` 几乎全天。所以这两个模型「徽标亮着但价格是目录价」是**上游没给折后价**,不是插件漏读。这条单独写了测试钉住:没有数字就**不能**编一个。
23
+ - **测试**:`tests/issue120-promo-discount.spec.ts` 增至 14 条,新增四条用的是**真实原文**(不是样例形状)——`strikethrough` 生效(`0.50x` → 0.50)、`0.00x` 免费算作 0 而不是「没有」、**only-badge 条目(deepseek 那条)徽标有、价格不动**、仍拒绝没有 `displayMode` 的折扣。原「未知 displayMode」那条改成用一个真正不存在的值,避免把已观测值钉成未知。
24
+
25
+ ## 5.6.1 (2026-10-23)
26
+
27
+ > 补丁版本:修 5.6.0 自己引入的一处显示回退。**请求路径、provider 路由、计费与凭据一律未变。**
28
+
29
+ ### Fixes
30
+
31
+ - **模型选择器里积分倍率显示了两遍(#120 回归)**
32
+ - **症状**:5.6.0 之后选择器每一行都是 `DeepSeek-V4.1-Flash · x0.11 · x0.11`。截图在 5.6.0 发布后几分钟内报告。
33
+ - **根因**:宿主的模型名**本来就以倍率结尾**(`workBuddyDisplayName` 拼的是 `"<name> · x<multiplier>"`,宿主原样渲染),#120 又在这个字符串旁边追加了一个倍率 span,于是同一个数字出现两次。**不是 #120 的判定错了,是我加渲染时没注意那串名字里已经有一个。**
34
+ - **修法**:新增 `splitCatalogRate()`,把倍率**从宿主字符串里剥出来**、再画到能承载折后值的那个 span 里——始终只有一个数字,且它是此刻生效的那个。后缀按**形状**识别(`· x<数字>` 结尾),不按快照的 `baseRate` 比:目录价可能在快照与渲染之间变化,靠值比对会留下旧后缀、又变成两个数字,正是这个函数要消掉的东西。
35
+ - **测试**:新增 `tests/issue120-picker-rate-duplicate.spec.tsx`(8 条)——剥离后缀保留原值、折扣时**替换**而非追加、快照过期仍按形状剥离、无倍率的名字不动、无后缀时用快照值、无后缀时仍能显示折扣、名字中间的 `x` 不动、`x0.00`(免费)不被当成「没有倍率」。
36
+
3
37
  ## 5.6.0 (2026-10-23)
4
38
 
5
39
  > 特性版本:两条都来自 issue 报告——#120(错峰时段的积分倍率没跟着变)与 #124(企业/教育版账号积分恒为 0、姓名显示成登录别名)。**请求路径、provider 路由、渠道门与计费口径一律未变**;改的是「倍率与徽标同源同时钟」和「企业账号读自己那张计费表」。
package/lib/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { C as defaultDesktopAuthCandidates, L as WorkBuddyUpstreamClient, M as workbuddyOwnAuthPath, Mt as hostPeersInfo, a as readHostHeartbeat, b as WorkBuddyCredentialStore, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, k as legacyWorkbuddyOwnAuthPath, l as piAiRuntimeInfo, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS, vt as WORKBUDDY_APP_EXECUTABLE_ENV, w as defaultDesktopAuthDirs, wt as findWorkbuddyAppExecutableWithSource, y as WORKBUDDY_AUTH_FILE_ENV } from "./host-heartbeat-DqTNlE6H.js";
2
+ import { C as defaultDesktopAuthCandidates, L as WorkBuddyUpstreamClient, M as workbuddyOwnAuthPath, Mt as hostPeersInfo, a as readHostHeartbeat, b as WorkBuddyCredentialStore, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, k as legacyWorkbuddyOwnAuthPath, l as piAiRuntimeInfo, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS, vt as WORKBUDDY_APP_EXECUTABLE_ENV, w as defaultDesktopAuthDirs, wt as findWorkbuddyAppExecutableWithSource, y as WORKBUDDY_AUTH_FILE_ENV } from "./host-heartbeat-DWoUWD70.js";
3
3
  import { realpathSync } from "node:fs";
4
4
  import { fileURLToPath } from "node:url";
5
5
  //#region src/bin.ts
package/lib/client.js CHANGED
@@ -2083,7 +2083,7 @@ window.__ModuleLoader__.load({
2083
2083
  for (const promo of sorted) {
2084
2084
  const discount = promo.discount;
2085
2085
  if (discount === void 0 || discount.multiplier === void 0) continue;
2086
- if (discount.displayMode !== "replace") continue;
2086
+ if (discount.displayMode !== "replace" && discount.displayMode !== "strikethrough") continue;
2087
2087
  if (!promoActiveAt(promo, nowMs)) continue;
2088
2088
  return discount.multiplier;
2089
2089
  }
@@ -8840,12 +8840,12 @@ window.__ModuleLoader__.load({
8840
8840
  (0, react.useEffect)(() => subscribeMinute(() => setTick((count) => count + 1)), []);
8841
8841
  const level = snapshot?.levels[modelId];
8842
8842
  const promo = snapshot === void 0 ? void 0 : selectPromoBadge(snapshot.promos[modelId], Date.now());
8843
- const baseRate = snapshot?.rates[modelId];
8844
- const rate = (snapshot === void 0 ? void 0 : selectPromoMultiplier(snapshot.promos[modelId], Date.now())) ?? baseRate;
8843
+ const discounted = snapshot === void 0 ? void 0 : selectPromoMultiplier(snapshot.promos[modelId], Date.now());
8844
+ const { base, rate } = splitCatalogRate(name, snapshot?.rates[modelId], discounted);
8845
8845
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
8846
8846
  className: "dsm-workbuddy-model-label",
8847
8847
  children: [
8848
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: name }),
8848
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: base }),
8849
8849
  rate === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
8850
8850
  className: "dsm-workbuddy-model-label-rate",
8851
8851
  children: [" · x", rate.toFixed(2)]
@@ -8858,6 +8858,36 @@ window.__ModuleLoader__.load({
8858
8858
  ]
8859
8859
  });
8860
8860
  }
8861
+ /**
8862
+ * Split the host's display name into "name" and "the multiplier to show".
8863
+ *
8864
+ * `workBuddyDisplayName` builds `"<name> · x<multiplier>"` and the host renders
8865
+ * that string verbatim, so appending another rate next to it printed the figure
8866
+ * twice in the picker (`DeepSeek-V4.1-Flash · x0.11 · x0.11`). The suffix is
8867
+ * therefore pulled back OUT of the string and re-drawn in the span that can
8868
+ * carry the discounted value — exactly one number, and it is the one that holds
8869
+ * right now.
8870
+ *
8871
+ * The suffix is recognised by SHAPE, not by comparing against the snapshot's
8872
+ * `baseRate`: the two can disagree (a promotion can change the catalog figure
8873
+ * between the snapshot and this render), and leaving the suffix in place then
8874
+ * prints two numbers again — the very defect this function removes.
8875
+ *
8876
+ * @param name - The host's rendered name, possibly ending in ` · x<rate>`.
8877
+ * @param baseRate - The catalog multiplier, as the snapshot reports it.
8878
+ * @param discounted - The rate to show instead, when a discount is running.
8879
+ * @returns The name without its rate suffix, and the single rate to draw.
8880
+ * `undefined` means "draw no number" (the name had none and no
8881
+ * discount is running).
8882
+ */
8883
+ function splitCatalogRate(name, baseRate, discounted) {
8884
+ const match = / · x(\d+(?:\.\d+)?)$/.exec(name);
8885
+ const fromName = match === null ? void 0 : Number(match[1]);
8886
+ return {
8887
+ base: match === null ? name : name.slice(0, match.index),
8888
+ rate: discounted ?? fromName ?? baseRate
8889
+ };
8890
+ }
8861
8891
  //#endregion
8862
8892
  //#region src/client/CodebuddyLive.tsx
8863
8893
  /**
@@ -1570,7 +1570,7 @@ function parseDiscount(value) {
1570
1570
  const fromString = multiplierOfCredits(raw["discountedCredits"]);
1571
1571
  const fromFactor = typeof raw["factor"] === "number" && Number.isFinite(raw["factor"]) && raw["factor"] >= 0 ? raw["factor"] : void 0;
1572
1572
  const multiplier = fromString ?? fromFactor;
1573
- const displayMode = raw["displayMode"] === "replace" ? "replace" : void 0;
1573
+ const displayMode = raw["displayMode"] === "replace" || raw["displayMode"] === "strikethrough" ? raw["displayMode"] : void 0;
1574
1574
  if (multiplier === void 0 && displayMode === void 0) return void 0;
1575
1575
  return {
1576
1576
  ...multiplier === void 0 ? {} : { multiplier },
@@ -6121,7 +6121,7 @@ function generationOf(version) {
6121
6121
  //#endregion
6122
6122
  //#region src/version.ts
6123
6123
  /** The npm package version this build was produced from. */
6124
- const WORKBUDDY_CONNECT_VERSION = "5.6.0";
6124
+ const WORKBUDDY_CONNECT_VERSION = "5.6.2";
6125
6125
  //#endregion
6126
6126
  //#region src/host-heartbeat.ts
6127
6127
  /**
package/lib/index.d.ts CHANGED
@@ -792,11 +792,30 @@ interface WorkBuddyPromoDiscount {
792
792
  */
793
793
  multiplier?: number;
794
794
  /**
795
- * The upstream's display instruction. `'replace'` means "show this instead of
796
- * the normal multiplier"; anything else is not yet observed, so it is not
797
- * acted on.
798
- */
799
- displayMode?: 'replace';
795
+ * The upstream's display instruction — how the discounted figure relates to
796
+ * the catalog one. BOTH spellings below are observed on the live CN feed
797
+ * (2026-10-23, eight entries dumped from `/v3/config`):
798
+ *
799
+ * `'replace'` — show the discounted figure INSTEAD of the normal one.
800
+ * Used by `hy3-free-trial-202608` (`"0x"`).
801
+ * `'strikethrough'` — the upstream's own client strike out the normal price
802
+ * and print the discounted one. Used by
803
+ * `glm-52-night-discount-202607` (`"0.50x"`),
804
+ * `hy4-night-discount-daytime-badge-202609` (`"0.00x"`)
805
+ * and `step-5-discount-daytime-badge-202610`.
806
+ *
807
+ * An earlier version accepted only `'replace'`, which silently dropped every
808
+ * real CN discount except the hy3 trial — the price never moved while the
809
+ * badge was lit (issue #120, second report). Both modes mean "the discounted
810
+ * figure is what you pay"; they differ only in whether the old figure is also
811
+ * shown on screen, which this plugin does not do either way (one number per
812
+ * row), so both are honoured identically here.
813
+ *
814
+ * `undefined` means the entry carries a discount the upstream did not label.
815
+ * That is NOT acted on: the field's absence says nothing about how to read
816
+ * the figure, and guessing "replace" would change a price on a guess.
817
+ */
818
+ displayMode?: 'replace' | 'strikethrough';
800
819
  }
801
820
  /** The upstream's explanatory copy for the badge. */
802
821
  interface WorkBuddyPromoHover {
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as prepareChatBody, A as parseWorkBuddyAuth, At as readAtRestKey, B as declaredTools, C as defaultDesktopAuthCandidates, Ct as findWorkbuddyAppExecutable, D as isEncryptedCredentialError, Dt as macosBundleExecutable, E as hasEncryptedCredentialFields, Et as isWorkbuddyBundle, F as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, Ft as writeFileAtomic, G as isFirstMessageGateBody, H as isContextTooLongBody, I as WorkBuddyCredentialRejectedError, J as openingEvidenceOfBody, K as knownRegionOf, L as WorkBuddyUpstreamClient, M as workbuddyOwnAuthPath, N as CONTEXT_OVERFLOW_MARKER, Nt as resolveDshHome, O as isVaultAccountId, Ot as macosNestedAppBundles, P as CREDENTIAL_REJECTED_CODE, Pt as withFileLock, Q as perturbChannelFingerprints, R as channelGateMemberOf, S as authFileName, St as fetchAtRestKeyPayload, T as defaultDesktopAuthPath, Tt as isEncryptedFieldWrapper, U as isConversationIdGateBody, V as forceNeutralHead, W as isCredentialRejectedError, X as parseReasoning, Y as parseCreditMultiplier, Z as parseUpstreamModel, _ as ENCRYPTED_CREDENTIAL_CODE, _t as nativeModalityOf, a as readHostHeartbeat, at as lastRefusalFor, b as WorkBuddyCredentialStore, bt as deriveAtRestKey, c as WORKBUDDY_CONNECT_VERSION, ct as recordRefusal, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, dt as WorkBuddyImageError, et as regionOf, f as WorkBuddyCatalog, ft as pickImageModel, g as withoutRouters, gt as imageDefaultFor, h as fallbackModelsFor, ht as NATIVE_MODALITY_BY_MODEL_ID, i as processStartTimeMs, it as defaultOffModelIds, j as workbuddyAccountId, jt as workbuddyAppExecutableCandidates, k as legacyWorkbuddyOwnAuthPath, kt as openEncryptedField, l as piAiRuntimeInfo, lt as withEffectiveOff, m as deriveCatalog, mt as queueNoticeText, n as clearHostHeartbeat, nt as attachBusyLevels, o as workbuddyHostHeartbeatPath, ot as offDefaultFor, p as applyContextBudgets, pt as parseQueueAnswer, q as modelIdOfBody, r as isHeartbeatProcessAlive, rt as REFUSES_OFF_MODEL_IDS, s as writeHostHeartbeat, st as offRefused, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, tt as requestEvidenceOf, u as FALLBACK_WORKBUDDY_MODELS, ut as DEFAULT_IMAGE_SIZE, v as WORKBUDDY_AUTH_FILENAME, vt as WORKBUDDY_APP_EXECUTABLE_ENV, w as defaultDesktopAuthDirs, x as WorkBuddyEncryptedCredentialError, xt as deriveAtRestKeyId, y as WORKBUDDY_AUTH_FILE_ENV, yt as clearAtRestKeyCache, z as classifyUpstreamError } from "./host-heartbeat-DqTNlE6H.js";
1
+ import { $ as prepareChatBody, A as parseWorkBuddyAuth, At as readAtRestKey, B as declaredTools, C as defaultDesktopAuthCandidates, Ct as findWorkbuddyAppExecutable, D as isEncryptedCredentialError, Dt as macosBundleExecutable, E as hasEncryptedCredentialFields, Et as isWorkbuddyBundle, F as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, Ft as writeFileAtomic, G as isFirstMessageGateBody, H as isContextTooLongBody, I as WorkBuddyCredentialRejectedError, J as openingEvidenceOfBody, K as knownRegionOf, L as WorkBuddyUpstreamClient, M as workbuddyOwnAuthPath, N as CONTEXT_OVERFLOW_MARKER, Nt as resolveDshHome, O as isVaultAccountId, Ot as macosNestedAppBundles, P as CREDENTIAL_REJECTED_CODE, Pt as withFileLock, Q as perturbChannelFingerprints, R as channelGateMemberOf, S as authFileName, St as fetchAtRestKeyPayload, T as defaultDesktopAuthPath, Tt as isEncryptedFieldWrapper, U as isConversationIdGateBody, V as forceNeutralHead, W as isCredentialRejectedError, X as parseReasoning, Y as parseCreditMultiplier, Z as parseUpstreamModel, _ as ENCRYPTED_CREDENTIAL_CODE, _t as nativeModalityOf, a as readHostHeartbeat, at as lastRefusalFor, b as WorkBuddyCredentialStore, bt as deriveAtRestKey, c as WORKBUDDY_CONNECT_VERSION, ct as recordRefusal, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, dt as WorkBuddyImageError, et as regionOf, f as WorkBuddyCatalog, ft as pickImageModel, g as withoutRouters, gt as imageDefaultFor, h as fallbackModelsFor, ht as NATIVE_MODALITY_BY_MODEL_ID, i as processStartTimeMs, it as defaultOffModelIds, j as workbuddyAccountId, jt as workbuddyAppExecutableCandidates, k as legacyWorkbuddyOwnAuthPath, kt as openEncryptedField, l as piAiRuntimeInfo, lt as withEffectiveOff, m as deriveCatalog, mt as queueNoticeText, n as clearHostHeartbeat, nt as attachBusyLevels, o as workbuddyHostHeartbeatPath, ot as offDefaultFor, p as applyContextBudgets, pt as parseQueueAnswer, q as modelIdOfBody, r as isHeartbeatProcessAlive, rt as REFUSES_OFF_MODEL_IDS, s as writeHostHeartbeat, st as offRefused, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, tt as requestEvidenceOf, u as FALLBACK_WORKBUDDY_MODELS, ut as DEFAULT_IMAGE_SIZE, v as WORKBUDDY_AUTH_FILENAME, vt as WORKBUDDY_APP_EXECUTABLE_ENV, w as defaultDesktopAuthDirs, x as WorkBuddyEncryptedCredentialError, xt as deriveAtRestKeyId, y as WORKBUDDY_AUTH_FILE_ENV, yt as clearAtRestKeyCache, z as classifyUpstreamError } from "./host-heartbeat-DWoUWD70.js";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import { appendFile, mkdir, open, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
4
4
  import { randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-connect-workbuddy",
3
3
  "displayName": "DSH Connect WorkBuddy",
4
4
  "description": "把本机登录的 WorkBuddy 模型(国内版与国际版)接入 DeepSeek Harness:每次刷新按上游目录自动读取每个模型的积分倍率,支持逐模型图片输入开关、多账号切换与账号池自动换号,并提供每日签到、卡片与输入框里的胶囊积分读数等只读积分概览。",
5
- "version": "5.6.0",
5
+ "version": "5.6.2",
6
6
  "author": "dingminhua",
7
7
  "copyright": "Copyright (c) 2026 LaoDing",
8
8
  "license": "MIT",