dsh-workbuddy-connect 0.7.8 → 0.7.9

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/README.en.md CHANGED
@@ -13,7 +13,7 @@ Brings the models available in your signed-in WorkBuddy / WorkBuddy AI desktop a
13
13
  ```text
14
14
  Please install and verify the DSH (DeepSeek Harness) plugin dsh-workbuddy-connect for me (project: https://github.com/corrinehu/dsh-workbuddy-connect).
15
15
  Start by reading the project README and confirming which interface I actually use (Web / Desktop / TUI) and its DSH core version — for the Desktop app, the bundled core, never the standalone CLI's version.
16
- Then install from the official npm package per the version mapping (0.2.1-rc.2 -> 0.7.8, 0.2.1-rc.1 -> 0.7.7, 0.2.0-rc.2 -> 0.7.6, rc.1 -> 0.7.0, 0.1.5-0.1.7 -> 0.6.5), preferring a locally supported non-market channel; on some Desktop distributions (the official Electron app in particular) the profile is app-managed and rules vary by distribution — check mine first, then explore its compliant install route, but never bypass its validation.
16
+ Then install from the official npm package per the version mapping (0.2.1-rc.2 -> 0.7.9, 0.2.1-rc.1 -> 0.7.7, 0.2.0-rc.2 -> 0.7.6, rc.1 -> 0.7.0, 0.1.5-0.1.7 -> 0.6.5), preferring a locally supported non-market channel; on some Desktop distributions (the official Electron app in particular) the profile is app-managed and rules vary by distribution — check mine first, then explore its compliant install route, but never bypass its validation.
17
17
  Ask for my consent and make backups before changing any local files or configuration.
18
18
  After installing, verify the WorkBuddy group appears in the model picker and walk me through one short conversation.
19
19
  If the Desktop install still fails generation validation (e.g. the @types/retry false positive), report it honestly, reference upstream dataelement/dsh-desktop#688, and suggest switching to the standalone Web / TUI — do not modify dependency packages or bypass the check.
@@ -23,7 +23,7 @@ Core-to-plugin quick reference (full table in "Compatibility and release history
23
23
 
24
24
  | DSH core | Recommended plugin |
25
25
  |---|---|
26
- | 0.2.1-rc.2 | 0.7.8 (npm latest) |
26
+ | 0.2.1-rc.2 | 0.7.9 (npm latest) |
27
27
  | 0.2.1-rc.1 | 0.7.7 |
28
28
  | 0.2.0-rc.2 | 0.7.6 |
29
29
  | 0.2.0-rc.1 | 0.7.0 |
@@ -33,8 +33,8 @@ Core-to-plugin quick reference (full table in "Compatibility and release history
33
33
  <summary>Manual install (command line, Web / TUI only)</summary>
34
34
 
35
35
  ```sh
36
- # Web (pin per your actual core — example: 0.2.1-rc.2 -> 0.7.8; or install from source via github:corrinehu/dsh-workbuddy-connect)
37
- dsh plugin --profile web add dsh-workbuddy-connect@0.7.8
36
+ # Web (pin per your actual core — example: 0.2.1-rc.2 -> 0.7.9; or install from source via github:corrinehu/dsh-workbuddy-connect)
37
+ dsh plugin --profile web add dsh-workbuddy-connect@0.7.9
38
38
  dsh web
39
39
 
40
40
  # TUI (example for a 0.1.x core; that profile needs pnpm 11, e.g. npx pnpm@11)
@@ -84,7 +84,7 @@ The same UI works unchanged inside the DSH 0.1.5 settings cards:
84
84
 
85
85
  ![Settings card showing the plugin](assets/2.png)
86
86
 
87
- The expanded card has three tabs: Status shows the account, token validity, total credit, catalog source, and reasoning-level detection; Context lists each model's context window, and the international version's "use the upstream's largest declared context window" switch lives here (on by default, so DSH sizes context compression to the largest declared window; turn it off to follow the upstream default instead, and the preference persists across restarts); Details shows per-package credit and model offers. The CN and international versions each get their own card.
87
+ The expanded card has three tabs: Status shows the account, token validity, total credit, catalog source, and reasoning-level detection; Context lists each model's context window, and the international version's "use the upstream's largest declared context window" switch lives here (on by default, so DSH sizes context compression to the largest declared window; turn it off to follow the upstream default instead, and the preference persists across restarts); Details combines credit packages with the same name and shows model offers. The CN and international versions each get their own card.
88
88
 
89
89
  ![Settings card showing account and remaining credit](assets/3.png)
90
90
 
@@ -119,7 +119,7 @@ dsh plugin --profile web exec dsh-workbuddy-connect doctor --provider workbuddy-
119
119
  - **International-version environments not yet covered**: on Windows / WSL / Linux no reliable source for the international app's version has been located yet, so the saved value or the built-in default is used. On macOS, real-shim checks covered complete GPT-family replies, tool calls, and continued turns.
120
120
  - **Behaviour change with no credentials**: a version whose app was never signed in — and that left no plugin-owned copy — no longer shows a model group. The CN version used to display a built-in fallback list, but every model on it failed when selected.
121
121
  - **The enterprise credit path currently covers the CN product only**: the international enterprise billing interface is unverified, so those accounts still read through the personal endpoint pending measurement. The enterprise branch could not be tested locally (the development machine holds a personal account); it was implemented from the official app's interface contract, and reports from enterprise users are welcome.
122
- - **Settings entries vary with the DSH version**: on 0.1.5 / 0.1.6 the plugin installs its settings sections as before (`authFile` / `authFileAI` stay readable from `settings.yaml` and the TUI `/settings`); on 0.1.7+ / 0.2.0 the settings service no longer offers that interface, so the plugin degrades to a settings-less provider — models, the picker, model visibility, and context windows keep working; only the "use the upstream's declared maximum context window" preference stops being persistable.
122
+ - **Settings entries vary with the DSH version**: older services use `installSection`; newer ones use SettingsForms. On DSH 0.2.1-rc.2, 0.7.9 restores the international edition's maximum-context switch, enabled by default. Turning it off uses the upstream default window, and the preference survives restarts. The switch appears only once this plugin's writable settings entry is ready; while waiting, the maximum window remains the default.
123
123
  - Relies on WorkBuddy client interfaces (not a public API); the plugin may need updates as WorkBuddy changes.
124
124
 
125
125
  ## Compatibility and release history
@@ -128,7 +128,7 @@ The plugin version must match the DSH core — a mismatched combination fails to
128
128
 
129
129
  | DSH core | Recommended plugin | Notes |
130
130
  |---|---|---|
131
- | `0.2.1-rc.2` | `0.7.8` | Targets this core only; `0.2.0-rc.2` and `0.2.1-rc.2` differ. Check the core bundled in your Desktop app. |
131
+ | `0.2.1-rc.2` | `0.7.9` | Targets this core only; `0.2.0-rc.2` and `0.2.1-rc.2` differ. Check the core bundled in your Desktop app. |
132
132
  | `0.2.1-rc.1` | `0.7.7` | The core moved to `@earendil-works/pi-ai` 1.3.0, so the plugin bundles 1.3.0 too — a leftover 0.87.1 overrides entry must be removed (see the pi-ai note below). |
133
133
  | `0.2.0-rc.2` | `0.7.6` | Previous core line; fixes enterprise catalog gaps and international chat requests blocked by the upstream channel check. Avoid 0.7.2 with its registry regression. |
134
134
  | `0.2.0-rc.1` | `0.7.0` | 0.7.1 drops rc.1 support; stay on this version. |
@@ -138,6 +138,7 @@ The plugin version must match the DSH core — a mismatched combination fails to
138
138
 
139
139
  Release history (full records at [releases](https://github.com/corrinehu/dsh-workbuddy-connect/releases)):
140
140
 
141
+ - **0.7.9**: restores the international edition's maximum-context switch on DSH 0.2.1-rc.2, including delayed initialization and persistent settings (PR [#172](https://github.com/corrinehu/dsh-workbuddy-connect/pull/172), by @1Dreamer666); avoids fetch-blocked local ports that could cause international-edition 502 errors (PR [#164](https://github.com/corrinehu/dsh-workbuddy-connect/pull/164), by @Xiong-Ziyi); combines credit packages with the same name to reduce repeated entries.
141
142
  - **0.7.8**: adapts to DSH 0.2.1-rc.2; bundles the official atomic-write helper instead of relying on host module exposure; refreshes the China catalog snapshot and distinguishes Hy3 / Hy3-X; improves probe result labels and fixes the credit-refresh placement and tooltip. Thanks to @chenpiyanghappy for PR [#156](https://github.com/corrinehu/dsh-workbuddy-connect/pull/156).
142
143
  - **0.7.7**: adapts to the DSH 0.2.1-rc.1 core: peers narrow to 0.2.1-rc.1 and the plugin-bundled `@earendil-works/pi-ai` moves to 1.3.0 with it (a profile-level leftover 0.87.1 overrides entry must be removed, or the plugin fails to load).
143
144
  - **0.7.6**: fixes enterprise accounts missing `glm-5.3-flashx` from the model catalog ([#123](https://github.com/corrinehu/dsh-workbuddy-connect/issues/123)), and fixes international chat requests being blocked by the upstream channel check ([#139](https://github.com/corrinehu/dsh-workbuddy-connect/issues/139) / [#142](https://github.com/corrinehu/dsh-workbuddy-connect/issues/142)).
@@ -149,9 +150,9 @@ Release history (full records at [releases](https://github.com/corrinehu/dsh-wor
149
150
  - **0.7.0**: adapts to DSH 0.2.0's settings-service rework and drops 0.1.x support.
150
151
  - **0.6.5**: final release of the 0.1.x line.
151
152
 
152
- Pinned-version install example: `dsh plugin --profile web add dsh-workbuddy-connect@0.7.8`.
153
+ Pinned-version install example: `dsh plugin --profile web add dsh-workbuddy-connect@0.7.9`.
153
154
 
154
- **pi-ai versions are per core line and must not be mixed**: the 0.2.1-rc.1 / rc.2 core lines (plugin 0.7.7 / 0.7.8 respectively) moved to `@earendil-works/pi-ai` 1.3.0 (bundled by the plugin), so a profile-level leftover `overrides: {'@earendil-works/pi-ai': '0.87.1'}` **must be removed** or the plugin fails to load; the 0.2.0-rc.2 core line (plugin 0.7.6) still requires `^0.87.1` — on that line, **do not** upgrade the profile-level pi-ai to 1.x, and do not act on third-party plugin managers' "updatable" prompts, which do not check peer constraints ([#86](https://github.com/corrinehu/dsh-workbuddy-connect/issues/86)).
155
+ **pi-ai versions are per core line and must not be mixed**: the 0.2.1-rc.1 / rc.2 core lines (plugin 0.7.7 / 0.7.8–0.7.9 respectively) moved to `@earendil-works/pi-ai` 1.3.0 (bundled by the plugin), so a profile-level leftover `overrides: {'@earendil-works/pi-ai': '0.87.1'}` **must be removed** or the plugin fails to load; the 0.2.0-rc.2 core line (plugin 0.7.6) still requires `^0.87.1` — on that line, **do not** upgrade the profile-level pi-ai to 1.x, and do not act on third-party plugin managers' "updatable" prompts, which do not check peer constraints ([#86](https://github.com/corrinehu/dsh-workbuddy-connect/issues/86)).
155
156
 
156
157
  ## Disclaimer
157
158
 
@@ -162,10 +163,11 @@ Pinned-version install example: `dsh plugin --profile web add dsh-workbuddy-conn
162
163
 
163
164
  ## Related Projects / Community Ecosystem
164
165
 
165
- - **[HanaAgent](https://github.com/liliMozi/openhanako)**: an independent personal AI Agent project.
166
+ - **[dsh-buddy-checkin](https://github.com/corrinehu/dsh-buddy-checkin)**: a daily check-in plugin for the WorkBuddy China edition, maintained by this project's author.
167
+ - **[dsh-workbuddy-switcher](https://github.com/ddGuo/dsh-workbuddy-switcher)**: a community-maintained WorkBuddy account management and switching plugin.
166
168
  - **[hana-workbuddy-connect](https://github.com/miaoyongxu/hana-workbuddy-connect)**: a community-maintained unofficial adapter project that reuses parts of the `dsh-workbuddy-connect` implementation to bring WorkBuddy / WorkBuddy AI models to HanaAgent.
167
169
 
168
- Both projects are maintained independently by third parties. The compatibility, security, and usage support of `hana-workbuddy-connect` are the responsibility of its maintainer. Listing them here is not an official endorsement, nor does it imply any stability or compatibility commitment on this plugin's internal interfaces.
170
+ See each project's documentation for supported versions and installation requirements. Except for `dsh-buddy-checkin`, these projects are independently maintained by third parties. Listing them here is not an official endorsement or a stability or compatibility commitment for this plugin's internal interfaces.
169
171
 
170
172
  ## Acknowledgements
171
173
 
package/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
  ```text
14
14
  请帮我安装并验证 DSH(DeepSeek Harness)插件 dsh-workbuddy-connect(项目地址:https://github.com/corrinehu/dsh-workbuddy-connect):
15
15
  先阅读项目 README,确认我实际使用的界面(Web / Desktop / TUI)与 DSH 内核版本,桌面版以 App 内置核心为准,不要用独立 CLI 的版本代替;
16
- 再按版本对应关系从 npm 正式包安装(0.2.1-rc.2→0.7.8、0.2.1-rc.1→0.7.7、0.2.0-rc.2→0.7.6、rc.1→0.7.0、0.1.5–0.1.7→0.6.5),优先走本机受支持的非插件市场渠道;部分 Desktop 发行版(尤其官方 Electron 版)的 profile 由 App 管理,各发行版规则未必相同,先确认我所用的发行版,再探查其合规的安装方式,但不得绕过其校验;
16
+ 再按版本对应关系从 npm 正式包安装(0.2.1-rc.2→0.7.9、0.2.1-rc.1→0.7.7、0.2.0-rc.2→0.7.6、rc.1→0.7.0、0.1.5–0.1.7→0.6.5),优先走本机受支持的非插件市场渠道;部分 Desktop 发行版(尤其官方 Electron 版)的 profile 由 App 管理,各发行版规则未必相同,先确认我所用的发行版,再探查其合规的安装方式,但不得绕过其校验;
17
17
  任何本地文件或配置改动,先征得我同意并做好备份;
18
18
  装完后验证模型选择器里出现 WorkBuddy 分组,并陪我完成一次简短对话;
19
19
  若 Desktop 安装仍因 generation 校验失败(如 @types/retry 误报),请如实报告、参照上游 dataelement/dsh-desktop#688,并建议我改用独立的 Web / TUI,不要修改依赖包或绕过校验。
@@ -23,7 +23,7 @@
23
23
 
24
24
  | DSH 内核 | 推荐插件版 |
25
25
  |---|---|
26
- | 0.2.1-rc.2 | 0.7.8(npm latest) |
26
+ | 0.2.1-rc.2 | 0.7.9(npm latest) |
27
27
  | 0.2.1-rc.1 | 0.7.7 |
28
28
  | 0.2.0-rc.2 | 0.7.6 |
29
29
  | 0.2.0-rc.1 | 0.7.0 |
@@ -33,8 +33,8 @@
33
33
  <summary>手动安装(命令行,仅 Web / TUI)</summary>
34
34
 
35
35
  ```sh
36
- # Web(按实际内核固定版本,示例为 0.2.1-rc.2 → 0.7.8;也可用 github:corrinehu/dsh-workbuddy-connect 从源码安装)
37
- dsh plugin --profile web add dsh-workbuddy-connect@0.7.8
36
+ # Web(按实际内核固定版本,示例为 0.2.1-rc.2 → 0.7.9;也可用 github:corrinehu/dsh-workbuddy-connect 从源码安装)
37
+ dsh plugin --profile web add dsh-workbuddy-connect@0.7.9
38
38
  dsh web
39
39
 
40
40
  # TUI(示例为 0.1.x 内核;该 profile 需用 pnpm 11 安装,如 npx pnpm@11)
@@ -84,7 +84,7 @@ dsh --profile dsh-tui
84
84
 
85
85
  ![设置卡片显示插件](assets/2.png)
86
86
 
87
- 卡片展开后分「状态 / 上下文 / 明细」三个标签:状态页展示账号、令牌有效期、合计积分、模型列表来源与推理档位检测;上下文页列出各模型上下文窗口,国际版可在这里关闭「使用上游声明的最大上下文窗口」(默认开启,DSH 按上游最大窗口安排上下文压缩;关闭后改用上游默认窗口,偏好持久化);明细页展示各套餐余量与模型优惠。国内版与国际版各有一张自己的卡片。
87
+ 卡片展开后分「状态 / 上下文 / 明细」三个标签:状态页展示账号、令牌有效期、合计积分、模型列表来源与推理档位检测;上下文页列出各模型上下文窗口,国际版可在这里关闭「使用上游声明的最大上下文窗口」(默认开启,DSH 按上游最大窗口安排上下文压缩;关闭后改用上游默认窗口,偏好持久化);明细页合并展示同名积分包的余量与模型优惠。国内版与国际版各有一张自己的卡片。
88
88
 
89
89
  ![设置卡片显示账号与剩余积分](assets/3.png)
90
90
 
@@ -120,7 +120,7 @@ dsh plugin --profile web exec dsh-workbuddy-connect doctor --provider workbuddy-
120
120
  - **国际版仍未覆盖的环境**:Windows / WSL / Linux 下国际版 App 的版本读取尚未找到可靠来源,会退回最近保存的版本或内置值。macOS 上已通过真实 shim 验证 GPT 系完整回复、工具调用与续轮。
121
121
  - **无凭据时的行为变化**:某版 App 从未登录、也没留下插件自留副本时,该版模型分组不再显示。此前国内版会显示一份内置兜底列表,但那些模型选了必然报错。
122
122
  - **企业账号积分目前仅覆盖国内版**:国际版企业账号的计费接口尚未验证,仍按个人版接口读取;待有实测结论后再扩展。企业账号分支在本机无法自测(开发机为个人账号),依据官方 App 的接口契约实现,欢迎企业账号用户反馈实测结果。
123
- - **设置入口随 DSH 版本而异**:0.1.5 / 0.1.6 上插件照常安装 `authFile` / `authFileAI` 等设置分区(`settings.yaml` 与 TUI `/settings` 均可读写);0.1.7+ / 0.2.0 的设置服务不再提供该接口,插件降级为无设置项的 provider——模型、选择器、模型显隐与上下文窗口照常工作,只是「使用上游声明的最大上下文窗口」这一偏好不再可持久化。
123
+ - **设置入口随 DSH 版本而异**:旧版设置服务使用 `installSection`,新版使用 SettingsForms。0.7.9 在 DSH 0.2.1-rc.2 上恢复国际版「使用上游声明的最大上下文窗口」开关,默认开启;关闭后使用上游默认窗口,设置在重启后保留。只有本插件的可写设置入口就绪后才显示开关,等待期间仍使用默认的最大窗口。
124
124
  - 依赖 WorkBuddy 客户端接口(非官方开放 API),WorkBuddy 更新后插件可能需要随之调整。
125
125
 
126
126
  ## 版本兼容与历史版本
@@ -129,7 +129,7 @@ dsh plugin --profile web exec dsh-workbuddy-connect doctor --provider workbuddy-
129
129
 
130
130
  | DSH 内核 | 推荐插件版 | 注意事项 |
131
131
  |---|---|---|
132
- | `0.2.1-rc.2` | `0.7.8` | 仅适配这一内核;`0.2.0-rc.2` 与 `0.2.1-rc.2` 不同,请核对 App 内置版本。 |
132
+ | `0.2.1-rc.2` | `0.7.9` | 仅适配这一内核;`0.2.0-rc.2` 与 `0.2.1-rc.2` 不同,请核对 App 内置版本。 |
133
133
  | `0.2.1-rc.1` | `0.7.7` | 内核升级到 `@earendil-works/pi-ai` 1.3.0,插件同步自带 1.3.0,历史遗留的 0.87.1 overrides 必须移除(见下方 pi-ai 说明)。 |
134
134
  | `0.2.0-rc.2` | `0.7.6` | 上一代内核线;修复企业账号模型目录缺项与国际版对话被渠道校验拦截,勿停留在有注册表回归的 0.7.2。 |
135
135
  | `0.2.0-rc.1` | `0.7.0` | 0.7.1 起不再支持 rc.1,请停留本版。 |
@@ -139,6 +139,7 @@ dsh plugin --profile web exec dsh-workbuddy-connect doctor --provider workbuddy-
139
139
 
140
140
  历次更新(完整记录见 [releases](https://github.com/corrinehu/dsh-workbuddy-connect/releases)):
141
141
 
142
+ - **0.7.9**:恢复 DSH 0.2.1-rc.2 上国际版最大上下文窗口开关,修复延迟初始化及设置持久化(PR [#172](https://github.com/corrinehu/dsh-workbuddy-connect/pull/172),by @1Dreamer666);避免本地服务绑定到 fetch 禁用端口导致国际版 502(PR [#164](https://github.com/corrinehu/dsh-workbuddy-connect/pull/164),by @Xiong-Ziyi);同名积分包合并展示,减少重复条目。
142
143
  - **0.7.8**:适配 DSH 0.2.1-rc.2;将官方原子写入工具打包到插件,避免依赖宿主暴露该模块;更新国内模型目录快照,区分 Hy3 / Hy3-X;优化推理检测结果展示,调整积分刷新按钮位置并修正悬停提示。感谢 @chenpiyanghappy 的 PR [#156](https://github.com/corrinehu/dsh-workbuddy-connect/pull/156)。
143
144
  - **0.7.7**:适配 DSH 0.2.1-rc.1 内核:peer 收窄到 0.2.1-rc.1,插件自带的 `@earendil-works/pi-ai` 随内核升级到 1.3.0(profile 级历史遗留的 0.87.1 overrides 必须移除,否则插件无法加载);修复国际版与国内版推理档位检测停留「检测未完成」——上游把档位拒绝码改拼为 `invalid_parameter_value`([#143](https://github.com/corrinehu/dsh-workbuddy-connect/issues/143) / [#148](https://github.com/corrinehu/dsh-workbuddy-connect/issues/148),PR [#145](https://github.com/corrinehu/dsh-workbuddy-connect/pull/145));修复中文 Windows 控制台下注册表缺键诊断因 GBK 编码被误判(zh-CN 实机验证)。
144
145
  - **0.7.6**:修复企业账号模型目录缺少 `glm-5.3-flashx`([#123](https://github.com/corrinehu/dsh-workbuddy-connect/issues/123)),修复国际版对话请求被上游渠道校验拦截([#139](https://github.com/corrinehu/dsh-workbuddy-connect/issues/139) / [#142](https://github.com/corrinehu/dsh-workbuddy-connect/issues/142))。
@@ -150,9 +151,9 @@ dsh plugin --profile web exec dsh-workbuddy-connect doctor --provider workbuddy-
150
151
  - **0.7.0**:适配 DSH 0.2.0 的设置服务改造,不再支持 0.1.x。
151
152
  - **0.6.5**:0.1.x 线最终版本。
152
153
 
153
- 固定版本安装示例:`dsh plugin --profile web add dsh-workbuddy-connect@0.7.8`。
154
+ 固定版本安装示例:`dsh plugin --profile web add dsh-workbuddy-connect@0.7.9`。
154
155
 
155
- **pi-ai 版本按内核线区分,不能混**:0.2.1-rc.1 / rc.2 内核线(分别对应插件 0.7.7 / 0.7.8)已升级到 `@earendil-works/pi-ai` 1.3.0(由插件自带),profile 级历史遗留的 `overrides: {'@earendil-works/pi-ai': '0.87.1'}` **必须移除**,否则插件会因加载到旧一代 pi-ai 而启动失败;0.2.0-rc.2 内核线(插件 0.7.6)仍要求 `^0.87.1`,在这条线上**不要**把 profile 级的 pi-ai 升到 1.x,第三方插件管理器的「可更新」提示不校验 peer 约束、不能照做([#86](https://github.com/corrinehu/dsh-workbuddy-connect/issues/86))。
156
+ **pi-ai 版本按内核线区分,不能混**:0.2.1-rc.1 / rc.2 内核线(分别对应插件 0.7.7 / 0.7.8–0.7.9)已升级到 `@earendil-works/pi-ai` 1.3.0(由插件自带),profile 级历史遗留的 `overrides: {'@earendil-works/pi-ai': '0.87.1'}` **必须移除**,否则插件会因加载到旧一代 pi-ai 而启动失败;0.2.0-rc.2 内核线(插件 0.7.6)仍要求 `^0.87.1`,在这条线上**不要**把 profile 级的 pi-ai 升到 1.x,第三方插件管理器的「可更新」提示不校验 peer 约束、不能照做([#86](https://github.com/corrinehu/dsh-workbuddy-connect/issues/86))。
156
157
 
157
158
  ## 免责声明
158
159
 
@@ -163,10 +164,11 @@ dsh plugin --profile web exec dsh-workbuddy-connect doctor --provider workbuddy-
163
164
 
164
165
  ## 相关项目 / 社区生态
165
166
 
166
- - **[HanaAgent](https://github.com/liliMozi/openhanako)**:独立的个人 AI Agent 项目。
167
+ - **[dsh-buddy-checkin](https://github.com/corrinehu/dsh-buddy-checkin)**:本项目作者维护的 WorkBuddy 国内版每日签到插件。
168
+ - **[dsh-workbuddy-switcher](https://github.com/ddGuo/dsh-workbuddy-switcher)**:社区维护的 WorkBuddy 账号管理与切换插件。
167
169
  - **[hana-workbuddy-connect](https://github.com/miaoyongxu/hana-workbuddy-connect)**:社区维护的非官方适配项目,复用 `dsh-workbuddy-connect` 的部分实现,为 HanaAgent 接入 WorkBuddy / WorkBuddy AI 模型。
168
170
 
169
- 以上项目均由第三方独立维护。其中,`hana-workbuddy-connect` 的兼容性、安全性及使用支持由其维护者负责。收录不代表官方背书,也不意味着本插件对内部接口提供稳定性或兼容性承诺。
171
+ 适用版本与安装要求请参阅各项目说明。除 `dsh-buddy-checkin` 外,以上项目均由第三方独立维护;收录不代表官方背书,也不构成本插件内部接口的稳定性或兼容性承诺。
170
172
 
171
173
  ## 致谢
172
174
 
package/lib/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { C as WORKBUDDY_VARIANTS, O as WorkBuddyUpstreamClient, Q as resolveAppVersion, S as CN_VARIANT, a as readHostHeartbeat, b as atRestKeyProviderFor, c as WORKBUDDY_CONNECT_VERSION, l as FALLBACK_WORKBUDDY_AI_MODELS, m as WorkBuddyCredentialStore, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS, w as variantFor } from "./host-heartbeat-sjotAEdb.js";
2
+ import { C as WORKBUDDY_VARIANTS, O as WorkBuddyUpstreamClient, Q as resolveAppVersion, S as CN_VARIANT, a as readHostHeartbeat, b as atRestKeyProviderFor, c as WORKBUDDY_CONNECT_VERSION, l as FALLBACK_WORKBUDDY_AI_MODELS, m as WorkBuddyCredentialStore, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS, w as variantFor } from "./host-heartbeat-SJaZ33tR.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
@@ -691,6 +691,25 @@ window.__ModuleLoader__.load({
691
691
  })]
692
692
  });
693
693
  }
694
+ /** Combine same-name packages for display; expiry data stays per package upstream. */
695
+ function groupCreditPackages(accounts) {
696
+ const groups = /* @__PURE__ */ new Map();
697
+ for (const account of accounts) {
698
+ const group = groups.get(account.packageName);
699
+ if (group === void 0) groups.set(account.packageName, {
700
+ packageName: account.packageName,
701
+ remain: account.remain,
702
+ size: account.size,
703
+ ...account.unlimited === true ? { unlimited: true } : {}
704
+ });
705
+ else {
706
+ group.remain += account.remain;
707
+ group.size = group.size > 0 && account.size > 0 ? group.size + account.size : 0;
708
+ if (account.unlimited === true) group.unlimited = true;
709
+ }
710
+ }
711
+ return [...groups.values()];
712
+ }
694
713
  /**
695
714
  * One billing package as a labeled progress bar.
696
715
  *
@@ -1489,13 +1508,13 @@ window.__ModuleLoader__.load({
1489
1508
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1490
1509
  style: quotaTitleStyle,
1491
1510
  children: t("creditsDetailHeading")
1492
- }), status.credits.accounts.filter((account) => account.packageName === "enterprise" || account.remain > 0 || account.unlimited === true).map((account, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CreditBar, {
1511
+ }), groupCreditPackages(status.credits.accounts).filter((account) => account.packageName === "enterprise" || account.remain > 0 || account.unlimited === true).map((account) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CreditBar, {
1493
1512
  label: account.packageName === "enterprise" ? t("packageEnterprise") : account.packageName,
1494
1513
  remain: account.remain,
1495
1514
  size: account.size,
1496
1515
  unlimited: account.unlimited,
1497
1516
  t
1498
- }, `${account.packageName}-${String(index)}`))]
1517
+ }, account.packageName))]
1499
1518
  }), status.models === void 0 || status.models.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1500
1519
  style: quotaListStyle,
1501
1520
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
@@ -2828,7 +2847,7 @@ window.__ModuleLoader__.load({
2828
2847
  };
2829
2848
  //#endregion
2830
2849
  //#region src/version.ts
2831
- const WORKBUDDY_CONNECT_VERSION = "0.7.8";
2850
+ const WORKBUDDY_CONNECT_VERSION = "0.7.9";
2832
2851
  //#endregion
2833
2852
  //#region src/client/WorkBuddyConfigPage.tsx
2834
2853
  /**
@@ -4542,7 +4542,7 @@ var WorkBuddyCatalog = class {
4542
4542
  };
4543
4543
  //#endregion
4544
4544
  //#region src/version.ts
4545
- const WORKBUDDY_CONNECT_VERSION = "0.7.8";
4545
+ const WORKBUDDY_CONNECT_VERSION = "0.7.9";
4546
4546
  //#endregion
4547
4547
  //#region src/host-heartbeat.ts
4548
4548
  /**
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as validAppVersion, A as extractDisplayErrorMessage, B as randomSentinel, C as WORKBUDDY_VARIANTS, D as WORKBUDDY_UPDATE_PATH, F as prepareCnChatBody, G as readCliVersion, H as FALLBACK_CN_APP_VERSION, I as prepareInternationalChatBody, J as WORKBUDDY_APP_VERSION_FILENAME, K as resolveChatIdentity, L as regionOf, M as normalizeCredits, N as parseModelCatalog, O as WorkBuddyUpstreamClient, P as prepareChatBody, Q as resolveAppVersion, R as PROBE_EFFORT_CANDIDATES, S as CN_VARIANT, U as chatUserAgent, V as CN_APP_VERSION_FILENAME, W as fallbackChatIdentity, X as installedAppVersion, Y as appUserAgent, Z as readBundleVersion, _ as desktopAuthCandidatesFor, a as readHostHeartbeat, b as atRestKeyProviderFor, c as WORKBUDDY_CONNECT_VERSION, d as WorkBuddyCatalog, f as WORKBUDDY_AUTH_FILENAME, g as defaultDesktopAuthPath, h as defaultDesktopAuthCandidates, i as processStartTimeMs, j as modelWithCurrentPromotion, k as classifyUpstreamError, l as FALLBACK_WORKBUDDY_AI_MODELS, m as WorkBuddyCredentialStore, n as clearHostHeartbeat, o as workbuddyHostHeartbeatPath, p as WORKBUDDY_AUTH_FILE_ENV, q as validCliVersion, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, u as FALLBACK_WORKBUDDY_MODELS, v as parseWorkBuddyAuth, w as variantFor, x as AI_VARIANT, y as workbuddyOwnAuthPath, z as probeModel } from "./host-heartbeat-sjotAEdb.js";
1
+ import { $ as validAppVersion, A as extractDisplayErrorMessage, B as randomSentinel, C as WORKBUDDY_VARIANTS, D as WORKBUDDY_UPDATE_PATH, F as prepareCnChatBody, G as readCliVersion, H as FALLBACK_CN_APP_VERSION, I as prepareInternationalChatBody, J as WORKBUDDY_APP_VERSION_FILENAME, K as resolveChatIdentity, L as regionOf, M as normalizeCredits, N as parseModelCatalog, O as WorkBuddyUpstreamClient, P as prepareChatBody, Q as resolveAppVersion, R as PROBE_EFFORT_CANDIDATES, S as CN_VARIANT, U as chatUserAgent, V as CN_APP_VERSION_FILENAME, W as fallbackChatIdentity, X as installedAppVersion, Y as appUserAgent, Z as readBundleVersion, _ as desktopAuthCandidatesFor, a as readHostHeartbeat, b as atRestKeyProviderFor, c as WORKBUDDY_CONNECT_VERSION, d as WorkBuddyCatalog, f as WORKBUDDY_AUTH_FILENAME, g as defaultDesktopAuthPath, h as defaultDesktopAuthCandidates, i as processStartTimeMs, j as modelWithCurrentPromotion, k as classifyUpstreamError, l as FALLBACK_WORKBUDDY_AI_MODELS, m as WorkBuddyCredentialStore, n as clearHostHeartbeat, o as workbuddyHostHeartbeatPath, p as WORKBUDDY_AUTH_FILE_ENV, q as validCliVersion, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, u as FALLBACK_WORKBUDDY_MODELS, v as parseWorkBuddyAuth, w as variantFor, x as AI_VARIANT, y as workbuddyOwnAuthPath, z as probeModel } from "./host-heartbeat-SJaZ33tR.js";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import { resolveImageAttachmentAccess, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
4
4
  import { dirname, join, resolve } from "node:path";
@@ -577,13 +577,6 @@ var WorkBuddyPiAiAdapter = class extends PiAiAdapter {
577
577
  };
578
578
  //#endregion
579
579
  //#region src/loopback.ts
580
- /**
581
- * Shared loopback gates for the plugin's local HTTP surfaces: the loopback
582
- * shim and the same-origin web-status route. Both are only ever meant to be
583
- * addressed through the machine's loopback interface.
584
- *
585
- * @module dsh-workbuddy-connect/loopback
586
- */
587
580
  /** Loopback hostnames a local plugin surface may be addressed by. */
588
581
  const LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
589
582
  "127.0.0.1",
@@ -623,6 +616,163 @@ function originIsLoopback(origin) {
623
616
  return false;
624
617
  }
625
618
  }
619
+ /**
620
+ * Ports fetch refuses to connect to, per the fetch standard's port blocking
621
+ * table (https://fetch.spec.whatwg.org/#port-blocking). Kept in sync with the
622
+ * `badPorts` table undici ships: only the entries a host's ephemeral range can
623
+ * actually reach matter, but carrying the whole list costs nothing and keeps
624
+ * the check honest on machines with a customised range.
625
+ */
626
+ const FETCH_BLOCKED_PORTS = /* @__PURE__ */ new Set([
627
+ 1,
628
+ 7,
629
+ 9,
630
+ 11,
631
+ 13,
632
+ 15,
633
+ 17,
634
+ 19,
635
+ 20,
636
+ 21,
637
+ 22,
638
+ 23,
639
+ 25,
640
+ 37,
641
+ 42,
642
+ 43,
643
+ 53,
644
+ 69,
645
+ 77,
646
+ 79,
647
+ 87,
648
+ 95,
649
+ 101,
650
+ 102,
651
+ 103,
652
+ 104,
653
+ 109,
654
+ 110,
655
+ 111,
656
+ 113,
657
+ 115,
658
+ 117,
659
+ 119,
660
+ 123,
661
+ 135,
662
+ 137,
663
+ 139,
664
+ 143,
665
+ 161,
666
+ 179,
667
+ 389,
668
+ 427,
669
+ 465,
670
+ 512,
671
+ 513,
672
+ 514,
673
+ 515,
674
+ 526,
675
+ 530,
676
+ 531,
677
+ 532,
678
+ 540,
679
+ 548,
680
+ 554,
681
+ 556,
682
+ 563,
683
+ 587,
684
+ 601,
685
+ 636,
686
+ 989,
687
+ 990,
688
+ 993,
689
+ 995,
690
+ 1719,
691
+ 1720,
692
+ 1723,
693
+ 2049,
694
+ 3659,
695
+ 4045,
696
+ 4190,
697
+ 5060,
698
+ 5061,
699
+ 6e3,
700
+ 6566,
701
+ 6665,
702
+ 6666,
703
+ 6667,
704
+ 6668,
705
+ 6669,
706
+ 6679,
707
+ 6697,
708
+ 10080
709
+ ]);
710
+ /** Whether fetch (and therefore pi-ai) refuses to connect to `port`. */
711
+ function isFetchBlockedPort(port) {
712
+ return FETCH_BLOCKED_PORTS.has(port);
713
+ }
714
+ /** Bind attempts allowed before giving up, including the first. */
715
+ const DEFAULT_MAX_ATTEMPTS = 16;
716
+ /**
717
+ * Bind `server` to an ephemeral port fetch will actually connect to, and
718
+ * resolve with the address it ended up on.
719
+ *
720
+ * `listen(0)` lets the OS hand out any free ephemeral port, and a handful of
721
+ * the ports it can hand out are on the fetch standard's bad-port list: fetch
722
+ * refuses to connect to them and fails with `TypeError: fetch failed` before a
723
+ * socket is opened. The shim is only ever reached through fetch (pi-ai calls
724
+ * it), so settling on one of those ports leaves the plugin with an endpoint
725
+ * that answers raw HTTP and rejects every real request. The port is not
726
+ * negotiable at request time, so the only fix is to bind again.
727
+ *
728
+ * A blocked port is closed and rebound; a listen error is fatal, as it was
729
+ * before, because nothing about picking another port would fix it.
730
+ */
731
+ async function bindToUsablePort(server, options) {
732
+ const maxAttempts = Math.max(1, options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS);
733
+ const isPortBlocked = options.isPortBlocked ?? isFetchBlockedPort;
734
+ let lastBlockedPort = 0;
735
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
736
+ const address = await listenOnEphemeralPort(server, options.host);
737
+ if (!isPortBlocked(address.port)) return address;
738
+ lastBlockedPort = address.port;
739
+ options.onBlockedPort?.(address.port, attempt);
740
+ await closeQuietly(server);
741
+ }
742
+ throw new Error(`could not bind a fetch-reachable loopback port after ${maxAttempts} attempts (last blocked port: ${lastBlockedPort})`);
743
+ }
744
+ /** One `listen(0)` on `host`, resolving with the address the OS handed out. */
745
+ function listenOnEphemeralPort(server, host) {
746
+ return new Promise((resolve, reject) => {
747
+ const cleanup = () => {
748
+ server.off("listening", onListening);
749
+ server.off("error", onError);
750
+ };
751
+ const onListening = () => {
752
+ cleanup();
753
+ const address = server.address();
754
+ if (address === null || typeof address === "string") {
755
+ reject(/* @__PURE__ */ new Error("workbuddy shim has no listening address"));
756
+ return;
757
+ }
758
+ resolve(address);
759
+ };
760
+ const onError = (error) => {
761
+ cleanup();
762
+ reject(error);
763
+ };
764
+ server.once("listening", onListening);
765
+ server.once("error", onError);
766
+ server.listen(0, host);
767
+ });
768
+ }
769
+ /** Close between attempts; a listener that already closed is not an error here. */
770
+ function closeQuietly(server) {
771
+ return new Promise((resolve) => {
772
+ server.closeAllConnections();
773
+ server.close(() => resolve());
774
+ });
775
+ }
626
776
  //#endregion
627
777
  //#region src/shim.ts
628
778
  /**
@@ -713,11 +863,12 @@ function createWorkBuddyShim(options) {
713
863
  const server = createServer((req, res) => {
714
864
  handle(req, res);
715
865
  });
716
- const ready = new Promise((resolve, reject) => {
717
- server.once("listening", () => resolve());
718
- server.once("error", reject);
719
- });
720
- server.listen(0, "127.0.0.1");
866
+ const ready = bindToUsablePort(server, {
867
+ host: "127.0.0.1",
868
+ onBlockedPort: (port, attempt) => {
869
+ logger?.warn(`dsh-workbuddy-connect: port ${port} is blocked for fetch; rebinding (attempt ${attempt})`);
870
+ }
871
+ }).then(() => void 0);
721
872
  const baseUrl = () => {
722
873
  const address = server.address();
723
874
  if (address === null || typeof address === "string") throw new Error("workbuddy shim has no listening address");
@@ -1862,6 +2013,38 @@ function legacySettingsOf(settings) {
1862
2013
  if (typeof candidate.update !== "function") return void 0;
1863
2014
  return settings;
1864
2015
  }
2016
+ /**
2017
+ * Narrow one host settings service to the modern DSH 0.2.x SettingsForms API, or
2018
+ * `undefined` when this host's generation does not carry it.
2019
+ */
2020
+ function modernSettingsOf(settings) {
2021
+ if (typeof settings !== "object" || settings === null) return void 0;
2022
+ const candidate = settings;
2023
+ if (typeof candidate.installSection === "function") return void 0;
2024
+ if (typeof candidate.describe !== "function") return void 0;
2025
+ if (typeof candidate.update !== "function") return void 0;
2026
+ return settings;
2027
+ }
2028
+ /**
2029
+ * Resolve this plugin's own profile entry id from SettingsForms descriptors.
2030
+ *
2031
+ * The real `describe()` (dsh-settings 0.2.1-rc.2, `SettingsForms.describe`)
2032
+ * hands out *serialized* form schemas (`volatileForm(schema).toJSON()`) and
2033
+ * no fiber reference, so neither a `desc.schema === Config` identity check
2034
+ * nor a fiber comparison can ever succeed — identity has to come from the
2035
+ * entry id the loader attached to this plugin's own fiber
2036
+ * (`ctx.fiber.entry.options.id`; the tree-qualified `entry.id` includes
2037
+ * parent prefixes that SettingsForms does not use). The package-name literal is the only fallback, for hosts where the
2038
+ * plugin was loaded programmatically and carries no entry: entry ids are
2039
+ * unique per composition, so the literal can only name this plugin. Neither
2040
+ * route matching an entry that is served answers `undefined` — never a
2041
+ * guess, because a wrong guess silently writes another plugin's
2042
+ * configuration.
2043
+ */
2044
+ function resolveModernSettingsEntryId(descriptors, ownEntryId) {
2045
+ if (ownEntryId !== void 0) return descriptors.find((row) => row.ns === ownEntryId)?.ns;
2046
+ return descriptors.find((row) => row.ns === "dsh-workbuddy-connect")?.ns;
2047
+ }
1865
2048
  //#endregion
1866
2049
  //#region src/index.ts
1867
2050
  /** Stable Cordis plugin name. */
@@ -1940,7 +2123,7 @@ const AUTH_FILE_FIELD = z.string().description("WorkBuddy desktop auth file (def
1940
2123
  const AUTH_FILE_AI_FIELD = z.string().description("WorkBuddy AI desktop auth file (defaults to the app's own location)");
1941
2124
  /** Probe authorization (shared by the plugin schema and the CN section). */
1942
2125
  const PROBE_CONSENT_FIELD = z.boolean().default(false).description("Authorize reasoning-effort probes (each probe sends real requests that may consume credit)");
1943
- const MAXIMUM_CONTEXT_WINDOW_FIELD = z.boolean().default(true).description("Use the largest context window declared by WorkBuddy AI when alternatives are available (on by default)");
2126
+ const MAXIMUM_CONTEXT_WINDOW_FIELD = z.boolean().default(true).volatile().description("Use the largest context window declared by WorkBuddy AI when alternatives are available (on by default)");
1944
2127
  const Config = z.object({
1945
2128
  authFile: AUTH_FILE_FIELD,
1946
2129
  authFileAI: AUTH_FILE_AI_FIELD,
@@ -1948,6 +2131,17 @@ const Config = z.object({
1948
2131
  useMaximumContextWindow: MAXIMUM_CONTEXT_WINDOW_FIELD
1949
2132
  });
1950
2133
  /**
2134
+ * Read the context-window preference off a resolved Config. Fields marked
2135
+ * `.volatile()` materialize as cosmokit volatile containers (`{ get() }`) so
2136
+ * the host can live-update them without remounting the plugin; plain hosts
2137
+ * and values set through this plugin are plain booleans. Both spellings
2138
+ * answer here.
2139
+ */
2140
+ function plainConfigBoolean(value) {
2141
+ if (value !== null && typeof value === "object" && typeof value.get === "function") return plainConfigBoolean(value.get());
2142
+ return value === void 0 ? void 0 : value === true;
2143
+ }
2144
+ /**
1951
2145
  * The CN side's settings section: only the fields that side edits.
1952
2146
  *
1953
2147
  * The section's namespace is what serves these fields to `settings.yaml` and
@@ -2009,7 +2203,7 @@ function createVariantRuntime(config, variant, current, identityOf, accountOf, k
2009
2203
  });
2010
2204
  const fallback = fallbackFor(variant);
2011
2205
  const catalog = new WorkBuddyCatalog(fallback);
2012
- if (variant.id !== CN_VARIANT.id) catalog.setUseMaximumContextWindow(config.useMaximumContextWindow === true);
2206
+ if (variant.id !== CN_VARIANT.id) catalog.setUseMaximumContextWindow(plainConfigBoolean(config.useMaximumContextWindow) === true);
2013
2207
  catalog.setVisible(false);
2014
2208
  const probeStore = new WorkBuddyProbeStore({
2015
2209
  pluginVersion: WORKBUDDY_CONNECT_VERSION,
@@ -2196,6 +2390,8 @@ function apply(ctx, config) {
2196
2390
  * checkbox that could not be saved.
2197
2391
  */
2198
2392
  let legacySettingsAvailable = false;
2393
+ /** Whether modern DSH 0.2.x SettingsForms service is available to persist settings. */
2394
+ let settingsAvailable = false;
2199
2395
  /**
2200
2396
  * Point a variant at an account identity, invalidating whatever the previous
2201
2397
  * one left behind.
@@ -2275,7 +2471,7 @@ function apply(ctx, config) {
2275
2471
  disabled: runtime.visibilityStore.disabled(account)
2276
2472
  };
2277
2473
  },
2278
- ...runtime.variant.id === CN_VARIANT.id ? {} : { useMaximumContextWindow: () => legacySettingsAvailable ? current().useMaximumContextWindow === true : void 0 }
2474
+ ...runtime.variant.id === CN_VARIANT.id ? {} : { useMaximumContextWindow: () => legacySettingsAvailable || settingsAvailable ? plainConfigBoolean(current().useMaximumContextWindow) === true : void 0 }
2279
2475
  });
2280
2476
  registerWorkBuddyProbeRoute(webCtx, {
2281
2477
  path: runtime.variant.probePath,
@@ -2350,50 +2546,191 @@ function apply(ctx, config) {
2350
2546
  });
2351
2547
  ctx.inject(["settings"], (settingsCtx) => {
2352
2548
  const legacy = legacySettingsOf(settingsCtx.settings);
2353
- if (legacy === void 0) {
2354
- ctx.logger.warn("dsh-workbuddy-connect: host settings service has no installSection API; per-variant settings and the maximum-context preference are unavailable");
2549
+ if (legacy !== void 0) {
2550
+ legacySettingsAvailable = true;
2551
+ /** Section sources; each falls back to its own slice when its side unloads. */
2552
+ const sources = {
2553
+ cn: () => config,
2554
+ ai: () => config
2555
+ };
2556
+ /** Merge both sections into the whole config the rest of the plugin reads. */
2557
+ const merged = () => {
2558
+ const aiMaximum = plainConfigBoolean(sources.ai().useMaximumContextWindow);
2559
+ return {
2560
+ ...sources.cn().authFile === void 0 ? {} : { authFile: sources.cn().authFile },
2561
+ ...sources.cn().probeConsent === void 0 ? {} : { probeConsent: sources.cn().probeConsent },
2562
+ ...sources.ai().authFileAI === void 0 ? {} : { authFileAI: sources.ai().authFileAI },
2563
+ ...aiMaximum === void 0 ? {} : { useMaximumContextWindow: aiMaximum }
2564
+ };
2565
+ };
2566
+ const applyMaximumContextWindow = (next) => {
2567
+ const runtime = runtimes.find((candidate) => candidate.variant.id !== CN_VARIANT.id);
2568
+ if (runtime?.catalog.setUseMaximumContextWindow(next.useMaximumContextWindow === true)) runtime.invalidate();
2569
+ };
2570
+ const repointStores = () => {
2571
+ const next = merged();
2572
+ applyMaximumContextWindow(next);
2573
+ for (const runtime of runtimes) runtime.store.setDesktopPath(configuredAuthFile(next, runtime.variant));
2574
+ };
2575
+ legacy.installSection(ctx, WORKBUDDY_SETTINGS_NS, CN_SECTION, config, {
2576
+ setSource(source) {
2577
+ sources.cn = source;
2578
+ current = merged;
2579
+ },
2580
+ onChange: repointStores
2581
+ });
2582
+ legacy.installSection(ctx, WORKBUDDY_AI_SETTINGS_NS, AI_SECTION, config, {
2583
+ setSource(source) {
2584
+ sources.ai = source;
2585
+ current = merged;
2586
+ },
2587
+ onChange: repointStores
2588
+ });
2589
+ setMaximumContextWindow = async (enabled) => {
2590
+ await legacy.update(WORKBUDDY_AI_SETTINGS_NS, { useMaximumContextWindow: enabled });
2591
+ return { state: "updated" };
2592
+ };
2355
2593
  return;
2356
2594
  }
2357
- legacySettingsAvailable = true;
2358
- /** Section sources; each falls back to its own slice when its side unloads. */
2359
- const sources = {
2360
- cn: () => config,
2361
- ai: () => config
2362
- };
2363
- /** Merge both sections into the whole config the rest of the plugin reads. */
2364
- const merged = () => ({
2365
- ...sources.cn().authFile === void 0 ? {} : { authFile: sources.cn().authFile },
2366
- ...sources.cn().probeConsent === void 0 ? {} : { probeConsent: sources.cn().probeConsent },
2367
- ...sources.ai().authFileAI === void 0 ? {} : { authFileAI: sources.ai().authFileAI },
2368
- ...sources.ai().useMaximumContextWindow === void 0 ? {} : { useMaximumContextWindow: sources.ai().useMaximumContextWindow }
2369
- });
2370
- const applyMaximumContextWindow = (next) => {
2371
- const runtime = runtimes.find((candidate) => candidate.variant.id !== CN_VARIANT.id);
2372
- if (runtime?.catalog.setUseMaximumContextWindow(next.useMaximumContextWindow === true)) runtime.invalidate();
2373
- };
2374
- const repointStores = () => {
2375
- const next = merged();
2376
- applyMaximumContextWindow(next);
2377
- for (const runtime of runtimes) runtime.store.setDesktopPath(configuredAuthFile(next, runtime.variant));
2378
- };
2379
- legacy.installSection(ctx, WORKBUDDY_SETTINGS_NS, CN_SECTION, config, {
2380
- setSource(source) {
2381
- sources.cn = source;
2382
- current = merged;
2383
- },
2384
- onChange: repointStores
2385
- });
2386
- legacy.installSection(ctx, WORKBUDDY_AI_SETTINGS_NS, AI_SECTION, config, {
2387
- setSource(source) {
2388
- sources.ai = source;
2389
- current = merged;
2390
- },
2391
- onChange: repointStores
2392
- });
2393
- setMaximumContextWindow = async (enabled) => {
2394
- await legacy.update(WORKBUDDY_AI_SETTINGS_NS, { useMaximumContextWindow: enabled });
2395
- return { state: "updated" };
2396
- };
2595
+ const modern = modernSettingsOf(settingsCtx.settings);
2596
+ if (modern !== void 0) {
2597
+ /**
2598
+ * The live preference as the host's own Config reports it.
2599
+ *
2600
+ * The forms layer is the single source of truth on a 0.2.x host: a write
2601
+ * lands in the profile document and comes back through `describe()`, so
2602
+ * nothing has to be mirrored in memory here (and a mirror is exactly how
2603
+ * runtime and saved state drift apart). The persisted default already
2604
+ * applies — the preference field defaults to `true` and the composition
2605
+ * handed this plugin its resolved `config`.
2606
+ */
2607
+ let liveConfig = {
2608
+ ...config,
2609
+ useMaximumContextWindow: plainConfigBoolean(config.useMaximumContextWindow) ?? true
2610
+ };
2611
+ /** Whether {@link liveConfig} came from a settings descriptor (see below). */
2612
+ let liveConfigBound = false;
2613
+ /**
2614
+ * Read this plugin's own entry out of the host's forms.
2615
+ *
2616
+ * The namespace on a 0.2.x host is the *profile entry id* — not the
2617
+ * `SettingsNamespace` the 0.1.x sections were installed under — so it is
2618
+ * whatever the user named this plugin in the profile composition.
2619
+ *
2620
+ * Identity cannot come from the descriptors themselves: `describe()`
2621
+ * hands out *serialized* forms (`volatileForm(schema).toJSON()`), never
2622
+ * the live Config object, and its rows carry no fiber — so
2623
+ * `desc.schema === Config` and fiber comparisons are always false
2624
+ * against the real service. The loader instead keys entries by id and
2625
+ * attaches them to the fibers it runs (`ctx.fiber.entry.options.id`, unlike the tree-qualified `entry.id`), which is
2626
+ * what {@link resolveModernSettingsEntryId} reads; the package-name
2627
+ * literal stays as the only fallback (entry ids are unique per
2628
+ * composition, so it can only name this plugin). Anything unmatched
2629
+ * answers `undefined` — a wrong guess would silently write another
2630
+ * plugin's configuration.
2631
+ */
2632
+ const settingsEntryId = () => {
2633
+ try {
2634
+ const entry = ctx.fiber?.entry;
2635
+ return resolveModernSettingsEntryId(modern.describe(), entry?.options?.id ?? entry?.id);
2636
+ } catch (error) {
2637
+ ctx.logger.warn("dsh-workbuddy-connect: reading this plugin's settings entry failed: %s", error);
2638
+ return;
2639
+ }
2640
+ };
2641
+ /**
2642
+ * Apply whatever the settings document now says.
2643
+ *
2644
+ * The value read is the descriptor's `value` — the projection of the live
2645
+ * fiber config, i.e. what the loader actually resolved — rather than
2646
+ * `user`, which names only the profile's own section and would miss an
2647
+ * inherited or default value that is nonetheless in effect.
2648
+ */
2649
+ const applyModernConfig = () => {
2650
+ if (!liveConfigBound) return;
2651
+ const runtime = runtimes.find((candidate) => candidate.variant.id !== CN_VARIANT.id);
2652
+ if (runtime?.catalog.setUseMaximumContextWindow(plainConfigBoolean(liveConfig.useMaximumContextWindow) === true)) runtime.invalidate();
2653
+ };
2654
+ setMaximumContextWindow = async (enabled) => {
2655
+ const ns = settingsEntryId();
2656
+ if (ns === void 0) return {
2657
+ state: "failed",
2658
+ reason: "no configurable settings entry for this plugin"
2659
+ };
2660
+ try {
2661
+ await modern.update(ns, { useMaximumContextWindow: enabled });
2662
+ if ((modern.describe().find((entry) => entry.ns === ns)?.value)?.useMaximumContextWindow !== enabled) return {
2663
+ state: "failed",
2664
+ reason: "the settings document did not accept the context-window preference"
2665
+ };
2666
+ } catch (error) {
2667
+ return {
2668
+ state: "failed",
2669
+ reason: error instanceof Error ? error.message.slice(0, 300) : String(error)
2670
+ };
2671
+ }
2672
+ liveConfig = {
2673
+ ...liveConfig,
2674
+ useMaximumContextWindow: enabled
2675
+ };
2676
+ current = () => liveConfig;
2677
+ applyModernConfig();
2678
+ return { state: "updated" };
2679
+ };
2680
+ let retry;
2681
+ let retryIndex = 0;
2682
+ let bindingStopped = false;
2683
+ const retryDelays = [
2684
+ 250,
2685
+ 500,
2686
+ 1e3,
2687
+ 2e3,
2688
+ 4e3,
2689
+ 8e3
2690
+ ];
2691
+ const clearRetry = () => {
2692
+ clearTimeout(retry);
2693
+ retry = void 0;
2694
+ };
2695
+ const bindModernConfig = () => {
2696
+ if (bindingStopped) return;
2697
+ try {
2698
+ const ns = settingsEntryId();
2699
+ const entry = ns === void 0 ? void 0 : modern.describe().find((desc) => desc.ns === ns);
2700
+ settingsAvailable = (entry?.schema === void 0 ? void 0 : new z(entry.schema))?.dict?.useMaximumContextWindow?.type === "boolean";
2701
+ if (settingsAvailable) {
2702
+ const stored = (entry?.value)?.useMaximumContextWindow;
2703
+ liveConfig = {
2704
+ ...liveConfig,
2705
+ useMaximumContextWindow: plainConfigBoolean(stored) ?? plainConfigBoolean(liveConfig.useMaximumContextWindow) ?? true
2706
+ };
2707
+ liveConfigBound = true;
2708
+ current = () => liveConfig;
2709
+ applyModernConfig();
2710
+ clearRetry();
2711
+ return;
2712
+ }
2713
+ } catch (error) {
2714
+ settingsAvailable = false;
2715
+ ctx.logger.warn("dsh-workbuddy-connect: reading the stored context-window preference failed: %s", error);
2716
+ }
2717
+ if (retry === void 0 && retryIndex < retryDelays.length) retry = setTimeout(() => {
2718
+ retry = void 0;
2719
+ bindModernConfig();
2720
+ }, retryDelays[retryIndex++]);
2721
+ };
2722
+ settingsCtx.effect(() => () => {
2723
+ bindingStopped = true;
2724
+ clearRetry();
2725
+ settingsAvailable = false;
2726
+ });
2727
+ settingsCtx.effect(() => ctx.on("settings/document-updated", () => {
2728
+ bindModernConfig();
2729
+ }));
2730
+ bindModernConfig();
2731
+ return;
2732
+ }
2733
+ ctx.logger.warn("dsh-workbuddy-connect: host settings service has no installSection or SettingsForms API; per-variant settings and the maximum-context preference are unavailable");
2397
2734
  });
2398
2735
  ctx.effect(() => () => {
2399
2736
  stopped = true;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-workbuddy-connect",
3
3
  "displayName": "DSH WorkBuddy Connect",
4
4
  "description": "将 WorkBuddy 桌面 App 包含的模型自动接入 DeepSeek Harness — bring WorkBuddy desktop app models into DeepSeek Harness with zero configuration.",
5
- "version": "0.7.8",
5
+ "version": "0.7.9",
6
6
  "author": "corrinehu",
7
7
  "keywords": [
8
8
  "dsh-plugin",