dsh-connect-workbuddy 3.5.0 → 4.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 CHANGED
@@ -1,5 +1,58 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.0.0 (2026-10-04)
4
+
5
+ ### ⚠️ Breaking Changes
6
+
7
+ - **侧边栏底部的积分行已被移除,积分读数改到对话输入框工具栏。**
8
+ - 如果你在用上一版的侧边栏积分行:它**不会**再出现。新版在**对话输入框左上角**显示一行可点击的读数(`WB CN · 1,072` / `WB AI · 88`),并**按本会话当前选中的模型自动切换区域**——原来那两行是常驻的,现在只在你用本插件的模型时才出现。
9
+ - **不需要重新配置**:原来那两个开关(`regions.<region>.showCreditsInMainUi`)**沿用同一个字段**,只是含义从「在侧边栏显示」变成「在对话框显示」。你此前关闭过的那一边,升级后依然是关闭的。
10
+ - **主版本号只因此 +1**:功能没有减少,但这个改动会让正在用它的用户**看不到原来那一行**,属于用户可见的行为变更,按语义化版本应当升 major 而不是 minor。
11
+
12
+ ### Features
13
+
14
+ - **积分读数移入对话输入框工具栏(`conversation.input.left`),按会话当前所选模型自动切换区域。**
15
+ - **做法沿用同族项目** `dsh-connect-trae`(MIT)的 composer 读数:一行可点击的读数 `WB CN · 1,072`,点击弹出锚定面板;面板里是**账号表格**(每账号一行余额,点行即切换到该账号);**手动刷新按钮放在面板内**而不是读数上;每账号余额**只在面板打开时抓**;5 分钟定时器在首取结束后才启动;失败保留上次数值。
16
+ - **为什么从侧边栏搬过来**:宿主的 `sidebar.footer.action` 是**单个 root 级共享行**——所有 bundle 都往那一行里塞。实测把它扩成两行时,该容器 `display:flex` 且无 `flex-wrap`/`flex-direction`,两行会被压成等宽两列而重叠。输入框这行是 **session 级且按 provider 归属**,装两个连接器插件也不会互相抢位置。这也是 trae 自己在 2.13.0 移除侧边栏积分行的同一结论。
17
+ - **按 provider 判定归属**:gate 读宿主的 `modelSelection` 投影(`next` 优先于 `lastUsed`,与宿主自身一致),只有本会话选中的模型属于本插件的 `workbuddy` / `workbuddy-global` 时才渲染。**换成别的 provider 就完全不渲染**——不占位、不发请求。
18
+ - **两个区域都支持**(与 trae 不同):trae 的国际版是订阅制、没有可比数字,本插件的国际版有真实余额。所以读数据当前模型所属区域显示,并标明 `WB CN` / `WB AI`,面板标题也按区域区分。
19
+ - **面板里的余额读数是「总额」**(`credits.total`,与卡片头部同一字段),且**读不到时显示 `—` 而不是 `0`**:宿主端点在该账号取不到余额时**省略** `credits` 字段,正是为了让界面不能对账号做出「没积分了」这种错误断言。
20
+ - **面板里标出「被限流」等不可用原因**:每个账号行若当前不能服务,就在账号名下方用**与账号池表格同一套文案**标出原因(被限流/积分耗尽/被拒绝/…)。复用宿主已有的 `exclusionOf` 判定(含冷却到期逻辑)与 `exclusionText` 标签函数(本版从 `AccountPool.tsx` 提到 `exclusion-text.ts` 共用),因此同一账号在池子表格与积分面板里不会出现两种说法。
21
+ - **读数只反映当前选中账号**(`store.resolve()` 取到的那个),不是该区域所有账号的汇总——它读的就是宿主 usage 文档里的 `credits.total`,与卡片头部同一字段。
22
+ - **新增宿主端点** `/account-credits`:按区域列出每个账号的余额,**每次打开面板才抓一次**(每账号一次上游读),不挂在 5 分钟循环上。单账号失败不影响其他行。
23
+ - **开关跟着 tab 走**:卡片的「在对话框显示 WB CN / WB AI 积分」只显示当前 tab 区域的那一个,写入 `regions.<region>.showCreditsInMainUi`(与 `enabled` 同级),并走 landed-check 的 `writeRegionCreditsShown`。**该区域 provider 关闭时**该行不渲染、开关置灰且显示为关闭(关闭的 provider 取不到积分,留着只会常驻失败)。
24
+ - **样式类名与组件一一对应**(本版修掉一个真实缺陷):组件从 `Points` 重命名为 `Credits` 时,只有两条 **trigger** 规则没跟着改,于是读数按钮匹配不到任何规则、回退成**浏览器默认按钮**(带边框的大方块)。新增用例断言「组件渲染的每个 `dsm-workbuddy-composer-*` 类名都在 CSS 里有定义」,并把触发器的「无边框/透明底/12px」写成显式契约。其余类名恰好一致,所以当时只有这两条出错、别的测试都没发现。
25
+ - **`react-dom` 必须在 `CLIENT_EXTERNALS` 里**:面板用 `createPortal` 挂到 `document.body`,而打包 react-dom 会把它的 CJS 开发构建内联进来(开头即 `process.env.NODE_ENV`),在浏览器**模块求值期**抛 `ReferenceError: process is not defined`,导致**整个客户端半边 import 失败**——这正是同族项目 2.12.0/2.13.0 的线上事故。本版除在 `tsdown.config.ts` 外置外,另加 `tests/client-runtime-imports.spec.ts` 静态守卫(未外置的裸导入、外置了但宿主未注册的模块,都会让它变红)。
26
+
27
+ <img src="docs/assets/dsh-connect-workbuddy-composer-credits.png" width="360" alt="对话输入框积分面板:国内版账号余额表格、刷新积分按钮与最近刷新时间" />
28
+
29
+ 面板实机截图(国内版):标题按区域为「WB CN 积分」,表格两列为账号与积分,当前生效账号用实心圆点标出,底部显示最近刷新时间。
30
+
31
+ ## 3.6.0 (2026-10-04)
32
+
33
+ ### Features
34
+
35
+ - **「DSH 上下文预算」增加 500K 挡位。**
36
+ - 该选择器原本只有「200K」与「模型原生窗口」两档。上游官方的挡位阶梯是 300K/600K/960K(或 1M),没有 500K,所以 500K 是一个**便利挡位**而非上游限制,按需求直接加。
37
+ - **门槛取 `原生窗口 > 500_000`**,而不是复用 200K 那档的 `> 200_000`:否则 192K 的 `hy3`、300K 的模型都会得到一个写着 500K、实际却高于自身原生窗口的按钮——`catalog.ts` 用 `Math.min(原生, 预算)` 兜底,点它等于什么都没发生,却看起来像用户主动做了降级。
38
+ - 未存过预算时该挡位**不选中**(`activeContextBudgets[id] === 500_000` 对 `undefined` 为假),默认仍是 200K 那档;`?? 200_000` 只属于 200K 挡位。
39
+
40
+ ### Fixes
41
+
42
+ - **国内版模型目录改读 App 自己那份 `/v3/config`,卡片显示的模型与价格不再和 App 对不上。**
43
+ - **现象**:同一个模型槽,App 里显示 `Hy4 preview (x0.00)`,插件卡片里显示 `Hy4 preview (x0.29)`。两边都没错,只是**读的不是同一份文档**——插件读 `/v2/enterprises/personal/models`(CLI 渠道名册,该槽位是付费的 `hy4-preview`),App 读 `/v3/config`(该槽位是免费的 `hy4-preview-f`)。两者显示名同为 "Hy4 preview",价格却不同,于是看起来像插件显示错了。
44
+ - **根因**:`fetchModels` 的国内分支固定读 `/v2`,国际分支读 `/v3`。国内版从来没有读过 App 的那份文档,因此名册与价格天然可能与 App 不一致;而 `/v2` 里还有一个上游目录里不存在的 `auto` 槽,`/v3` 里则有 `auto` 没有的 `hy4-preview-f` 与 `minimax-m2.7`。
45
+ - **修法**:国内分支改为读 `/v3/config`,**沿用 CLI 的 User-Agent**——这一步是关键:`/v3` 按客户端渠道返回不同名册,桌面端渠道的国内名册(29 条)里**没有** `hy4-preview-f`,只有 CLI 渠道的 17 条名册有。实测确认 `/v3` 接受国内分支原有的整套请求头(`X-Domain` / `X-Product` / `X-Requested-With` 一个都不需要加),所以这是一处常量改动,没有协议风险。另加回退:`/v3` 请求失败或返回非 0 时退回旧的 `/v2` 并记一条 `ctx.logger.warn`,避免网关不再提供新文档时整个区域拿不到模型。回退覆盖三种失败:传输错误、信封 code 非 0、以及**解析出的名册为空**(`selectCliModels` 对空名册抛错)——三者都会退回旧源,不会让区域变空。
46
+ - **升级后需要手动做的两件事**(名册换源,不是可选项):
47
+ 1. 旧源独有的 `auto` 与 `hy4-preview` 不再出现在名册里;新源独有的是 `hy4-preview-f` 与 `minimax-m2.7`(两者并集 19 条,交集 17 条,实测)。
48
+ 2. 若你此前勾选的正是 `hy4-preview`,刷新后这个勾会消失(`refreshModels` 用 `freshIds.has(id)` 过滤),而 `hy4-preview-f` **不会**被自动勾上——这是既有设计:插件从不替你勾选你没选过的模型(`stillEnabled` 的注释即此意)。勾一下新出现的 `Hy4 preview` 即可,它是免费的那只;其余已勾选的模型不受影响,模型列表也不会变空。
49
+
50
+ - **`regions.<区域>` 为 `null` 时不再让读取路径崩溃(防御性加固,当前不可达)。**
51
+ - **背景**:为排查 issue #31 实测时发现,schemastery 的 `simplify()` 会把**空槽**序列化成 `null`(`{ cn: {} }` → `{ cn: null }`)。而 `regionStateOf` 的守卫只拒绝 `undefined`,所以 `null` 槽会被原样返回,`regionEnabled` 随即在 `.enabled` 上抛 `Cannot read properties of null`。
52
+ - **可达性**:**当前不可达**。宿主解析配置时会把存下来的 `null` 槽重新展开成完整的默认 regionState,实测 `Config({ regions: { cn: null } })` 得到的是一整份默认值而非 `null`。因此这是纯防御,不是某个已知崩溃的修复。
53
+ - **改动**:守卫补上 `&& stored !== null`,并新增 3 个回归用例(返回对象而非 null、`null` 槽与「缺失」槽行为一致地回落到遗留扁平字段、`regionEnabled` 不抛)。
54
+ - **顺带**:排查过程中的结论已贴在 issue #31——该 issue 里最后一条指向本插件的线索(`accountId` 是「存在且为 undefined」的自有键)**被实测证伪**:`accountId` 在解析后是**缺失**,且遍历 7 种真实输入的解析结果都没有任何「值为 undefined 的自有键」,`simplify()` 之后与 JSON 往返完全相等。
55
+
3
56
  ## 3.5.0 (2026-10-04)
4
57
 
5
58
  ### Features
package/README.en.md CHANGED
@@ -27,8 +27,11 @@ A [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) bund
27
27
  - **Model management** — refresh the full catalog from upstream and enable or disable each model individually; refresh is a draft operation that only takes effect on save. Upstream also reports credit multiplier, context/output limits, and reasoning efforts; **image input is decided by a vendor-verified table**: a refresh pre-checks only models the vendor's own documentation calls natively multimodal, leaves documented text-only models unchecked, and treats anything without a vendor source as unverified and unchecked rather than guessing. Upstream's `supportsImages` is a *platform* declaration, not the model's capability, so it does not drive that decision at all. You can still tick any box by hand, and your selection is kept locally. **Every model row also carries a “Can disable” (`off`) checkbox**: it decides whether that model gets the “turn thinking off entirely” level. The default comes from a reviewed built-in table (the 9 models that claim the capability but answer HTTP 400 for it start unticked), and you can change it per model — **untick** for a newly refusing upstream model, **tick** for one the table wrongly hides or you have verified works; your ticks are saved on first save and recomputed no further.
28
28
  - **Model availability testing, including cooldown recovery times** — working out when a limited model frees up used to mean guessing through repeated failures. Now every model row has a **Test** button that gives you a direct answer for whether the model works right now and, if not, when it comes back. **It sends a real-volume request (about 25k input tokens)**, rather than "ping it and see if it answers" — because the upstream throttles on request **SIZE** (measured: about 20k tokens pass, about 30k are refused), so a small request looks fine while every request in a long conversation may be refused. That is exactly where "the test passes but I am limited" came from. **Where the recovery time comes from**: the upstream's 429 carries **no** `Retry-After` header (and no `X-RateLimit-*`), but it writes the reset time into the response **body** — `{"code":6004,"msg":"…将在 2026-09-30 02:30:30 UTC+8 重置…"}`. The plugin parses that time and converts it using the UTC offset the message states, so the result does not depend on your machine's timezone; the card then shows “✗ Rate limited · usable again after 2026/9/30 02:30:30”. When none of the sources states a time, the plugin **never invents a countdown** and says plainly that no time was given. A rate limit deliberately does **not** borrow the monthly refresh time, which would tell you to wait weeks for a limit that clears in seconds. Probes run in the Host, so **the credential never reaches the page**, and results are not written to settings — your saved selection is untouched.
29
29
 
30
- **Two things to know.** ① **It really does spend credits** — one measured probe reported `credit` 0.02 (a refused one 429s and costs almost nothing). So testing is **manual and per-model only**: there is deliberately no “Test selected” batch button, because sweeping the whole list from one click is an uncontrolled spend — and the Host enforces the same rule, **accepting a single model only** and refusing multi-model requests. ② The upstream advertises a 1M context yet throttles far below it, and the plugin advertises each model's **native** window to DSH (no longer clamped to 200K as of 3.5.0), so DSH compacts very late — hitting the throttle in a long session follows directly from that. To make DSH compact sooner, set a “DSH context budget” cap on the model's row.
30
+ **Two things to know.** ① **It really does spend credits** — one measured probe reported `credit` 0.02 (a refused one 429s and costs almost nothing). So testing is **manual and per-model only**: there is deliberately no “Test selected” batch button, because sweeping the whole list from one click is an uncontrolled spend — and the Host enforces the same rule, **accepting a single model only** and refusing multi-model requests. ② The upstream advertises a 1M context yet throttles far below it, and the plugin advertises each model's **native** window to DSH (no longer clamped to 200K as of 3.5.0), so DSH compacts very late — hitting the throttle in a long session follows directly from that. To make DSH compact sooner, set a “DSH context budget” cap on the model's row (200K / 500K / the model's native window).
31
31
  - **Local account switching** — discovers the multiple sign-in credentials WorkBuddy's desktop app leaves behind and lets you switch per region. Tokens are never written to DSH settings.
32
+ - **Credit readout in the composer toolbar (follows the selected model, per region)** — a clickable readout sits at the left of the composer toolbar (e.g. `WB CN · 1,072`) and **switches region automatically with the model this session is using**: pick a WB CN model and it shows the domestic balance, pick a WB AI model and it shows the international one; with any other provider's model it does not appear at all. It refreshes every 5 minutes and can also be refreshed by hand. Clicking it opens a panel: a **table of balances per account**, where clicking a row switches to that account (the same setting the card's account picker writes), and an account that cannot serve right now is **labelled in its row** (e.g. rate-limited, out of credits) using the same wording as the account-pool table. The per-account figures are fetched only while the panel is open, so the 5-minute background refresh never multiplies by the account count. To turn a side off, untick “Show WB CN / WB AI credits in the composer” in that region's tab on the card; while a region's provider is switched off, the readout disappears and its switch is disabled.
33
+
34
+ <img src="docs/assets/dsh-connect-workbuddy-composer-credits.png" width="360" alt="Composer credit panel: a per-account balance table for the domestic region, with a refresh button and a last-refreshed time" />
32
35
  - **Account pool: batch actions across several accounts, plus automatic failover** — tick the accounts you want into a pool and you get **one-click check-in for every account**, **one-click testing of every account against a model you choose** (defaulting to the region's zero-multiplier free model), and with **Enable the account pool** switched on, **automatic failover**: a request that fails upstream is retried against the pool's other usable accounts, in order, until one serves it or none is left. Membership starts **empty and is an explicit opt-in**: "signed in on this machine" is not the same as "spend this account's credits and claim its rewards". The pool's **ranking decides who serves every request** (usable first, then higher credits, soonest-expiring, freshest credential), and on a failure the next usable member is tried. It **never writes back your saved selection** — switch the pool off and your own account applies again immediately. **The switch only decides whether the PLUGIN chooses** — with it off, the plugin stops picking and switching accounts for you, but check-in and testing stay available (they are manual actions). Testing reuses the model rows' real-volume probe, so it **does cost real credits**; **automatic check-in is deliberately not offered** — a check-in claims real rewards, so it only ever runs when you press the button. See [Account pool](#account-pool).
33
36
  - **Actionable advice when a credential is refused** — when the upstream rejects the selected account's token, the plugin actually **probes** the other local sign-ins: if one still answers, it tells you to switch to it (with a one-click switch) instead of vaguely asking you to sign in again — re-authenticating fixes nothing while you are pinned to a revoked backup. Only when there is genuinely no other local account does it ask you to sign in again.
34
37
  - **Credits live in the account pool** — the member table shows each account's remaining credits and nearest expiry, and credits are the ranking's second key (more credits serves first). The queries are **read-only** and consume no credits.
@@ -50,7 +53,7 @@ DSH PiAiAdapter (one stack per provider)
50
53
 
51
54
  The CN and international sides each own a complete runtime stack — credential store, model catalog, loopback shim, adapter — with visibility filtered by credential domain (`workbuddy.ai` / `codebuddy.ai` → international, anything else → CN), so **both regions' accounts can be signed in and used by different sessions at the same time**. The international product has two brand domains: the desktop app signs in at `workbuddy.ai`, while the CodeBuddy CLI signs in at `codebuddy.ai` — both are international.
52
55
 
53
- The model catalog comes from each region's correct source: CN reads `/v2/enterprises/personal/models`, while international reads `<global gateway>/v3/config` — the config service serves a different roster per client channel, and only the desktop user agent yields the account's real 20-model list (including the free `deepseek-v4.1-flash`). The credits overview comes from `https://www.codebuddy.cn/v2/billing/meter/get-user-resource` (international accounts auto-route to the global gateway their credential belongs to, `workbuddy.ai` or `codebuddy.ai` — tokens are not interchangeable across the two brand domains); all are read-only.
56
+ The model catalog comes from each region's correct source, and both read the **same** `/v3/config` document the WorkBuddy app itself consumes; they differ only in which client identity requests it: CN uses the CodeBuddy CLI user agent, international uses the desktop user agent (the config service serves a different roster per client channel — only the CN CLI channel's roster carries the free `hy4-preview-f`, while the international side needs the desktop agent to get the account's real 20-model list, including the free `deepseek-v4.1-flash`). If CN cannot read `/v3/config`, it falls back to the legacy `/v2/enterprises/personal/models` and logs a line — that legacy roster is the CLI channel's, where the same slot carries the paid `hy4-preview` (`x0.29`) rather than the free `hy4-preview-f` (`x0.00`) the app shows, so it is used only while degraded. The credits overview comes from `https://www.codebuddy.cn/v2/billing/meter/get-user-resource` (international accounts auto-route to the global gateway their credential belongs to, `workbuddy.ai` or `codebuddy.ai` — tokens are not interchangeable across the two brand domains); all are read-only.
54
57
 
55
58
  Credentials are read (read-only) from the WorkBuddy desktop app's own auth file. Refreshed tokens are kept per region in `$DSH_HOME/.workbuddy-auth.cn.json` and `$DSH_HOME/.workbuddy-auth.global.json` (two simultaneously signed-in accounts never overwrite each other; the legacy single file `.workbuddy-auth.json` is still read as a migration source); the desktop app's file is never written.
56
59
 
package/README.md CHANGED
@@ -34,8 +34,11 @@
34
34
  - **模型管理** —— 从上游刷新完整模型目录,逐项勾选启用或禁用;刷新是草稿操作,点保存才生效。上游同时给出积分倍率、上下文/输出上限与推理档位;**图片输入按厂商核验表判定**:刷新时只有厂商文档明确写为原生多模态的模型才自动勾选,文档写明纯文本的不勾,**没有厂商资料的一律按「未核实」不猜**(上游的 `supportsImages` 是平台侧声明,不等于模型能看图,不参与判定)。用户仍可随时手动勾选,勾选状态保存在本地。**每个模型行还有「可关闭思考」(`off`)勾选框**:决定要不要为该模型提供「完全关闭思考」这一档位。默认由内置复核名单给出(上游声称可关闭、实测却对它返回 HTTP 400 的 9 个模型默认不勾),你可以按模型改勾/取消——**取消**用于上游新出现的拒绝模型,**勾上**用于名单误伤、或你已确认可用的模型;首次保存即写入你的勾选,之后不再重算。
35
35
  - **模型可用性测试(含冷却恢复时间)** —— 限量模型的冷却时间过去只能靠反复试错去猜。现在每个模型行有一个「测试」按钮,直接告诉你它此刻能否使用、不能用时何时恢复。**它发的是一条真实体积的请求(约 25k 输入 token)**,而不是「发个 ping 看通不通」——因为上游的限流**按请求体积触发**(实测约 20k 能过、约 30k 被拒),小请求看着正常、长对话里却可能每次都被拒,那正是「测试通过、实际被限」的来源。**恢复时间的来源**:上游的 429 **不带** `Retry-After` 响应头(也没有 `X-RateLimit-*`),但它把重置时间写在**响应体**里——`{"code":6004,"msg":"…将在 2026-09-30 02:30:30 UTC+8 重置…"}`。插件从响应体解析该时间(并按其声明的 UTC 偏移换算,不受你本机时区影响),因此卡片能直接显示「✗ 被限流 · 2026/9/30 02:30:30 之后可再用」。三处来源都没说时,插件**不编造倒计时**,如实显示「上游未给出何时恢复」。限流**不会**去借月周期包的刷新时间(那会让人为几秒的限流去等几周)。探测在后台发起,**凭据不会传到页面**;结果不写入设置,也不改变你已保存的勾选。
36
36
 
37
- **两点需要知道**:① **它真的花钱**——实测一次报 `credit` 0.02(被限流时直接 429,几乎不花)。因此**只能按单个模型手工点**,没有「测试已勾选」这类批量按钮:一次覆盖整个列表就是一次不可控支出(服务端也据此**只接受单个模型**,多模型请求会被拒)。② 上游虽宣称 1M 上下文,但节流线远低于此;插件按模型的**原生窗口**向 DSH 宣称(3.5.0 起不再默认压到 200K),所以 DSH 很晚才压缩对话——长会话撞限流是这条机制的直接结果。想让 DSH 更早压缩,就在模型行上手动设一个「DSH 上下文预算」上限。
37
+ **两点需要知道**:① **它真的花钱**——实测一次报 `credit` 0.02(被限流时直接 429,几乎不花)。因此**只能按单个模型手工点**,没有「测试已勾选」这类批量按钮:一次覆盖整个列表就是一次不可控支出(服务端也据此**只接受单个模型**,多模型请求会被拒)。② 上游虽宣称 1M 上下文,但节流线远低于此;插件按模型的**原生窗口**向 DSH 宣称(3.5.0 起不再默认压到 200K),所以 DSH 很晚才压缩对话——长会话撞限流是这条机制的直接结果。想让 DSH 更早压缩,就在模型行上手动设一个「DSH 上下文预算」上限(可选 200K / 500K / 模型原生窗口)。
38
38
  - **本机账号切换** —— 自动发现 WorkBuddy 桌面端留下的多个登录凭据,可按区域切换账号;Token 不写入 DSH 设置。
39
+ - **对话输入框积分读数(国内版 / 国际版自动切换)** —— 对话输入框左侧工具栏常驻一行可点击的读数(如 `WB CN · 1,072`),**按本会话当前选中的模型自动切换区域**:选中 WB CN 的模型就显示国内版余额,选中 WB AI 的模型就显示国际版余额;用其它 provider 的模型时该读数完全不出现。每 5 分钟自动刷新,也可点开手动刷新。点击读数弹出面板:**按账号列出的余额表格**,点某一行即可切换到该账号(与卡片上的账号选择是同一个设置);**当前不能用的账号会在行内标出原因**(如「被限流」「积分耗尽」),标注口径与账号池表格一致。面板里的余额只在打开时才去查,因此不会让 5 分钟的后台刷新按账号数翻倍。要关掉某一边,就在卡片对应区域的 tab 里取消勾选「在对话框显示 WB CN / WB AI 积分」;该区域的供应商被关闭时,读数不显示、开关也置灰。
40
+
41
+ <img src="docs/assets/dsh-connect-workbuddy-composer-credits.png" width="360" alt="对话输入框积分面板:国内版账号余额表格、刷新积分按钮与最近刷新时间" />
39
42
  - **账号池:多账号批量操作与自动换号** —— 把要用的账号勾进池子,即可**一键签到所有账号**、**一键测试所有账号 × 你指定的模型**(默认挑该区域倍率为 0 的免费模型);开启**启用账号池**即同时打开**自动换号**——某个请求在上游失败时,插件会依次用池内其他可用账号重试,直到有一个接手、或全部不可用为止,然后才把错误报给你。成员**默认一个都不含**、必须显式勾选(「本机有登录」不等于「要拿它去签到和花积分」);**开启后由池子的排序决定每一次请求用谁**(可用优先,其次积分高、快过期的积分包、凭据更新的);失败时依次用池内其他可用账号重试。排序**从不写回你保存的选择**——关掉开关,你选定的账号立刻完全生效。只有请求本身不合法(HTTP 400)不换号:同一个请求体换谁都是 400,重试只会白等。**这个开关只管「插件要不要自己决定」**:关掉它,插件不再替你选账号、也不再替你换号,但**签到与测试仍然可用**(它们是手动动作)。测试走的是与模型行同一套真实体积探测,因此**有真实成本**;**不提供自动签到**——签到会真实领取奖励,只在你点按钮时执行。详见[账号池](#账号池)。
40
43
  - **凭据失效时给出可执行的建议** —— 上游拒绝当前账号的 token 时,插件会**实际探测**本机其他账号:若有账号仍然可用,就直接告诉你「切换到它即可」(并附一键切换),而不是笼统地让你重新登录——被钉在一个作废的旧备份上时,重新登录并不能解决问题。只有本机确实没有别的账号可切换时,才会提示重新登录。
41
44
  - **积分信息在账号池里** —— 成员表逐账号显示剩余积分与最近到期时间,排序的第二键就是积分(谁多谁优先)。查询是**只读**的、不消耗积分。
@@ -57,7 +60,7 @@ DSH PiAiAdapter(每个 provider 一套)
57
60
 
58
61
  国内版与国际版各持一套完整的运行时栈——凭据 store、模型 catalog、回环 shim、adapter——按凭据域名(`workbuddy.ai` / `codebuddy.ai` → 国际版,其余 → 国内版)隔离可见账号,所以**两个区域的账号可以同时在线、同时被不同会话使用**。国际版有两个品牌域名:桌面端登录在 `workbuddy.ai`,CodeBuddy CLI 登录在 `codebuddy.ai`,两者都是国际版。
59
62
 
60
- 模型目录按区域取自各自的正确来源:国内版走 `/v2/enterprises/personal/models`,国际版走 `<国际网关>/v3/config`(配置服务按客户端渠道返回不同清单,国际版必须用桌面端 User-Agent 才能取到账号真实的 20 个模型,含免费的 `deepseek-v4.1-flash`)。积分概览走 `https://www.codebuddy.cn/v2/billing/meter/get-user-resource`(国际版账号自动路由到其凭据所属的国际网关,`workbuddy.ai` 或 `codebuddy.ai`——两个品牌域名的凭据互不通用),均为只读接口。
63
+ 模型目录按区域取自各自的正确来源,两者都是 **App 自己读的那份** `/v3/config`,区别只在用哪个客户端身份去取:国内版用 CodeBuddy CLI 的 User-Agent,国际版用桌面端 User-Agent(配置服务按客户端渠道返回不同清单;国内版只有 CLI 渠道的清单里带免费的 `hy4-preview-f`,国际版则必须用桌面端才能取到账号真实的 20 个模型,含免费的 `deepseek-v4.1-flash`)。国内版若 `/v3/config` 取不到,会退回旧的 `/v2/enterprises/personal/models` 并记一条日志——那份旧名册是 CLI 渠道的,同名槽位挂着付费的 `hy4-preview`(`x0.29`)而不是 App 显示的免费 `hy4-preview-f`(`x0.00`),所以只在降级时使用。积分概览走 `https://www.codebuddy.cn/v2/billing/meter/get-user-resource`(国际版账号自动路由到其凭据所属的国际网关,`workbuddy.ai` 或 `codebuddy.ai`——两个品牌域名的凭据互不通用),均为只读接口。
61
64
 
62
65
  凭据读取自 WorkBuddy 桌面 App 自身的 auth 文件(只读);刷新得到的 token 按区域存放在 `$DSH_HOME/.workbuddy-auth.cn.json` 与 `$DSH_HOME/.workbuddy-auth.global.json`(双账号同时在线互不覆盖;旧的单文件 `.workbuddy-auth.json` 作为迁移来源保留读取),桌面端文件永不被写入。
63
66
 
package/lib/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { A as workbuddyOwnAuthPath, C as defaultDesktopAuthDirs, D as legacyWorkbuddyOwnAuthPath, P as WorkBuddyUpstreamClient, S as defaultDesktopAuthCandidates, U as WORKBUDDY_APP_EXECUTABLE_ENV, Y as findWorkbuddyAppExecutableWithSource, a as readHostHeartbeat, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, 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-C6G_goTD.js";
2
+ import { A as workbuddyOwnAuthPath, C as defaultDesktopAuthDirs, D as legacyWorkbuddyOwnAuthPath, P as WorkBuddyUpstreamClient, S as defaultDesktopAuthCandidates, U as WORKBUDDY_APP_EXECUTABLE_ENV, Y as findWorkbuddyAppExecutableWithSource, a as readHostHeartbeat, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, 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-vvVjdY_q.js";
3
3
  import { realpathSync } from "node:fs";
4
4
  import { fileURLToPath } from "node:url";
5
5
  //#region src/bin.ts