dsh-workbuddy-connect 0.7.7 → 0.7.8
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 +12 -9
- package/README.md +12 -9
- package/THIRD_PARTY_NOTICES.txt +24 -0
- package/lib/bin.js +1 -1
- package/lib/client.js +93 -31
- package/lib/{host-heartbeat-FytEpUGO.js → host-heartbeat-sjotAEdb.js} +351 -19
- package/lib/index.d.ts +7 -2
- package/lib/index.js +4 -2
- package/package.json +27 -27
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.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.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.
|
|
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,8 @@ 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.
|
|
26
|
+
| 0.2.1-rc.2 | 0.7.8 (npm latest) |
|
|
27
|
+
| 0.2.1-rc.1 | 0.7.7 |
|
|
27
28
|
| 0.2.0-rc.2 | 0.7.6 |
|
|
28
29
|
| 0.2.0-rc.1 | 0.7.0 |
|
|
29
30
|
| 0.1.5 – 0.1.7 | 0.6.5 |
|
|
@@ -32,8 +33,8 @@ Core-to-plugin quick reference (full table in "Compatibility and release history
|
|
|
32
33
|
<summary>Manual install (command line, Web / TUI only)</summary>
|
|
33
34
|
|
|
34
35
|
```sh
|
|
35
|
-
# Web (pin per your actual core — example: 0.2.
|
|
36
|
-
dsh plugin --profile web add dsh-workbuddy-connect@0.7.
|
|
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
|
|
37
38
|
dsh web
|
|
38
39
|
|
|
39
40
|
# TUI (example for a 0.1.x core; that profile needs pnpm 11, e.g. npx pnpm@11)
|
|
@@ -59,11 +60,11 @@ dsh --profile dsh-tui
|
|
|
59
60
|
|
|
60
61
|

|
|
61
62
|
|
|
62
|
-
- **Image input**: most models accept images pasted or dropped straight into the conversation (GLM-5.3-Flash,
|
|
63
|
+
- **Image input**: most models accept images pasted or dropped straight into the conversation (GLM-5.3-Flash, the DeepSeek-V4 series, and more); the few text-only models (e.g. GLM-5.1 / GLM-5.2 on the China endpoint) clearly say so.
|
|
63
64
|
|
|
64
65
|
- **Reasoning levels**: levels explicitly declared by WorkBuddy appear directly — for example, GLM-5.3 and GLM-5.3-Flash offer low / high / max. For models without declared levels, Web and Desktop offer a manual check in the model picker (it sends a few requests and may consume credit); models without a check result or selectable levels keep using WorkBuddy's default.
|
|
65
66
|
|
|
66
|
-
- **Balance at a glance**: with a WorkBuddy / WorkBuddy AI model selected, the composer shows the account's remaining credit next to the model selector (e.g. `WB.3,401 credits`, `WB AI.3,401 credits` on the international app). Credit is not polled automatically (every status read is a real billing query): one read on mount, then refresh on demand with the ↻
|
|
67
|
+
- **Balance at a glance**: with a WorkBuddy / WorkBuddy AI model selected, the composer shows the account's remaining credit next to the model selector (e.g. `WB.3,401 credits`, `WB AI.3,401 credits` on the international app). Credit is not polled automatically (every status read is a real billing query): one read on mount, then refresh on demand with the ↻ before the balance. During session switching, the controls may briefly hide until the model selection is ready, then return automatically. The refresh button remains available even when the model has no reasoning-level detection entry or the balance cannot be read.
|
|
67
68
|
|
|
68
69
|
- **Status and detection**: Settings → Plugins → the matching card shows the account, token validity, remaining credit, and model offers; it also refreshes the model list manually, shows whether the current list came from the upstream or the built-in fallback, and runs manual reasoning-level detection for eligible models. On DSH 0.1.5 the entry lives under Settings → Plugins; on 0.1.6+ it moved to the left-sidebar Plugins panel (workbuddy-connect → View) — and the Settings → Models page shows no WorkBuddy entries on either generation, by design.
|
|
69
70
|
|
|
@@ -127,7 +128,8 @@ The plugin version must match the DSH core — a mismatched combination fails to
|
|
|
127
128
|
|
|
128
129
|
| DSH core | Recommended plugin | Notes |
|
|
129
130
|
|---|---|---|
|
|
130
|
-
| `0.2.1-rc.
|
|
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. |
|
|
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). |
|
|
131
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. |
|
|
132
134
|
| `0.2.0-rc.1` | `0.7.0` | 0.7.1 drops rc.1 support; stay on this version. |
|
|
133
135
|
| `0.1.5` – `0.1.7` | `0.6.5` | Final release for the old cores; 0.7.0 targets 0.2.0 only. |
|
|
@@ -136,6 +138,7 @@ The plugin version must match the DSH core — a mismatched combination fails to
|
|
|
136
138
|
|
|
137
139
|
Release history (full records at [releases](https://github.com/corrinehu/dsh-workbuddy-connect/releases)):
|
|
138
140
|
|
|
141
|
+
- **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).
|
|
139
142
|
- **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).
|
|
140
143
|
- **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)).
|
|
141
144
|
- **0.7.5**: restores composer controls after session switching ([#125](https://github.com/corrinehu/dsh-workbuddy-connect/issues/125) / [#132](https://github.com/corrinehu/dsh-workbuddy-connect/issues/132)), fixes WSL helper environment forwarding ([#133](https://github.com/corrinehu/dsh-workbuddy-connect/issues/133)), and adds community projects to both READMEs.
|
|
@@ -146,9 +149,9 @@ Release history (full records at [releases](https://github.com/corrinehu/dsh-wor
|
|
|
146
149
|
- **0.7.0**: adapts to DSH 0.2.0's settings-service rework and drops 0.1.x support.
|
|
147
150
|
- **0.6.5**: final release of the 0.1.x line.
|
|
148
151
|
|
|
149
|
-
Pinned-version install example: `dsh plugin --profile web add dsh-workbuddy-connect@0.7.
|
|
152
|
+
Pinned-version install example: `dsh plugin --profile web add dsh-workbuddy-connect@0.7.8`.
|
|
150
153
|
|
|
151
|
-
**pi-ai versions are per core line and must not be mixed**: the 0.2.1-rc.1 core
|
|
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)).
|
|
152
155
|
|
|
153
156
|
## Disclaimer
|
|
154
157
|
|
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.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.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 管理,各发行版规则未必相同,先确认我所用的发行版,再探查其合规的安装方式,但不得绕过其校验;
|
|
17
17
|
任何本地文件或配置改动,先征得我同意并做好备份;
|
|
18
18
|
装完后验证模型选择器里出现 WorkBuddy 分组,并陪我完成一次简短对话;
|
|
19
19
|
若 Desktop 安装仍因 generation 校验失败(如 @types/retry 误报),请如实报告、参照上游 dataelement/dsh-desktop#688,并建议我改用独立的 Web / TUI,不要修改依赖包或绕过校验。
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
|
|
24
24
|
| DSH 内核 | 推荐插件版 |
|
|
25
25
|
|---|---|
|
|
26
|
-
| 0.2.1-rc.
|
|
26
|
+
| 0.2.1-rc.2 | 0.7.8(npm latest) |
|
|
27
|
+
| 0.2.1-rc.1 | 0.7.7 |
|
|
27
28
|
| 0.2.0-rc.2 | 0.7.6 |
|
|
28
29
|
| 0.2.0-rc.1 | 0.7.0 |
|
|
29
30
|
| 0.1.5 – 0.1.7 | 0.6.5 |
|
|
@@ -32,8 +33,8 @@
|
|
|
32
33
|
<summary>手动安装(命令行,仅 Web / TUI)</summary>
|
|
33
34
|
|
|
34
35
|
```sh
|
|
35
|
-
# Web(按实际内核固定版本,示例为 0.2.
|
|
36
|
-
dsh plugin --profile web add dsh-workbuddy-connect@0.7.
|
|
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
|
|
37
38
|
dsh web
|
|
38
39
|
|
|
39
40
|
# TUI(示例为 0.1.x 内核;该 profile 需用 pnpm 11 安装,如 npx pnpm@11)
|
|
@@ -59,11 +60,11 @@ dsh --profile dsh-tui
|
|
|
59
60
|
|
|
60
61
|

|
|
61
62
|
|
|
62
|
-
- **图片输入**:大部分模型支持在对话里直接粘贴或拖入图片(GLM-5.3-Flash、
|
|
63
|
+
- **图片输入**:大部分模型支持在对话里直接粘贴或拖入图片(GLM-5.3-Flash、DeepSeek-V4 系列等);少数纯文字模型(如 GLM-5.1、GLM-5.2)会明确提示不支持。
|
|
63
64
|
|
|
64
65
|
- **推理档位**:WorkBuddy 明确声明的档位会直接显示,例如 GLM-5.3 和 GLM-5.3-Flash 可选 low / high / max。未声明档位的模型可在 Web 和 Desktop 的模型选择器中手动检测(发送少量请求,可能消耗积分);未检测或没有可用档位的模型使用 WorkBuddy 默认档位。
|
|
65
66
|
|
|
66
|
-
- **余额速览**:选中 WorkBuddy / WorkBuddy AI 模型时,输入框底部在模型选择器旁显示当前账号的剩余积分(如 `WB.3,401(积分)`、国际版 `WB AI.3,401
|
|
67
|
+
- **余额速览**:选中 WorkBuddy / WorkBuddy AI 模型时,输入框底部在模型选择器旁显示当前账号的剩余积分(如 `WB.3,401(积分)`、国际版 `WB AI.3,401(积分)`)。积分不自动轮询(每次读取都是真实计费查询),挂载时读取一次后可点积分文字前方的 ↻ 手动刷新。切换会话时组件可能短暂隐藏,模型选择就绪后自动恢复;即使模型没有推理档位检测入口或余额读取失败,刷新按钮仍保留。
|
|
67
68
|
|
|
68
69
|
- **信息查看与检测**:设置 → 插件 → 对应卡片可查看账号、令牌有效期、剩余积分和模型优惠,也可手动刷新模型列表并查看当前列表来自上游还是内置兜底,或对可检测模型手动检测推理档位。DSH 0.1.5 的入口在「设置 → 插件」,0.1.6+ 移到主界面左侧栏「插件」面板(workbuddy-connect → 查看);「设置 → 模型」页两代都不显示 WorkBuddy 条目,属有意设计。
|
|
69
70
|
|
|
@@ -128,7 +129,8 @@ dsh plugin --profile web exec dsh-workbuddy-connect doctor --provider workbuddy-
|
|
|
128
129
|
|
|
129
130
|
| DSH 内核 | 推荐插件版 | 注意事项 |
|
|
130
131
|
|---|---|---|
|
|
131
|
-
| `0.2.1-rc.
|
|
132
|
+
| `0.2.1-rc.2` | `0.7.8` | 仅适配这一内核;`0.2.0-rc.2` 与 `0.2.1-rc.2` 不同,请核对 App 内置版本。 |
|
|
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 说明)。 |
|
|
132
134
|
| `0.2.0-rc.2` | `0.7.6` | 上一代内核线;修复企业账号模型目录缺项与国际版对话被渠道校验拦截,勿停留在有注册表回归的 0.7.2。 |
|
|
133
135
|
| `0.2.0-rc.1` | `0.7.0` | 0.7.1 起不再支持 rc.1,请停留本版。 |
|
|
134
136
|
| `0.1.5` – `0.1.7` | `0.6.5` | 旧内核最终版;0.7.0 起仅面向 0.2.0。 |
|
|
@@ -137,6 +139,7 @@ dsh plugin --profile web exec dsh-workbuddy-connect doctor --provider workbuddy-
|
|
|
137
139
|
|
|
138
140
|
历次更新(完整记录见 [releases](https://github.com/corrinehu/dsh-workbuddy-connect/releases)):
|
|
139
141
|
|
|
142
|
+
- **0.7.8**:适配 DSH 0.2.1-rc.2;将官方原子写入工具打包到插件,避免依赖宿主暴露该模块;更新国内模型目录快照,区分 Hy3 / Hy3-X;优化推理检测结果展示,调整积分刷新按钮位置并修正悬停提示。感谢 @chenpiyanghappy 的 PR [#156](https://github.com/corrinehu/dsh-workbuddy-connect/pull/156)。
|
|
140
143
|
- **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 实机验证)。
|
|
141
144
|
- **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))。
|
|
142
145
|
- **0.7.5**:修复切换会话后输入框余额与刷新按钮消失([#125](https://github.com/corrinehu/dsh-workbuddy-connect/issues/125) / [#132](https://github.com/corrinehu/dsh-workbuddy-connect/issues/132)),修复 WSL 调用 Windows 凭据解密程序的环境转发([#133](https://github.com/corrinehu/dsh-workbuddy-connect/issues/133)),README 收录社区项目([#130](https://github.com/corrinehu/dsh-workbuddy-connect/issues/130))。
|
|
@@ -147,9 +150,9 @@ dsh plugin --profile web exec dsh-workbuddy-connect doctor --provider workbuddy-
|
|
|
147
150
|
- **0.7.0**:适配 DSH 0.2.0 的设置服务改造,不再支持 0.1.x。
|
|
148
151
|
- **0.6.5**:0.1.x 线最终版本。
|
|
149
152
|
|
|
150
|
-
固定版本安装示例:`dsh plugin --profile web add dsh-workbuddy-connect@0.7.
|
|
153
|
+
固定版本安装示例:`dsh plugin --profile web add dsh-workbuddy-connect@0.7.8`。
|
|
151
154
|
|
|
152
|
-
**pi-ai 版本按内核线区分,不能混**:0.2.1-rc.1
|
|
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))。
|
|
153
156
|
|
|
154
157
|
## 免责声明
|
|
155
158
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
@deepseek-ai/dsh-atomic-write 0.2.1-rc.2
|
|
2
|
+
Bundled from https://github.com/deepseek-ai/deepseek-harness/tree/main/packages/util/atomic-write
|
|
3
|
+
|
|
4
|
+
MIT License
|
|
5
|
+
|
|
6
|
+
Copyright (c) 2026 DeepSeek
|
|
7
|
+
|
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
in the Software without restriction, including without limitation the rights
|
|
11
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
furnished to do so, subject to the following conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be included in all
|
|
16
|
+
copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
SOFTWARE.
|
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-
|
|
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";
|
|
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
|
@@ -456,6 +456,37 @@ window.__ModuleLoader__.load({
|
|
|
456
456
|
color: "var(--dsw-alias-state-success-primary, #22a06b)"
|
|
457
457
|
};
|
|
458
458
|
/**
|
|
459
|
+
* Chip for a probe that came back `non-validating`.
|
|
460
|
+
*
|
|
461
|
+
* Amber rather than the success green: the upstream accepted a sentinel effort,
|
|
462
|
+
* which tells us it does not check the parameter — not that any level works.
|
|
463
|
+
* The state is an observation, and it used to share the green chip with the
|
|
464
|
+
* verified levels, which read as "these are the supported levels".
|
|
465
|
+
*/
|
|
466
|
+
const probeChipNotValidatingStyle = {
|
|
467
|
+
padding: "1px 8px",
|
|
468
|
+
borderRadius: 999,
|
|
469
|
+
fontSize: 11,
|
|
470
|
+
lineHeight: "18px",
|
|
471
|
+
background: "var(--dsw-alias-state-warning-subtle, rgba(214, 142, 23, 0.14))",
|
|
472
|
+
color: "var(--dsw-alias-state-warning-primary, #b26a00)"
|
|
473
|
+
};
|
|
474
|
+
/** Chip for a probe that never produced an answer (cancelled or failed). */
|
|
475
|
+
const probeChipUnknownStyle = {
|
|
476
|
+
padding: "1px 8px",
|
|
477
|
+
borderRadius: 999,
|
|
478
|
+
fontSize: 11,
|
|
479
|
+
lineHeight: "18px",
|
|
480
|
+
background: "var(--dsw-alias-fill-tertiary, rgba(127, 127, 127, 0.12))",
|
|
481
|
+
color: "var(--dsw-alias-label-tertiary, #8a8a8a)"
|
|
482
|
+
};
|
|
483
|
+
/** Pick the chip style that matches what the observation actually says. */
|
|
484
|
+
function probeChipStyleFor(result) {
|
|
485
|
+
if (result.validation === "validating" && result.efforts.length > 0) return modelBadgeChipStyle;
|
|
486
|
+
if (result.validation === "non-validating") return probeChipNotValidatingStyle;
|
|
487
|
+
return probeChipUnknownStyle;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
459
490
|
* Localize an upstream promotional badge label, with an unknown-badge fallback.
|
|
460
491
|
*
|
|
461
492
|
* The CN catalog spells badges in Chinese (`限时免费`, `夜间折扣`); the
|
|
@@ -467,9 +498,32 @@ window.__ModuleLoader__.load({
|
|
|
467
498
|
function modelBadgeLabel(badge, t) {
|
|
468
499
|
if (badge === "限时免费") return t("badgeLimitedFree");
|
|
469
500
|
if (badge === "夜间折扣") return t("badgeNightDiscount");
|
|
501
|
+
if (badge === "夜间免费") return t("badgeNightFree");
|
|
502
|
+
if (badge === "独家限免") return t("badgeExclusiveFree");
|
|
470
503
|
if (badge === "Free now") return t("badgeFreeNow");
|
|
471
504
|
return badge;
|
|
472
505
|
}
|
|
506
|
+
/**
|
|
507
|
+
* Upstream badge labels that already say "you pay nothing", in either language.
|
|
508
|
+
*
|
|
509
|
+
* The catalog reports both `free: true` and a promotional badge for the same
|
|
510
|
+
* row (`hy3` carries `限时免费`, `step-5-preview` carries `独家限免`). Rendering
|
|
511
|
+
* the generic free chip on top of those printed the same fact twice and, worse,
|
|
512
|
+
* pushed the second chip past the row's horizontal budget — it collapsed into a
|
|
513
|
+
* sliver instead of wrapping. One chip per row is enough: a badge that already
|
|
514
|
+
* states the offer is preferred over the bare "Free".
|
|
515
|
+
*/
|
|
516
|
+
const FREE_STATEMENT_BADGES = /* @__PURE__ */ new Set([
|
|
517
|
+
"限时免费",
|
|
518
|
+
"夜间免费",
|
|
519
|
+
"独家限免",
|
|
520
|
+
"Free now"
|
|
521
|
+
]);
|
|
522
|
+
/** Whether a row still needs the generic free chip next to its badges. */
|
|
523
|
+
function needsFreeChip(free, badges) {
|
|
524
|
+
if (free !== true) return false;
|
|
525
|
+
return !(badges ?? []).some((badge) => FREE_STATEMENT_BADGES.has(badge));
|
|
526
|
+
}
|
|
473
527
|
const progressTrackStyle = {
|
|
474
528
|
height: 8,
|
|
475
529
|
overflow: "hidden",
|
|
@@ -718,7 +772,7 @@ window.__ModuleLoader__.load({
|
|
|
718
772
|
children: [model.badges?.map((badge) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
719
773
|
style: modelBadgeChipStyle,
|
|
720
774
|
children: modelBadgeLabel(badge, t)
|
|
721
|
-
}, badge)), model.free
|
|
775
|
+
}, badge)), needsFreeChip(model.free, model.badges) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
722
776
|
style: modelBadgeChipStyle,
|
|
723
777
|
children: t("freeModel")
|
|
724
778
|
}) : null]
|
|
@@ -822,7 +876,7 @@ window.__ModuleLoader__.load({
|
|
|
822
876
|
style: modelBadgeChipStyle,
|
|
823
877
|
children: modelBadgeLabel(badge, t)
|
|
824
878
|
}, badge)),
|
|
825
|
-
model.free
|
|
879
|
+
needsFreeChip(model.free, model.badges) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
826
880
|
style: modelBadgeChipStyle,
|
|
827
881
|
children: t("freeModel")
|
|
828
882
|
}) : null
|
|
@@ -923,7 +977,9 @@ window.__ModuleLoader__.load({
|
|
|
923
977
|
style: quotaGroupStyle,
|
|
924
978
|
children: probe.candidates.map((id) => {
|
|
925
979
|
const result = probe.results.find((entry) => entry.id === id);
|
|
926
|
-
const
|
|
980
|
+
const row = models?.find((model) => model.id === id);
|
|
981
|
+
const name = row?.name ?? result?.name ?? id;
|
|
982
|
+
const declared = (row?.declaredEfforts?.length ?? 0) > 0 ? row.declaredEfforts.join(" / ") : row?.defaultEffort;
|
|
927
983
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
928
984
|
style: modelOfferStyle,
|
|
929
985
|
children: [
|
|
@@ -932,8 +988,8 @@ window.__ModuleLoader__.load({
|
|
|
932
988
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: name }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
933
989
|
style: probeRowEndStyle,
|
|
934
990
|
children: [result === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
935
|
-
style:
|
|
936
|
-
children: result.validation === "validating" && result.efforts.length > 0 ? result.efforts.join(" / ") :
|
|
991
|
+
style: probeChipStyleFor(result),
|
|
992
|
+
children: result.validation === "validating" && result.efforts.length > 0 ? result.efforts.join(" / ") : result.validation === "non-validating" ? t("probeResultNotValidating") + (declared === void 0 ? "" : t("probeDeclaredSuffix", { levels: declared })) : t("probeResultUnknown")
|
|
937
993
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
938
994
|
type: "button",
|
|
939
995
|
style: buttonStyle$2,
|
|
@@ -1560,7 +1616,7 @@ window.__ModuleLoader__.load({
|
|
|
1560
1616
|
lineHeight: "16px",
|
|
1561
1617
|
color: "var(--dsw-alias-label-tertiary)",
|
|
1562
1618
|
whiteSpace: "nowrap",
|
|
1563
|
-
padding: "0 4px"
|
|
1619
|
+
padding: "0 4px 0 0"
|
|
1564
1620
|
};
|
|
1565
1621
|
/**
|
|
1566
1622
|
* The badge's ↻ (#99): the only re-read after the mount fetch. An icon-only
|
|
@@ -1890,17 +1946,7 @@ window.__ModuleLoader__.load({
|
|
|
1890
1946
|
const showTooltip = tooltipVisible && !confirming && note === void 0;
|
|
1891
1947
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1892
1948
|
style: wrapperStyle,
|
|
1893
|
-
onMouseEnter: () => {
|
|
1894
|
-
setTooltipVisible(true);
|
|
1895
|
-
},
|
|
1896
|
-
onMouseLeave: () => {
|
|
1897
|
-
setTooltipVisible(false);
|
|
1898
|
-
},
|
|
1899
1949
|
children: [
|
|
1900
|
-
credit === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1901
|
-
style: creditStyle,
|
|
1902
|
-
children: credit
|
|
1903
|
-
}),
|
|
1904
1950
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1905
1951
|
type: "button",
|
|
1906
1952
|
style: {
|
|
@@ -1929,6 +1975,10 @@ window.__ModuleLoader__.load({
|
|
|
1929
1975
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M20 12a8 8 0 1 1-2.34-5.66" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M20 4v4h-4" })]
|
|
1930
1976
|
})
|
|
1931
1977
|
}),
|
|
1978
|
+
credit === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1979
|
+
style: creditStyle,
|
|
1980
|
+
children: credit
|
|
1981
|
+
}),
|
|
1932
1982
|
visible && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1933
1983
|
type: "button",
|
|
1934
1984
|
"aria-label": text,
|
|
@@ -1939,6 +1989,12 @@ window.__ModuleLoader__.load({
|
|
|
1939
1989
|
onClick: () => {
|
|
1940
1990
|
setConfirming(true);
|
|
1941
1991
|
},
|
|
1992
|
+
onMouseEnter: () => {
|
|
1993
|
+
setTooltipVisible(true);
|
|
1994
|
+
},
|
|
1995
|
+
onMouseLeave: () => {
|
|
1996
|
+
setTooltipVisible(false);
|
|
1997
|
+
},
|
|
1942
1998
|
onFocus: () => {
|
|
1943
1999
|
setTooltipVisible(true);
|
|
1944
2000
|
},
|
|
@@ -2772,7 +2828,7 @@ window.__ModuleLoader__.load({
|
|
|
2772
2828
|
};
|
|
2773
2829
|
//#endregion
|
|
2774
2830
|
//#region src/version.ts
|
|
2775
|
-
const WORKBUDDY_CONNECT_VERSION = "0.7.
|
|
2831
|
+
const WORKBUDDY_CONNECT_VERSION = "0.7.8";
|
|
2776
2832
|
//#endregion
|
|
2777
2833
|
//#region src/client/WorkBuddyConfigPage.tsx
|
|
2778
2834
|
/**
|
|
@@ -2815,15 +2871,15 @@ window.__ModuleLoader__.load({
|
|
|
2815
2871
|
//#region src/client/locales.ts
|
|
2816
2872
|
/** Plugin-card copy registered under the settings.workbuddy locale namespace. */
|
|
2817
2873
|
const en = {
|
|
2818
|
-
title: "DSH WorkBuddy Connect",
|
|
2819
|
-
intro: "Use the models in the WorkBuddy desktop app directly in DSH — zero configuration, ready out of the box.",
|
|
2820
|
-
titleAI: "DSH WorkBuddy AI Connect",
|
|
2874
|
+
title: "DSH WorkBuddy Connect (China)",
|
|
2875
|
+
intro: "Use the models in the WorkBuddy desktop app (China edition) directly in DSH — zero configuration, ready out of the box.",
|
|
2876
|
+
titleAI: "DSH WorkBuddy AI Connect (International)",
|
|
2821
2877
|
introAI: "Use the models in the WorkBuddy AI international desktop app directly in DSH — zero configuration, ready out of the box.",
|
|
2822
2878
|
expand: "Expand",
|
|
2823
2879
|
collapse: "Collapse",
|
|
2824
2880
|
loading: "Loading account…",
|
|
2825
2881
|
signedOut: "Not signed in",
|
|
2826
|
-
signedOutHint: "Sign in once in the WorkBuddy desktop app; this plugin follows that sign-in automatically.",
|
|
2882
|
+
signedOutHint: "Sign in once in the WorkBuddy desktop app (China edition); this plugin follows that sign-in automatically.",
|
|
2827
2883
|
signedOutHintAI: "Sign in once in the WorkBuddy AI desktop app; this plugin follows that sign-in automatically.",
|
|
2828
2884
|
signedInAs: "Signed in as {nickname}",
|
|
2829
2885
|
accessTokenExpires: "Access token expires {time} (refresh is automatic)",
|
|
@@ -2873,17 +2929,19 @@ window.__ModuleLoader__.load({
|
|
|
2873
2929
|
badgeLimitedFree: "Limited-time free",
|
|
2874
2930
|
badgeNightDiscount: "Night discount",
|
|
2875
2931
|
badgeFreeNow: "Free now",
|
|
2932
|
+
badgeExclusiveFree: "Exclusive free",
|
|
2933
|
+
badgeNightFree: "Free at night",
|
|
2876
2934
|
rate: "{rate} credits per message",
|
|
2877
2935
|
rateUnknown: "Price unavailable — refresh to update",
|
|
2878
2936
|
probeLabel: "Reasoning levels",
|
|
2879
2937
|
probeTooltipIdle: "Detect the reasoning levels {model} accepts",
|
|
2880
2938
|
probeTooltipVerified: "Accepted levels: {levels} · click to detect again",
|
|
2881
|
-
probeTooltipNotValidating: "This model
|
|
2939
|
+
probeTooltipNotValidating: "This model accepts any effort value, so the probe cannot tell the levels apart · click to re-probe",
|
|
2882
2940
|
probeTooltipRetry: "Detection did not complete · click to retry",
|
|
2883
2941
|
probeBubbleBody: "Send test requests to confirm the available reasoning levels. May consume a small amount of credit.",
|
|
2884
2942
|
probeConfirmAction: "Confirm",
|
|
2885
2943
|
probeNoteVerified: "Detected: {levels}",
|
|
2886
|
-
probeNoteNotValidating: "
|
|
2944
|
+
probeNoteNotValidating: "Could not probe the levels: the upstream does not validate the effort parameter — that is not the same as no level being supported",
|
|
2887
2945
|
probeNoteUnknown: "Detection did not complete",
|
|
2888
2946
|
probeNoteDismiss: "Got it",
|
|
2889
2947
|
probeHeading: "Reasoning effort detection",
|
|
@@ -2899,7 +2957,8 @@ window.__ModuleLoader__.load({
|
|
|
2899
2957
|
probeConfirmBody: "Send test requests to {model} to confirm its available reasoning levels. May consume a small amount of credit.",
|
|
2900
2958
|
cancel: "Cancel",
|
|
2901
2959
|
probeResultVerified: "Verified levels: {levels}",
|
|
2902
|
-
probeResultNotValidating: "
|
|
2960
|
+
probeResultNotValidating: "Upstream ignores the effort parameter",
|
|
2961
|
+
probeDeclaredSuffix: " (catalogue declares {levels})",
|
|
2903
2962
|
probeResultUnknown: "Detection did not complete",
|
|
2904
2963
|
probeResultAt: "Detected {time}",
|
|
2905
2964
|
probeResultEmpty: "No detectable models right now.",
|
|
@@ -2939,15 +2998,15 @@ window.__ModuleLoader__.load({
|
|
|
2939
2998
|
agentUpgradePrompt: "Please open {repository}, check its latest version, and install or update the plugin \"dsh-workbuddy-connect\" in my current DSH profile following the project README (the install command differs by profile). After upgrading, fully quit and restart DSH."
|
|
2940
2999
|
};
|
|
2941
3000
|
const zh = {
|
|
2942
|
-
title: "DSH WorkBuddy Connect",
|
|
2943
|
-
intro: "在 DSH 中直接使用 WorkBuddy
|
|
2944
|
-
titleAI: "DSH WorkBuddy AI Connect",
|
|
3001
|
+
title: "DSH WorkBuddy Connect(国内版)",
|
|
3002
|
+
intro: "在 DSH 中直接使用 WorkBuddy 国内版桌面 App 包含的模型,开箱即用,无需额外配置。",
|
|
3003
|
+
titleAI: "DSH WorkBuddy AI Connect(国际版)",
|
|
2945
3004
|
introAI: "在 DSH 中直接使用 WorkBuddy AI 国际版桌面 App 包含的模型,开箱即用,无需额外配置。",
|
|
2946
3005
|
expand: "展开",
|
|
2947
3006
|
collapse: "收起",
|
|
2948
3007
|
loading: "正在读取账号…",
|
|
2949
3008
|
signedOut: "未登录",
|
|
2950
|
-
signedOutHint: "在 WorkBuddy
|
|
3009
|
+
signedOutHint: "在 WorkBuddy 国内版桌面 App 里登录一次即可,插件会自动跟随当前登录的账号。",
|
|
2951
3010
|
signedOutHintAI: "在 WorkBuddy AI 国际版桌面 App 里登录一次即可,插件会自动跟随当前登录的账号。",
|
|
2952
3011
|
signedInAs: "已登录:{nickname}",
|
|
2953
3012
|
accessTokenExpires: "访问令牌 {time} 过期(自动续期)",
|
|
@@ -2997,17 +3056,19 @@ window.__ModuleLoader__.load({
|
|
|
2997
3056
|
badgeLimitedFree: "限时免费",
|
|
2998
3057
|
badgeNightDiscount: "夜间折扣",
|
|
2999
3058
|
badgeFreeNow: "限时免费",
|
|
3059
|
+
badgeExclusiveFree: "独家限免",
|
|
3060
|
+
badgeNightFree: "夜间免费",
|
|
3000
3061
|
rate: "{rate} 积分/次",
|
|
3001
3062
|
rateUnknown: "价格未知 — 刷新后更新",
|
|
3002
3063
|
probeLabel: "推理等级",
|
|
3003
3064
|
probeTooltipIdle: "检测 {model} 可用的推理档位",
|
|
3004
3065
|
probeTooltipVerified: "已接受:{levels} · 点击可重新检测",
|
|
3005
|
-
probeTooltipNotValidating: "
|
|
3066
|
+
probeTooltipNotValidating: "该模型接受任意档位参数,探测无法区分可用档位 · 点击可重新检测",
|
|
3006
3067
|
probeTooltipRetry: "检测未完成 · 点击重试",
|
|
3007
3068
|
probeBubbleBody: "发送探测请求以确认可用推理档位。可能消耗少量积分。",
|
|
3008
3069
|
probeConfirmAction: "确认检测",
|
|
3009
3070
|
probeNoteVerified: "已检测:{levels}",
|
|
3010
|
-
probeNoteNotValidating: "
|
|
3071
|
+
probeNoteNotValidating: "无法探测档位:上游不校验该参数 —— 这不等于该档位不被支持",
|
|
3011
3072
|
probeNoteUnknown: "检测未完成",
|
|
3012
3073
|
probeNoteDismiss: "知道了",
|
|
3013
3074
|
probeHeading: "推理档位检测",
|
|
@@ -3023,7 +3084,8 @@ window.__ModuleLoader__.load({
|
|
|
3023
3084
|
probeConfirmBody: "向 {model} 发送探测请求,以确认可用推理档位。可能消耗少量积分。",
|
|
3024
3085
|
cancel: "取消",
|
|
3025
3086
|
probeResultVerified: "已验证接受的档位:{levels}",
|
|
3026
|
-
probeResultNotValidating: "
|
|
3087
|
+
probeResultNotValidating: "上游不校验档位参数",
|
|
3088
|
+
probeDeclaredSuffix: "(目录声明 {levels})",
|
|
3027
3089
|
probeResultUnknown: "检测未完成",
|
|
3028
3090
|
probeResultAt: "检测于 {time}",
|
|
3029
3091
|
probeResultEmpty: "当前没有可检测的模型。",
|
|
@@ -1,11 +1,226 @@
|
|
|
1
|
-
import { readFile, rm, writeFile } from "node:fs/promises";
|
|
1
|
+
import { lstat, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import { homedir, release } from "node:os";
|
|
3
3
|
import { basename, dirname, join } from "node:path";
|
|
4
|
-
import { withFileLock, writeFileAtomic } from "@deepseek-ai/dsh-atomic-write";
|
|
5
|
-
import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
6
4
|
import { createDecipheriv, createHash, randomBytes, randomUUID } from "node:crypto";
|
|
5
|
+
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
6
|
+
import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
7
7
|
import { execFile, execFileSync } from "node:child_process";
|
|
8
8
|
import { accessSync, constants, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
9
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-atomic-write@0.2.1-rc.2_@deepseek-ai+cordis@4.0.5/node_modules/@deepseek-ai/dsh-atomic-write/lib/index.js
|
|
10
|
+
/**
|
|
11
|
+
* Zero-dependency atomic file replacement and writer coordination.
|
|
12
|
+
* `writeFileAtomic` writes a random-suffix sibling with exclusive create and
|
|
13
|
+
* the caller's permission bits, then renames it over the target, so readers
|
|
14
|
+
* observe either the old or the new complete content and a replaced file ends
|
|
15
|
+
* up with exactly the stated mode. `withFileLock` serializes cross-process
|
|
16
|
+
* writers of one file through a `wx`-created `<file>.lock` sibling, so a
|
|
17
|
+
* read-modify-write cycle can never resurrect a state another writer just
|
|
18
|
+
* replaced; readers stay lock-free because the rename commit is atomic. A lock
|
|
19
|
+
* whose recorded holder process no longer exists is taken over.
|
|
20
|
+
* @module @deepseek-ai/dsh-atomic-write
|
|
21
|
+
*/
|
|
22
|
+
const WINDOWS_TRANSIENT_RENAME_ERRORS = /* @__PURE__ */ new Set([
|
|
23
|
+
"EACCES",
|
|
24
|
+
"EBUSY",
|
|
25
|
+
"EPERM"
|
|
26
|
+
]);
|
|
27
|
+
const WINDOWS_RENAME_RETRY_INITIAL_MS = 20;
|
|
28
|
+
const WINDOWS_RENAME_RETRY_MAX_MS = 200;
|
|
29
|
+
const WINDOWS_RENAME_RETRY_LIMIT = 8;
|
|
30
|
+
/** Whether Windows reported temporary interference with an atomic replacement. */
|
|
31
|
+
function isTransientWindowsRenameError(error) {
|
|
32
|
+
if (process.platform !== "win32") return false;
|
|
33
|
+
return WINDOWS_TRANSIENT_RENAME_ERRORS.has(error?.code ?? "");
|
|
34
|
+
}
|
|
35
|
+
/** Replace the target after bounded retries for transient Windows interference. */
|
|
36
|
+
async function renameAtomicTemp(temp, filename) {
|
|
37
|
+
let delay = WINDOWS_RENAME_RETRY_INITIAL_MS;
|
|
38
|
+
for (let retries = 0;; retries += 1) {
|
|
39
|
+
try {
|
|
40
|
+
await rename(temp, filename);
|
|
41
|
+
return;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (!isTransientWindowsRenameError(error)) throw error;
|
|
44
|
+
if (retries >= WINDOWS_RENAME_RETRY_LIMIT) throw error;
|
|
45
|
+
}
|
|
46
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
47
|
+
delay = Math.min(delay * 2, WINDOWS_RENAME_RETRY_MAX_MS);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Replace `filename` with `content` in one atomic step, creating parent
|
|
52
|
+
* directories. The content is first written to a random-suffix sibling opened
|
|
53
|
+
* with exclusive create (`wx`): the open refuses to follow a symlink planted
|
|
54
|
+
* at the temp path, and the fresh inode carries `options.mode` through the
|
|
55
|
+
* rename, so replacing a wider-permission file narrows it without a chmod
|
|
56
|
+
* race. The rename also replaces a symlinked target itself instead of writing
|
|
57
|
+
* through to its referent, and the same-directory sibling keeps the rename on
|
|
58
|
+
* one filesystem. Windows replacement retries transient `EACCES`, `EBUSY`,
|
|
59
|
+
* and `EPERM` failures for a bounded interval while the complete temp file
|
|
60
|
+
* remains the rename source. On any remaining failure the temp file is
|
|
61
|
+
* removed and the failure rethrown. Crash durability (fsync) is out of scope.
|
|
62
|
+
* @param filename - final path receiving the content.
|
|
63
|
+
* @param content - complete next file content.
|
|
64
|
+
* @param options - permission bits for the replacement inode.
|
|
65
|
+
*/
|
|
66
|
+
async function writeFileAtomic(filename, content, options) {
|
|
67
|
+
await mkdir(dirname(filename), {
|
|
68
|
+
recursive: true,
|
|
69
|
+
...options.dirMode === void 0 ? {} : { mode: options.dirMode }
|
|
70
|
+
});
|
|
71
|
+
const temp = `${filename}.${randomBytes(6).toString("hex")}.tmp`;
|
|
72
|
+
try {
|
|
73
|
+
await writeFile(temp, content, {
|
|
74
|
+
mode: options.mode,
|
|
75
|
+
flag: "wx"
|
|
76
|
+
});
|
|
77
|
+
await renameAtomicTemp(temp, filename);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
await rm(temp, { force: true });
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** Whether an exclusive create found an existing lock. */
|
|
84
|
+
async function isLockContention(error, lockPath) {
|
|
85
|
+
const code = error?.code;
|
|
86
|
+
if (code === "EEXIST") return true;
|
|
87
|
+
if (code !== "EPERM") return false;
|
|
88
|
+
try {
|
|
89
|
+
await lstat(lockPath);
|
|
90
|
+
return true;
|
|
91
|
+
} catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** Whether the holder a `<pid>\n` record names is proven gone: a signal probe finds no such process. */
|
|
96
|
+
function holderExited(record) {
|
|
97
|
+
if (!/^\d+\n$/.test(record)) return false;
|
|
98
|
+
const pid = Number(record.trim());
|
|
99
|
+
if (pid === 0 || pid > 2147483647) return false;
|
|
100
|
+
if (pid === process.pid) return false;
|
|
101
|
+
try {
|
|
102
|
+
process.kill(pid, 0);
|
|
103
|
+
return false;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
return error.code === "ESRCH";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** The lock file's content, or undefined when it cannot be read. */
|
|
109
|
+
async function readLockRecord(lockPath) {
|
|
110
|
+
try {
|
|
111
|
+
return await readFile(lockPath, "utf8");
|
|
112
|
+
} catch (error) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Remove the lock when its recorded holder exited. Contenders that read the
|
|
118
|
+
* same record serialize on a claim file named after it. Under the claim, the
|
|
119
|
+
* claimant re-reads the lock and probes its PID again, and removes it only
|
|
120
|
+
* when it still holds that record and that PID is still gone: the record's
|
|
121
|
+
* holder can no longer release it, and no other contender can remove it
|
|
122
|
+
* without the claim, so a removal never deletes a lock another contender
|
|
123
|
+
* acquired after the dead holder's, including one whose holder reused the PID.
|
|
124
|
+
* @returns Whether this call removed the dead holder's lock.
|
|
125
|
+
*/
|
|
126
|
+
async function takeOverExitedLock(lockPath) {
|
|
127
|
+
const record = await readLockRecord(lockPath);
|
|
128
|
+
if (record === void 0 || !holderExited(record)) return false;
|
|
129
|
+
const claim = `${lockPath}.takeover-${createHash("sha256").update(record).digest("hex").slice(0, 16)}`;
|
|
130
|
+
try {
|
|
131
|
+
await writeFile(claim, `${process.pid}\n`, {
|
|
132
|
+
mode: 384,
|
|
133
|
+
flag: "wx"
|
|
134
|
+
});
|
|
135
|
+
} catch (error) {
|
|
136
|
+
const code = error.code;
|
|
137
|
+
if (code === "EEXIST" || code === "EPERM") return false;
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
if (await readLockRecord(lockPath) !== record || !holderExited(record)) return false;
|
|
142
|
+
try {
|
|
143
|
+
await rm(lockPath, { force: true });
|
|
144
|
+
} catch (error) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
return true;
|
|
148
|
+
} finally {
|
|
149
|
+
await rm(claim, { force: true }).catch((error) => {});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Retry cadence for a contended lock. These stay robustness invariants of the
|
|
154
|
+
* cross-process write protocol rather than deployment tunables: they govern how
|
|
155
|
+
* often a contender asks, which no caller has a reason to vary.
|
|
156
|
+
*/
|
|
157
|
+
const LOCK_RETRY_INITIAL_MS = 20;
|
|
158
|
+
const LOCK_RETRY_MAX_MS = 200;
|
|
159
|
+
/**
|
|
160
|
+
* How long a contender waits when the caller states no limit — sized for the
|
|
161
|
+
* render-and-rename cycle every call site had when this package was written.
|
|
162
|
+
* Expiry fails the contender rather than guessing whether the existing lock
|
|
163
|
+
* still has an owner. How long is *worth* waiting is a property of the
|
|
164
|
+
* operation the lock holder runs, which is why {@link FileLockOptions.waitMs}
|
|
165
|
+
* exists; the value here is the floor for an operation that does file work
|
|
166
|
+
* alone.
|
|
167
|
+
*/
|
|
168
|
+
const DEFAULT_LOCK_WAIT_MS = 2e3;
|
|
169
|
+
/**
|
|
170
|
+
* Hold the cross-process writer lock for `filename` around one operation. The
|
|
171
|
+
* lock is a `wx`-created sibling (`<filename>.lock`); paired with the
|
|
172
|
+
* rename-based commit of {@link writeFileAtomic}, readers stay lock-free and
|
|
173
|
+
* only writers contend. `EEXIST` is contention directly; an `EPERM` is
|
|
174
|
+
* contention only when a fresh `lstat` confirms the lock path exists, covering
|
|
175
|
+
* Windows exclusive-create behavior. Windows retries one unconfirmed EPERM
|
|
176
|
+
* because the holder can release before the probe; a repeated unconfirmed
|
|
177
|
+
* permission error is rethrown. The lock records its holder's PID. A contender
|
|
178
|
+
* removes the lock and retries at once when no process with that PID exists
|
|
179
|
+
* (`ESRCH`); any other lock, including one whose holder exists under another
|
|
180
|
+
* user (`EPERM`) or whose record is incomplete, is waited for. Contention backs
|
|
181
|
+
* off exponentially and times out after the deadline. A holder whose PID a
|
|
182
|
+
* live process reused keeps its lock until an operator removes it. Takeover
|
|
183
|
+
* proves only that the recorded process exited: an operation that starts other
|
|
184
|
+
* writers must stop them with it or leave its successor a way to find them.
|
|
185
|
+
* PIDs are compared on the contender's host, so writers on other hosts or in
|
|
186
|
+
* other PID namespaces sharing the file are unsupported and could both hold
|
|
187
|
+
* the lock. The parent directory must exist.
|
|
188
|
+
* @param filename - the file whose writers this lock serializes.
|
|
189
|
+
* @param operation - the read-render-commit cycle to run while holding the lock.
|
|
190
|
+
* @param options - acquisition options; omitted waits {@link DEFAULT_LOCK_WAIT_MS}.
|
|
191
|
+
* @returns the operation's result; the lock releases on both outcomes.
|
|
192
|
+
*/
|
|
193
|
+
async function withFileLock(filename, operation, options) {
|
|
194
|
+
const lockPath = `${filename}.lock`;
|
|
195
|
+
const deadline = Date.now() + (options?.waitMs ?? DEFAULT_LOCK_WAIT_MS);
|
|
196
|
+
let delay = LOCK_RETRY_INITIAL_MS;
|
|
197
|
+
let retriedUnconfirmedPermissionError = false;
|
|
198
|
+
for (;;) {
|
|
199
|
+
options?.signal?.throwIfAborted();
|
|
200
|
+
try {
|
|
201
|
+
await writeFile(lockPath, `${process.pid}\n`, {
|
|
202
|
+
mode: 384,
|
|
203
|
+
flag: "wx"
|
|
204
|
+
});
|
|
205
|
+
break;
|
|
206
|
+
} catch (error) {
|
|
207
|
+
if (!await isLockContention(error, lockPath)) {
|
|
208
|
+
if (process.platform !== "win32" || error?.code !== "EPERM" || retriedUnconfirmedPermissionError) throw error;
|
|
209
|
+
retriedUnconfirmedPermissionError = true;
|
|
210
|
+
} else if (await takeOverExitedLock(lockPath)) continue;
|
|
211
|
+
}
|
|
212
|
+
if (Date.now() >= deadline) throw new Error(`atomic-write: timed out waiting for the writer lock at ${lockPath}`);
|
|
213
|
+
await setTimeout$1(delay, void 0, { signal: options?.signal });
|
|
214
|
+
delay = Math.min(delay * 2, LOCK_RETRY_MAX_MS);
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
options?.signal?.throwIfAborted();
|
|
218
|
+
return await operation();
|
|
219
|
+
} finally {
|
|
220
|
+
await rm(lockPath, { force: true });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
//#endregion
|
|
9
224
|
//#region src/app-version.ts
|
|
10
225
|
/**
|
|
11
226
|
* The international desktop app's version, used as the `/v3/config` UA.
|
|
@@ -1519,7 +1734,41 @@ function parseModelCatalog(data, international = false, promoBadges) {
|
|
|
1519
1734
|
}
|
|
1520
1735
|
const models = cliIds.map((id) => byId.get(id)).filter((model) => model !== void 0);
|
|
1521
1736
|
if (models.length === 0) throw new Error("workbuddy model catalog resolved to an empty list");
|
|
1522
|
-
return models;
|
|
1737
|
+
return disambiguateDisplayNames(models);
|
|
1738
|
+
}
|
|
1739
|
+
/**
|
|
1740
|
+
* Make display names unique within one catalog.
|
|
1741
|
+
*
|
|
1742
|
+
* The CN document names both `hy3` and `hy3-x` "Hy3" — two neighbouring rows
|
|
1743
|
+
* whose rates differ (`x0.00` free versus `x0.05`), so the picker and the probe
|
|
1744
|
+
* card render two identical rows and the user cannot tell which one they are
|
|
1745
|
+
* paying for. The static fallback spells the second one "Hy3-X", but the live
|
|
1746
|
+
* fetch overwrote that correction: as soon as the roster gained the id, the
|
|
1747
|
+
* collision came back through the parse path.
|
|
1748
|
+
*
|
|
1749
|
+
* The suffix comes from the id itself — whatever follows the shared name stem
|
|
1750
|
+
* — so `hy3-x` reads "Hy3-X" again. Ids are untouched: they are the wire
|
|
1751
|
+
* contract the request path and the shim route on. Only exact (case-
|
|
1752
|
+
* insensitive) collisions are rewritten; a distinct upstream name such as
|
|
1753
|
+
* `MiniMax-M3` for `minimax-m3-pay` keeps the upstream's own wording.
|
|
1754
|
+
*/
|
|
1755
|
+
function disambiguateDisplayNames(models) {
|
|
1756
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1757
|
+
return models.map((model) => {
|
|
1758
|
+
const key = model.name.trim().toLowerCase();
|
|
1759
|
+
if (!seen.has(key)) {
|
|
1760
|
+
seen.add(key);
|
|
1761
|
+
return model;
|
|
1762
|
+
}
|
|
1763
|
+
const tail = model.id.toLowerCase().startsWith(key) ? model.id.slice(key.length) : "";
|
|
1764
|
+
if (tail === "") return model;
|
|
1765
|
+
const name = `${model.name}${tail.toUpperCase()}`;
|
|
1766
|
+
seen.add(name.toLowerCase());
|
|
1767
|
+
return {
|
|
1768
|
+
...model,
|
|
1769
|
+
name
|
|
1770
|
+
};
|
|
1771
|
+
});
|
|
1523
1772
|
}
|
|
1524
1773
|
/** Extract the promotions covering `model` from the `modelPromotions` array. */
|
|
1525
1774
|
function parsePromotions(value, model) {
|
|
@@ -3436,12 +3685,12 @@ var WorkBuddyCredentialStore = class {
|
|
|
3436
3685
|
*/
|
|
3437
3686
|
/**
|
|
3438
3687
|
* Static CLI models observed on the CN endpoint (re-verified against the live
|
|
3439
|
-
* `/v3/config` document 2026-
|
|
3688
|
+
* `/v3/config` document 2026-10-17, including the thinking-effort and billing
|
|
3440
3689
|
* metadata). The upstream refresh replaces this list at startup; it exists so
|
|
3441
3690
|
* the provider registers with a usable catalog even while the first fetch is
|
|
3442
3691
|
* in flight or offline.
|
|
3443
3692
|
*
|
|
3444
|
-
* The list tracks the `cli` agent's model roster exactly — the
|
|
3693
|
+
* The list tracks the `cli` agent's model roster exactly — the 19 models it
|
|
3445
3694
|
* offered that day. The roster churns quickly (`auto`, `kimi-k3-1`,
|
|
3446
3695
|
* `minimax-m3` each appeared or vanished within days, and a competing patch's
|
|
3447
3696
|
* 2026-09-22 snapshot named three ids that were gone a day later), so this
|
|
@@ -3452,6 +3701,11 @@ var WorkBuddyCredentialStore = class {
|
|
|
3452
3701
|
* document, and the `free` flag follows the normalized `x0.00` credits
|
|
3453
3702
|
* marker.
|
|
3454
3703
|
*
|
|
3704
|
+
* Image capability is also verbatim, and it moved since the previous capture:
|
|
3705
|
+
* `glm-5.2` and `glm-5.1` now report `supportsImages: false`. Claiming it for
|
|
3706
|
+
* them admitted an attachment the upstream then rejected after the message was
|
|
3707
|
+
* durable, so this table follows the document rather than the old snapshot.
|
|
3708
|
+
*
|
|
3455
3709
|
* Deliberately NOT baked in: promotional badges. `限时免费` and friends are
|
|
3456
3710
|
* dynamic console-side promotions with no reliable validity window, so a
|
|
3457
3711
|
* static table would keep them alive long after the offers end. The live
|
|
@@ -3463,7 +3717,7 @@ const FALLBACK_WORKBUDDY_MODELS = [
|
|
|
3463
3717
|
{
|
|
3464
3718
|
id: "hy4-preview",
|
|
3465
3719
|
name: "Hy4 preview",
|
|
3466
|
-
contextWindow:
|
|
3720
|
+
contextWindow: 96e4,
|
|
3467
3721
|
maxTokens: 64e3,
|
|
3468
3722
|
supportsImages: true,
|
|
3469
3723
|
reasoning: {
|
|
@@ -3512,6 +3766,52 @@ const FALLBACK_WORKBUDDY_MODELS = [
|
|
|
3512
3766
|
free: false
|
|
3513
3767
|
}
|
|
3514
3768
|
},
|
|
3769
|
+
{
|
|
3770
|
+
id: "step-5-preview",
|
|
3771
|
+
name: "Step-5-Preview",
|
|
3772
|
+
contextWindow: 1e6,
|
|
3773
|
+
maxTokens: 64e3,
|
|
3774
|
+
supportsImages: true,
|
|
3775
|
+
reasoning: {
|
|
3776
|
+
supports: true,
|
|
3777
|
+
onlyReasoning: true,
|
|
3778
|
+
supportedEfforts: [
|
|
3779
|
+
"low",
|
|
3780
|
+
"medium",
|
|
3781
|
+
"high"
|
|
3782
|
+
],
|
|
3783
|
+
defaultEffort: "high",
|
|
3784
|
+
canDisableThinking: false
|
|
3785
|
+
},
|
|
3786
|
+
billing: {
|
|
3787
|
+
credits: "x0.00 credits",
|
|
3788
|
+
free: true
|
|
3789
|
+
}
|
|
3790
|
+
},
|
|
3791
|
+
{
|
|
3792
|
+
id: "space-bunny",
|
|
3793
|
+
name: "Space-Bunny",
|
|
3794
|
+
contextWindow: 1e6,
|
|
3795
|
+
maxTokens: 128e3,
|
|
3796
|
+
supportsImages: true,
|
|
3797
|
+
reasoning: {
|
|
3798
|
+
supports: true,
|
|
3799
|
+
onlyReasoning: true,
|
|
3800
|
+
supportedEfforts: [
|
|
3801
|
+
"low",
|
|
3802
|
+
"medium",
|
|
3803
|
+
"high",
|
|
3804
|
+
"xhigh",
|
|
3805
|
+
"max"
|
|
3806
|
+
],
|
|
3807
|
+
defaultEffort: "max",
|
|
3808
|
+
canDisableThinking: false
|
|
3809
|
+
},
|
|
3810
|
+
billing: {
|
|
3811
|
+
credits: "x0.08 credits",
|
|
3812
|
+
free: false
|
|
3813
|
+
}
|
|
3814
|
+
},
|
|
3515
3815
|
{
|
|
3516
3816
|
id: "deepseek-v4.1-flash",
|
|
3517
3817
|
name: "Deepseek-V4.1-Flash",
|
|
@@ -3521,11 +3821,16 @@ const FALLBACK_WORKBUDDY_MODELS = [
|
|
|
3521
3821
|
reasoning: {
|
|
3522
3822
|
supports: true,
|
|
3523
3823
|
onlyReasoning: true,
|
|
3824
|
+
supportedEfforts: [
|
|
3825
|
+
"low",
|
|
3826
|
+
"high",
|
|
3827
|
+
"max"
|
|
3828
|
+
],
|
|
3524
3829
|
defaultEffort: "high",
|
|
3525
|
-
canDisableThinking:
|
|
3830
|
+
canDisableThinking: true
|
|
3526
3831
|
},
|
|
3527
3832
|
billing: {
|
|
3528
|
-
credits: "x0.
|
|
3833
|
+
credits: "x0.11 credits",
|
|
3529
3834
|
free: false
|
|
3530
3835
|
}
|
|
3531
3836
|
},
|
|
@@ -3538,7 +3843,12 @@ const FALLBACK_WORKBUDDY_MODELS = [
|
|
|
3538
3843
|
reasoning: {
|
|
3539
3844
|
supports: true,
|
|
3540
3845
|
onlyReasoning: true,
|
|
3541
|
-
|
|
3846
|
+
supportedEfforts: [
|
|
3847
|
+
"low",
|
|
3848
|
+
"high",
|
|
3849
|
+
"max"
|
|
3850
|
+
],
|
|
3851
|
+
defaultEffort: "max",
|
|
3542
3852
|
canDisableThinking: false
|
|
3543
3853
|
},
|
|
3544
3854
|
billing: {
|
|
@@ -3561,19 +3871,41 @@ const FALLBACK_WORKBUDDY_MODELS = [
|
|
|
3561
3871
|
"max"
|
|
3562
3872
|
],
|
|
3563
3873
|
defaultEffort: "high",
|
|
3564
|
-
canDisableThinking:
|
|
3874
|
+
canDisableThinking: false
|
|
3565
3875
|
},
|
|
3566
3876
|
billing: {
|
|
3567
3877
|
credits: "x0.06 credits",
|
|
3568
3878
|
free: false
|
|
3569
3879
|
}
|
|
3570
3880
|
},
|
|
3881
|
+
{
|
|
3882
|
+
id: "glm-5.3-flashx",
|
|
3883
|
+
name: "GLM-5.3-FlashX",
|
|
3884
|
+
contextWindow: 1e6,
|
|
3885
|
+
maxTokens: 131072,
|
|
3886
|
+
supportsImages: true,
|
|
3887
|
+
reasoning: {
|
|
3888
|
+
supports: true,
|
|
3889
|
+
onlyReasoning: true,
|
|
3890
|
+
supportedEfforts: [
|
|
3891
|
+
"low",
|
|
3892
|
+
"high",
|
|
3893
|
+
"max"
|
|
3894
|
+
],
|
|
3895
|
+
defaultEffort: "high",
|
|
3896
|
+
canDisableThinking: false
|
|
3897
|
+
},
|
|
3898
|
+
billing: {
|
|
3899
|
+
credits: "x0.14 credits",
|
|
3900
|
+
free: false
|
|
3901
|
+
}
|
|
3902
|
+
},
|
|
3571
3903
|
{
|
|
3572
3904
|
id: "glm-5.2",
|
|
3573
3905
|
name: "GLM-5.2",
|
|
3574
3906
|
contextWindow: 1e6,
|
|
3575
3907
|
maxTokens: 64e3,
|
|
3576
|
-
supportsImages:
|
|
3908
|
+
supportsImages: false,
|
|
3577
3909
|
reasoning: {
|
|
3578
3910
|
supports: true,
|
|
3579
3911
|
onlyReasoning: true,
|
|
@@ -3590,7 +3922,7 @@ const FALLBACK_WORKBUDDY_MODELS = [
|
|
|
3590
3922
|
name: "GLM-5.1",
|
|
3591
3923
|
contextWindow: 2e5,
|
|
3592
3924
|
maxTokens: 48e3,
|
|
3593
|
-
supportsImages:
|
|
3925
|
+
supportsImages: false,
|
|
3594
3926
|
reasoning: {
|
|
3595
3927
|
supports: true,
|
|
3596
3928
|
onlyReasoning: true,
|
|
@@ -3620,10 +3952,10 @@ const FALLBACK_WORKBUDDY_MODELS = [
|
|
|
3620
3952
|
}
|
|
3621
3953
|
},
|
|
3622
3954
|
{
|
|
3623
|
-
id: "minimax-m3",
|
|
3955
|
+
id: "minimax-m3-pay",
|
|
3624
3956
|
name: "MiniMax-M3",
|
|
3625
3957
|
contextWindow: 512e3,
|
|
3626
|
-
maxTokens:
|
|
3958
|
+
maxTokens: 128e3,
|
|
3627
3959
|
supportsImages: true,
|
|
3628
3960
|
reasoning: {
|
|
3629
3961
|
supports: true,
|
|
@@ -3649,12 +3981,12 @@ const FALLBACK_WORKBUDDY_MODELS = [
|
|
|
3649
3981
|
canDisableThinking: false
|
|
3650
3982
|
},
|
|
3651
3983
|
billing: {
|
|
3652
|
-
credits: "x0.
|
|
3984
|
+
credits: "x0.26 credits",
|
|
3653
3985
|
free: false
|
|
3654
3986
|
}
|
|
3655
3987
|
},
|
|
3656
3988
|
{
|
|
3657
|
-
id: "kimi-k3-
|
|
3989
|
+
id: "kimi-k3-2",
|
|
3658
3990
|
name: "Kimi-K3",
|
|
3659
3991
|
contextWindow: 1e6,
|
|
3660
3992
|
maxTokens: 32e3,
|
|
@@ -3666,7 +3998,7 @@ const FALLBACK_WORKBUDDY_MODELS = [
|
|
|
3666
3998
|
canDisableThinking: false
|
|
3667
3999
|
},
|
|
3668
4000
|
billing: {
|
|
3669
|
-
credits: "x1.62
|
|
4001
|
+
credits: "x1.62",
|
|
3670
4002
|
free: false
|
|
3671
4003
|
}
|
|
3672
4004
|
},
|
|
@@ -4210,7 +4542,7 @@ var WorkBuddyCatalog = class {
|
|
|
4210
4542
|
};
|
|
4211
4543
|
//#endregion
|
|
4212
4544
|
//#region src/version.ts
|
|
4213
|
-
const WORKBUDDY_CONNECT_VERSION = "0.7.
|
|
4545
|
+
const WORKBUDDY_CONNECT_VERSION = "0.7.8";
|
|
4214
4546
|
//#endregion
|
|
4215
4547
|
//#region src/host-heartbeat.ts
|
|
4216
4548
|
/**
|
package/lib/index.d.ts
CHANGED
|
@@ -1129,12 +1129,12 @@ declare class WorkBuddyCredentialStore {
|
|
|
1129
1129
|
type WorkBuddyModelInfo = WorkBuddyUpstreamModel;
|
|
1130
1130
|
/**
|
|
1131
1131
|
* Static CLI models observed on the CN endpoint (re-verified against the live
|
|
1132
|
-
* `/v3/config` document 2026-
|
|
1132
|
+
* `/v3/config` document 2026-10-17, including the thinking-effort and billing
|
|
1133
1133
|
* metadata). The upstream refresh replaces this list at startup; it exists so
|
|
1134
1134
|
* the provider registers with a usable catalog even while the first fetch is
|
|
1135
1135
|
* in flight or offline.
|
|
1136
1136
|
*
|
|
1137
|
-
* The list tracks the `cli` agent's model roster exactly — the
|
|
1137
|
+
* The list tracks the `cli` agent's model roster exactly — the 19 models it
|
|
1138
1138
|
* offered that day. The roster churns quickly (`auto`, `kimi-k3-1`,
|
|
1139
1139
|
* `minimax-m3` each appeared or vanished within days, and a competing patch's
|
|
1140
1140
|
* 2026-09-22 snapshot named three ids that were gone a day later), so this
|
|
@@ -1145,6 +1145,11 @@ type WorkBuddyModelInfo = WorkBuddyUpstreamModel;
|
|
|
1145
1145
|
* document, and the `free` flag follows the normalized `x0.00` credits
|
|
1146
1146
|
* marker.
|
|
1147
1147
|
*
|
|
1148
|
+
* Image capability is also verbatim, and it moved since the previous capture:
|
|
1149
|
+
* `glm-5.2` and `glm-5.1` now report `supportsImages: false`. Claiming it for
|
|
1150
|
+
* them admitted an attachment the upstream then rejected after the message was
|
|
1151
|
+
* durable, so this table follows the document rather than the old snapshot.
|
|
1152
|
+
*
|
|
1148
1153
|
* Deliberately NOT baked in: promotional badges. `限时免费` and friends are
|
|
1149
1154
|
* dynamic console-side promotions with no reliable validity window, so a
|
|
1150
1155
|
* static table would keep them alive long after the offers end. The live
|
package/lib/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
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-
|
|
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";
|
|
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";
|
|
5
|
-
import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
6
5
|
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
6
|
+
import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
7
7
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { createProvider } from "@earendil-works/pi-ai";
|
|
9
9
|
import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
|
|
@@ -1282,6 +1282,8 @@ async function workBuddyWebStatus(deps) {
|
|
|
1282
1282
|
...model.billing?.badges !== void 0 && model.billing.badges.length > 0 ? { badges: model.billing.badges } : {},
|
|
1283
1283
|
...rate === void 0 ? {} : { credits: rate },
|
|
1284
1284
|
...model.billing?.rateUnknown === true ? { rateUnknown: true } : {},
|
|
1285
|
+
...(model.reasoning?.supportedEfforts?.length ?? 0) > 0 ? { declaredEfforts: model.reasoning.supportedEfforts } : {},
|
|
1286
|
+
...model.reasoning?.defaultEffort !== void 0 ? { defaultEffort: model.reasoning.defaultEffort } : {},
|
|
1285
1287
|
...typeof model.contextWindow === "number" && model.contextWindow > 0 ? { contextWindow: model.contextWindow } : {},
|
|
1286
1288
|
...typeof defaultContextWindow === "number" && defaultContextWindow > 0 && defaultContextWindow < model.contextWindow ? { defaultContextWindow } : {},
|
|
1287
1289
|
...maxContextWindow === void 0 || maxContextWindow <= defaultContextWindow ? {} : { maxContextWindow },
|
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.
|
|
5
|
+
"version": "0.7.8",
|
|
6
6
|
"author": "corrinehu",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"dsh-plugin",
|
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
"cordis.patch.yml",
|
|
42
42
|
"README.md",
|
|
43
43
|
"README.en.md",
|
|
44
|
-
"LICENSE"
|
|
44
|
+
"LICENSE",
|
|
45
|
+
"THIRD_PARTY_NOTICES.txt"
|
|
45
46
|
],
|
|
46
47
|
"license": "MIT",
|
|
47
48
|
"repository": {
|
|
@@ -71,36 +72,35 @@
|
|
|
71
72
|
},
|
|
72
73
|
"peerDependencies": {
|
|
73
74
|
"@deepseek-ai/cordis": "^4.0.5",
|
|
74
|
-
"@deepseek-ai/dsh-
|
|
75
|
-
"@deepseek-ai/dsh-
|
|
76
|
-
"@deepseek-ai/dsh-
|
|
77
|
-
"@deepseek-ai/dsh-
|
|
78
|
-
"@deepseek-ai/dsh-llm": "0.2.1-rc.
|
|
79
|
-
"@deepseek-ai/dsh-
|
|
80
|
-
"@deepseek-ai/dsh-settings": "0.2.1-rc.1",
|
|
75
|
+
"@deepseek-ai/dsh-attachment": "0.2.1-rc.2",
|
|
76
|
+
"@deepseek-ai/dsh-home-paths": "0.2.1-rc.2",
|
|
77
|
+
"@deepseek-ai/dsh-host-webserver": "0.2.1-rc.2",
|
|
78
|
+
"@deepseek-ai/dsh-llm": "0.2.1-rc.2",
|
|
79
|
+
"@deepseek-ai/dsh-llm-pi-ai": "0.2.1-rc.2",
|
|
80
|
+
"@deepseek-ai/dsh-settings": "0.2.1-rc.2",
|
|
81
81
|
"@deepseek-ai/schemastery": "^3.18.5",
|
|
82
82
|
"react": "^18.2.0"
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
85
|
"@deepseek-ai/cordis": "^4.0.5",
|
|
86
|
-
"@deepseek-ai/dsh-atomic-write": "0.2.1-rc.
|
|
87
|
-
"@deepseek-ai/dsh-attachment": "0.2.1-rc.
|
|
88
|
-
"@deepseek-ai/dsh-client-locale": "0.2.1-rc.
|
|
89
|
-
"@deepseek-ai/dsh-client-ui-conversation": "0.2.1-rc.
|
|
90
|
-
"@deepseek-ai/dsh-client-ui-model-selection": "0.2.1-rc.
|
|
91
|
-
"@deepseek-ai/dsh-client-ui-plugin-manager": "0.2.1-rc.
|
|
92
|
-
"@deepseek-ai/dsh-client-ui-settings-plugins": "0.2.1-rc.
|
|
93
|
-
"@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.
|
|
94
|
-
"@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.
|
|
95
|
-
"@deepseek-ai/dsh-credentials": "0.2.1-rc.
|
|
96
|
-
"@deepseek-ai/dsh-authorization": "0.2.1-rc.
|
|
97
|
-
"@deepseek-ai/dsh-fs": "0.2.1-rc.
|
|
98
|
-
"@deepseek-ai/dsh-launch-environment": "0.2.1-rc.
|
|
99
|
-
"@deepseek-ai/dsh-home-paths": "0.2.1-rc.
|
|
100
|
-
"@deepseek-ai/dsh-host-webserver": "0.2.1-rc.
|
|
101
|
-
"@deepseek-ai/dsh-llm": "0.2.1-rc.
|
|
102
|
-
"@deepseek-ai/dsh-llm-pi-ai": "0.2.1-rc.
|
|
103
|
-
"@deepseek-ai/dsh-settings": "0.2.1-rc.
|
|
86
|
+
"@deepseek-ai/dsh-atomic-write": "0.2.1-rc.2",
|
|
87
|
+
"@deepseek-ai/dsh-attachment": "0.2.1-rc.2",
|
|
88
|
+
"@deepseek-ai/dsh-client-locale": "0.2.1-rc.2",
|
|
89
|
+
"@deepseek-ai/dsh-client-ui-conversation": "0.2.1-rc.2",
|
|
90
|
+
"@deepseek-ai/dsh-client-ui-model-selection": "0.2.1-rc.2",
|
|
91
|
+
"@deepseek-ai/dsh-client-ui-plugin-manager": "0.2.1-rc.2",
|
|
92
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "0.2.1-rc.2",
|
|
93
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.2",
|
|
94
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.2",
|
|
95
|
+
"@deepseek-ai/dsh-credentials": "0.2.1-rc.2",
|
|
96
|
+
"@deepseek-ai/dsh-authorization": "0.2.1-rc.2",
|
|
97
|
+
"@deepseek-ai/dsh-fs": "0.2.1-rc.2",
|
|
98
|
+
"@deepseek-ai/dsh-launch-environment": "0.2.1-rc.2",
|
|
99
|
+
"@deepseek-ai/dsh-home-paths": "0.2.1-rc.2",
|
|
100
|
+
"@deepseek-ai/dsh-host-webserver": "0.2.1-rc.2",
|
|
101
|
+
"@deepseek-ai/dsh-llm": "0.2.1-rc.2",
|
|
102
|
+
"@deepseek-ai/dsh-llm-pi-ai": "0.2.1-rc.2",
|
|
103
|
+
"@deepseek-ai/dsh-settings": "0.2.1-rc.2",
|
|
104
104
|
"@deepseek-ai/schemastery": "^3.18.5",
|
|
105
105
|
"@earendil-works/pi-ai": "1.3.0",
|
|
106
106
|
"@types/node": "^22.20.0",
|