dsh-connect-workbuddy 4.3.2 → 4.3.3

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,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.3.3 (2026-10-12)
4
+
5
+ ### Fixes
6
+
7
+ - **月度套餐的余额改用容量池(`CapacityRemain`),不再用本周期余额(`CycleCapacityRemain`)——国际版账号不再被误报「积分耗尽」为 0。**(issue #48)
8
+ - **现象(报告者,`global` 区域 / macOS)**:两个国际版账号在 DSH 里显示积分耗尽,而同一账号在 WorkBuddy 客户端里完全正常;用同批 token 直接打 `/v2/chat/completions` 返回 **200**,证明账号并未耗尽。
9
+ - **根因**:上游对**同一个月度套餐**(`CapacityType === 4`)同时返回**两套容量字段**,它们回答的是两个不同的问题——`CapacityRemain`/`CapacitySize`/`CapacityUsed` 是**容量池**,`CycleCapacityRemain`/`CycleCapacitySize`/`CycleCapacityUsed` 是**本计费周期**。旧代码对月度套餐只读周期字段 `remain = monthly ? numberField('CycleCapacityRemain') : …`。
10
+ - **本机以真实账单接口复现(3 个国际版账号全部命中)**:`CapacityRemain=100` 而 `CycleCapacityRemain=0`、`CycleCapacityUsed=100`,且 `CycleEndTime="2026-10-31 23:59:59"`(**周期尚未结束**)、`Status=0`、`ExpiredTime=""`。即套餐有效、池里还有 100,插件却报 **0**。对照组:同机 CN 账号两字段一致(500/500),读数为 2267,**所以国内版看不出异常**——但 CN 也在受影响之列:本机 `老丁`/`LaoDing` 两个 CN 账号的月度套餐同样是池 500 而周期余额更小(实测同一时刻分别为 `CycleCapacityRemain=125` 与 `0`),修复后读数分别为 **1343→1718** 与 **768→1268**;`老丁` 的增量 +375 恰等于 `500 − 125`,正好说明这一个字段差异在两个区域都存在、且月度套餐的周期余额会随消耗变化。报告者「cn 是否受影响未能对照」一节由此有答案。
11
+ - **修法**:余额一律读容量池 `CapacityRemain`(两种套餐都是),`size` 同理统一读 `CapacitySize`(真实月度行上 `CapacitySize` 与 `CycleCapacitySize` 同时存在且相等,取前者;后者保留为回退)。**周期字段继续读取**并作为新的 `cycleRemain` 随包透出——它回答的是「本周期额度是否用完」这个**不同**的问题,而 `refreshAtMs`(周期续期点)的提示文案只有靠它才成立。**没有采用报告者建议的 `Math.max(两者)`**:那两个字段不是同一量的两种口径,取较大值会把「本周期已用完、等 10/31 续期」误报成「仍有余额」,正是 `quotaRefreshAtMs` 冷却提示要避免的误导。
12
+ - **修法未触动 gift 路径**(`CapacityType 1`):仍读 `CapacityRemain`、仍按「耗尽或过期即丢弃」处理——本缺陷只存在于月度分支。
13
+ - **实测(真实接口,修复前后对照)**:3 个国际版账号 `0 → 100`(各自 `CapacityRemain=100`);CN `17749714179` 保持 2267 不变(该账号两字段本来就一致);CN `老丁` 1343→1718、`LaoDing` 768→1268。**CN 侧读数上升不是回归,是修正**:这两个账号的月度套餐池余额(500)一直高于本周期余额,旧代码只读了后者。
14
+ - **守卫 `tests/issue48-monthly-pool-credits.spec.ts`(8 例)**:直接驱动**真实的 `fetchCredits` 解析器**(stub 掉的只有 `fetch`),用**真实账单行的原始字段值**(池 100 / 周期 0 / 周期未结束)构造夹具。原有的全部套件都在边界上 mock 掉整个 `fetchCredits`,**这正是该缺陷能漏出去的原因**——字段解析这一层此前零覆盖。覆盖:读池而非周期(回归本体)、`cycleRemain` 单独透出且 `refreshAtMs` 仍在、多账号求和、两字段一致时不受影响(同时通过新旧代码,用来说明「CN 没有开始按另一种方式读」)、周期耗尽但池充足时账号不被丢弃、gift 路径不变(含耗尽/过期两条丢弃分支)、负值不进入 total、月度与 gift 混合不重复计数。**变异验证**:把 `remain` 改回 `numberField('CycleCapacityRemain')` → 3 例变红,报错正是 `expected +0 to be 100`(与现场症状同形)。
15
+ - **边界透传守卫**:`tests/web-status.spec.ts` 增补断言,钉住 `remain` 与 `cycleRemain` **各自独立**地跨 host→client 边界存活(`toMatchObject` 不会因字段丢失而变红),并钉住 gift **不带**该字段(缺失而非 0)。**变异验证**:删掉 `web-status.ts` 里的透传 → 立即变红。
16
+
3
17
  ## 4.3.2 (2026-10-12)
4
18
 
5
19
  ### Fixes
package/lib/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { C as defaultDesktopAuthDirs, F as WorkBuddyUpstreamClient, G as WORKBUDDY_APP_EXECUTABLE_ENV, O as legacyWorkbuddyOwnAuthPath, S as defaultDesktopAuthCandidates, Z as findWorkbuddyAppExecutableWithSource, a as readHostHeartbeat, at as hostPeersInfo, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, j as workbuddyOwnAuthPath, l as piAiRuntimeInfo, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS, v as WORKBUDDY_AUTH_FILE_ENV, y as WorkBuddyCredentialStore } from "./host-heartbeat-DyU_tIIj.js";
2
+ import { C as defaultDesktopAuthDirs, F as WorkBuddyUpstreamClient, G as WORKBUDDY_APP_EXECUTABLE_ENV, O as legacyWorkbuddyOwnAuthPath, S as defaultDesktopAuthCandidates, Z as findWorkbuddyAppExecutableWithSource, a as readHostHeartbeat, at as hostPeersInfo, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, j as workbuddyOwnAuthPath, l as piAiRuntimeInfo, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS, v as WORKBUDDY_AUTH_FILE_ENV, y as WorkBuddyCredentialStore } from "./host-heartbeat-DE8M7vXk.js";
3
3
  import { realpathSync } from "node:fs";
4
4
  import { fileURLToPath } from "node:url";
5
5
  //#region src/bin.ts
@@ -1763,8 +1763,9 @@ var WorkBuddyUpstreamClient = class {
1763
1763
  const account = raw;
1764
1764
  const numberField = (key) => typeof account[key] === "number" ? account[key] : 0;
1765
1765
  const monthly = numberField("CapacityType") === 4;
1766
- const size = monthly ? numberField("CycleCapacitySize") : numberField("CapacitySize");
1767
- const remain = monthly ? numberField("CycleCapacityRemain") : numberField("CapacityRemain");
1766
+ const size = numberField("CapacitySize") || numberField("CycleCapacitySize");
1767
+ const remain = numberField("CapacityRemain");
1768
+ const cycleRemain = monthly ? numberField("CycleCapacityRemain") : void 0;
1768
1769
  const cappedRemain = remain < 0 ? 0 : remain;
1769
1770
  const cycleEndMs = parseDate(account["CycleEndTime"]);
1770
1771
  const expiresAtMs = monthly ? void 0 : parseDate(account["ExpiredTime"]) ?? cycleEndMs;
@@ -1781,6 +1782,7 @@ var WorkBuddyUpstreamClient = class {
1781
1782
  remain: cappedRemain,
1782
1783
  size,
1783
1784
  monthly,
1785
+ ...cycleRemain === void 0 ? {} : { cycleRemain: Math.max(0, cycleRemain) },
1784
1786
  ...refreshAtMs === void 0 ? {} : { refreshAtMs },
1785
1787
  ...expiresAtMs === void 0 ? {} : { expiresAtMs }
1786
1788
  });
@@ -3707,7 +3709,7 @@ function generationOf(version) {
3707
3709
  //#endregion
3708
3710
  //#region src/version.ts
3709
3711
  /** The npm package version this build was produced from. */
3710
- const WORKBUDDY_CONNECT_VERSION = "4.3.2";
3712
+ const WORKBUDDY_CONNECT_VERSION = "4.3.3";
3711
3713
  //#endregion
3712
3714
  //#region src/host-heartbeat.ts
3713
3715
  /**
package/lib/index.d.ts CHANGED
@@ -605,10 +605,26 @@ interface WorkBuddyUpstreamModel {
605
605
  descriptionEn?: string;
606
606
  supportsToolCall?: boolean;
607
607
  }
608
- /** One billing package and its remaining credit, already aggregated. */
609
608
  /** One billing package as the upstream returns it, dates already parsed. */
610
609
  interface WorkBuddyCreditPackage {
611
610
  packageName: string;
611
+ /**
612
+ * Remaining credit, always read from `CapacityRemain` (issue #48).
613
+ *
614
+ * The upstream reports a monthly resource (`CapacityType 4`) with TWO
615
+ * capacity families, and they answer different questions:
616
+ *
617
+ * - `CapacityRemain` / `CapacitySize` / `CapacityUsed` — the capacity POOL.
618
+ * - `CycleCapacityRemain` / `CycleCapacitySize` / `CycleCapacityUsed` — the
619
+ * CURRENT BILLING CYCLE.
620
+ *
621
+ * "How much credit do I have" is the pool, so that is what this field
622
+ * carries. Reading `CycleCapacityRemain` here was the defect: on three real
623
+ * global accounts the upstream returned `CapacityRemain=100` together with
624
+ * `CycleCapacityRemain=0` and `CycleCapacityUsed=100` inside an UNFINISHED
625
+ * cycle (`CycleEndTime` days out, `Status=0`), so the plugin reported 0
626
+ * credits for accounts that were not exhausted at all.
627
+ */
612
628
  remain: number;
613
629
  size: number;
614
630
  /** CapacityType 4: refreshed every cycle and never expires. */
@@ -617,6 +633,16 @@ interface WorkBuddyCreditPackage {
617
633
  refreshAtMs?: number;
618
634
  /** One-off expiry; the package disappears from the account at this time. */
619
635
  expiresAtMs?: number;
636
+ /**
637
+ * Remaining credit in the CURRENT cycle; only on monthly packages.
638
+ *
639
+ * Kept beside {@link remain} because it answers the one question the pool
640
+ * total cannot: whether THIS cycle's allowance is spent. That is what makes
641
+ * `refreshAtMs` meaningful — "you are out until the cycle resets" is a
642
+ * different statement from "you are out of credit", and only the cycle
643
+ * figure can tell them apart.
644
+ */
645
+ cycleRemain?: number;
620
646
  }
621
647
  /** Aggregated credit answer for one credential. */
622
648
  interface WorkBuddyCredits {
@@ -2017,10 +2043,18 @@ declare function nextRegionEnabled(value: unknown, region: WorkBuddyWebRegion, e
2017
2043
  /** One credit package as the upstream returns it, node-free. */
2018
2044
  interface WorkBuddyWebCreditPackage {
2019
2045
  packageName: string;
2046
+ /** Capacity-pool balance (`CapacityRemain`); see the host-side type for issue #48. */
2020
2047
  remain: number;
2021
2048
  size: number;
2022
2049
  /** CapacityType 4: refreshed every cycle and never expires. */
2023
2050
  monthly: boolean;
2051
+ /**
2052
+ * Balance of the CURRENT cycle (`CycleCapacityRemain`); only on monthly
2053
+ * packages. `0` on a live package means this cycle's allowance is spent,
2054
+ * which is what {@link cycleRefreshMs} is telling the user to wait for — and
2055
+ * is deliberately NOT the same as {@link remain} being 0 (issue #48).
2056
+ */
2057
+ cycleRemain?: number;
2024
2058
  /** Next cycle start (the monthly refresh point) in ms; only on monthly packages. */
2025
2059
  cycleRefreshMs?: number;
2026
2060
  /** One-off expiry in ms; the package disappears from the account then. */
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as isWorkbuddyBundle, A as workbuddyAccountId, B as parseCreditMultiplier, C as defaultDesktopAuthDirs, D as isVaultAccountId, E as isEncryptedCredentialError, F as WorkBuddyUpstreamClient, G as WORKBUDDY_APP_EXECUTABLE_ENV, H as parseUpstreamModel, I as classifyUpstreamError, J as deriveAtRestKeyId, K as clearAtRestKeyCache, L as declaredTools, M as CREDENTIAL_REJECTED_CODE, N as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, O as legacyWorkbuddyOwnAuthPath, P as WorkBuddyCredentialRejectedError, Q as isEncryptedFieldWrapper, R as isCredentialRejectedError, S as defaultDesktopAuthCandidates, T as hasEncryptedCredentialFields, U as prepareChatBody, V as parseReasoning, W as regionOf, X as findWorkbuddyAppExecutable, Y as fetchAtRestKeyPayload, _ as WORKBUDDY_AUTH_FILENAME, a as readHostHeartbeat, b as WorkBuddyEncryptedCredentialError, c as WORKBUDDY_CONNECT_VERSION, ct as writeFileAtomic, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, et as macosBundleExecutable, f as WorkBuddyCatalog, g as ENCRYPTED_CREDENTIAL_CODE, h as fallbackModelsFor, i as processStartTimeMs, it as workbuddyAppExecutableCandidates, j as workbuddyOwnAuthPath, k as parseWorkBuddyAuth, l as piAiRuntimeInfo, m as deriveCatalog, n as clearHostHeartbeat, nt as openEncryptedField, o as workbuddyHostHeartbeatPath, ot as resolveDshHome, p as applyContextBudgets, q as deriveAtRestKey, r as isHeartbeatProcessAlive, rt as readAtRestKey, s as writeHostHeartbeat, st as withFileLock, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, tt as macosNestedAppBundles, u as FALLBACK_WORKBUDDY_MODELS, v as WORKBUDDY_AUTH_FILE_ENV, w as defaultDesktopAuthPath, x as authFileName, y as WorkBuddyCredentialStore, z as knownRegionOf } from "./host-heartbeat-DyU_tIIj.js";
1
+ import { $ as isWorkbuddyBundle, A as workbuddyAccountId, B as parseCreditMultiplier, C as defaultDesktopAuthDirs, D as isVaultAccountId, E as isEncryptedCredentialError, F as WorkBuddyUpstreamClient, G as WORKBUDDY_APP_EXECUTABLE_ENV, H as parseUpstreamModel, I as classifyUpstreamError, J as deriveAtRestKeyId, K as clearAtRestKeyCache, L as declaredTools, M as CREDENTIAL_REJECTED_CODE, N as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, O as legacyWorkbuddyOwnAuthPath, P as WorkBuddyCredentialRejectedError, Q as isEncryptedFieldWrapper, R as isCredentialRejectedError, S as defaultDesktopAuthCandidates, T as hasEncryptedCredentialFields, U as prepareChatBody, V as parseReasoning, W as regionOf, X as findWorkbuddyAppExecutable, Y as fetchAtRestKeyPayload, _ as WORKBUDDY_AUTH_FILENAME, a as readHostHeartbeat, b as WorkBuddyEncryptedCredentialError, c as WORKBUDDY_CONNECT_VERSION, ct as writeFileAtomic, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, et as macosBundleExecutable, f as WorkBuddyCatalog, g as ENCRYPTED_CREDENTIAL_CODE, h as fallbackModelsFor, i as processStartTimeMs, it as workbuddyAppExecutableCandidates, j as workbuddyOwnAuthPath, k as parseWorkBuddyAuth, l as piAiRuntimeInfo, m as deriveCatalog, n as clearHostHeartbeat, nt as openEncryptedField, o as workbuddyHostHeartbeatPath, ot as resolveDshHome, p as applyContextBudgets, q as deriveAtRestKey, r as isHeartbeatProcessAlive, rt as readAtRestKey, s as writeHostHeartbeat, st as withFileLock, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, tt as macosNestedAppBundles, u as FALLBACK_WORKBUDDY_MODELS, v as WORKBUDDY_AUTH_FILE_ENV, w as defaultDesktopAuthPath, x as authFileName, y as WorkBuddyCredentialStore, z as knownRegionOf } from "./host-heartbeat-DE8M7vXk.js";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import { readFile } from "node:fs/promises";
4
4
  import { randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
@@ -3619,6 +3619,7 @@ function toCredits(answer) {
3619
3619
  remain: pack.remain,
3620
3620
  size: pack.size,
3621
3621
  monthly: pack.monthly,
3622
+ ...pack.cycleRemain === void 0 ? {} : { cycleRemain: pack.cycleRemain },
3622
3623
  ...pack.refreshAtMs === void 0 ? {} : { cycleRefreshMs: pack.refreshAtMs },
3623
3624
  ...pack.expiresAtMs === void 0 ? {} : { expiresAtMs: pack.expiresAtMs }
3624
3625
  })),
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": "4.3.2",
5
+ "version": "4.3.3",
6
6
  "author": "dingminhua",
7
7
  "copyright": "Copyright (c) 2026 LaoDing",
8
8
  "license": "MIT",