dsh-connect-workbuddy 3.6.0 → 4.2.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,83 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.2.0 (2026-10-10)
4
+
5
+ ### Fixes
6
+
7
+ - **导入不再拒掉插件自己导出的账号:CN 聊天网关 `copilot.tencent.com` 现在被识别为国内站。**
8
+ - **现象**:卡片导出 8 个账号,再把同一个文件导入回来,结果是「导入 5 / 跳过 3」——**导出的文件自己导不回去**。用户反馈这 3 个账号在另一台电脑上完全正常。
9
+ - **根因**:`copilot.tencent.com` 不是第三个上游,它**就是本插件自己的国内聊天网关**(`CN_CHAT_BASE = 'https://copilot.tencent.com'`,见 `src/upstream.ts`),而真实凭据的 `domain` 字段就写着它。4.1.x 的导入闸门(`knownRegionOf`,为防伪冒域名而做的「正向识别」)只认 `workbuddy.cn` / `codebuddy.cn` / `workbuddy.ai` / `codebuddy.ai`,`copilot.tencent.com` 匹配不上任何一个 → 返回 `undefined` → 计入 `skipped`。于是插件导出得出、导入不认,自相矛盾。
10
+ - **为什么 4.2.0 而不是 4.1.3**:4.1.0 刚把「批量导出 / 导入」作为新功能交付,4.1.2 收紧区域闸门时把它变成了自身不可用——跨机器迁移这条主路径在 4.1.x 整条线上都是断的,属于功能级缺陷而非补丁级瑕疵。
11
+ - **修法**:`knownRegionOf` 的 CN 表加入 `copilot.tencent.com` 及其子域。仍按可注册域匹配,仿冒主机(`copilot.tencent.com.evil.com`、`evilcopilot.tencent.com`)依旧被拒,闸门没有放宽成默认归 CN。
12
+ - 回归测试两条:一条钉住网关域名与仿冒主机的判定(`tests/transfer.spec.ts`),一条复刻「5 个登录域名 + 3 个网关域名混在一个导出文件」的完整往返,确认导入 CN 得 8/0、导入 global 得 0/8。
13
+
14
+
15
+ - **导入不再拒掉插件自己导出的账号:CN 聊天网关 `copilot.tencent.com` 现在被识别为国内站。**
16
+ - **现象**:卡片导出 8 个账号,再把同一个文件导入回来,结果是「导入 5 / 跳过 3」——**导出的文件自己导不回去**。用户反馈这 3 个账号在另一台电脑上完全正常。
17
+ - **根因**:`copilot.tencent.com` 不是第三个上游,它**就是本插件自己的国内聊天网关**(`CN_CHAT_BASE = 'https://copilot.tencent.com'`,见 `src/upstream.ts`),而真实凭据的 `domain` 字段就写着它。4.1.2 的导入闸门(`knownRegionOf`,为防伪冒域名而做的「正向识别」)只认 `workbuddy.cn` / `codebuddy.cn` / `workbuddy.ai` / `codebuddy.ai`,`copilot.tencent.com` 匹配不上任何一个 → 返回 `undefined` → 计入 `skipped`。于是插件导出得出、导入不认,自相矛盾。
18
+ - **修法**:`knownRegionOf` 的 CN 表加入 `copilot.tencent.com` 及其子域。仍按可注册域匹配,仿冒主机(`copilot.tencent.com.evil.com`、`evilcopilot.tencent.com`)依旧被拒,闸门没有放宽成默认归 CN。
19
+ - 回归测试两条:一条钉住网关域名与仿冒主机的判定,一条复刻「5 个登录域名 + 3 个网关域名混在一个导出文件」的完整往返。
20
+
21
+ ## 4.1.2 (2026-10-10)
22
+
23
+ ### Features
24
+
25
+ - **账号可自行添加、迁移与逐个管理,不必再装桌面 App。**(合并自 PR #38)
26
+ - **扫码添加账号**:卡片新增「扫码添加账号」,点开授权链接在浏览器完成登录即可,无需先装 WorkBuddy 桌面 App。会话只存在进程内(600 秒超时),拿到凭据即终结,token 不落独立账号库。
27
+ - **统一凭据库**:所有凭据统一落盘到 `$DSH_HOME/workbuddy-vault/<region>/<accountId>.json`,一账号一文件——扫码添加的、桌面扫描到的、插件刷新过的,都汇入同一目录。桌面 App 的认证文件**始终只读**,插件永不写入或删除它们。
28
+ - **账号行级「测试 / 删除」**:表格每行新增两个按钮。单账号测试复用批处理同一套探测依赖与模型解析,因此行级与批量测的是同一个模型、产生同一种测量。删除只删插件自己的 vault 副本;若该账号来自桌面端,下次扫描会重新出现,文案如实说明。
29
+ - **账号批量导出 / 导入**:多选导出 JSON 文件,选文件后给出**脱敏预览**(只展示字段与 `hasToken`,token 明文不进预览),再勾选导入。文件格式与同类凭据管理工具互通;`uin` 会从 `auth_raw` 恢复,保证「导出 → 换机 → 导入」收敛到同一账号 id,不产生影子账号。关闭 issue #41。
30
+
31
+ ### Fixes
32
+
33
+ - **扫码添加账号与导出/导入账号移入「账号池设置」区块。**
34
+ - 两处原先夹在积分区与账号池表格之间,语义上是悬空的——上面是余额、下面是表格,它们自己像两个孤儿。现在归入账号池设置区块,与账号管理其它操作收在一起。
35
+ - **放在该区块的偏好设置之上**,这是有意的:区块开头写着「点保存才生效」,而扫码登录与导入导出是**即时生效**、没有草稿的动作。放在那句提示语之下,会让用户以为要点保存——反之亦然。
36
+ - 两者共用一个虚线边框容器,与下方「点保存」的实线设置行区分开:虚线标记「点了就生效」,实线标记「要保存」。
37
+ - **未登录时同样显示**:账号池区块在未登录时不渲染,而这恰恰是这两项最要紧的时候——扫码是不装桌面 App 时唯一能加账号的方式,从别的电脑导出的文件也是这台机器凭空获得账号的唯一途径。两种状态渲染的是同一个 `accountTools` 节点,顺序与容器完全一致,不会让用户以为这是两个功能。
38
+ - **扫码添加账号与导出/导入账号改为各占一行,不再各占一个大区块。**
39
+ - 原先两者各是一个带虚线边框、内边距 12px 的整块,静止状态下就要占掉三行(标题 + 说明 + 按钮),把账号表格和模型列表挤到需要滚动才能看完。现在两处都是**一行工具条**:标签、状态文字、按钮同处一行,按钮右对齐。
40
+ - **状态文字与标签同行**,并且是卡片变窄时**最先被截断**的那一段(`text-overflow: ellipsis`),而不是把整条栏撑成两行。
41
+ - 导出/导入的**静止说明文字从常驻栏移进了导出确认弹窗**:那句「文件里是账号凭据——请妥善保管」在凭据真正离开机器的那一刻才有意义,常驻只是白白占掉三分之一行。
42
+ - 弹窗本身保持块级布局不变:它要容纳可滚动的账号列表和逐条勾选框,塞不进一行。
43
+ - **修复每次重启都把全部账号重新刷一遍 token 的循环。**
44
+ - 插件刷新出的 token 会写入 vault,但**取用**时仍无条件让桌面 App 的 live 文件优先(该优先是实测结论:上游会吊销备份文件里的 token,不能推翻)。结果每次重启都重新读到桌面那份较旧的 token、判定临近过期、再刷一次,刷新循环从未真正停止。
45
+ - 修法是给 vault 条目加一个**来源标记**:记录该条目是对哪个桌面文件刷新后写下的。取用时若标记与当前读到的文件同名,说明这份副本就是它的刷新结果、必然更新,于是可以取代桌面那份;而**仅靠「活得更久」仍然不能**越过 live 文件。
46
+ - 回归测试模拟 5 次重启并断言刷新只发生 1 次——这正是此前没有任何测试守住的场景。
47
+ - **修复扫码登录与账号导入导出在界面上完全不可用。** 卡片的两处请求漏拼 `action` 参数,宿主一律返回 400。新增 `withWorkBuddyRouteAction` 统一构造,并补上**客户端 ↔ 宿主 URL 契约测试**:此前测试都是手写带 `action` 的 URL,客户端拼出来的地址从未被验证过。
48
+ - **导入的区域门禁由「默认归 CN」改为「正向识别」。** 原实现把无法识别的域名一律当作 CN,而导入文件里的 `domain` 是文件作者可控的伪造值,仿冒域名(如 `workbuddy.ai.evil.com`)会被写进 CN vault。现在只有**确认属于目标区域**的域名才入库,认不出的一律计入 `skipped`;`regionOf` 保持原样,因为它服务的是上游签发的域名。
49
+ - **修复删除账号时删除任意文件。** `accountId` 取自查询参数并直接拼进文件路径,`join` 会规范化 `..`,可越出 vault 目录。现在按账号 id 的真实形态(sha256 前 24 位十六进制)校验,路由层与存储层各一道。
50
+ - **删除账号改为在文件锁内进行。** 原实现绕过锁并删掉 `.lock`,与并发的刷新写入相撞时会让两个写者同时进入临界区。
51
+ - **导出响应加 `Cache-Control: no-store`。** 导出会把 access/refresh token 明文交给浏览器用于下载,不应进入任何缓存。
52
+
53
+ ## 4.0.0 (2026-10-04)
54
+
55
+ ### ⚠️ Breaking Changes
56
+
57
+ - **侧边栏底部的积分行已被移除,积分读数改到对话输入框工具栏。**
58
+ - 如果你在用上一版的侧边栏积分行:它**不会**再出现。新版在**对话输入框左上角**显示一行可点击的读数(`WB CN · 1,072` / `WB AI · 88`),并**按本会话当前选中的模型自动切换区域**——原来那两行是常驻的,现在只在你用本插件的模型时才出现。
59
+ - **不需要重新配置**:原来那两个开关(`regions.<region>.showCreditsInMainUi`)**沿用同一个字段**,只是含义从「在侧边栏显示」变成「在对话框显示」。你此前关闭过的那一边,升级后依然是关闭的。
60
+ - **主版本号只因此 +1**:功能没有减少,但这个改动会让正在用它的用户**看不到原来那一行**,属于用户可见的行为变更,按语义化版本应当升 major 而不是 minor。
61
+
62
+ ### Features
63
+
64
+ - **积分读数移入对话输入框工具栏(`conversation.input.left`),按会话当前所选模型自动切换区域。**
65
+ - **做法沿用同族项目** `dsh-connect-trae`(MIT)的 composer 读数:一行可点击的读数 `WB CN · 1,072`,点击弹出锚定面板;面板里是**账号表格**(每账号一行余额,点行即切换到该账号);**手动刷新按钮放在面板内**而不是读数上;每账号余额**只在面板打开时抓**;5 分钟定时器在首取结束后才启动;失败保留上次数值。
66
+ - **为什么从侧边栏搬过来**:宿主的 `sidebar.footer.action` 是**单个 root 级共享行**——所有 bundle 都往那一行里塞。实测把它扩成两行时,该容器 `display:flex` 且无 `flex-wrap`/`flex-direction`,两行会被压成等宽两列而重叠。输入框这行是 **session 级且按 provider 归属**,装两个连接器插件也不会互相抢位置。这也是 trae 自己在 2.13.0 移除侧边栏积分行的同一结论。
67
+ - **按 provider 判定归属**:gate 读宿主的 `modelSelection` 投影(`next` 优先于 `lastUsed`,与宿主自身一致),只有本会话选中的模型属于本插件的 `workbuddy` / `workbuddy-global` 时才渲染。**换成别的 provider 就完全不渲染**——不占位、不发请求。
68
+ - **两个区域都支持**(与 trae 不同):trae 的国际版是订阅制、没有可比数字,本插件的国际版有真实余额。所以读数据当前模型所属区域显示,并标明 `WB CN` / `WB AI`,面板标题也按区域区分。
69
+ - **面板里的余额读数是「总额」**(`credits.total`,与卡片头部同一字段),且**读不到时显示 `—` 而不是 `0`**:宿主端点在该账号取不到余额时**省略** `credits` 字段,正是为了让界面不能对账号做出「没积分了」这种错误断言。
70
+ - **面板里标出「被限流」等不可用原因**:每个账号行若当前不能服务,就在账号名下方用**与账号池表格同一套文案**标出原因(被限流/积分耗尽/被拒绝/…)。复用宿主已有的 `exclusionOf` 判定(含冷却到期逻辑)与 `exclusionText` 标签函数(本版从 `AccountPool.tsx` 提到 `exclusion-text.ts` 共用),因此同一账号在池子表格与积分面板里不会出现两种说法。
71
+ - **读数只反映当前选中账号**(`store.resolve()` 取到的那个),不是该区域所有账号的汇总——它读的就是宿主 usage 文档里的 `credits.total`,与卡片头部同一字段。
72
+ - **新增宿主端点** `/account-credits`:按区域列出每个账号的余额,**每次打开面板才抓一次**(每账号一次上游读),不挂在 5 分钟循环上。单账号失败不影响其他行。
73
+ - **开关跟着 tab 走**:卡片的「在对话框显示 WB CN / WB AI 积分」只显示当前 tab 区域的那一个,写入 `regions.<region>.showCreditsInMainUi`(与 `enabled` 同级),并走 landed-check 的 `writeRegionCreditsShown`。**该区域 provider 关闭时**该行不渲染、开关置灰且显示为关闭(关闭的 provider 取不到积分,留着只会常驻失败)。
74
+ - **样式类名与组件一一对应**(本版修掉一个真实缺陷):组件从 `Points` 重命名为 `Credits` 时,只有两条 **trigger** 规则没跟着改,于是读数按钮匹配不到任何规则、回退成**浏览器默认按钮**(带边框的大方块)。新增用例断言「组件渲染的每个 `dsm-workbuddy-composer-*` 类名都在 CSS 里有定义」,并把触发器的「无边框/透明底/12px」写成显式契约。其余类名恰好一致,所以当时只有这两条出错、别的测试都没发现。
75
+ - **`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` 静态守卫(未外置的裸导入、外置了但宿主未注册的模块,都会让它变红)。
76
+
77
+ <img src="docs/assets/dsh-connect-workbuddy-composer-credits.png" width="360" alt="对话输入框积分面板:国内版账号余额表格、刷新积分按钮与最近刷新时间" />
78
+
79
+ 面板实机截图(国内版):标题按区域为「WB CN 积分」,表格两列为账号与积分,当前生效账号用实心圆点标出,底部显示最近刷新时间。
80
+
3
81
  ## 3.6.0 (2026-10-04)
4
82
 
5
83
  ### Features
package/README.en.md CHANGED
@@ -29,8 +29,12 @@ A [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) bund
29
29
 
30
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.
37
+ - **Add accounts by QR sign-in, import/export them in bulk, test or delete them one by one** — the WorkBuddy desktop app is no longer a prerequisite: the card's **Add account by QR code** opens an authorization link and signing in finishes in the browser. Every credential lands in the plugin's own vault (`$DSH_HOME/workbuddy-vault/<region>/<accountId>.json`, one file per account), and the desktop app's auth files stay **read-only and are never modified**. Each row of the account table gains **Test** and **Remove**: a row test reuses the same probe as the batch run, so the two are comparable; removing deletes only the copy the plugin stored, and an account that came from the desktop app reappears on the next scan. **Export / import accounts** moves credentials to another machine: pick accounts to export a JSON file → choose a file and read a **redacted preview** (field names and whether a token is present; the token itself never enters the preview) → tick what to import. An import overwrites per account rather than duplicating, and **only a record positively identified as belonging to that region is stored** — a domain the plugin cannot classify is skipped and counted as skipped. See [Account pool](#account-pool).
34
38
  - **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.
35
39
  - **Multi-candidate credential paths** — probes the platform defaults for macOS / Windows / Linux in turn, overridable by environment variable or directly in the card. **When nothing is found, the card lists which paths were probed and why each one failed**: five distinct reasons (absent / unreadable / no usable token / encrypted with no key available / belongs to the other region), so the easiest case to misdiagnose — signed in, but the desktop app is not present — says the app must be there instead of telling you to sign in again. The **belongs to the other region** reason is different in kind: that sign-in is real and usable, it is simply filed under the other tab, so the card states the fix outright — switch tabs, no need to sign in again. The list says it once: the paragraph states the conclusion, the collapsed list supplies the per-path detail, and the two never repeat each other.
36
40
  - **Secure loopback shim** — one random port + in-process random secret per region; the real WorkBuddy token is never handed to pi-ai.
package/README.md CHANGED
@@ -36,8 +36,12 @@
36
36
 
37
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 时,插件会**实际探测**本机其他账号:若有账号仍然可用,就直接告诉你「切换到它即可」(并附一键切换),而不是笼统地让你重新登录——被钉在一个作废的旧备份上时,重新登录并不能解决问题。只有本机确实没有别的账号可切换时,才会提示重新登录。
44
+ - **扫码添加账号、批量导入导出、逐账号测试/删除** —— 不必先装 WorkBuddy 桌面 App:卡片里的「扫码添加账号」点开授权链接、在浏览器登录完成即可。所有凭据统一落到插件自己的凭据库(`$DSH_HOME/workbuddy-vault/<region>/<accountId>.json`,一账号一文件),**桌面 App 的认证文件始终只读、永不被改动**。账号表格每一行右侧有「测试」与「删除」:测试与批量测试走同一套探测,结果可比;删除只删插件自己存的那份,若账号来自桌面端,下次扫描会重新出现。「导出 / 导入账号」可把凭据搬到另一台机器:多选导出 JSON → 选文件后先看**脱敏预览**(只显示字段与是否有 token,明文不进预览)→ 勾选导入。导入按账号去重覆盖,**只有确认属于该区域**的记录才入库,认不出的域名会被跳过并如实计入「已跳过」。详见[账号池](#账号池)。
41
45
  - **积分信息在账号池里** —— 成员表逐账号显示剩余积分与最近到期时间,排序的第二键就是积分(谁多谁优先)。查询是**只读**的、不消耗积分。
42
46
  - **凭据路径多候选** —— macOS / Windows / Linux 逐一探测默认位置,也支持环境变量与卡片内直接指定。**未登录时卡片会列出「探测过哪些路径、各自为什么没成」**:五档原因(不存在 / 读不了 / 没有可用 token / 字段已加密且拿不到密钥 / 属于另一个地区)分开呈现,因此「已经登录了、但桌面 App 不在场」这种最容易误判的情况会明说需要 App 在场,而不是叫你重新登录一次。**属于另一个地区**那一档尤其不同:登录信息是真实可用的,只是挂在另一个标签页下,卡片会直接给出结论「切换标签页即可,不用重新登录」。列表只讲一次——提示段落说结论,折叠列表给路径细节,两处不重复同一件事。
43
47
  - **安全 loopback shim** —— 每区域一个随机端口 + 进程内随机 secret,真实 WorkBuddy token 不交给 pi-ai。
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-Cg2ahl1n.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, 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-C4TXu8ew.js";
3
3
  import { realpathSync } from "node:fs";
4
4
  import { fileURLToPath } from "node:url";
5
5
  //#region src/bin.ts