dsh-connect-workbuddy 2.0.10 → 2.0.12
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 +113 -4
- package/README.en.md +3 -1
- package/README.md +3 -1
- package/lib/bin.js +1 -1
- package/lib/client.js +202 -69
- package/lib/{host-heartbeat-DPbL9oi2.js → host-heartbeat-ChtMu37E.js} +212 -18
- package/lib/index.d.ts +165 -8
- package/lib/index.js +151 -21
- package/package.json +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,100 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 2.0.
|
|
3
|
+
## 2.0.12 (2026-09-23)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- **支持 DSH 0.1.7 线,且同一个构建同时服务 0.1.5 与 0.1.7**(合并社区 PR #14,作者 [@qikairo7](https://github.com/qikairo7))。这不是「多支持一个版本」的可选增强——**在 0.1.7 上插件此前根本装配不起来**。
|
|
8
|
+
|
|
9
|
+
- **根因:settings 服务换型,`installSection` 消失。** 0.1.5 的 `SettingsProvider` 提供 `installSection(owner, ns, schema, entry, hooks)`;0.1.7 换成 `SettingsForms`,该方法**整个移除**(我在 `dsh-settings` / `dsh-config-editor` / `dsh-client-ui-settings-plugins` 三个包的整份 tarball 里逐一确认无此符号,也未迁移到其他包),取而代之的是 `configure({auto}, owner)`。而插件此前**无保护地裸调用** `ctx.settings.installSection(...)`——`inject = ['llm','settings']` 两个服务在 0.1.7 上都存在,所以 `apply()` 会一路执行到那一行。实测(用一个行为等同 0.1.7 的 settings 服务装配):
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
apply() THREW -> ctx.settings.installSection is not a function
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
即**整个插件装配失败**:两个 provider 都不注册,卡片与 Web 状态路由同样不存在。这与「某个功能坏掉」在用户侧都是「用不了」,但成因完全不同——**也因此「重新登录」「重装插件」一概无效**。现在改为按能力探测:有 `configure` 走 `configure`,否则回落 `installSection`。
|
|
16
|
+
- **易失字段必须显式声明(issue #13 在 0.1.7 线上的真正根因)。** 0.1.7 的 settings 写入门要求插件 schema 把可写字段标记为 volatile,否则**写入被直接拒绝**(`Plugin entry "workbuddy" has no volatile fields`),`scope.set()` 却正常 resolve——于是卡片回读校验抛 `settings field "regions" was not persisted`。新增 `asVolatile()`:运行时探测 `schema.volatile()`,**有则调用、无则退化为 identity no-op**。这一条是兼容易失的关键——`volatile()` 在 3.18.3 才出现,更早的版本上 `asVolatile()` 必须**原样返回**同一个 schema 对象(identity no-op),而不是手写 `meta.volatile = true`:后者会绕过 schemastery 自身的 `validateVolatileSchema` 校验,产出一个它自己都不认的 schema,因此**故意不那样做**。
|
|
17
|
+
- **0.1.7 以「活引用」交付配置值。** 0.1.7 把配置值以 `{get(): T}` 形式交给 `apply()`,不解包则 `config.regions`、`value.accounts[region]`、`config.authFile` 静默变成对象或 `undefined`——表现为「设置明明写了却读不到」。新增 `unwrapVolatile()` 并在所有读取路径上解包,同时监听 `loader/volatile-update` 在每次写入后重读选择。
|
|
18
|
+
- **客户端双线槽位。** 两条线声明的是**互不相交**的槽位集合(0.1.5 只有 `settings.plugin.item`;0.1.7 是插件管理器的 `plugins.bundle.config` / `plugins.row.config`)。卡片现在逐个注册且**各自独立 try/catch**——一个槽位在某一线上不存在时,不能把另一条线的注册一起带走。settings scope 也按能力获取:0.1.7 走 `configForms.get(ns)`(并从镜像里取 Host 实际服务的 namespace,插件可能挂在别的 entry id 下),0.1.5 回落 `settingsScope.bind()`。
|
|
19
|
+
- **折叠箭头改为纯 CSS。** 两线 primitives 的图标名不重叠(`…Outline14` vs `…OutlineRegular`),没有任何一个静态图标 import 能同时服务两边。
|
|
20
|
+
|
|
21
|
+
### Bug Fixes
|
|
22
|
+
|
|
23
|
+
- **CI 在 windows-latest 上红了 2 例**(2.0.11 引入,本仓库自身的缺陷)。新增的 darwin 候选断言写死了 POSIX 字面量(`expect(mac).toContain('/Applications/WorkBuddy.app/Contents/MacOS/Electron')`)。生产代码没问题——它用 `join` 拼路径;错的是**测试**:它断言的是「跑测试的机器是 POSIX」,而非「darwin 候选列表对不对」。Windows 上 `join` 产出反斜杠,于是必然失败,而 macOS 上恒绿——**这正是它能一路走到 CI 的原因**。改为全部经 `join` 构造期望值,并补一条回归守卫:候选路径不得**混用**分隔符(用字面量 `/` 拼接在 Windows 上会产出 `\Applications/WorkBuddy.app`,任何正确路径都不会长这样)。
|
|
24
|
+
|
|
25
|
+
- **易失标记一生效,0.1.5 线的设置区就整个消失**(合并 PR #14 后由本仓库查出并修复的真缺陷)。`asVolatile()` 给字段打上 volatile 标记后,该字段的值会被包成 `{get(): T}` **活引用**。而 `installSection(owner, ns, schema, entry, hooks)` 的第 4 个参数 `entry` 会被服务**校验并 `structuredClone`**,于是拿到活引用时逐字段抛错:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
$.authFile expected string but got [object Object]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
结果是命名空间**注册不上**——卡片设置区静默消失(实测 13 个测试变红)。
|
|
32
|
+
|
|
33
|
+
- **为什么 PR14 与 CI 都没发现**:两边都把 schemastery 锁在 **3.18.2**,那里没有 `volatile()`,`asVolatile()` 退化为 identity no-op,**这条路径从未被执行过**。而 PR14 自己又把 schemastery 钉进 `dependencies: 3.18.4`——npm 用户会解析到嵌套的 3.18.4,`volatile()` 真正生效,**恰好走进没人测过的分支**。开发环境掩盖了用户环境的缺陷,这是本次最值得记取的教训。
|
|
34
|
+
- **修法**:新增 `unwrapVolatileDeep()`,在把 config 交给 settings 服务前**递归**剥掉全部活引用(既有的 `unwrapVolatile()` 只剥调用方读取的那一层,够普通读路径用,不够交给服务校验用)。数组会被重建而非就地改写,调用方的 config 对象不受影响。
|
|
35
|
+
- **顺带把 `asVolatile()` 导出**,让「有 `volatile()` 则打标记 / 没有则恒等 no-op」两条分支**在任何机器上都能被确定性地断言**;否则把钉住的版本一提,no-op 那条分支的覆盖就会静默消失(而这正是手写 `meta.volatile` 会绕过校验的那条防线)。
|
|
36
|
+
|
|
37
|
+
### Tests
|
|
38
|
+
|
|
39
|
+
测试总数 300 → 331。
|
|
40
|
+
|
|
41
|
+
- **双 settings 形态兼容(新增 2 例,这是 PR14 缺的那块)**:PR14 只测了 0.1.5 形态——而它修的恰恰是 **0.1.7 分支**,这样的套件在 0.1.7 分支再次坏掉时**不会变红**,正是让本次缺陷穿过 CI 的原因。现在两条路径各有独立用例:0.1.7 形态(有 `configure`、**无** `installSection`)下必须装配成功、两个 provider 均注册、且确实走了 `configure({auto:true})`;0.1.5 形态下必须继续走 `installSection`(否则命名空间不会注册)。**变异验证**:把 `installSection` 改回无保护裸调用,第一条立即变红。
|
|
42
|
+
- **活引用不得喂给 settings 服务(新增 3 例)**:`unwrapVolatileDeep()` 必须递归剥净(含嵌套与数组)、且不就地改写调用方对象;`installSection` 拿到的 entry 必须无活引用(断言注册结果与 `base` 形状,而不是断言内部调用,实现改动无法伪造);两条 volatile 分支各自被确定性断言。**变异验证**:把 `installSection` 的 entry 改回原始 config,**13 个用例立即变红**。
|
|
43
|
+
- **供应商开关的落盘校验(新增 5 例)**:`locked` scope 下必须抛错、错误须标明 `regions`、正常落盘须成功、整槽位其余字段须原样带过、槽位存在但 `enabled` 是错值也须判失败(浅检查会放过「开关没真的关掉」)。
|
|
44
|
+
- **测试读取也走生产 helper**:新增 `readSettings()`——0.1.7 上 `settings.get()` 返回的同样是活引用,直接读 `doc.accounts.cn` 得到 `undefined`,那是**任何真实消费方都不会看到的形状**。断言统一经 `unwrapVolatileDeep()` 解包,避免测试在真实消费方看不见的形状上通过或失败。
|
|
45
|
+
- 其余为 PR14 自带的兼容与槽位隔离用例,以及上一版的凭据修复用例,全部保留。
|
|
46
|
+
|
|
47
|
+
**双版本验证**:同一份代码在 schemastery **3.18.4**(volatile 生效)与 **3.18.2**(no-op 分支)下**均为 326 通过**。
|
|
48
|
+
|
|
49
|
+
- **供应商开关是唯一漏掉「落盘校验」的设置写入**(同批查出的第二处)。关掉某个区域的供应商时,卡片直接调 `settingsScope.set('regions', …)`,**没有**读回校验,也**没接上**插件自己的 Host 兜底端点——而账号选择与模型目录两处早就两者皆有。于是 0.1.7 上那个「`set()` 正常 resolve 但什么都没存」的 scope 会让开关**看起来生效、随后静默复原**,正是 issue #13 的同一类静默失败。现在开关改走 `writeRegionEnabled()`,与其余写入同一条「先 scope、没落盘再走 Host 端点」的路径,要么写进去、要么明确报错。
|
|
50
|
+
|
|
51
|
+
- 整槽位照旧原样带过(只改 `enabled`),用户的模型目录、勾选、图片开关与上下文预算不受影响。
|
|
52
|
+
- 回归测试用 `locked` scope(`set()` 正常 resolve、不落盘)钉住;**变异验证**:把它改回直接调 `scope.set()`,3 个用例立即变红。
|
|
53
|
+
|
|
54
|
+
**同时把开发环境的 schemastery 提到 3.18.4**(`pnpm-workspace.yaml` 的 override)。此前它锁在 3.18.2,于是 CI 只覆盖 no-op 分支——**而用户实际拿到的是 3.18.4**(`dependencies` 钉住的那份嵌套副本)。开发环境与用户环境不一致,正是这个缺陷能同时躲过 PR 评审与 CI 的原因。代价是 no-op 分支不再由「装到的版本恰好很旧」顺带覆盖,因此 `asVolatile()` 改为导出,并用一条不依赖安装版本的用例把它钉死。
|
|
55
|
+
|
|
56
|
+
### Docs
|
|
57
|
+
|
|
58
|
+
- 本条目记录 0.1.7 的三项差异(settings 换型、易失声明、活引用)与其判定方式,供后续 DSH 升级时对照。
|
|
59
|
+
|
|
60
|
+
## 2.0.11 (2026-09-23)
|
|
61
|
+
|
|
62
|
+
### Bug Fixes
|
|
63
|
+
|
|
64
|
+
- **macOS 上「App 装着、账号也登录着,插件却说你没登录」**(issue #15,真机复现)。WorkBuddy 桌面端 **5.6.0 起**把 auth 文件的 token 字段改为 `$wbEncrypted` 加密信封,**macOS 与 Windows 同样如此**——此前「加密是 Windows 先行」的判断已不成立。而解密的密钥只能向本机 App 现取,于是「能不能找到 App」直接决定了「读不读得出凭据」。
|
|
65
|
+
|
|
66
|
+
- **根因:macOS 可执行文件名是写死的,而且写错了。** `workbuddyAppExecutableCandidates()` 把路径拼成 `<bundle>/Contents/MacOS/WorkBuddy`,但两个真实 bundle(`WorkBuddy.app` / `WorkBuddy AI.app`)的 `CFBundleExecutable` 都是 **`Electron`**——该路径并不存在。于是 `findWorkbuddyAppExecutable()` 返回 undefined,密钥取不到,加密凭据一律读不出来。真机实测:修正后 `doctor` 从 `unavailable` 变为 `available (/Applications/WorkBuddy.app/Contents/MacOS/Electron)`,`keyId=9127dea1b44020a7` 与真实构建一致。
|
|
67
|
+
- **二进制名改为向 bundle 自己问**(新增 `macosBundleExecutable()`,读 `Info.plist` 的 `CFBundleExecutable`)。App 改名、或换个二进制名,都不再需要改插件;plist 缺失或名字为空、含路径分隔符、为 `.`/`..` 时返回 undefined,**绝不猜一个路径**(plist 是磁盘数据,穿越会让 `execFile` 拿到 bundle 之外的东西)。
|
|
68
|
+
- **候选补齐国际版与「App 被归入子目录」两种情况**:新增 `WorkBuddy AI.app`(只装国际版的机器此前零命中),并对 `/Applications` 与 `~/Applications` **向下扫一层**,覆盖 `/Applications/IDE/WorkBuddy.app` 这类整理习惯。扫到的候选必须先用 `CFBundleIdentifier` 确认身份(`com.tencent.workbuddy*` / `com.workbuddy.*`,按点号边界匹配)才会被使用——**每个 Electron 应用的二进制都叫 `Electron`**,只按名字匹配就有可能启动另一个产品;拒绝一个候选只损失一次回退,接受错的那个是在用户机器上跑别的程序。这条有独立测试钉住。
|
|
69
|
+
- **Windows 候选补 `%LOCALAPPDATA%\WorkBuddy\WorkBuddy.exe`**:此前只探 `%LOCALAPPDATA%\Programs\WorkBuddy` 与两个 `Program Files`,装在用户级目录(同源 issue 里就有 `E:\WorkBuddy\WorkBuddy.exe` 这类自定义位置)的机器找不到 App。
|
|
70
|
+
- **macOS 分支改为可注入(`readBundleExecutable`)**:它与 `platform`/`home`/`env` 同一性质——会读真实文件系统,不留缝的话「期望的候选列表」会取决于跑测试的机器恰好装没装 App(本机装了、CI 没装),正是 2.0.6 已经修过的那类红灯。
|
|
71
|
+
|
|
72
|
+
- **「未登录」与「登录信息读不出来」不再共用同一句话**(同一 issue 的用户可见症状)。这两件事在 `readAll()` 之后都表现为「账号数为 0」,此前一律抛 `no signed-in WorkBuddy account found … sign in once in the WorkBuddy desktop app`。但加密文件读不出来时用户**本来就是登录着的**,那句「重新登录一次」指向的是唯一无效的动作——issue #15 报告者的原话正是这句。
|
|
73
|
+
|
|
74
|
+
- 新增 `WorkBuddyEncryptedCredentialError`(以 `ENCRYPTED_CREDENTIAL_CODE` 标记而非 `instanceof`,跨打包边界仍可靠,与 `WorkBuddyCredentialRejectedError` 同一约定):`resolve()` 在**失败路径上**跑一次 `diagnose()`,只要有一个桌面文件归因 `encrypted`,就改抛这条,文案给出「安装桌面 App,或用 `WORKBUDDY_APP_EXECUTABLE` 指定其位置」,并明说重新登录无效。**健康路径一个探针都不跑。**
|
|
75
|
+
- **诊断自身抛错不得顶掉真正的错误**:探测会读盘,失败时退回原来那句通用文案(有测试)。
|
|
76
|
+
- **顺带删掉一句不可达的过滤**:初版写了 `reason === 'encrypted' && source === 'desktop'`,但 `encrypted` 只由 `probeAuthFile()` 赋予,而插件自有副本从不经过它(按自有文档形状解析,损坏时是 `invalid`/`unreadable`)——该条件永远为真。留着它会让代码看起来在防守一件它并没防的事;测试改为直接钉住真正的不变量(自有副本必须归 `invalid`,以及不存在 `source === 'dsh'` 的 `encrypted`),变异验证确认可咬住。
|
|
77
|
+
- **卡片文案同步**:`signedOutNotice()` 让 `encrypted` 抢在通用提示之前(新增 `row.signedOutEncrypted`),因为通用提示说的正是那个无效动作。此前该建议藏在折叠区里的 `searchedEncryptedNotice`,而**段落**仍写着「重新登录一次」——用户多半只读那一段。既然段落已经承担,折叠区里那份重复提示连同其专属样式一并删除(2.0.10 刚立下的「段落说结论、列表给细节」分工,这次是它的延伸)。
|
|
78
|
+
- 优先级:`selectionLost` > `wrong-region` > `encrypted` > 通用提示。`wrong-region` 仍排在前面——那种情况下凭据是可读的,切个标签页即可,比装 App 更省事。
|
|
79
|
+
|
|
80
|
+
### Tests
|
|
81
|
+
|
|
82
|
+
测试总数 300 → 316(新增 16 例)。
|
|
83
|
+
|
|
84
|
+
- `tests/at-rest.spec.ts`(+10 例):`macosBundleExecutable` 必须读出 `Electron` 而不是按 App 名猜(**直接钉住本次根因**)、bundle 缺失时返回 undefined 而非猜路径、**四种穿越名(`../Evil` / `..` / `.` / `sub/Bin`)一律拒绝**、无 `CFBundleExecutable` 时返回 undefined;`isWorkbuddyBundle` 认国内版与国际版两个 id、**拒绝另一个 Electron 应用**(含 `com.workbuddyish` / `org.workbuddyevil` 这类仅前缀相似的)、拒绝不可读与无 id 的 bundle;候选列表含国际版与用户级目录、Windows 用户级安装被探到。macOS 相关断言全部走注入的 bundle reader,**套件结果不依赖跑测试的机器装没装 App**。
|
|
85
|
+
- `tests/auth.spec.ts`(+5 例):加密文件 + 取不到密钥必须抛 `WorkBuddyEncryptedCredentialError`、`paths` 指向该文件、文案含 `WORKBUDDY_APP_EXECUTABLE` 且**明确不含**「sign in again」与通用那句(对渲染出的字符串断言,不是对输入);真正没有凭据的机器仍走通用文案;密钥可用时照常解析成功;**自有副本损坏必须归 `invalid` 且不得产生加密结论**;`diagnose()` 抛错时退回通用文案。
|
|
86
|
+
- `tests/searched-paths.spec.ts`(+1 例,改 2 例):加密原因必须在**段落**里被回答、且不回落去回显 `resolve()` 的消息;对渲染后的字符串断言「重新登录」这句话没有以任何形式残留。删掉了 `SearchedView.encrypted` 的两条断言——该字段已随折叠区那份重复提示一并删除(见下)。
|
|
87
|
+
|
|
88
|
+
**变异验证(实测)**:共 8 处定向改写,7 处被测试咬住。**其中 1 处(`source === 'desktop'` 过滤)逃脱**——正因如此才发现它是不可达的死代码并删除,同时把测试改为钉住真正的不变量,再次变异确认可咬住。其余:把 macOS 二进制名改回 `'WorkBuddy'`、从候选里删掉 `WorkBuddy AI.app`、让身份校验恒为 `true`、关掉加密归类、去掉穿越防护、把 `encrypted` 移出段落优先级、调换 `wrong-region` 与 `encrypted` 的次序——逐一挂掉对应用例。
|
|
89
|
+
|
|
90
|
+
**顺带清掉两处「看着像在防守、其实没有消费者」的代码**(与上面那句同源):`describeMissingCredential()` 里不可达的 `source === 'desktop'` 过滤,以及 `SearchedView.encrypted`。后者的消费者是折叠区那份重复提示;提示既已由段落取代,字段留着只会让「列表」与「段落」在下次改动时再次漂移。
|
|
91
|
+
|
|
92
|
+
### Docs
|
|
93
|
+
|
|
94
|
+
- `README.md` / `README.en.md`:已知限制改口径——加密自 **5.6.0** 起、**macOS 同样在内**;补「二进制名向 bundle 现问」「含国际版 + 子目录扫描 + 身份校验」「未登录 ≠ 读不出来」三条。
|
|
95
|
+
- `docs/DESIGN.md` §6 风险与边界:新增第 6 条,记录本次取证结论与三个决定(向 bundle 问名字、扫子目录但必须校验身份、失败分类下沉到 `resolve()`),并记下那句不可达过滤为何被删。
|
|
96
|
+
|
|
97
|
+
## 2.0.10 (2026-09-22)
|
|
4
98
|
|
|
5
99
|
### Bug Fixes
|
|
6
100
|
|
|
@@ -31,7 +125,7 @@
|
|
|
31
125
|
|
|
32
126
|
- `docs/DESIGN.md` §3.4:记录「段落说结论、列表给细节」的分工,以及 `wrong-region` 为什么是**发现**而不是**缺席**。
|
|
33
127
|
|
|
34
|
-
## 2.0.9 (2026-09-
|
|
128
|
+
## 2.0.9 (2026-09-22)
|
|
35
129
|
|
|
36
130
|
### Features
|
|
37
131
|
|
|
@@ -60,7 +154,7 @@
|
|
|
60
154
|
- `README.md` / `README.en.md`:功能特性新增「未登录时列出探测过的路径与原因」一条。
|
|
61
155
|
- `docs/DESIGN.md` §3.4:记录未登录诊断的四档原因划分,以及「`encrypted` 必须独立成档」的理由。
|
|
62
156
|
|
|
63
|
-
## 2.0.8 (2026-09-
|
|
157
|
+
## 2.0.8 (2026-09-22)
|
|
64
158
|
|
|
65
159
|
### Features
|
|
66
160
|
|
|
@@ -90,7 +184,7 @@
|
|
|
90
184
|
- `README.md` / `README.en.md`:「功能特性」新增「可单独关闭任一版本供应商」一条,并在双供应商那条说明关闭后的效果与代价。
|
|
91
185
|
- `docs/DESIGN.md`:说明区域开关为何必须落在注册层(`replace([])`),以及它与 issue #12「无账号隐藏」的维度差异。
|
|
92
186
|
|
|
93
|
-
## 2.0.7 (2026-09-
|
|
187
|
+
## 2.0.7 (2026-09-22)
|
|
94
188
|
|
|
95
189
|
### Features
|
|
96
190
|
|
|
@@ -249,6 +343,21 @@
|
|
|
249
343
|
|
|
250
344
|
- `README.md` / `README.en.md` 的「已知限制 / Known limitations」补充账号失效后的表现与恢复方式,并说明重新登录桌面端无法修复该状态。
|
|
251
345
|
|
|
346
|
+
## 2.0.3 (2026-09-17)
|
|
347
|
+
|
|
348
|
+
### Fixes
|
|
349
|
+
|
|
350
|
+
- **解析单数 `effort` 形态的推理声明,单数模型不再注册为非推理**(issue #7)。上游 `reasoning` 元数据存在两种拼写:复数 `supportedEfforts` 形态,与单数 `effort` 形态(`deepseek-v4.1-flash`、`kimi-k3`、`gemini-3.5-flash` 等)。原 `parseReasoning` 只读复数,单数形态整档被丢弃,模型被注册为 `reasoning: false`,thinking 永远无法启用。
|
|
351
|
+
|
|
352
|
+
- **实测证实单数 `effort` 是默认档而非唯一档**:两个网关(CN `copilot.tencent.com` / 国际 `www.workbuddy.ai`)均确认——`deepseek-v4.1-flash` 对 `low`/`high`/`max` 全部返回 200 且 `reasoning_content` 各异;`kimi-k3-1` 接受未声明的 `high` 档;不发 `reasoning_effort` 时思考字符数为 0,即上游默认关闭思考。
|
|
353
|
+
- **依据实证在解析层把单数形态折叠为复数**:`supportedEfforts` 展开为全阶梯 `low`/`medium`/`high`/`xhigh`/`max`(复数载荷观测到的词表,`minimal` 从未出现)、`effort` 折入 `defaultEffort`、`canDisableThinking` 视为 `true`(`off` 可用)。
|
|
354
|
+
- **复数形态逐字段透传不受影响**(`hy4-preview` 仍仅 `high` 且锁定 `off`);未识别的单数 `effort` 值按单档透传,交由 adapter 的已知档位过滤。
|
|
355
|
+
- **adapter / 卡片 / 持久化零改动即自动生效**:改动只落在解析层。
|
|
356
|
+
|
|
357
|
+
### Tests
|
|
358
|
+
|
|
359
|
+
- `tests/upstream.spec.ts` 新增 55 行、`tests/adapter.spec.ts` 新增 20 行,共 6 例:覆盖单数折叠、混合形态优先级、以及线上真实载荷的集成断言。
|
|
360
|
+
|
|
252
361
|
## 2.0.2 (2026-09-15)
|
|
253
362
|
|
|
254
363
|
### Fixes
|
package/README.en.md
CHANGED
|
@@ -123,7 +123,9 @@ Marketplace screenshots and GitHub README badges are two separate mechanisms:
|
|
|
123
123
|
- Users upgrading from before 2.0.0 may still carry the old top-level `accountId` (the migration source). It applies only to its own region and **stops applying once that region is explicitly cleared (`accounts.<region> = ""`)** — otherwise the account you cleared would be silently restored at the next startup. The card shows whether a region is "using the saved account" or "following the app's current sign-in": the restored default is usually the very account you just cleared, so without that line there is no way to tell a successful clear from a no-op.
|
|
124
124
|
- **On Windows, an account change may fail to save while `settings.yaml` is held open.** DSH updates that file by writing a temp file and replacing the target; an antivirus scanner, a sync client (OneDrive), or an open editor can lock it briefly, and once the replacement retries are exhausted the failed write does **not** make `set()` reject. The plugin therefore reads the value back after writing: it shows "cleared" **only once the write is confirmed on disk**, and on failure it reports the error and keeps your previous choice (never a false confirmation that the old selection silently contradicts). Close whatever holds the file and retry.
|
|
125
125
|
- Windows and Linux credential paths are derived from platform conventions and are not verified on real hardware; set `WORKBUDDY_AUTH_FILE` if yours lives elsewhere.
|
|
126
|
-
- **Encrypted credentials require the desktop app to be present.** Newer desktop builds encrypt the token fields in the auth file, and the plugin needs that app's own native binding to obtain the field key. If the app is not installed, was uninstalled, or lives outside the probed paths (set `WORKBUDDY_APP_EXECUTABLE`), an encrypted file cannot be read — the account then shows as signed out, and the plugin **never** degrades into emitting a truncated token. `doctor` reports whether this capability is available. The key is a build-time constant that may rotate with app releases; the plugin fetches it from the local app each run and caches nothing on disk, so an app upgrade needs no plugin upgrade.
|
|
126
|
+
- **Encrypted credentials require the desktop app to be present.** Newer desktop builds (**from 5.6.0, on macOS as well as Windows**) encrypt the token fields in the auth file, and the plugin needs that app's own native binding to obtain the field key. If the app is not installed, was uninstalled, or lives outside the probed paths (set `WORKBUDDY_APP_EXECUTABLE`), an encrypted file cannot be read — the account then shows as signed out, and the plugin **never** degrades into emitting a truncated token. `doctor` reports whether this capability is available. The key is a build-time constant that may rotate with app releases; the plugin fetches it from the local app each run and caches nothing on disk, so an app upgrade needs no plugin upgrade.
|
|
127
|
+
- **The executable is located from the bundle's own declaration, not guessed from the app name.** The WorkBuddy macOS bundles declare `CFBundleExecutable` as `Electron` (not `WorkBuddy`), so the plugin reads each bundle's `Info.plist` to learn the binary's name. Both the domestic `WorkBuddy.app` and the international `WorkBuddy AI.app` are candidates, and an app filed into a subdirectory of an applications folder (e.g. `/Applications/IDE/WorkBuddy.app`) is found by a one-level scan that confirms each candidate's bundle identifier before use — so a different Electron app can never be launched by mistake.
|
|
128
|
+
- **"Signed out" and "your sign-in cannot be read" are reported separately.** When an encrypted file cannot be read, the advice is to install the desktop app or point `WORKBUDDY_APP_EXECUTABLE` at it — **not** to sign in again, which cannot help: the credential is fine, the key is what is missing.
|
|
127
129
|
|
|
128
130
|
## Disclaimer
|
|
129
131
|
|
package/README.md
CHANGED
|
@@ -131,7 +131,9 @@ dsh plugin --profile desktop add /Users/dmh2002/DshProject/dsh-connect-workbuddy
|
|
|
131
131
|
- 从 2.0.0 之前的版本升级而来的用户,设置里可能仍留有旧版的顶层 `accountId`(迁移来源)。它只作用于其所属区域,且**该区域被显式清除(`accounts.<区域> = ""`)后即不再生效**——否则清除掉的账号会在下次启动时被静默恢复。卡片会显示每个区域当前是「使用保存的账号」还是「跟随 App 当前登录」:清除后回到的默认账号往往与刚清掉的账号是同一个,没有这行提示就无法分辨清除是否生效。
|
|
132
132
|
- **Windows:设置文件 `settings.yaml` 被占用时,账号改动可能无法保存**。DSH 用「写临时文件 + 覆盖替换」的方式更新该文件,而杀毒软件、OneDrive 等同步盘或正在打开该文件的编辑器会短暂锁住它;重试耗尽后,写入失败**不会**让 `set()` 报错。插件因此会在写入后回读校验:**确认落盘才显示「已清除」**,失败则给出明确错误并保留原来的选择(不会显示一个旧选择默默反驳的假确认)。遇到该提示时,关闭占用该文件的程序后重试即可。
|
|
133
133
|
- Windows / Linux 的凭据默认路径为按平台约定推导,未经真机验证;必要时可通过环境变量 `WORKBUDDY_AUTH_FILE` 指定实际位置。
|
|
134
|
-
- **加密凭据依赖桌面 App
|
|
134
|
+
- **加密凭据依赖桌面 App 在场**:新版桌面端(**5.6.0 起,macOS 与 Windows 同样如此**)加密了 auth 文件里的 token 字段,插件需要调用该 App 自身的原生绑定才能取回字段密钥。因此 App 若未安装、被卸载,或安装在探测路径之外(用 `WORKBUDDY_APP_EXECUTABLE` 指定),加密文件就读不出来——此时账号会显示为未登录,而**不会**退化成读出一个残缺的 token。`doctor` 会报告这项能力是否可用。该密钥是构建期常量,随 App 版本可能轮换;插件每次向本机 App 现取,不缓存到磁盘,所以 App 升级后无需升级插件。
|
|
135
|
+
- **App 可执行文件按 bundle 自身声明定位,不按 App 名猜**:WorkBuddy 的 macOS bundle 里 `CFBundleExecutable` 是 `Electron`(不是 `WorkBuddy`),因此插件读取 bundle 的 `Info.plist` 来决定二进制名。国内版 `WorkBuddy.app` 与国际版 `WorkBuddy AI.app` 都在候选内;App 被归入 `/Applications` 子目录(如 `/Applications/IDE/WorkBuddy.app`)时,插件还会向下扫一层并用 bundle id 确认身份后才使用——**不会**因为同名的其他 Electron 应用而误启动它。
|
|
136
|
+
- **「未登录」与「登录信息读不出来」是两件事**:加密文件读不出时,插件给出的建议是「安装桌面 App / 用 `WORKBUDDY_APP_EXECUTABLE` 指定其位置」,而**不是**「重新登录一次」——后者对这种情况无效(凭据本身是好的,缺的是密钥)。
|
|
135
137
|
|
|
136
138
|
## 免责声明
|
|
137
139
|
|
package/lib/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { O as workbuddyOwnAuthPath, T as legacyWorkbuddyOwnAuthPath, W as findWorkbuddyAppExecutable, _ as WorkBuddyCredentialStore, a as readHostHeartbeat, b as defaultDesktopAuthCandidates, c as WORKBUDDY_CONNECT_VERSION, g as WORKBUDDY_AUTH_FILE_ENV, j as WorkBuddyUpstreamClient, l as FALLBACK_WORKBUDDY_MODELS, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS_GLOBAL, x as defaultDesktopAuthDirs, z as WORKBUDDY_APP_EXECUTABLE_ENV } from "./host-heartbeat-ChtMu37E.js";
|
|
3
3
|
import { realpathSync } from "node:fs";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
//#region src/bin.ts
|