dsh-connect-workbuddy 5.14.1 → 5.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +148 -0
- package/README.en.md +57 -8
- package/README.md +55 -6
- package/lib/bin.js +1 -1
- package/lib/client.js +133 -11
- package/lib/{host-heartbeat-CCvdfUtL.js → host-heartbeat-891AaZGW.js} +1 -1
- package/lib/index.d.ts +50 -2
- package/lib/index.js +162 -15
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,98 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.15.1 (2026-10-30)
|
|
4
|
+
|
|
5
|
+
> **积分读数从「权限切换器旁边」挪到「模型选择器左边」。** 两者原本相隔 8px 且都是
|
|
6
|
+
> 可点元素——想去点「完全权限」,结果点到读数、弹出积分浮层。读数不是操作按钮,
|
|
7
|
+
> 紧贴操作按钮只会制造误触。
|
|
8
|
+
|
|
9
|
+
### 变更
|
|
10
|
+
|
|
11
|
+
- **积分读数改挂 `conversation.input.right`**(同步自 `dsh-connect-trae` 的 issue #71)。
|
|
12
|
+
- **动机**:`conversation.input.left` 渲染在 `div.tools` 的**最末尾**,紧挨
|
|
13
|
+
`div.modes` 里的权限选择器,间距只有 8px;两个都是可点元素,误触后弹出的是
|
|
14
|
+
积分浮层——一个纯读数被当成了操作目标。
|
|
15
|
+
- **新位置**:宿主把 `conversation.input.right` 渲染在 `div.trailing` 的
|
|
16
|
+
`standardControls` 内、`conversation.input.model`(模型选择器)**之前**。
|
|
17
|
+
迁过去后与权限选择器之间隔开整段空白。
|
|
18
|
+
- **宿主要改吗**:不用。`conversation.input.right` 早已是 `kind: list` /
|
|
19
|
+
`scope: session` 的现成座位。
|
|
20
|
+
- **老 profile 无需迁移**:`creditPosition` 的两个取值(`composer` / `stats`)、
|
|
21
|
+
设置名、`order: 4` 全部不变。`composer` 的含义是「带模型选择器的那一行」,
|
|
22
|
+
而 `input.right` 渲染的正是那一行。
|
|
23
|
+
- **「输入框下方」那一档不受影响**:`stats` 仍挂 `conversation.composer.dock`。
|
|
24
|
+
|
|
25
|
+
### 补齐一个测试盲点(本次发现)
|
|
26
|
+
|
|
27
|
+
`tests/host-slot-surface.spec.ts` 的 `injectedSlots()` 只读
|
|
28
|
+
`ctx.slots.inject('…')` 的参数,**不读 `register({ name })`**。而 `name` 才是
|
|
29
|
+
注册表实际用来索引的字段——实测:只把 `register({ name })` 从 `input.right`
|
|
30
|
+
改回 `input.left`,整个文件仍然全绿,而插件会「注入一个座位、注册到另一个座位」。
|
|
31
|
+
|
|
32
|
+
现补一条一致性断言:`inject()` 的座位名与 `register({ name })` 必须相等,
|
|
33
|
+
并带「守卫的守卫」(匹配对数 ≥ 3,防止正则失效后空转)。变异验证:只改
|
|
34
|
+
`register({ name })`,新用例立刻红并点名两个座位。
|
|
35
|
+
|
|
36
|
+
### 测试
|
|
37
|
+
|
|
38
|
+
- 全仓 **2675** 例通过(159 文件),类型检查通过。
|
|
39
|
+
- `tests/host-slot-surface.spec.ts` 的 `CLAIMED_SLOTS` 白名单与其余源码注释同步。
|
|
40
|
+
- 构建产物中 `conversation.input.right` 出现 2 次、`input.left` 出现 0 次。
|
|
41
|
+
|
|
42
|
+
## 5.15.0 (2026-10-30)
|
|
43
|
+
|
|
44
|
+
> 一个「每次开新对话都要重新挑思考强度」的抱怨(issue #179),和它附带的那个更值得追查的现象:
|
|
45
|
+
> 对话中途切换账号后,输入框的强度标签会从 `low` 变回 `high`。前者是缺的设置项,后者是
|
|
46
|
+
> **插件的默认值被写死成 `high`** 造成的显示回退——请求本身没变,但用户没法从界面上确认这一点。
|
|
47
|
+
|
|
48
|
+
### Features
|
|
49
|
+
|
|
50
|
+
- **每个模型可以单独指定默认思考强度,卡片上直接选(issue #179)**
|
|
51
|
+
- **现场**:`deepseek-v4.1-flash` 上游默认 `high`,而报告者通常用 `low`,于是每开一个新对话都要
|
|
52
|
+
在宿主的模型菜单里再挑一次。卡片原文只在模型行上**只读**地印一行「推理强度:low / high / max」。
|
|
53
|
+
- **改法**:这一行换成 `<select>`。第一项是「默认(high)」——括号里就是上游为这个模型声明的档位,其余各项就是
|
|
54
|
+
该模型自己声明的档位——选中的值存进区域槽位的新字段 `defaultThinkingEfforts`(按模型 id 存),
|
|
55
|
+
由适配器读成该模型的 `defaultEffort` 对外公布,宿主再把它写进每一个「没指定档位」的请求。
|
|
56
|
+
- **为什么必须是「每模型」而不是路由级**:本机 CN 名册 15 个模型的档位词汇各不相同——
|
|
57
|
+
`deepseek-v4.1-flash`/`glm-5.3` 是 `low/high/max`,`glm-5.2`/`deepseek-v4-pro` 是 `high/xhigh`,
|
|
58
|
+
`step-5-preview` 是 `low/medium/high`,`space-bunny`/`minimax-m3.1-flash` 有五个档位。
|
|
59
|
+
一个路由级常量(旧值 `WORKBUDDY_DEFAULT_EFFORT = 'high'`)不可能对整份名册都成立,而点名一个
|
|
60
|
+
模型没有的档位是硬错 `UNSUPPORTED_REASONING_EFFORT`(issue #90)。所以选择器只列该模型声明的档位,
|
|
61
|
+
名字照上游原样(不本地化、不跨模型统一),这和 qoder 插件的做法一致——它也是这么做的。
|
|
62
|
+
- **「跟随默认」是真正的答案,不是「未设置」**:上游每个模型都自己声明 `defaultEffort`
|
|
63
|
+
(本机实测:13 个是 `high`,`space-bunny` 与 `minimax-m3.1-flash` 是 `max`)。缺省项就走这条声明,
|
|
64
|
+
所以第一项的标签里必须写出**是哪一档**(「默认(high)」),否则「默认」两个字没说清任何事。
|
|
65
|
+
- **`off` 刻意不在这个字段里**:某个模型是否提供「关闭思考」是 `offModelIds` 的职责(issue #34),
|
|
66
|
+
两处都管会让同一个问题有两个能互相矛盾的答案。这个模块的解析函数遇到 `off` 一律当未设置。
|
|
67
|
+
- **保存的是整张表**:`--save` 的槽位合并只做两层,嵌套字典是**整体替换**的,所以卡片每次送来
|
|
68
|
+
完整的映射。这一点很重要——「跟随默认」正是用**键不存在**来表达的,只送一部分等于删掉其余模型的
|
|
69
|
+
选择。
|
|
70
|
+
- **测试**:`tests/issue179-default-thinking-effort.spec.ts`(19 条)。把本提交的
|
|
71
|
+
`advertiseDefaultEffort` 撤回,或把默认值写死回 `'high'`,其中 4 条会红。
|
|
72
|
+
|
|
73
|
+
### Bug Fixes
|
|
74
|
+
|
|
75
|
+
- **账号切换后输入框的强度标签不再回退成 `high`(issue #179 附带现象)**
|
|
76
|
+
- **现场**:报告者写到「对话开始后,如果中途触发账号切换,ui 处会从 low 变成 high,不清楚是只有
|
|
77
|
+
ui 切换还是实际请求的也是」。
|
|
78
|
+
- **根因(读宿主 `dsh-client-ui-model-selection` 与 `dsh-llm` 源码确认)**:输入框那个标签的值是
|
|
79
|
+
`current.reasoningEffort ?? reasoning.defaultEffort`,即**会话里的选择优先,否则用模型目录通告的
|
|
80
|
+
默认值**。而 `defaultEffort` 来自 `llm/adapters-updated` 触发的目录重载——本插件每次设置变更
|
|
81
|
+
(含账号切换)都会走 `applySelection` → `invalidateCatalog()` 发这个事件。插件此前把
|
|
82
|
+
`defaultEffort` 写死成路由级 `'high'`,于是重载后标签拿到的是 `high`,把用户选的 `low` 盖掉了;
|
|
83
|
+
而**持久化的会话选择(`model/selection` 事件)没变,所以真正发出的请求仍然是 `low`**。
|
|
84
|
+
- **结论:只是显示,请求没变。** 会话日志可查:`request/header` 里记录的是 `reasoningEffort: "high"`
|
|
85
|
+
(旧默认)或用户显式选的值,与 `model/selection` 一致,从未出现过「标签显示 low 而请求发 high」。
|
|
86
|
+
- **修法**:默认值不再是常量,而是该模型自己的声明/用户选择。目录重载后通告的 `defaultEffort` 与
|
|
87
|
+
用户选择一致,标签因此稳定,不需要改动宿主。
|
|
88
|
+
- **测试**:同上文件的「an account switch does not move the session's tier」两组,断言目录重载前后
|
|
89
|
+
`defaultEffort` 不变、且请求体里发出的是选中的档位。
|
|
90
|
+
|
|
91
|
+
### Docs
|
|
92
|
+
|
|
93
|
+
- 卡片的「推理强度:low / high / max」只读行与两条语言条目(`row.modelReasoning`)一并删除:
|
|
94
|
+
选择器本身就是那个信息面,留着等于同一件事说两遍。
|
|
95
|
+
|
|
3
96
|
## 5.14.1 (2026-10-30)
|
|
4
97
|
|
|
5
98
|
> 一个「新模型进名册后被当成纯文本」的缺口,与 #168 同形:宿主以 `MODEL_DOES_NOT_SUPPORT_IMAGES`
|
|
@@ -36,6 +129,61 @@
|
|
|
36
129
|
而本版起核验表里多了两条证据档位为「上游目录自述」的条目,照旧文读会以为这两条不该预勾选。
|
|
37
130
|
现在写明「只有核验表**收录**为原生多模态的才勾」,并点出两档证据与「新增 id 仍要厂商来源」。
|
|
38
131
|
|
|
132
|
+
- **(5.14.1 发布后补记,仅文档)新增「更新」一节与置顶帖,专讲「市场滞后、请手动或用 AI 升上来」**:
|
|
133
|
+
两条 README 各加一节 `## 更新` / `## Update`,正文落在
|
|
134
|
+
[`docs/PINNED-UPGRADE-LAG.md`](docs/PINNED-UPGRADE-LAG.md)(不随包发布),并贴成长期置顶 issue
|
|
135
|
+
[#181](https://github.com/dingminhua/dsh-connect-workbuddy/issues/181)。三条实测支撑:
|
|
136
|
+
- **市场索引按包各自滞后**:2026-10-30 构建的 `dsh-plugin-catalog` 快照里本插件仍只有 5.13.1、
|
|
137
|
+
`@dmh2002/dsh-connect-qoder` 仍只有 1.15.0,而同一份快照里 `@changfenhuang/dsh-genui`(0.15.0)、
|
|
138
|
+
`dsh-client-auto-continue`(0.17.0)、`billion-context`(0.1.209)都是最新——所以「别人的包有新版」
|
|
139
|
+
不能证明「本插件也该有」。
|
|
140
|
+
- **pnpm 等待期两种形态都实测了**(隔离目录、pnpm 10.34.5、`--lockfile-only`,未触碰用户环境):
|
|
141
|
+
显式 `minimumReleaseAge=1440` 时浮动 `^5.13.1` 解析回 **5.13.1 且不报错**,精确 `5.14.1` 报
|
|
142
|
+
`ERR_PNPM_NO_MATURE_MATCHING_VERSION`;在 profile 的 `pnpm-workspace.yaml` 写
|
|
143
|
+
`minimumReleaseAgeExclude: [dsh-connect-workbuddy]` 后精确版本立即装上。**这条实验顺手纠正了
|
|
144
|
+
「精确版本号也会被静默压回」的读法**:被压回的是浮动范围,精确写法会如实报错。
|
|
145
|
+
- **纠正两条旧自查口径**(旧置顶里写着,实际都不成立):`/workbuddy status` 并不存在(插件注册的
|
|
146
|
+
斜杠命令是 `/codebuddy`,供转发本机 CodeBuddy CLI);卡片「诊断」栏也没有 `pluginVersion` 行——
|
|
147
|
+
那一栏恰好八行(区域 / 域名 / 服务账号 / 登录有效期 / 目录模型数 / 已启用数 / 账号池 / 最近一次失败)。
|
|
148
|
+
版本只在 `doctor` 第一行与 `status --json` 的 `version` 字段里。本来只改文档,因此**没有升版本号**;
|
|
149
|
+
#162 与 #125 两条置顶的同一处错误也一并改了。
|
|
150
|
+
|
|
151
|
+
- **(5.14.1 发布后补记,仅文档)rc.5 宿主复核:无需改动,另订正两条 README 里的一句旧归因**
|
|
152
|
+
- **新增 [`docs/DSH-0.2.1-RC5-FOLLOWUP.md`](docs/DSH-0.2.1-RC5-FOLLOWUP.md)**:rc.3 → rc.5 的
|
|
153
|
+
完整复核。结论是**本插件不被影响**,理由可逐条复验:14 个直接依赖里 **13 个可打包的 `lib/`
|
|
154
|
+
与 rc.3 逐字节相同**(上一轮 rc.1→rc.3 至少有 `dsh-client-ui-primitives` 动过 75 个文件),
|
|
155
|
+
`dsh-app-boot` / `dsh-host-webserver` / `dsh-system-prompt` / `dsh-harness-dev-context` /
|
|
156
|
+
`dsh-file-reference` / `dsh-web-app` 六个关键包也逐字节相同;宿主对 rc.1~rc.5 五个版本
|
|
157
|
+
实算全部 ADMITTED,`0.3.0-rc.1` 仍被挡住。
|
|
158
|
+
- **rc.5 唯一的新特性不在本插件面上**:#7484 给 `conversation.chat.reasoning.body` 拆出
|
|
159
|
+
`rawText`(原始输出)与 `text`(已准备显示的文本),并新增 `reasoning-text.ts` 剥
|
|
160
|
+
`<signature>…</signature>` 信封。本插件对该槽**零贡献**(`grep -rn "conversation.chat" src/ tests/`
|
|
161
|
+
零命中),且宿主没有删字段——旧实现读 `text` 仍拿到字符串,只是语义从「原始」变成「已准备」。
|
|
162
|
+
记为「将来若要在展开推理体里插东西时必须同时读两个字段」的一条约定,不是待办。
|
|
163
|
+
- **本机实测**:运行中的 rc.5 GUI 上四个槽位占用齐全(`conversation.input.left` / `composer.dock`
|
|
164
|
+
的 `active: true`,`shell.quota-notice` 在位,`model.label` 的 `-0.5` 落在 qoder `-1` 之后属
|
|
165
|
+
`single` 槽常态);`GET …/usage?region=cn` 200 / 18317 字节 / 15 模型 / 9 账号,
|
|
166
|
+
`POST …/media-models` 200,`GET …/models-refresh` 404 是 POST-only 的正确行为。
|
|
167
|
+
- **订正 README 的旧归因(两条)**:`README.md` / `README.en.md` 的「`0.2.x` 预发布」注里都还写着
|
|
168
|
+
「宿主门禁只校验 `@deepseek-ai/dsh*` 命名空间,且 rc 宿主上按 `link:` 照常加载」——后半句是
|
|
169
|
+
[`DSH-0.2.1-RC3-FOLLOWUP.md`](docs/DSH-0.2.1-RC3-FOLLOWUP.md) §3 已经订正过的那句错的,
|
|
170
|
+
README 当时漏改。现在写明「闸门确实校验,只是用 `includePrerelease: true` 放过」,并链到 §3。
|
|
171
|
+
- **没有升版本号**:本次是纯文档提交(`docs:`),与 5.14.1 之后那次置顶帖补记同例。
|
|
172
|
+
- **新增 [`tests/host-slot-surface.spec.ts`](tests/host-slot-surface.spec.ts)(5 条),把那句
|
|
173
|
+
「无需改动」的前提钉成测试**:本插件认领的槽位是**封闭集合**(恰好那四个,且没有一个落在
|
|
174
|
+
`conversation.chat.*` 命名空间里);`conversation.chat.reasoning.body` 在 `src/` 里**零引用**;
|
|
175
|
+
另有一条**条件断言**——源码哪天碰到那个槽,就必须读 `rawText`(`text` 在 rc.5 起是已处理过的
|
|
176
|
+
显示文本,重复处理即错)。最后一条是 POLICY 守卫:`docs/DSH-0.2.1-RC5-FOLLOWUP.md` 必须还在、
|
|
177
|
+
且仍写着 `rawText` 与结论。变异验证做了三次:往允许表里加第五个槽 → 第 1 条红;在 `src/` 里
|
|
178
|
+
引用该槽 → 第 3、4 条红;引用它并**只**读 `text` → 第 4 条单独红。复核文档无法自我执行,
|
|
179
|
+
这 5 条是它的执行部分。
|
|
180
|
+
- **测试**:本仓库自有套件 **157 文件 / 2655 条通过**(2 条按平台跳过),`typecheck` 与 `build`
|
|
181
|
+
通过;把 `@deepseek-ai/dsh*` 全部 alias 到 rc.5 产物再跑全量是 **156 通过 / 1 失败 2653 条**,
|
|
182
|
+
唯一红的是 `tests/adapter-outgoing-body.spec.ts` 的 legacy 两格——该文件按
|
|
183
|
+
`piAiRuntimeInfo()` 选象限,而夹具里这个检测器读的是本仓库的 0.85.1、运行时导入却被 alias
|
|
184
|
+
换成了 1.1.0,属**夹具环境的两份库错配**,非宿主回归:同一配置下摘掉 pi-ai alias 即 6/6 通过,
|
|
185
|
+
仓库自己的 0.85.1 线上也 6/6 通过。
|
|
186
|
+
|
|
39
187
|
### Notes
|
|
40
188
|
|
|
41
189
|
- **未合入:PR #175 的「胶囊独立开关」**。它假设「输入框胶囊」与「卡片积分概览」共用一个开关,但
|
package/README.en.md
CHANGED
|
@@ -32,7 +32,7 @@ It depends on the WorkBuddy client's own endpoints (not a public API), so the pl
|
|
|
32
32
|
- **Dual parallel providers (CN + international)** — the CN side registers as the `workbuddy` provider (`GLM-5.3`, `DeepSeek-V4-Pro`, `Kimi-K3`, `MiniMax-M3`, `Hy3`, etc.), the international side as `workbuddy-global` (`GPT-5.6`, `Gemini-3.5-Flash`, `GLM-5.3`, `Kimi-K3`, etc.). **A region's models appear in the DSH model picker when that region has an account**: with accounts on both sides, both rosters are selectable at once, and different sessions can each pick a side without interfering. **A region with no local sign-in lists no models** — it cannot serve a single request, so listing them would offer nothing but failures. Sign in once for that region and its models come back on "detect accounts again" or after a restart; the provider itself stays registered either way, because its settings card is how you sign in (re-detection lives in the pool block). Model names carry the upstream credit multiplier (e.g. `GLM-5.3 · x0.79`), matching WorkBuddy's own model menu.
|
|
33
33
|
- **Tabbed settings card** — the card's top carries a "Domestic / Global" tab bar; each tab holds its own account pool and model management. Account, directory, selection, and unsaved drafts are fully isolated per tab — switching accounts or refreshing models on one tab never touches the other side's runtime catalog or sessions.
|
|
34
34
|
- **Switch either provider off on its own** — each tab carries an on/off checkbox (both checked by default). Unchecking one **withdraws that provider from DSH's model picker entirely** (it is not merely a hidden tab): its adapter route and its "Settings → Models" directory entry are both pulled, and startup no longer sends pointless requests for it. Account, credits, directory, and selection are all kept, and re-checking restores everything. The typical use is "I have no use for the international side" — switch it off and keep the picker clean. Note: a session that had already selected a model from the switched-off provider will report `NO_ADAPTER` on its next call; the card states this plainly on the switched-off tab instead of failing silently.
|
|
35
|
-
- **Model management** — refresh the full catalog from upstream and enable or disable each model individually; refresh is a draft operation that only takes effect on save. Upstream also reports credit multiplier, context/output limits, and reasoning efforts; **image input is decided by a verified table**: a refresh pre-checks only models the table records as natively multimodal, leaves documented text-only models unchecked, and treats any id without a source as unverified and unchecked rather than guessing. The table carries two evidence tiers — vendor documentation (13 entries) and **the upstream catalog's own description** (2 entries: `space-bunny` and `minimax-m3.1-flash`, whose descriptions say “native multimodal”) — while a NEW id still needs a vendor source; see the “Image input” section of [docs/DESIGN.md](docs/DESIGN.md). Upstream's `supportsImages` is a *platform* declaration, not the model's capability, so it does not drive that decision at all. You can still tick any box by hand, and your selection is kept locally. **Every model row also carries a “Can disable” (`off`) checkbox**: it decides whether that model gets the “turn thinking off entirely” level. The default comes from a reviewed built-in table (the 9 models that claim the capability but answer HTTP 400 for it start unticked), and you can change it per model — **untick** for a model you do not need the level on. **The 9 tabled models cannot be ticked at all** (their row is marked "refuses off" with an explanatory tooltip): the level is not merely an extra option — pi-ai uses it as the DEFAULT for every request that names no tier, and a compaction summary is exactly such a request, so one tick made every `/compact` for that model fail with HTTP 400 / code 11133 (issue #83). Models outside the table are still decided entirely by your selection and their own declaration; your ticks are saved on first save and recomputed no further.
|
|
35
|
+
- **Model management** — refresh the full catalog from upstream and enable or disable each model individually; refresh is a draft operation that only takes effect on save. Upstream also reports credit multiplier, context/output limits, and reasoning efforts; **every model row carries a "default reasoning" picker** (issue #179): which tier that model uses when a request names none — new conversations, compaction summaries and session titles are all such requests. The options are exactly the tiers that model declares (across one roster `deepseek`/`glm-5.3` say `low/high/max`, `glm-5.2` says `high/xhigh`, `space-bunny` has five), so this can only be chosen per model and not once for the route, and the first option reads "Default (high)", naming the tier upstream itself declares for that model. **This picker also replaces the old read-only "Reasoning: …" line** — it already lists every tier, so saying it twice adds nothing. **"Turn thinking off" is not here**: that stays the job of the `off` checkbox below. **image input is decided by a verified table**: a refresh pre-checks only models the table records as natively multimodal, leaves documented text-only models unchecked, and treats any id without a source as unverified and unchecked rather than guessing. The table carries two evidence tiers — vendor documentation (13 entries) and **the upstream catalog's own description** (2 entries: `space-bunny` and `minimax-m3.1-flash`, whose descriptions say “native multimodal”) — while a NEW id still needs a vendor source; see the “Image input” section of [docs/DESIGN.md](docs/DESIGN.md). Upstream's `supportsImages` is a *platform* declaration, not the model's capability, so it does not drive that decision at all. You can still tick any box by hand, and your selection is kept locally. **Every model row also carries a “Can disable” (`off`) checkbox**: it decides whether that model gets the “turn thinking off entirely” level. The default comes from a reviewed built-in table (the 9 models that claim the capability but answer HTTP 400 for it start unticked), and you can change it per model — **untick** for a model you do not need the level on. **The 9 tabled models cannot be ticked at all** (their row is marked "refuses off" with an explanatory tooltip): the level is not merely an extra option — pi-ai uses it as the DEFAULT for every request that names no tier, and a compaction summary is exactly such a request, so one tick made every `/compact` for that model fail with HTTP 400 / code 11133 (issue #83). Models outside the table are still decided entirely by your selection and their own declaration; your ticks are saved on first save and recomputed no further.
|
|
36
36
|
|
|
37
37
|
<img src="docs/assets/dsh-connect-workbuddy-model-picker.png" width="420" alt="DSH model picker: the same campaign badge (night discount / Free now) drawn after the model name, per region (no account data)" />
|
|
38
38
|
- **Campaign badges on model rows** — the upstream model catalog carries campaign information (free trials, night discounts, limited-time free use, subscription-only tiers, and the like). The plugin adds **no extra request** (it reuses the catalog configuration it already fetches when refreshing models) and draws a pill badge right on the model row: the upstream supplies both the colour and the wording, and hovering shows the upstream's campaign text, its start/end dates, and its daily window (e.g. “night 23:00–7:50”). **A badge is a function of the current moment**: it shows only inside the window the upstream declares and disappears on its own when that window closes; a window that crosses midnight is judged in the timezone the upstream declared; when several campaigns hit the same model, only the highest-priority one is drawn. A benefit marker without an end date (such as “subscription only”) stays until upstream withdraws it. **The multiplier shown is the price you actually pay**: while a “night discount” is running, the card and the picker both show the discounted `x0.06` instead of the catalog figure, and both go back on their own when the window closes — no restart needed (issue #120). **A badge is a hint, not behaviour** — it reports the campaign and discount upstream currently advertises, and makes no choice for you. **The same badge now appears in the DSH model picker**: every menu row draws the identical pill after the model name and before the load icon — menu rows use the upstream's short wording ("night", "free"), the composer trigger beside the input uses the full one ("night discount", "exclusive free"); judged from the same data at the same clock, because the screen where the model is chosen is exactly where "is it free right now" matters most. (Both regions' catalogs ship the campaign fields: read as the app, the CN `/v3/config` carries 8 promotions + 1 tier badge and the global one carries 5 + 1; the evidence and the decision rules are written up in [docs/PROMO_BADGE.md](docs/PROMO_BADGE.md).)
|
|
@@ -184,9 +184,11 @@ One class of state in the pool **does not go through a draft**: probe results, c
|
|
|
184
184
|
|
|
185
185
|
> ⚠️ **Version requirement: DSH 0.1.7-rc.1 or newer (current window: `0.1.7-rc.1` – `0.2.x` GA).** Since v2.1.0 this plugin only supports hosts on DSH 0.1.7-rc.1 and above (older hosts cannot resolve the dependencies to install this version); hosts on 0.1.5 and earlier should stay on v2.0.15. Since v2.1.2 the upper bound is **`<0.3.0-0`**; the 0.3.0 line is explicitly refused. See [Supported host range](#supported-host-range) below.
|
|
186
186
|
|
|
187
|
-
> ⚠️ **For the CN 「channel verification」 400 (`code 11128`), make sure the plugin is ≥ 5.14.
|
|
187
|
+
> ⚠️ **For the CN 「channel verification」 400 (`code 11128`), make sure the plugin is ≥ 5.14.1.** From 5.13.1 the plugin rewrites the known fixed fingerprints **before the request is sent**, and 5.14.0 adds the Web-GUI paragraph's SECOND sentence (#167, the branch still reproducing on 5.13.1); 5.12.x and earlier only had the neutral-head + zero-width ladder, which measured **400 on every rung** for the same body (matrix in [#164](https://github.com/dingminhua/dsh-connect-workbuddy/issues/164)). Anyone left on an older build keeps hitting a 400 that is already fixed, and it looks like "fails every single time, restarting does nothing".
|
|
188
188
|
>
|
|
189
|
-
>
|
|
189
|
+
> ⚠️ **Do not treat the marketplace's "latest" as the schedule — it reads an index that lags.** The community marketplace serves a `dsh-plugin-catalog` snapshot, collected per package: **the snapshot built on 2026-10-30 still listed only 5.13.1 for this plugin** while npm `latest` was **5.14.1**, and a desktop host separately flags any release under 24 hours old as "waiting". Trust `npm view dsh-connect-workbuddy version` and [Releases](https://github.com/dingminhua/dsh-connect-workbuddy/releases), and follow [Update](#update) below (pin the **exact version**); or hand [`docs/PINNED-UPGRADE-LAG.md`](docs/PINNED-UPGRADE-LAG.md) §3 to an AI and let it run.
|
|
190
|
+
>
|
|
191
|
+
> The other thing that stops people from upgrading: **pnpm's 24-hour release-age window** (`minimumReleaseAge`) **silently resolves a floating range back to the previous version**. When you need this fixed right now, pin the **exact version**: `dsh-connect-workbuddy@5.14.1`.
|
|
190
192
|
|
|
191
193
|
### Supported host range
|
|
192
194
|
|
|
@@ -204,9 +206,12 @@ DSH's shipped gate (`evaluatePluginCompatibility` in `packages/boot/app-boot/src
|
|
|
204
206
|
> npm semver admits a prerelease only when a comparator carries a prerelease on the **same `major.minor.patch` tuple**.
|
|
205
207
|
> The declared `>=0.1.7-rc.1 <0.3.0-0` opens only `0.1.7-x`, while `<0.3.0-0` excludes the `0.3.0` tuple — so versions such as
|
|
206
208
|
> **`0.2.1-rc.1` satisfy neither comparator** (`satisfies('0.2.1-rc.1', …) === false`).
|
|
207
|
-
> The practical impact is small: the host gate only evaluates the `@deepseek-ai/dsh*` namespace
|
|
208
|
-
>
|
|
209
|
-
>
|
|
209
|
+
> The practical impact is small: the host gate only evaluates the `@deepseek-ai/dsh*` namespace (other peers are skipped entirely), and
|
|
210
|
+
> it evaluates those peer ranges with `semver.satisfies(rt, range, { includePrerelease: true })` — so rc hosts are **admitted**: the gate
|
|
211
|
+
> really does check, it just lets prereleases through (the corrected attribution is in
|
|
212
|
+
> [`docs/DSH-0.2.1-RC3-FOLLOWUP.md`](docs/DSH-0.2.1-RC3-FOLLOWUP.md) §3). It is also **not fixable by editing the range** — no static range
|
|
213
|
+
> can cover "any `0.2.x` prerelease"; five candidate formulations were computed and refuted (see CHANGELOG 4.7.0). It resolves itself once
|
|
214
|
+
> the host ships `0.2.1` GA.
|
|
210
215
|
|
|
211
216
|
> The upper bound is written `<0.3.0-0`, not `<0.3.0`: **every DSH tag to date is a prerelease** (`-alpha.N` / `-rc.N`), so `<0.3.0` alone would admit the entire unverified `0.3.0-alpha.1` / `0.3.0-rc.1` line, leaving the guardrail inert — exactly the defect shape 2.1.2 fixes. `tests/dsh-line.spec.ts` guards this contract, including the prerelease cliff.
|
|
212
217
|
|
|
@@ -226,7 +231,9 @@ npm install dsh-connect-workbuddy
|
|
|
226
231
|
|
|
227
232
|
Restart the DSH process after install/update/uninstall.
|
|
228
233
|
|
|
229
|
-
> ⚠️ **Pin an EXACT version for a fresh release; do not rely on a floating range.** pnpm enforces a release-age window (`minimumReleaseAge`, 24h by default), so shortly after a version ships a floating range is **silently resolved back to the previous one**: `^4.4.2` still resolves to `4.4.2`, while an exact `4.4.3` gets `4.4.3` immediately. Both spellings "install successfully", so the symptom is *"the upgrade command finished and the version did not change"* — easily misread as a failed release. Measured (isolated directory,
|
|
234
|
+
> ⚠️ **Pin an EXACT version for a fresh release; do not rely on a floating range.** pnpm enforces a release-age window (`minimumReleaseAge`, 24h by default), so shortly after a version ships a floating range is **silently resolved back to the previous one**: `^4.4.2` still resolves to `4.4.2`, while an exact `4.4.3` gets `4.4.3` immediately. Both spellings "install successfully", so the symptom is *"the upgrade command finished and the version did not change"* — easily misread as a failed release. Measured twice (isolated directory, `--lockfile-only`, touching nothing of the user's environment):
|
|
235
|
+
>
|
|
236
|
+
> First run, pnpm 12.3.4 (the 4.4.x era):
|
|
230
237
|
>
|
|
231
238
|
> | Target spelling | Resolved version |
|
|
232
239
|
> | --- | --- |
|
|
@@ -234,7 +241,16 @@ Restart the DSH process after install/update/uninstall.
|
|
|
234
241
|
> | exact `4.4.3` | **4.4.3** |
|
|
235
242
|
> | floating + `--config.minimum-release-age=0` | **4.4.3** |
|
|
236
243
|
>
|
|
237
|
-
>
|
|
244
|
+
> Second run, pnpm 10.34.5 (2026-10-30, 5.14.1 eleven hours old, `minimumReleaseAge=1440` set explicitly):
|
|
245
|
+
>
|
|
246
|
+
> | Target spelling | Result |
|
|
247
|
+
> | --- | --- |
|
|
248
|
+
> | floating `^5.13.1` | resolves to **5.13.1**, **no error** ⚠️ |
|
|
249
|
+
> | exact `5.14.1` | `ERR_PNPM_NO_MATURE_MATCHING_VERSION`, refused outright |
|
|
250
|
+
> | exact `5.14.1` + `minimumReleaseAgeExclude: [dsh-connect-workbuddy]` in the profile's `pnpm-workspace.yaml` | **5.14.1** ✅ |
|
|
251
|
+
> | setting absent (pnpm default) | floating `^5.13.1` → **5.14.1** ✅ |
|
|
252
|
+
>
|
|
253
|
+
> To pick up a new release right away, pin it: `dsh plugin --profile desktop add dsh-connect-workbuddy@5.14.1`; if the window refuses it, see [`docs/PINNED-UPGRADE-LAG.md`](docs/PINNED-UPGRADE-LAG.md) §2 for the two routes (wait out the window, or add that exclude line). A floating range catches up once the window elapses.
|
|
238
254
|
|
|
239
255
|
The plugin also runs under **Web** and **TUI**; pick the command matching your profile:
|
|
240
256
|
|
|
@@ -243,6 +259,39 @@ dsh plugin --profile web add dsh-connect-workbuddy # Web
|
|
|
243
259
|
dsh plugin --profile dsh-tui add dsh-connect-workbuddy # TUI
|
|
244
260
|
```
|
|
245
261
|
|
|
262
|
+
## Update
|
|
263
|
+
|
|
264
|
+
> **Whatever the marketplace shows is a snapshot; `npm view dsh-connect-workbuddy version` and [Releases](https://github.com/dingminhua/dsh-connect-workbuddy/releases) are the truth.**
|
|
265
|
+
> Measured 2026-10-30: the community index (`dsh-plugin-catalog`, built that same day) still listed only **5.13.1** for this plugin while npm `latest` was **5.14.1**. Lag happens **per package** — the same index carried `@changfenhuang/dsh-genui` 0.15.0, `dsh-client-auto-continue` 0.17.0 and `billion-context` 0.1.209 (all current) — so "that other package shows a new version" proves nothing about this one.
|
|
266
|
+
|
|
267
|
+
**Manual update (one command)**:
|
|
268
|
+
|
|
269
|
+
```sh
|
|
270
|
+
dsh plugin --profile desktop add dsh-connect-workbuddy@5.14.1
|
|
271
|
+
dsh plugin --profile desktop exec dsh-connect-workbuddy doctor # first line carries the version
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**Let an AI do it**: paste the block below into DSH; it runs the version check, the install and the verification, and comes back to you only when a pnpm setting has to change:
|
|
275
|
+
|
|
276
|
+
```text
|
|
277
|
+
Update dsh-connect-workbuddy to the latest version on npm. Follow these steps, do not skip any:
|
|
278
|
+
1) run `npm view dsh-connect-workbuddy version` first to learn what latest currently is;
|
|
279
|
+
2) install the EXACT version: `dsh plugin --profile desktop add dsh-connect-workbuddy@<version from step 1>`;
|
|
280
|
+
3) if it fails with ERR_PNPM_NO_MATURE_MATCHING_VERSION, do NOT fall back to a floating range
|
|
281
|
+
(that silently installs the older build); tell me how long this version still has to wait, or ask
|
|
282
|
+
whether to add `minimumReleaseAgeExclude: dsh-connect-workbuddy` to the profile's pnpm-workspace.yaml;
|
|
283
|
+
4) afterwards run `dsh plugin --profile desktop exec dsh-connect-workbuddy doctor`
|
|
284
|
+
and read the version on its first line back to me;
|
|
285
|
+
5) remind me to restart/reload DSH (the bundle's host half loads at startup).
|
|
286
|
+
My profile is desktop.
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
> Do not tell an AI to "retry until it succeeds": inside the window every retry gets the same refusal, and pnpm offers no one-shot bypass.
|
|
290
|
+
|
|
291
|
+
**Confirming it actually changed**: the first line of `doctor` (`WorkBuddy Connect 5.14.1 on …`), or the top-level `version` in `status --json`. **Do not** look for `/workbuddy status` (the slash command this plugin registers is `/codebuddy`, which forwards to the local CodeBuddy CLI), and **do not** look for a version row in the card's Diagnostics tab (it has exactly eight rows — region, domain, serving account, sign-in expiry, catalog size, enabled count, account pool, last failure — and no version). Restart or reload, then judge.
|
|
292
|
+
|
|
293
|
+
Details (how the market index and the pnpm window each stop you, and both ways through) are in [`docs/PINNED-UPGRADE-LAG.md`](docs/PINNED-UPGRADE-LAG.md); the same text is posted as a long-term pinned issue.
|
|
294
|
+
|
|
246
295
|
## Platform support
|
|
247
296
|
|
|
248
297
|
All three platforms are supported targets; the differences below are **implementation facts**, not polite ways of saying "not yet supported". To change this section, change [`docs/WINDOWS.md`](docs/WINDOWS.md) too — that file is where platform-related constraints are registered.
|
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
- **插件卡片 tab 切换** —— 设置卡片顶部为「国内版 / 国际版」两个 tab,各含独立的账号池与模型管理;每个 tab 的账号、目录、勾选与未保存草稿完全隔离——在一个 tab 里切账号或刷新模型,不会触碰另一边的运行时目录与会话。
|
|
41
41
|
- **可单独关闭任一版本供应商** —— 每个 tab 右侧有一个勾选框(默认都勾选)。**取消勾选即把该供应商从 DSH 模型选择器里彻底撤掉**(不是只藏起 tab):它的路由与「设置 → 模型」页的目录条目一并摘除,启动时也不再为它发无用请求。账号、积分、目录与勾选**全部保留**,重新勾选即完整恢复。典型用法是「我根本用不到国际版」——关掉它,让选择器干净。注意:若某会话此前选中的正是被关闭供应商的模型,该会话再调用会报 `NO_ADAPTER`,卡片会在关闭态的 tab 上明确提示这一点,不会静默失败。
|
|
42
42
|
- **设置卡片分四栏:账号 / 模型 / 能力 / 诊断** —— 原来这张卡片是一列 3000 多像素的长页,把两类互不相干的事情混排在一起:**运维账号**(谁在服务、还剩多少、切换顺序、签到日志)与**编辑目录**(勾模型、上下文预算、推理档、关闭思考)。它们连提交语义都不同——账号改动点一下即生效,目录改动是草稿、必须点保存——所以「保存」按钮常常离它要保存的东西隔着三屏。现在分栏,一栏只做一件事;**切栏既不会重新拉取,也不会丢掉未保存的目录草稿**(非活动栏留在页面里,只是通过 `hidden` 不可见,因此也不进入无障碍树)。第三栏「能力」放的是**授权类开关**——允许调用本机 CodeBuddy CLI、允许模型自主生图、积分读数显示在哪——它们点一下即生效、从不经过保存,而且每一个都留着长自己子选项的位置(CLI 的工作目录、生图的尺寸或价格上限),所以不挤在账号表格上方那一小条里。第四栏「诊断」是只读的事实表:当前区域、上游域名、真正在服务的账号名、登录有效期、目录里共几个模型、启用了几个、账号池开关,以及上游最近一次错误的**原文**(原样引用,不翻译、不概括)——这一栏存在的唯一理由就是能被整段贴进问题反馈,所以里面没有任何会改设置的控件。
|
|
43
|
-
- **模型管理** ——
|
|
43
|
+
- **模型管理** —— 从上游刷新完整模型目录,逐项勾选启用或禁用;刷新是草稿操作,点保存才生效。上游同时给出积分倍率、上下文/输出上限与推理档位;**每个模型行上有一个「默认推理强度」选择器**(issue #179):请求未指定档位时该模型用哪一档——新对话、压缩摘要、会话标题都属于这类请求。选项就是该模型自己声明的那些档位(名册里 `deepseek`/`glm-5.3` 是 `low/high/max`,`glm-5.2` 是 `high/xhigh`,`space-bunny` 有五个,所以只能逐模型选、不能全局一个值),第一项是「默认(high)」——括号里写明上游为这个模型声明的档位。**这一行同时也是原来那行只读「推理强度:…」的替代**——选择器已经列全了档位,不必再多说一遍。**「关闭思考」不在这里**,它仍是下面那个 `off` 勾选框的职责。**图片输入按核验表判定**:刷新时只有核验表收录为原生多模态的模型才自动勾选,写明纯文本的不勾,**没有资料的 id 一律按「未核实」不猜**(上游的 `supportsImages` 是平台侧声明,不等于模型能看图,不参与判定)。核验表分两档证据——厂商文档(13 条)与**上游目录自述**(2 条:`space-bunny`、`minimax-m3.1-flash`,各自的描述里写着「原生多模态」)——但新增 id 仍要求厂商来源,详见 [docs/DESIGN.md](docs/DESIGN.md) 的「图片输入」一节。用户仍可随时手动勾选,勾选状态保存在本地。**每个模型行还有「可关闭思考」(`off`)勾选框**:决定要不要为该模型提供「完全关闭思考」这一档位。默认由内置复核名单给出(上游声称可关闭、实测却对它返回 HTTP 400 的 9 个模型默认不勾),你可以按模型改勾/取消——**取消**用于当前不需要该档位的模型;**名单内那 9 个模型不可勾选**(行上标「拒绝 off」,悬停有说明):该档位被勾上后不只是多一个选项,pi-ai 会把它当作**所有未指定档位请求的默认值**,压缩摘要请求正属于这一类,勾一次等于让该模型的每一次 `/compact` 都 400 / 11133(issue #83)。名单外的模型仍完全由你的勾选与自身声明决定;首次保存即写入你的勾选,之后不再重算。
|
|
44
44
|
|
|
45
45
|
<img src="docs/assets/dsh-connect-workbuddy-model-picker.png" width="420" alt="DSH 模型选择器:同一枚活动徽标(夜间折扣 / Free now)画在模型名之后(无账号信息)" />
|
|
46
46
|
- **模型行上的活动徽标** —— 上游的模型目录里带着活动信息(免费试用、夜间折扣、限时免费、订阅优先等),插件**不新增任何请求**(复用刷新模型时本来就会拉的那份目录配置),直接在模型行上画一枚胶囊徽标:颜色与文案都由上游给出,悬停能看到上游的活动说明、活动的起止日期与每日生效时段(例如「夜间 23:00–7:50」)。**徽标是「此刻」的函数**:活动只在它自己声明的时间窗内显示,窗口一关徽标就自己消失;跨零点的时段按上游声明的时区判断;同一个模型命中多条活动时只画优先级最高的那条。上游没给结束时间的权益标记(如「订阅优先」)则常驻,直到上游把它下架。**徽标只是提示,不改变模型行为**——它标注的是上游此刻给出的活动与折扣,不替你做选择。**同一枚徽标也出现在 DSH 的模型选择器里**:菜单每一行都在模型名之后、队列图标之前画同样的胶囊 —— 菜单行用上游的短词(「夜间」「限免」),输入框旁那个触发器用完整词(「夜间折扣」「独家限免」);判定用的是同一份数据、同一个时钟,因为选模型的那一屏正好是最该看到「现在免费吗」的地方。(两个区域的上游目录都下发活动字段:国内版按 App 身份读 `/v3/config` 时带 8 条促销 + 1 条等级标记,国际版带 5 条 + 1 条;取证与判定规则见 [docs/PROMO_BADGE.md](docs/PROMO_BADGE.md)。)
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
- **视频生成工具 `workbuddy_draw_video`(国内版 / 国际版,默认关闭,逐区授权)** —— 把 WorkBuddy 的视频能力交给 agent:「帮我把这段描述做成 5 秒视频」,模型自己调用工具,跑完把 **mp4 的本地路径**贴回来(视频是二进制,只回 `file://` 路径并渲染成链接,不塞进对话体)。**它和生图不是一个形状**:视频是**异步任务**,`POST /v2/videos/generations` 只回一个 `taskId`,插件再轮询 `POST /v2/videos/tasks` 直到 `completed`(5 秒一次、上限 10 分钟),最后把产物下载到 `os.tmpdir()/workbuddy-video-gen/`。**同一个工具也做图生视频**:给 `image` 参数(与生图同一套输入形态)就切到 `image-to-video` 标签组并按输入图决定比例,不给就是文生视频——**模式由「有没有图」唯一决定**。其余参数:`seconds`(默认 5,须在该模型 `[min,max]` 内)、`resolution`(不在模型 `resolutions` 内会回落默认值并给出告警)、`aspect_ratio`(仅文生视频,`16:9`/`9:16`/`1:1`)、`negative_prompt`、`enable_audio`。**默认不开启**、逐区勾选「允许模型自主生视频」,不勾时工具**根本不注册**;失败后 60 秒冷却,但参数错、没登录、读不到输入图、以及**上游在建任务之前就退回的 4xx**(它明说没扣积分)这类**一分钱没花**的失败不进冷却——改完参数可以立刻重试,不用白等一分钟;上游若在拒绝文案里报了时长区间(`supported range: 4-15 seconds`),插件会记住它,下次同样的越界值直接在本地拦下。**模型可按模式钉住**:卡片「能力」栏在视频开关下方按文生 / 图生各给一行选择框,写进 `preferredVideoModels`(按区、按标签存);它是**软约束**——显式 `model` 写错照旧报错,钉住的模型若已从目录里消失则静默回落到组内第一个。两个区的模型目录不同(国内区 `kling-video-v3` / `minimax-video-h3`,国际区 `kling-video-v3`),id 不跨区。**实测单价(国内区)**:图生视频 4 秒 57.14(`minimax-video-h3`)/ 68.57(`kling-video-v3`),文生视频 5 秒 71.43 / 128.57 积分;**产物实测**是 H.264 + AAC、24fps 的 `1280x720` 或 `1344x768`(没有 1080p,`resolution: "1080p"` 会被拒并回落默认),视频码率 3.7–11.5 Mbps,秒数以 24fps 帧数为准(请求 5 秒得 5.09–5.17 秒)。取证、协议与联调记录见 [docs/VIDEO_GEN.md](docs/VIDEO_GEN.md)。
|
|
54
54
|
|
|
55
55
|
- **本机账号切换** —— 自动发现 WorkBuddy 桌面端留下的多个登录凭据,可按区域切换账号;Token 不写入 DSH 设置。
|
|
56
|
-
- **对话输入框积分读数(国内版 / 国际版自动切换)** —— 对话输入框左侧工具栏常驻一行可点击的读数(如 `WB CN · 1,072`),**按本会话当前选中的模型自动切换区域**:选中 WB CN 的模型就显示国内版余额,选中 WB AI 的模型就显示国际版余额;用其它 provider 的模型时该读数完全不出现。每 5 分钟自动刷新,也可点开手动刷新。点击读数弹出面板:**按账号列出的余额表格**,点某一行即可切换到该账号(与卡片上的账号选择是同一个设置);**当前不能用的账号会在行内标出原因**(如「被限流」「积分耗尽」),标注口径与账号池表格一致。面板里的余额只在打开时才去查,因此不会让 5 分钟的后台刷新按账号数翻倍。要关掉某一边,就在卡片对应区域的 tab
|
|
56
|
+
- **对话输入框积分读数(国内版 / 国际版自动切换)** —— 对话输入框左侧工具栏常驻一行可点击的读数(如 `WB CN · 1,072`),**按本会话当前选中的模型自动切换区域**:选中 WB CN 的模型就显示国内版余额,选中 WB AI 的模型就显示国际版余额;用其它 provider 的模型时该读数完全不出现。每 5 分钟自动刷新,也可点开手动刷新。点击读数弹出面板:**按账号列出的余额表格**,点某一行即可切换到该账号(与卡片上的账号选择是同一个设置);**当前不能用的账号会在行内标出原因**(如「被限流」「积分耗尽」),标注口径与账号池表格一致。面板里的余额只在打开时才去查,因此不会让 5 分钟的后台刷新按账号数翻倍。要关掉某一边,就在卡片对应区域的 tab 下取消勾选「在输入框工具栏显示积分读数」;该区域的供应商被关闭时,读数不显示、开关也置灰。同一个开关下面还有「显示位置」这一行:默认在输入框工具栏(与模型选择同一行,位于模型选择器左侧、与权限切换器隔开),改成「输入框下方」后读数落到轮次/步数/token 统计那一行——装了别的插件把工具栏座位占掉时用得上。
|
|
57
57
|
|
|
58
58
|
<img src="docs/assets/dsh-connect-workbuddy-composer-credits.png" width="360" alt="对话输入框积分面板:国内版账号余额表格、一键测试按钮与最近刷新时间(账号名已打码)" />
|
|
59
59
|
- **账号池:多账号批量操作与自动换号** —— 把要用的账号勾进池子,即可**签到勾选的账号**、**测试勾选的账号 × 你指定的模型**(默认挑该区域倍率为 0 的免费模型);开启**启用账号池**即同时打开**自动换号**——某个请求在上游失败时,插件会依次用池内其他可用账号重试,直到有一个接手、或全部不可用为止,然后才把错误报给你。成员**默认一个都不含**、必须显式勾选(「本机有登录」不等于「要拿它去签到和花积分」);**开启后由池子的排序决定每一次请求用谁**(可用优先,其次快过期先花、积分高、凭据更新的);失败时依次用池内其他可用账号重试。排序**从不写回你保存的选择**——关掉开关,你选定的账号立刻完全生效。只有请求本身不合法(HTTP 400)不换号:同一个请求体换谁都是 400,重试只会白等。**这个开关只管「插件要不要自己决定」**:关掉它,插件不再替你选账号、也不再替你换号,但**签到与测试仍然可用**(它们是手动动作)。测试走的是与模型行同一套真实体积探测,因此**有真实成本**;**不提供自动签到**——签到会真实领取奖励,只在你点按钮时执行。详见[账号池](#账号池)。
|
|
@@ -196,7 +196,9 @@ DSH PiAiAdapter(每个 provider 一套)
|
|
|
196
196
|
|
|
197
197
|
> ⚠️ **国内区的「渠道校验」400(`code 11128`)请确保插件 ≥ 5.14.0。** 5.13.1 起会在**发出请求之前**按句改写已知的固定指纹,5.14.0 又补上了 Web GUI 段落的第二句(#167,5.13.1 上仍然必现的那一支);5.12.x 及更早只有「中性头 + 零宽扰动」三级救援,在同一份 body 上实测**全部 400**(对照矩阵见 [#164](https://github.com/dingminhua/dsh-connect-workbuddy/issues/164))。停在旧版的人会反复撞上这个已经修好的 400,而表现是「每次必现、重启也没用」。
|
|
198
198
|
>
|
|
199
|
-
>
|
|
199
|
+
> ⚠️ **别把插件市场里那个「最新」当日程表——它读的是一份会落后的索引。** 社区市场走 `dsh-plugin-catalog` 的快照(按包各自收录),**2026-10-30 那份快照里本插件只到 5.13.1**,而 npm `latest` 已是 **5.14.1**;桌面宿主另有「24 小时内发布的版本先标成「新版等待中」」这条规则。以 `npm view dsh-connect-workbuddy version` 与 [Releases](https://github.com/dingminhua/dsh-connect-workbuddy/releases) 为准,按下方[「更新」](#更新)一节做(写**精确版本号**),或者把 [`docs/PINNED-UPGRADE-LAG.md`](docs/PINNED-UPGRADE-LAG.md) §3 那段话丢给 AI 让它替你跑。
|
|
200
|
+
>
|
|
201
|
+
> 另一个会让人升级不到的原因:**pnpm 的 24 小时新版本等待期**(`minimumReleaseAge`)会把浮动范围**静默压回上一个版本**。需要立刻修复时请写**精确版本号**:`dsh plugin --profile desktop add dsh-connect-workbuddy@5.14.1`(原因见下方「刚发布的版本请写精确版本号」那条)。
|
|
200
202
|
|
|
201
203
|
### 受支持的宿主范围
|
|
202
204
|
|
|
@@ -214,7 +216,10 @@ DSH 的随包门禁(`packages/boot/app-boot/src/plugin-compatibility.ts` 的 `
|
|
|
214
216
|
> npm semver 规定:预发布版本只有当一个比较器**在同一条 `major.minor.patch` 元组上**带预发布标签时才被放行。
|
|
215
217
|
> 现声明 `>=0.1.7-rc.1 <0.3.0-0` 只对 `0.1.7-x` 开口,`<0.3.0-0` 又排除了 `0.3.0` 元组,因此
|
|
216
218
|
> **`0.2.1-rc.1` 这类版本两条比较器都不满足**(`satisfies('0.2.1-rc.1', …) === false`)。
|
|
217
|
-
> 实际影响很小:宿主门禁只校验 `@deepseek-ai/dsh*`
|
|
219
|
+
> 实际影响很小:宿主门禁只校验 `@deepseek-ai/dsh*` 命名空间(其余 peer 不参与),而它对这批 peer
|
|
220
|
+
> 的范围求值用的是 `semver.satisfies(rt, range, { includePrerelease: true })`,所以 rc 宿主照旧
|
|
221
|
+
> **放行**——闸门确实校验了,只是用 `includePrerelease` 放过(归因订正见
|
|
222
|
+
> [`docs/DSH-0.2.1-RC3-FOLLOWUP.md`](docs/DSH-0.2.1-RC3-FOLLOWUP.md) §3)。
|
|
218
223
|
> 这**不是**靠改范围能解决的问题——任何静态范围都覆盖不了「任意 `0.2.x` 预发布」,已逐一实算否证五种写法
|
|
219
224
|
> (见 CHANGELOG 4.7.0)。等宿主发 `0.2.1` 正式版即自然成立。
|
|
220
225
|
|
|
@@ -238,7 +243,9 @@ npm install dsh-connect-workbuddy
|
|
|
238
243
|
|
|
239
244
|
安装、更新或卸载 bundle 后,需要重启对应的 DSH 进程。
|
|
240
245
|
|
|
241
|
-
> ⚠️ **刚发布的版本请写「精确版本号」,不要依赖浮动范围。** pnpm 有**新版本等待期**(`minimumReleaseAge`,默认 24 小时),所以一个版本刚发布不久时,浮动范围会被**静默压回上一个版本**:声明 `^4.4.2` 时它仍解析到 `4.4.2`,而写精确的 `4.4.3` 则立即拿到 `4.4.3
|
|
246
|
+
> ⚠️ **刚发布的版本请写「精确版本号」,不要依赖浮动范围。** pnpm 有**新版本等待期**(`minimumReleaseAge`,默认 24 小时),所以一个版本刚发布不久时,浮动范围会被**静默压回上一个版本**:声明 `^4.4.2` 时它仍解析到 `4.4.2`,而写精确的 `4.4.3` 则立即拿到 `4.4.3`。由于**两种写法都「安装成功」**,表现是「升级命令跑完了、版本却没变」,很容易被误判成发布失败。已实测两轮(隔离目录、`--lockfile-only`,未触碰用户环境):
|
|
247
|
+
>
|
|
248
|
+
> 第一轮 pnpm 12.3.4(4.4.x 当时):
|
|
242
249
|
>
|
|
243
250
|
> | 目标写法 | 解析到的版本 |
|
|
244
251
|
> | --- | --- |
|
|
@@ -246,7 +253,16 @@ npm install dsh-connect-workbuddy
|
|
|
246
253
|
> | 精确 `4.4.3` | **4.4.3** |
|
|
247
254
|
> | 浮动 + `--config.minimum-release-age=0` | **4.4.3** |
|
|
248
255
|
>
|
|
249
|
-
>
|
|
256
|
+
> 第二轮 pnpm 10.34.5(2026-10-30,5.14.1 发布 11 小时,显式设 `minimumReleaseAge=1440`):
|
|
257
|
+
>
|
|
258
|
+
> | 目标写法 | 结果 |
|
|
259
|
+
> | --- | --- |
|
|
260
|
+
> | 浮动 `^5.13.1` | 解析到 **5.13.1**,**不报错** ⚠️ |
|
|
261
|
+
> | 精确 `5.14.1` | `ERR_PNPM_NO_MATURE_MATCHING_VERSION`,明确拒绝 |
|
|
262
|
+
> | 精确 `5.14.1` + profile `pnpm-workspace.yaml` 里 `minimumReleaseAgeExclude: [dsh-connect-workbuddy]` | **5.14.1** ✅ |
|
|
263
|
+
> | 不设该项(pnpm 默认) | 浮动 `^5.13.1` → **5.14.1** ✅ |
|
|
264
|
+
>
|
|
265
|
+
> 想立刻用上新版,请写精确版本号:`dsh plugin --profile desktop add dsh-connect-workbuddy@5.14.1`;若被等待期拒绝,见 [`docs/PINNED-UPGRADE-LAG.md`](docs/PINNED-UPGRADE-LAG.md) §2 的两条路(等窗口过去,或加那行 exclude)。过了等待期之后,浮动范围会自然跟上。
|
|
250
266
|
|
|
251
267
|
插件在 **Web**、**Desktop**、**TUI** 三种界面下均可运行,按你使用的 profile 选择对应命令:
|
|
252
268
|
|
|
@@ -255,6 +271,39 @@ dsh plugin --profile web add dsh-connect-workbuddy # Web
|
|
|
255
271
|
dsh plugin --profile dsh-tui add dsh-connect-workbuddy # TUI
|
|
256
272
|
```
|
|
257
273
|
|
|
274
|
+
## 更新
|
|
275
|
+
|
|
276
|
+
> **市场里显示几就是几,那是快照;`npm view dsh-connect-workbuddy version` 与 [Releases](https://github.com/dingminhua/dsh-connect-workbuddy/releases) 才是准的。**
|
|
277
|
+
> 2026-10-30 实测:社区市场索引(`dsh-plugin-catalog` 当天构建)里本插件仍只列到 **5.13.1**,npm `latest` 已是 **5.14.1**。滞后是**按包各自发生**的——同一份索引里 `@changfenhuang/dsh-genui` 0.15.0、`dsh-client-auto-continue` 0.17.0、`billion-context` 0.1.209 都是新的,所以「别人那个包有新版」不能证明「你这个包也该有」。
|
|
278
|
+
|
|
279
|
+
**手动更新(一条命令)**:
|
|
280
|
+
|
|
281
|
+
```sh
|
|
282
|
+
dsh plugin --profile desktop add dsh-connect-workbuddy@5.14.1
|
|
283
|
+
dsh plugin --profile desktop exec dsh-connect-workbuddy doctor # 第一行就是版本
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**让 AI 更新**:把下面这段整段发给 DSH 里的 AI,它会把版本号、安装与核对都跑完,只在需要改 pnpm 配置时回来问你:
|
|
287
|
+
|
|
288
|
+
```text
|
|
289
|
+
把 dsh-connect-workbuddy 更新到 npm 上的最新版,按下面的步骤来,不要跳步:
|
|
290
|
+
1) 先跑 `npm view dsh-connect-workbuddy version` 确认当前 latest 是多少;
|
|
291
|
+
2) 用精确版本号安装:`dsh plugin --profile desktop add dsh-connect-workbuddy@<上一步得到的版本>`;
|
|
292
|
+
3) 如果报 ERR_PNPM_NO_MATURE_MATCHING_VERSION,不要改用浮动范围硬装(那会静默装回旧版),
|
|
293
|
+
告诉我这个版本还要等多久,或者问我是否要在 profile 的 pnpm-workspace.yaml 里加
|
|
294
|
+
minimumReleaseAgeExclude: dsh-connect-workbuddy;
|
|
295
|
+
4) 装完跑 `dsh plugin --profile desktop exec dsh-connect-workbuddy doctor`,
|
|
296
|
+
把第一行的版本号念给我听;
|
|
297
|
+
5) 提醒我重启/重载 DSH(bundle 的 host 半边在启动时加载)。
|
|
298
|
+
我的 profile 是 desktop。
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
> 别让 AI「反复重试到成功」:等待期内重试只会拿到同一个拒绝,pnpm 没有一次性的绕过开关。
|
|
302
|
+
|
|
303
|
+
**怎么确认真的升上去了**:`doctor` 的第一行(`WorkBuddy Connect 5.14.1 on …`),或 `status --json` 顶层的 `version`。**不要**找 `/workbuddy status`(本插件注册的斜杠命令是 `/codebuddy`,用于转发本机 CodeBuddy CLI),也**不要**在卡片「诊断」栏找版本号(那一栏只有区域 / 域名 / 服务账号 / 登录有效期 / 目录模型数 / 已启用数 / 账号池 / 最近一次失败八行,没有版本行)。装完**重启或重载**再判断。
|
|
304
|
+
|
|
305
|
+
细节(市场索引与 pnpm 等待期分别怎么拦住你、两条绕过路径)见 [`docs/PINNED-UPGRADE-LAG.md`](docs/PINNED-UPGRADE-LAG.md),同一份正文也贴成了长期置顶 issue。
|
|
306
|
+
|
|
258
307
|
## 平台支持
|
|
259
308
|
|
|
260
309
|
三个平台都是受支持目标;下列差异是**实现事实**,不是「尚未支持」的委婉说法。要改这一节,请同时改 [`docs/WINDOWS.md`](docs/WINDOWS.md)——那份文件是平台相关约束的登记处。
|
package/lib/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as WorkBuddyCredentialStore, D as defaultDesktopAuthDirs, E as defaultDesktopAuthCandidates, I as workbuddyOwnAuthPath, M as legacyWorkbuddyOwnAuthPath, Qt as hostPeersInfo, Rt as WORKBUDDY_APP_EXECUTABLE_ENV, S as WORKBUDDY_AUTH_FILE_ENV, V as WorkBuddyUpstreamClient, Wt as findWorkbuddyAppExecutableWithSource, a as readHostHeartbeat, c as WORKBUDDY_CONNECT_VERSION, d as rosterSeenPath, f as piAiRuntimeInfo, m as FALLBACK_WORKBUDDY_MODELS_GLOBAL, o as workbuddyHostHeartbeatPath, p as FALLBACK_WORKBUDDY_MODELS, r as isHeartbeatProcessAlive, u as describeRosterSeenPayload } from "./host-heartbeat-
|
|
2
|
+
import { C as WorkBuddyCredentialStore, D as defaultDesktopAuthDirs, E as defaultDesktopAuthCandidates, I as workbuddyOwnAuthPath, M as legacyWorkbuddyOwnAuthPath, Qt as hostPeersInfo, Rt as WORKBUDDY_APP_EXECUTABLE_ENV, S as WORKBUDDY_AUTH_FILE_ENV, V as WorkBuddyUpstreamClient, Wt as findWorkbuddyAppExecutableWithSource, a as readHostHeartbeat, c as WORKBUDDY_CONNECT_VERSION, d as rosterSeenPath, f as piAiRuntimeInfo, m as FALLBACK_WORKBUDDY_MODELS_GLOBAL, o as workbuddyHostHeartbeatPath, p as FALLBACK_WORKBUDDY_MODELS, r as isHeartbeatProcessAlive, u as describeRosterSeenPayload } from "./host-heartbeat-891AaZGW.js";
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
import { realpathSync } from "node:fs";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
package/lib/client.js
CHANGED
|
@@ -278,6 +278,7 @@ window.__ModuleLoader__.load({
|
|
|
278
278
|
"imageToolEnabled",
|
|
279
279
|
"offModelIds",
|
|
280
280
|
"contextBudgets",
|
|
281
|
+
"defaultThinkingEfforts",
|
|
281
282
|
"pool"
|
|
282
283
|
]);
|
|
283
284
|
/** A non-null, non-array object: the only shape a slot or a level can have. */
|
|
@@ -4551,6 +4552,40 @@ window.__ModuleLoader__.load({
|
|
|
4551
4552
|
});
|
|
4552
4553
|
}
|
|
4553
4554
|
//#endregion
|
|
4555
|
+
//#region src/default-effort.ts
|
|
4556
|
+
/** The tiers DSH/pi-ai speak. Mirrors `THINKING_LEVELS` in `./adapter.ts`. */
|
|
4557
|
+
const THINKING_LEVELS = [
|
|
4558
|
+
"minimal",
|
|
4559
|
+
"low",
|
|
4560
|
+
"medium",
|
|
4561
|
+
"high",
|
|
4562
|
+
"xhigh",
|
|
4563
|
+
"max"
|
|
4564
|
+
];
|
|
4565
|
+
/**
|
|
4566
|
+
* The tiers a model actually declares, in DSH's order, without `off`.
|
|
4567
|
+
*
|
|
4568
|
+
* `off` is excluded deliberately: it is a DIFFERENT question with its own owner
|
|
4569
|
+
* (`offModelIds` / `src/off-thinking.ts`, issue #34), and a "default thinking
|
|
4570
|
+
* effort" of `off` would silently re-open the #83 defect for every caller that
|
|
4571
|
+
* names no tier.
|
|
4572
|
+
*/
|
|
4573
|
+
function declaredEfforts(reasoning) {
|
|
4574
|
+
const supported = reasoning?.supportedEfforts ?? [];
|
|
4575
|
+
return THINKING_LEVELS.filter((level) => supported.includes(level));
|
|
4576
|
+
}
|
|
4577
|
+
/**
|
|
4578
|
+
* The tier list the card offers for one model.
|
|
4579
|
+
*
|
|
4580
|
+
* Exactly the model's own declared tiers — no localising, no cross-model
|
|
4581
|
+
* unification. The vocabularies genuinely differ (`low/high/max` on deepseek,
|
|
4582
|
+
* `high/xhigh` on glm-5.2), so a synthesized ladder would offer rows that fail,
|
|
4583
|
+
* and the upstream's names are what the user sees in every other client.
|
|
4584
|
+
*/
|
|
4585
|
+
function selectableEfforts(reasoning) {
|
|
4586
|
+
return declaredEfforts(reasoning);
|
|
4587
|
+
}
|
|
4588
|
+
//#endregion
|
|
4554
4589
|
//#region src/client/busy-icon.tsx
|
|
4555
4590
|
/** The word for a level, in the card's language, for the tooltip. */
|
|
4556
4591
|
const TITLES_ZH = {
|
|
@@ -5287,6 +5322,15 @@ window.__ModuleLoader__.load({
|
|
|
5287
5322
|
.dsm-workbuddy-model-details{display:flex;align-items:center;justify-content:space-between;gap:12px;min-width:0}
|
|
5288
5323
|
.dsm-workbuddy-model-meta{display:flex;align-items:center;gap:7px 12px;flex-wrap:wrap;color:var(--dsw-alias-label-tertiary,#999);font-size:11px;line-height:16px}
|
|
5289
5324
|
.dsm-workbuddy-model-meta-tag{padding:1px 7px;border-radius:999px;font-size:11px;line-height:15px;background:rgba(174,179,187,.11);color:var(--dsw-alias-label-secondary,#c6c9d0)}
|
|
5325
|
+
/* The per-model default thinking tier (issue #179). It lives INSIDE the meta row
|
|
5326
|
+
so it sits on the same baseline as the context/output figures it belongs with,
|
|
5327
|
+
and stays a small inline control rather than a third row of its own — the row
|
|
5328
|
+
already carries the probe buttons on its right-hand side. */
|
|
5329
|
+
.dsm-workbuddy-model-effort{display:inline-flex;align-items:center;gap:5px;cursor:pointer}
|
|
5330
|
+
.dsm-workbuddy-model-effort-select{font:inherit;font-size:11px;line-height:15px;color:var(--dsw-alias-label-secondary,#c6c9d0);background:rgba(174,179,187,.11);border:1px solid transparent;border-radius:999px;padding:1px 6px;cursor:pointer;max-width:180px}
|
|
5331
|
+
.dsm-workbuddy-model-effort-select:hover:not(:disabled){border-color:var(--dsw-alias-border-l2,#3a3d45)}
|
|
5332
|
+
.dsm-workbuddy-model-effort-select:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
|
|
5333
|
+
.dsm-workbuddy-model-effort-select:disabled{opacity:.55;cursor:default}
|
|
5290
5334
|
.dsm-workbuddy-context-budget{display:flex;align-items:center;justify-content:flex-end;gap:12px;flex:none;margin:0;padding:0;border:0;color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:11px;line-height:16px}
|
|
5291
5335
|
.dsm-workbuddy-context-budget label{display:inline-flex;align-items:center;gap:4px;cursor:pointer}
|
|
5292
5336
|
.dsm-workbuddy-context-budget input{margin:0;accent-color:var(--dsw-alias-brand-primary,#5686fe)}
|
|
@@ -7519,6 +7563,11 @@ window.__ModuleLoader__.load({
|
|
|
7519
7563
|
const budget = activeContextBudgets[id];
|
|
7520
7564
|
if (typeof budget === "number") stillBudgets[id] = budget;
|
|
7521
7565
|
}
|
|
7566
|
+
const stillEfforts = {};
|
|
7567
|
+
for (const id of freshIds) {
|
|
7568
|
+
const effort = activeDefaultEfforts[id];
|
|
7569
|
+
if (typeof effort === "string" && effort !== "") stillEfforts[id] = effort;
|
|
7570
|
+
}
|
|
7522
7571
|
setDrafts((prev) => ({
|
|
7523
7572
|
...prev,
|
|
7524
7573
|
[activeRegion]: {
|
|
@@ -7526,7 +7575,8 @@ window.__ModuleLoader__.load({
|
|
|
7526
7575
|
enabledIds: new Set(stillEnabled),
|
|
7527
7576
|
imageIds: new Set(upstreamImages),
|
|
7528
7577
|
offIds: new Set(activeOffIds),
|
|
7529
|
-
contextBudgets: stillBudgets
|
|
7578
|
+
contextBudgets: stillBudgets,
|
|
7579
|
+
defaultThinkingEfforts: stillEfforts
|
|
7530
7580
|
}
|
|
7531
7581
|
}));
|
|
7532
7582
|
} catch (error) {
|
|
@@ -7561,7 +7611,9 @@ window.__ModuleLoader__.load({
|
|
|
7561
7611
|
const activeOffIds = draft?.offIds ?? savedOffIds;
|
|
7562
7612
|
settingsScope?.getSnapshot().value;
|
|
7563
7613
|
const savedContextBudgets = status.status === "signed-in" ? status.contextBudgets ?? {} : {};
|
|
7614
|
+
const savedDefaultEfforts = status.status === "signed-in" ? status.defaultThinkingEfforts ?? {} : {};
|
|
7564
7615
|
const activeContextBudgets = draft?.contextBudgets ?? savedContextBudgets;
|
|
7616
|
+
const activeDefaultEfforts = draft?.defaultThinkingEfforts ?? savedDefaultEfforts;
|
|
7565
7617
|
const dirty = draft !== void 0;
|
|
7566
7618
|
/**
|
|
7567
7619
|
* Whether the card's inputs accept edits.
|
|
@@ -7583,7 +7635,8 @@ window.__ModuleLoader__.load({
|
|
|
7583
7635
|
enabledIds: new Set(activeEnabledIds),
|
|
7584
7636
|
imageIds: new Set(activeImageIds),
|
|
7585
7637
|
offIds: new Set(activeOffIds),
|
|
7586
|
-
contextBudgets: { ...activeContextBudgets }
|
|
7638
|
+
contextBudgets: { ...activeContextBudgets },
|
|
7639
|
+
defaultThinkingEfforts: { ...activeDefaultEfforts }
|
|
7587
7640
|
})
|
|
7588
7641
|
}));
|
|
7589
7642
|
};
|
|
@@ -7635,6 +7688,27 @@ window.__ModuleLoader__.load({
|
|
|
7635
7688
|
}
|
|
7636
7689
|
}));
|
|
7637
7690
|
};
|
|
7691
|
+
/**
|
|
7692
|
+
* Choose one model's default thinking tier (issue #179).
|
|
7693
|
+
*
|
|
7694
|
+
* `''` — the picker's first option — means "follow the upstream default", and
|
|
7695
|
+
* is written as the ABSENCE of the key rather than as an empty string. The
|
|
7696
|
+
* stored map is the whole answer the runtime reads, so an `''` entry would
|
|
7697
|
+
* persist a tier nothing declares and then silently fall back; deleting the
|
|
7698
|
+
* key keeps "no opinion" representable, which is what lets a future upstream
|
|
7699
|
+
* change to `defaultEffort` take effect for this model.
|
|
7700
|
+
*/
|
|
7701
|
+
const setDefaultThinkingEffort = (modelId, effort) => {
|
|
7702
|
+
editDraft((current) => {
|
|
7703
|
+
const next = { ...current.defaultThinkingEfforts };
|
|
7704
|
+
if (effort === "") delete next[modelId];
|
|
7705
|
+
else next[modelId] = effort;
|
|
7706
|
+
return {
|
|
7707
|
+
...current,
|
|
7708
|
+
defaultThinkingEfforts: next
|
|
7709
|
+
};
|
|
7710
|
+
});
|
|
7711
|
+
};
|
|
7638
7712
|
const discardModels = () => {
|
|
7639
7713
|
setDrafts((prev) => {
|
|
7640
7714
|
const next = { ...prev };
|
|
@@ -7882,7 +7956,8 @@ window.__ModuleLoader__.load({
|
|
|
7882
7956
|
imageModelIds: [...activeImageIds],
|
|
7883
7957
|
imageSelectionExplicit: true,
|
|
7884
7958
|
offModelIds: [...activeOffIds],
|
|
7885
|
-
contextBudgets: activeContextBudgets
|
|
7959
|
+
contextBudgets: activeContextBudgets,
|
|
7960
|
+
defaultThinkingEfforts: activeDefaultEfforts
|
|
7886
7961
|
});
|
|
7887
7962
|
if (mounted.current) setSaving(false);
|
|
7888
7963
|
discardModels();
|
|
@@ -8938,7 +9013,34 @@ window.__ModuleLoader__.load({
|
|
|
8938
9013
|
children: [
|
|
8939
9014
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.modelContext", { context: formatCapacity(model.nativeContextWindow, t("row.modelUnknown")) }) }),
|
|
8940
9015
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.modelOutput", { output: formatCapacity(model.maxTokens, t("row.modelUnknown")) }) }),
|
|
8941
|
-
|
|
9016
|
+
(() => {
|
|
9017
|
+
const efforts = selectableEfforts(model.reasoning);
|
|
9018
|
+
if (efforts.length === 0) return null;
|
|
9019
|
+
const saved = activeDefaultEfforts[model.id];
|
|
9020
|
+
const chosen = typeof saved === "string" && efforts.includes(saved) ? saved : "";
|
|
9021
|
+
const upstream = model.reasoning?.defaultEffort;
|
|
9022
|
+
const followLabel = upstream !== void 0 && efforts.includes(upstream) ? t("row.modelEffortFollowNamed", { effort: upstream }) : t("row.modelEffortFollow");
|
|
9023
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
9024
|
+
className: "dsm-workbuddy-model-effort",
|
|
9025
|
+
title: t("row.modelEffortHint"),
|
|
9026
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.modelEffortLabel") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
9027
|
+
className: "dsm-workbuddy-model-effort-select",
|
|
9028
|
+
value: chosen,
|
|
9029
|
+
disabled: !canWrite || saving,
|
|
9030
|
+
"aria-label": t("row.modelEffortAria", { model: model.name }),
|
|
9031
|
+
onChange: (event) => {
|
|
9032
|
+
setDefaultThinkingEffort(model.id, event.currentTarget.value);
|
|
9033
|
+
},
|
|
9034
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
9035
|
+
value: "",
|
|
9036
|
+
children: followLabel
|
|
9037
|
+
}), efforts.map((level) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
9038
|
+
value: level,
|
|
9039
|
+
children: level
|
|
9040
|
+
}, level))]
|
|
9041
|
+
})]
|
|
9042
|
+
});
|
|
9043
|
+
})()
|
|
8942
9044
|
]
|
|
8943
9045
|
})
|
|
8944
9046
|
}),
|
|
@@ -9345,7 +9447,8 @@ window.__ModuleLoader__.load({
|
|
|
9345
9447
|
//#endregion
|
|
9346
9448
|
//#region src/client/ComposerCredits.tsx
|
|
9347
9449
|
/**
|
|
9348
|
-
* Credit readout for the composer
|
|
9450
|
+
* Credit readout for the composer row (`conversation.input.right`, rendered
|
|
9451
|
+
* immediately before the model picker).
|
|
9349
9452
|
*
|
|
9350
9453
|
* 参考:dingminhua/dsh-connect-trae(MIT,Copyright (c) 2026 LaoDing)
|
|
9351
9454
|
* — 该项目的 `client/ComposerPoints.tsx`:读数形态(`<区域> · <余额>`)、
|
|
@@ -10967,7 +11070,11 @@ window.__ModuleLoader__.load({
|
|
|
10967
11070
|
"row.modelOffHint": "Offer the “off” level for this model",
|
|
10968
11071
|
"row.modelOffRefused": "refuses off",
|
|
10969
11072
|
"row.modelOffRefusedHint": "Measured to reject the “off” level with HTTP 400 (code 11133) on both gateways. With this ticked, every request that carries no reasoning tier — compaction summaries above all — would fail, so it cannot be enabled here.",
|
|
10970
|
-
"row.
|
|
11073
|
+
"row.modelEffortLabel": "Default reasoning",
|
|
11074
|
+
"row.modelEffortAria": "Default reasoning level for {model}",
|
|
11075
|
+
"row.modelEffortHint": "The reasoning level this model uses when a request names none — new conversations, compaction summaries and session titles. Only the levels WorkBuddy itself declares for this model are offered; “Default (x)” means WorkBuddy’s own declared default for it.",
|
|
11076
|
+
"row.modelEffortFollow": "Default",
|
|
11077
|
+
"row.modelEffortFollowNamed": "Default ({effort})",
|
|
10971
11078
|
"row.modelUnknown": "Unknown",
|
|
10972
11079
|
"row.modelCapabilityPending": "Only capabilities advertised by WorkBuddy are shown.",
|
|
10973
11080
|
"row.modelsTestEnabled": "Test enabled models",
|
|
@@ -11403,7 +11510,11 @@ window.__ModuleLoader__.load({
|
|
|
11403
11510
|
"row.modelOffHint": "为这个模型提供「关闭思考」档位",
|
|
11404
11511
|
"row.modelOffRefused": "拒绝 off",
|
|
11405
11512
|
"row.modelOffRefusedHint": "实测在两个网关上都拒绝「关闭思考」档位(HTTP 400 / code 11133)。勾选后,所有未指定推理档位的请求(尤其是压缩摘要)都会失败,因此这里不能启用。",
|
|
11406
|
-
"row.
|
|
11513
|
+
"row.modelEffortLabel": "默认推理强度",
|
|
11514
|
+
"row.modelEffortAria": "{model} 的默认推理强度",
|
|
11515
|
+
"row.modelEffortHint": "请求未指定档位时这个模型使用的推理强度——新对话、压缩摘要、会话标题都属于这类请求。这里只列出 WorkBuddy 为该模型声明的档位;「默认(x)」里的 x 就是 WorkBuddy 自己声明的默认档位。",
|
|
11516
|
+
"row.modelEffortFollow": "默认",
|
|
11517
|
+
"row.modelEffortFollowNamed": "默认({effort})",
|
|
11407
11518
|
"row.modelUnknown": "未知",
|
|
11408
11519
|
"row.modelCapabilityPending": "仅展示 WorkBuddy 接口明确公布的模型能力。",
|
|
11409
11520
|
"row.modelsTestEnabled": "一键测试启用模型",
|
|
@@ -11815,8 +11926,19 @@ window.__ModuleLoader__.load({
|
|
|
11815
11926
|
}
|
|
11816
11927
|
};
|
|
11817
11928
|
/**
|
|
11818
|
-
* Credit readout in the composer
|
|
11819
|
-
* session-scope list slot).
|
|
11929
|
+
* Credit readout in the composer row (`conversation.input.right`, a
|
|
11930
|
+
* session-scope list slot rendered immediately before the model picker).
|
|
11931
|
+
*
|
|
11932
|
+
* `input.right` rather than `input.left` (ported from dsh-connect-trae's
|
|
11933
|
+
* issue #71): the host renders `input.right` inside `div.trailing`'s
|
|
11934
|
+
* `standardControls`, i.e. IMMEDIATELY BEFORE the model picker, while
|
|
11935
|
+
* `input.left` sits at the very end of `div.tools` — 8px from the
|
|
11936
|
+
* permission selector. Both are clickable, so reaching for "完全权限"
|
|
11937
|
+
* landed on the readout and opened the credit popover. A readout is not an
|
|
11938
|
+
* action; next to the model picker it stops competing with a control the
|
|
11939
|
+
* user is actually aiming at. The `composer` SETTING value keeps its
|
|
11940
|
+
* meaning ("the row that carries the model picker"), so saved profiles
|
|
11941
|
+
* need no migration.
|
|
11820
11942
|
*
|
|
11821
11943
|
* 参考:dingminhua/dsh-connect-trae(MIT,Copyright (c) 2026 LaoDing)
|
|
11822
11944
|
* — 该项目的 `registerComposerPoints`:无条件注册插槽、由 gate 读
|
|
@@ -11834,8 +11956,8 @@ window.__ModuleLoader__.load({
|
|
|
11834
11956
|
*/
|
|
11835
11957
|
const registerComposerCredits = () => {
|
|
11836
11958
|
try {
|
|
11837
|
-
return ctx.slots.inject("conversation.input.
|
|
11838
|
-
name: "conversation.input.
|
|
11959
|
+
return ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
|
|
11960
|
+
name: "conversation.input.right",
|
|
11839
11961
|
id: "dsh-connect-workbuddy-composer-credits",
|
|
11840
11962
|
order: 4,
|
|
11841
11963
|
locale: namespace,
|
|
@@ -7932,7 +7932,7 @@ function describeRosterSeenPayload(raw) {
|
|
|
7932
7932
|
//#endregion
|
|
7933
7933
|
//#region src/version.ts
|
|
7934
7934
|
/** The npm package version this build was produced from. */
|
|
7935
|
-
const WORKBUDDY_CONNECT_VERSION = "5.
|
|
7935
|
+
const WORKBUDDY_CONNECT_VERSION = "5.15.1";
|
|
7936
7936
|
//#endregion
|
|
7937
7937
|
//#region src/host-heartbeat.ts
|
|
7938
7938
|
/**
|
package/lib/index.d.ts
CHANGED
|
@@ -2295,6 +2295,7 @@ interface ContextRescue {
|
|
|
2295
2295
|
}
|
|
2296
2296
|
//#endregion
|
|
2297
2297
|
//#region src/adapter.d.ts
|
|
2298
|
+
/** Provider route this bundle owns for the domestic (CN) gateway. */
|
|
2298
2299
|
declare const WORKBUDDY_PROVIDER = "workbuddy";
|
|
2299
2300
|
/** Provider route this bundle owns for the international gateway. */
|
|
2300
2301
|
declare const WORKBUDDY_GLOBAL_PROVIDER = "workbuddy-global";
|
|
@@ -2364,6 +2365,20 @@ interface WorkBuddyAdapterOptions {
|
|
|
2364
2365
|
* request; failures are swallowed.
|
|
2365
2366
|
*/
|
|
2366
2367
|
onForeignThinking?: (blocks: readonly ForeignThinkingBlock[]) => void;
|
|
2368
|
+
/**
|
|
2369
|
+
* The per-model default thinking tier the user saved (issue #179), keyed by
|
|
2370
|
+
* model id.
|
|
2371
|
+
*
|
|
2372
|
+
* A GETTER, not a captured map: the choice is a setting the user can change
|
|
2373
|
+
* mid-session, and a captured copy would keep dispatching the old tier until
|
|
2374
|
+
* a reload. Production reads the live region slot; tests pass a literal.
|
|
2375
|
+
*
|
|
2376
|
+
* An absent key means "follow the upstream's own `defaultEffort`" — see
|
|
2377
|
+
* `src/default-effort.ts` for how that is resolved against the model's real
|
|
2378
|
+
* vocabulary, which is what keeps a stale saved tier from ever reaching the
|
|
2379
|
+
* wire as an `UNSUPPORTED_REASONING_EFFORT`.
|
|
2380
|
+
*/
|
|
2381
|
+
defaultThinkingEfforts?: () => Readonly<Record<string, string | undefined>>;
|
|
2367
2382
|
}
|
|
2368
2383
|
/** What {@link createWorkBuddyAdapter} hands back. */
|
|
2369
2384
|
interface WorkBuddyAdapter {
|
|
@@ -3443,7 +3458,8 @@ declare function nextRegionEnabled(value: unknown, region: WorkBuddyWebRegion, e
|
|
|
3443
3458
|
/**
|
|
3444
3459
|
* The two seats the credit readout can occupy (issue #62).
|
|
3445
3460
|
*
|
|
3446
|
-
* `composer` is the composer
|
|
3461
|
+
* `composer` is the composer row (`conversation.input.right`, rendered just
|
|
3462
|
+
* before the model picker); `stats` is
|
|
3447
3463
|
* the ambient dock below the composer card (`conversation.composer.dock`),
|
|
3448
3464
|
* beside the shell's own round/step/token pills. Named as a union rather than a
|
|
3449
3465
|
* boolean because a third seat is plausible and `showAtTop: boolean` would have
|
|
@@ -3989,6 +4005,20 @@ type WorkBuddyWebUsage =
|
|
|
3989
4005
|
accounts: boolean;
|
|
3990
4006
|
authFile: boolean;
|
|
3991
4007
|
};
|
|
4008
|
+
/**
|
|
4009
|
+
* The per-model default thinking tiers the user saved (issue #179), keyed by
|
|
4010
|
+
* model id.
|
|
4011
|
+
*
|
|
4012
|
+
* Carried here for the same measured reason as {@link contextBudgets}: on the
|
|
4013
|
+
* affected 0.1.7 deployment the browser settings mirror never picks up a
|
|
4014
|
+
* write made through the Host save endpoint, so rendering this from a
|
|
4015
|
+
* snapshot made a successful choice look like it reverted.
|
|
4016
|
+
*
|
|
4017
|
+
* An absent key is not "no opinion": it means "follow this model's own
|
|
4018
|
+
* upstream `defaultEffort`", which is exactly what the card's first option
|
|
4019
|
+
* says out loud.
|
|
4020
|
+
*/
|
|
4021
|
+
defaultThinkingEfforts?: Record<string, string>;
|
|
3992
4022
|
/**
|
|
3993
4023
|
* The persisted per-region context budgets as the Host reads them. The
|
|
3994
4024
|
* browser settings mirror can lag a write made through the Host save
|
|
@@ -4399,6 +4429,14 @@ interface WorkBuddyStatusRouteOptions {
|
|
|
4399
4429
|
offModelIds(region: WorkBuddyRegion): readonly string[];
|
|
4400
4430
|
/** Saved local DSH context budgets by model id, for the requested region. */
|
|
4401
4431
|
contextBudgets(region: WorkBuddyRegion): Readonly<Record<string, number | undefined>>;
|
|
4432
|
+
/**
|
|
4433
|
+
* The saved per-model default thinking tiers (issue #179), keyed by model id.
|
|
4434
|
+
*
|
|
4435
|
+
* Optional so a Host built before the feature keeps serving: the card then
|
|
4436
|
+
* treats every model as "follow the upstream default" and offers the picker
|
|
4437
|
+
* with only that option.
|
|
4438
|
+
*/
|
|
4439
|
+
defaultThinkingEfforts?(region: WorkBuddyRegion): Readonly<Record<string, string | undefined>>;
|
|
4402
4440
|
/** Re-read the live catalog of one region from the upstream. */
|
|
4403
4441
|
discoverModels?(region: WorkBuddyRegion, signal?: AbortSignal): Promise<readonly WorkBuddyModelInfo[]>;
|
|
4404
4442
|
/**
|
|
@@ -5012,7 +5050,7 @@ interface WorkBuddyRegionState {
|
|
|
5012
5050
|
*
|
|
5013
5051
|
* Two seats, because the first one is not always available:
|
|
5014
5052
|
*
|
|
5015
|
-
* - `composer` — the composer
|
|
5053
|
+
* - `composer` — the composer row (`conversation.input.right`), shared
|
|
5016
5054
|
* with the shell's own controls. It is the closer, more glanceable seat,
|
|
5017
5055
|
* and it is the default so nothing moves for anyone who does not choose.
|
|
5018
5056
|
* - `stats` — the ambient dock below the composer card
|
|
@@ -5067,6 +5105,16 @@ interface WorkBuddyRegionState {
|
|
|
5067
5105
|
offModelIds?: string[];
|
|
5068
5106
|
/** Local DSH context budget per model in this region. */
|
|
5069
5107
|
contextBudgets?: Record<string, WorkBuddyContextBudget>;
|
|
5108
|
+
/**
|
|
5109
|
+
* The thinking tier each model runs on when a caller names none (issue #179).
|
|
5110
|
+
*
|
|
5111
|
+
* Keyed by model id because the vocabularies disagree across one roster (see
|
|
5112
|
+
* `regionStateConfig`'s declaration of the same field). An ABSENT key follows
|
|
5113
|
+
* the model's own upstream `defaultEffort` — the runtime answer is computed by
|
|
5114
|
+
* `defaultEffortFor` in `src/default-effort.ts`, which is the only place that
|
|
5115
|
+
* decides, and which refuses to name a tier the model does not declare.
|
|
5116
|
+
*/
|
|
5117
|
+
defaultThinkingEfforts?: Record<string, string>;
|
|
5070
5118
|
/** This region's account-pool preferences (opt-in; see {@link WorkBuddyPoolPreferences}). */
|
|
5071
5119
|
pool?: WorkBuddyPoolPreferences;
|
|
5072
5120
|
}
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as parseCreditMultiplier, $t as resolveDshHome, A as isEncryptedCredentialError, At as pickMediaModel, B as WorkBuddyCredentialRejectedError, Bt as deriveAtRestKey, C as WorkBuddyCredentialStore, Ct as IMAGE_TAG_TEXT_TO_IMAGE, D as defaultDesktopAuthDirs, Dt as imageMimeForPath, E as defaultDesktopAuthCandidates, Et as imageDataUrl, F as workbuddyAccountId, Ft as NATIVE_MODALITY_BY_MODEL_ID, G as forceNeutralHead, Gt as isEncryptedFieldWrapper, H as channelGateMemberOf, Ht as fetchAtRestKeyPayload, I as workbuddyOwnAuthPath, It as imageDefaultFor, J as isCredentialRejectedError, Jt as macosNestedAppBundles, K as isContextTooLongBody, Kt as isWorkbuddyBundle, L as CONTEXT_OVERFLOW_MARKER, Lt as nativeModalityOf, M as legacyWorkbuddyOwnAuthPath, Mt as parseQueueAnswer, N as parseWorkBuddyAuth, Nt as queueNoticeText, O as defaultDesktopAuthPath, Ot as isBareBase64Image, P as preferredAccountName, Pt as pruneTempDir, Q as openingEvidenceOfBody, R as CREDENTIAL_REJECTED_CODE, Rt as WORKBUDDY_APP_EXECUTABLE_ENV, S as WORKBUDDY_AUTH_FILE_ENV, St as IMAGE_TAG_IMAGE_TO_IMAGE, T as authFileName, Tt as WorkBuddyImageError, U as classifyUpstreamError, Ut as findWorkbuddyAppExecutable, V as WorkBuddyUpstreamClient, Vt as deriveAtRestKeyId, W as declaredTools, X as knownRegionOf, Xt as readAtRestKey, Y as isFirstMessageGateBody, Yt as openEncryptedField, Z as modelIdOfBody, Zt as workbuddyAppExecutableCandidates, _ as deriveCatalog, _t as WorkBuddyVideoError, a as readHostHeartbeat, at as requestEvidenceOf, b as ENCRYPTED_CREDENTIAL_CODE, bt as resolveVideoParams, c as WORKBUDDY_CONNECT_VERSION, ct as REFUSES_OFF_MODEL_IDS, dt as offDefaultFor, en as withFileLock, et as parseReasoning, f as piAiRuntimeInfo, ft as offRefused, g as applyContextBudgets, gt as VIDEO_TAG_TEXT_TO_VIDEO, h as WorkBuddyCatalog, ht as VIDEO_TAG_IMAGE_TO_VIDEO, i as processStartTimeMs, it as regionOf, j as isVaultAccountId, jt as sniffImageMime, k as hasEncryptedCredentialFields, kt as parseImageDataUrl, l as createFileRosterSeenStore, lt as defaultOffModelIds, m as FALLBACK_WORKBUDDY_MODELS_GLOBAL, mt as withEffectiveOff, n as clearHostHeartbeat, nt as perturbChannelFingerprints, o as workbuddyHostHeartbeatPath, ot as rewriteSessionTitleModel, p as FALLBACK_WORKBUDDY_MODELS, pt as recordRefusal, q as isConversationIdGateBody, qt as macosBundleExecutable, r as isHeartbeatProcessAlive, rt as prepareChatBody, s as writeHostHeartbeat, st as attachBusyLevels, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, tn as writeFileAtomic, tt as parseUpstreamModel, ut as lastRefusalFor, v as fallbackModelsFor, vt as parseSupportedSecondsRange, w as WorkBuddyEncryptedCredentialError, wt as MAX_INPUT_IMAGE_BYTES, x as WORKBUDDY_AUTH_FILENAME, xt as DEFAULT_IMAGE_SIZE, y as withoutRouters, yt as pickVideoModel, z as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, zt as clearAtRestKeyCache } from "./host-heartbeat-
|
|
1
|
+
import { $ as parseCreditMultiplier, $t as resolveDshHome, A as isEncryptedCredentialError, At as pickMediaModel, B as WorkBuddyCredentialRejectedError, Bt as deriveAtRestKey, C as WorkBuddyCredentialStore, Ct as IMAGE_TAG_TEXT_TO_IMAGE, D as defaultDesktopAuthDirs, Dt as imageMimeForPath, E as defaultDesktopAuthCandidates, Et as imageDataUrl, F as workbuddyAccountId, Ft as NATIVE_MODALITY_BY_MODEL_ID, G as forceNeutralHead, Gt as isEncryptedFieldWrapper, H as channelGateMemberOf, Ht as fetchAtRestKeyPayload, I as workbuddyOwnAuthPath, It as imageDefaultFor, J as isCredentialRejectedError, Jt as macosNestedAppBundles, K as isContextTooLongBody, Kt as isWorkbuddyBundle, L as CONTEXT_OVERFLOW_MARKER, Lt as nativeModalityOf, M as legacyWorkbuddyOwnAuthPath, Mt as parseQueueAnswer, N as parseWorkBuddyAuth, Nt as queueNoticeText, O as defaultDesktopAuthPath, Ot as isBareBase64Image, P as preferredAccountName, Pt as pruneTempDir, Q as openingEvidenceOfBody, R as CREDENTIAL_REJECTED_CODE, Rt as WORKBUDDY_APP_EXECUTABLE_ENV, S as WORKBUDDY_AUTH_FILE_ENV, St as IMAGE_TAG_IMAGE_TO_IMAGE, T as authFileName, Tt as WorkBuddyImageError, U as classifyUpstreamError, Ut as findWorkbuddyAppExecutable, V as WorkBuddyUpstreamClient, Vt as deriveAtRestKeyId, W as declaredTools, X as knownRegionOf, Xt as readAtRestKey, Y as isFirstMessageGateBody, Yt as openEncryptedField, Z as modelIdOfBody, Zt as workbuddyAppExecutableCandidates, _ as deriveCatalog, _t as WorkBuddyVideoError, a as readHostHeartbeat, at as requestEvidenceOf, b as ENCRYPTED_CREDENTIAL_CODE, bt as resolveVideoParams, c as WORKBUDDY_CONNECT_VERSION, ct as REFUSES_OFF_MODEL_IDS, dt as offDefaultFor, en as withFileLock, et as parseReasoning, f as piAiRuntimeInfo, ft as offRefused, g as applyContextBudgets, gt as VIDEO_TAG_TEXT_TO_VIDEO, h as WorkBuddyCatalog, ht as VIDEO_TAG_IMAGE_TO_VIDEO, i as processStartTimeMs, it as regionOf, j as isVaultAccountId, jt as sniffImageMime, k as hasEncryptedCredentialFields, kt as parseImageDataUrl, l as createFileRosterSeenStore, lt as defaultOffModelIds, m as FALLBACK_WORKBUDDY_MODELS_GLOBAL, mt as withEffectiveOff, n as clearHostHeartbeat, nt as perturbChannelFingerprints, o as workbuddyHostHeartbeatPath, ot as rewriteSessionTitleModel, p as FALLBACK_WORKBUDDY_MODELS, pt as recordRefusal, q as isConversationIdGateBody, qt as macosBundleExecutable, r as isHeartbeatProcessAlive, rt as prepareChatBody, s as writeHostHeartbeat, st as attachBusyLevels, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, tn as writeFileAtomic, tt as parseUpstreamModel, ut as lastRefusalFor, v as fallbackModelsFor, vt as parseSupportedSecondsRange, w as WorkBuddyEncryptedCredentialError, wt as MAX_INPUT_IMAGE_BYTES, x as WORKBUDDY_AUTH_FILENAME, xt as DEFAULT_IMAGE_SIZE, y as withoutRouters, yt as pickVideoModel, z as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, zt as clearAtRestKeyCache } from "./host-heartbeat-891AaZGW.js";
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import { appendFile, mkdir, open, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
4
4
|
import { randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
@@ -566,6 +566,7 @@ const REGION_MAP_FOREIGN_KEYS = /* @__PURE__ */ new Set([
|
|
|
566
566
|
"imageToolEnabled",
|
|
567
567
|
"offModelIds",
|
|
568
568
|
"contextBudgets",
|
|
569
|
+
"defaultThinkingEfforts",
|
|
569
570
|
"pool"
|
|
570
571
|
]);
|
|
571
572
|
/** A non-null, non-array object: the only shape a slot or a level can have. */
|
|
@@ -1142,6 +1143,58 @@ function claimableBenefits(benefits, nowMs) {
|
|
|
1142
1143
|
return benefits.map((benefit) => benefitOf(benefit, nowMs)).filter((view) => view.state === "claimable").sort((left, right) => (left.daysLeft ?? 1e6) - (right.daysLeft ?? 1e6));
|
|
1143
1144
|
}
|
|
1144
1145
|
//#endregion
|
|
1146
|
+
//#region src/default-effort.ts
|
|
1147
|
+
/** The tiers DSH/pi-ai speak. Mirrors `THINKING_LEVELS` in `./adapter.ts`. */
|
|
1148
|
+
const THINKING_LEVELS$1 = [
|
|
1149
|
+
"minimal",
|
|
1150
|
+
"low",
|
|
1151
|
+
"medium",
|
|
1152
|
+
"high",
|
|
1153
|
+
"xhigh",
|
|
1154
|
+
"max"
|
|
1155
|
+
];
|
|
1156
|
+
/**
|
|
1157
|
+
* The tier this bundle assumed before issue #179, and the last-resort fallback.
|
|
1158
|
+
*
|
|
1159
|
+
* Kept as the final fallback rather than becoming dead: a model that declares
|
|
1160
|
+
* `canDisableThinking` only (no tier list) still needs an explicit tier the way
|
|
1161
|
+
* it always did, and #83's fix — never leave the choice to pi-ai — must survive
|
|
1162
|
+
* this feature.
|
|
1163
|
+
*/
|
|
1164
|
+
const WORKBUDDY_DEFAULT_EFFORT = "high";
|
|
1165
|
+
/**
|
|
1166
|
+
* The tiers a model actually declares, in DSH's order, without `off`.
|
|
1167
|
+
*
|
|
1168
|
+
* `off` is excluded deliberately: it is a DIFFERENT question with its own owner
|
|
1169
|
+
* (`offModelIds` / `src/off-thinking.ts`, issue #34), and a "default thinking
|
|
1170
|
+
* effort" of `off` would silently re-open the #83 defect for every caller that
|
|
1171
|
+
* names no tier.
|
|
1172
|
+
*/
|
|
1173
|
+
function declaredEfforts(reasoning) {
|
|
1174
|
+
const supported = reasoning?.supportedEfforts ?? [];
|
|
1175
|
+
return THINKING_LEVELS$1.filter((level) => supported.includes(level));
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* The one tier a model should dispatch at when a caller names none.
|
|
1179
|
+
*
|
|
1180
|
+
* @param model - the catalog entry, carrying the upstream's own declaration.
|
|
1181
|
+
* @param saved - the user's per-model choice, keyed by model id; an absent (or
|
|
1182
|
+
* unsupported, or stale) entry falls through to the upstream default.
|
|
1183
|
+
* @returns a tier the model declares, or `undefined` when it declares none — in
|
|
1184
|
+
* which case the adapter's existing `off` normalization (issue #90) is still
|
|
1185
|
+
* the right answer and this module must not invent one.
|
|
1186
|
+
*/
|
|
1187
|
+
function defaultEffortFor(model, saved = {}) {
|
|
1188
|
+
const declared = declaredEfforts(model.reasoning);
|
|
1189
|
+
if (declared.length === 0) return void 0;
|
|
1190
|
+
const asked = saved[model.id];
|
|
1191
|
+
if (typeof asked === "string" && declared.includes(asked)) return asked;
|
|
1192
|
+
const upstreamDefault = model.reasoning?.defaultEffort;
|
|
1193
|
+
if (typeof upstreamDefault === "string" && declared.includes(upstreamDefault)) return upstreamDefault;
|
|
1194
|
+
if (declared.includes("high")) return WORKBUDDY_DEFAULT_EFFORT;
|
|
1195
|
+
return declared[0];
|
|
1196
|
+
}
|
|
1197
|
+
//#endregion
|
|
1145
1198
|
//#region src/adapter.ts
|
|
1146
1199
|
/**
|
|
1147
1200
|
* The WorkBuddy pi-ai providers: loopback-backed adapters registered
|
|
@@ -1161,13 +1214,6 @@ function claimableBenefits(benefits, nowMs) {
|
|
|
1161
1214
|
* @module dsh-connect-workbuddy/adapter
|
|
1162
1215
|
*/
|
|
1163
1216
|
/** Provider route this bundle owns for the domestic (CN) gateway. */
|
|
1164
|
-
/**
|
|
1165
|
-
* The tier a request gets when it names none — compaction summaries, above all
|
|
1166
|
-
* (issue #83). See the `reasoning` field in `profileFor` for why an absent
|
|
1167
|
-
* default is not "no opinion" but "whatever pi-ai's level map says", which is
|
|
1168
|
-
* how `off` became the silent default for `/compact`.
|
|
1169
|
-
*/
|
|
1170
|
-
const WORKBUDDY_DEFAULT_EFFORT = "high";
|
|
1171
1217
|
const WORKBUDDY_PROVIDER = "workbuddy";
|
|
1172
1218
|
/** Provider route this bundle owns for the international gateway. */
|
|
1173
1219
|
const WORKBUDDY_GLOBAL_PROVIDER = "workbuddy-global";
|
|
@@ -2006,17 +2052,32 @@ var EffortNormalizingAdapter = class extends PiAiAdapter {
|
|
|
2006
2052
|
* normalized at once.
|
|
2007
2053
|
*/
|
|
2008
2054
|
effortCapable;
|
|
2009
|
-
|
|
2055
|
+
/**
|
|
2056
|
+
* The tier one exact model should advertise as its default when a caller
|
|
2057
|
+
* names none (issue #179).
|
|
2058
|
+
*
|
|
2059
|
+
* Read LIVE per resolve/prepare call, like {@link effortCapable}: the user can
|
|
2060
|
+
* change the choice mid-session, and `applySelection` re-invalidates the
|
|
2061
|
+
* catalog on every settings change. A captured value would make the picker
|
|
2062
|
+
* show the new default while requests kept the old one.
|
|
2063
|
+
*
|
|
2064
|
+
* Returns `undefined` for a model with no tier vocabulary — the caller then
|
|
2065
|
+
* leaves the advertised `reasoning` block exactly as pi-ai built it, which is
|
|
2066
|
+
* the `off`-only shape `advertiseOffOnly` below depends on.
|
|
2067
|
+
*/
|
|
2068
|
+
defaultEffort;
|
|
2069
|
+
constructor(config, effortCapable, defaultEffort = () => void 0) {
|
|
2010
2070
|
super(config);
|
|
2011
2071
|
this.effortCapable = effortCapable;
|
|
2072
|
+
this.defaultEffort = defaultEffort;
|
|
2012
2073
|
}
|
|
2013
2074
|
async resolveModel(provider, model, signal) {
|
|
2014
|
-
return this.advertiseOffOnly(await super.resolveModel(provider, model, signal));
|
|
2075
|
+
return this.advertiseDefaultEffort(this.advertiseOffOnly(await super.resolveModel(provider, model, signal)));
|
|
2015
2076
|
}
|
|
2016
2077
|
async prepareCall(provider, model, signal) {
|
|
2017
2078
|
const call = await super.prepareCall(provider, model, signal);
|
|
2018
2079
|
return {
|
|
2019
|
-
model: this.advertiseOffOnly(call.model),
|
|
2080
|
+
model: this.advertiseDefaultEffort(this.advertiseOffOnly(call.model)),
|
|
2020
2081
|
stream: (options) => call.stream(this.pinEffort(options))
|
|
2021
2082
|
};
|
|
2022
2083
|
}
|
|
@@ -2024,6 +2085,35 @@ var EffortNormalizingAdapter = class extends PiAiAdapter {
|
|
|
2024
2085
|
return super.stream(this.pinEffort(options));
|
|
2025
2086
|
}
|
|
2026
2087
|
/**
|
|
2088
|
+
* Advertise the per-model default tier (issue #179).
|
|
2089
|
+
*
|
|
2090
|
+
* The host materializes `reasoningEffort` into EVERY request that names none
|
|
2091
|
+
* from this exact field (`LlmRuntime.resolveCallWithInfo`), which is what makes
|
|
2092
|
+
* a saved per-model default real rather than a display preference: without
|
|
2093
|
+
* `defaultEffort` the host leaves the config field empty and pi-ai falls back
|
|
2094
|
+
* to `profile.reasoning` — the one route-wide constant this feature exists to
|
|
2095
|
+
* replace.
|
|
2096
|
+
*
|
|
2097
|
+
* Deliberately a no-op when the model declares no tier: pi-ai's `off`-only
|
|
2098
|
+
* block must keep its EXACT advertised shape, or the host's prepared-call
|
|
2099
|
+
* config check (`callConfigEquals`) starts disagreeing with callers that omit
|
|
2100
|
+
* the field — see `advertiseOffOnly` above.
|
|
2101
|
+
*/
|
|
2102
|
+
advertiseDefaultEffort(info) {
|
|
2103
|
+
if (info.reasoning === void 0) return info;
|
|
2104
|
+
const effort = this.defaultEffort(info.id);
|
|
2105
|
+
if (effort === void 0) return info;
|
|
2106
|
+
if (!info.reasoning.efforts.some((level) => level.id === effort)) return info;
|
|
2107
|
+
if (info.reasoning.defaultEffort === effort) return info;
|
|
2108
|
+
return {
|
|
2109
|
+
...info,
|
|
2110
|
+
reasoning: {
|
|
2111
|
+
...info.reasoning,
|
|
2112
|
+
defaultEffort: ReasoningEffortId(effort)
|
|
2113
|
+
}
|
|
2114
|
+
};
|
|
2115
|
+
}
|
|
2116
|
+
/**
|
|
2027
2117
|
* Give a reasoning-less model the one tier it can dispatch at. No
|
|
2028
2118
|
* `defaultEffort` on purpose: the host materializes one into EVERY request
|
|
2029
2119
|
* that names no tier, and that would turn the host's own prepared-call config
|
|
@@ -2091,8 +2181,8 @@ function createWorkBuddyAdapter(options) {
|
|
|
2091
2181
|
configuredMaxTokens: /* @__PURE__ */ new Map(),
|
|
2092
2182
|
modelErrors: /* @__PURE__ */ new Map(),
|
|
2093
2183
|
/**
|
|
2094
|
-
* The
|
|
2095
|
-
* for an absent one (issue #83).
|
|
2184
|
+
* The ROUTE-WIDE fallback tier, pinned to the SAME value pi-ai's level map
|
|
2185
|
+
* would fill in for an absent one (issue #83).
|
|
2096
2186
|
*
|
|
2097
2187
|
* `dsh-llm-pi-ai` resolves every request's tier as
|
|
2098
2188
|
* `options.reasoningEffort ?? profile.reasoning`, so an absent default here
|
|
@@ -2101,7 +2191,14 @@ function createWorkBuddyAdapter(options) {
|
|
|
2101
2191
|
* model like `deepseek-v4.1-flash` to disable thinking, which it answers with
|
|
2102
2192
|
* 400 / 11133. Nothing structurally prevents a level or a catalog snapshot
|
|
2103
2193
|
* from disagreeing with the adapter's own map, so the default must never be
|
|
2104
|
-
* that.
|
|
2194
|
+
* that.
|
|
2195
|
+
*
|
|
2196
|
+
* Since issue #179 this is only the LAST resort: `advertiseDefaultEffort`
|
|
2197
|
+
* publishes the user's per-MODEL choice as `defaultEffort`, and the host
|
|
2198
|
+
* materializes that into every request that names none — so a user who
|
|
2199
|
+
* picked `low` for one model gets `low` while every other model keeps its own
|
|
2200
|
+
* upstream default. This constant survives for a model whose catalog entry
|
|
2201
|
+
* declares no default at all, which is the only case that reaches here.
|
|
2105
2202
|
*/
|
|
2106
2203
|
reasoning: WORKBUDDY_DEFAULT_EFFORT,
|
|
2107
2204
|
...REQUEST_IMAGE_BUDGETS,
|
|
@@ -2121,13 +2218,26 @@ function createWorkBuddyAdapter(options) {
|
|
|
2121
2218
|
* without an `invalidate` round trip.
|
|
2122
2219
|
*/
|
|
2123
2220
|
const effortCapable = (model) => catalog.current().some((info) => info.id === model && workBuddyThinkingLevelMap(info) !== void 0);
|
|
2221
|
+
/**
|
|
2222
|
+
* The tier one exact model should run on when a caller names none (#179).
|
|
2223
|
+
*
|
|
2224
|
+
* Resolved against the LIVE catalog and the user's LIVE saved map, exactly
|
|
2225
|
+
* like `effortCapable`: the answer is a function of two things that both
|
|
2226
|
+
* change mid-session, and caching either would let the advertised default and
|
|
2227
|
+
* the dispatched tier drift apart.
|
|
2228
|
+
*/
|
|
2229
|
+
const resolveDefaultEffort = (model) => {
|
|
2230
|
+
const info = catalog.current().find((entry) => entry.id === model);
|
|
2231
|
+
if (info === void 0) return void 0;
|
|
2232
|
+
return defaultEffortFor(info, options.defaultThinkingEfforts?.() ?? {});
|
|
2233
|
+
};
|
|
2124
2234
|
return {
|
|
2125
2235
|
adapter: new EffortNormalizingAdapter({
|
|
2126
2236
|
profiles: () => profiles,
|
|
2127
2237
|
auth: INERT_AUTH,
|
|
2128
2238
|
resolveApiKey: async () => shim.token(),
|
|
2129
2239
|
...resolveAttachments === void 0 ? {} : { resolveAttachments }
|
|
2130
|
-
}, effortCapable),
|
|
2240
|
+
}, effortCapable, resolveDefaultEffort),
|
|
2131
2241
|
invalidate: () => {
|
|
2132
2242
|
profiles = buildProfiles();
|
|
2133
2243
|
}
|
|
@@ -10425,6 +10535,7 @@ async function workBuddyWebStatus(deps, region, options = {}) {
|
|
|
10425
10535
|
return {
|
|
10426
10536
|
status: "signed-in",
|
|
10427
10537
|
contextBudgets: Object.fromEntries(Object.entries(deps.contextBudgets(region)).filter(([, value]) => typeof value === "number")),
|
|
10538
|
+
defaultThinkingEfforts: Object.fromEntries(Object.entries(deps.defaultThinkingEfforts?.(region) ?? {}).filter(([, value]) => typeof value === "string" && value !== "")),
|
|
10428
10539
|
diagVolatile: {
|
|
10429
10540
|
regions: volatileFlagOf("regions"),
|
|
10430
10541
|
accounts: volatileFlagOf("accounts"),
|
|
@@ -11284,6 +11395,30 @@ const regionStateConfig = z.object({
|
|
|
11284
11395
|
*/
|
|
11285
11396
|
imageSelectionExplicit: z.boolean().default(false),
|
|
11286
11397
|
contextBudgets: z.dict(z.number().step(1).min(1)).default({}),
|
|
11398
|
+
/**
|
|
11399
|
+
* The thinking tier each model runs on when a caller names none (issue #179).
|
|
11400
|
+
*
|
|
11401
|
+
* Keyed by model id because the tier VOCABULARY is per model, not per route:
|
|
11402
|
+
* the live CN roster carries `low/high/max` (deepseek, glm-5.3), `high/xhigh`
|
|
11403
|
+
* (glm-5.2, deepseek-v4-pro), `low/medium/high` (step-5-preview) and five
|
|
11404
|
+
* tiers on `space-bunny` / `minimax-m3.1-flash`. A single route-wide default —
|
|
11405
|
+
* the `WORKBUDDY_DEFAULT_EFFORT` constant this replaces — therefore cannot be
|
|
11406
|
+
* right for more than a subset of the roster, and naming a tier a model does
|
|
11407
|
+
* not have is a hard `UNSUPPORTED_REASONING_EFFORT` (issue #90).
|
|
11408
|
+
*
|
|
11409
|
+
* An ABSENT key means "follow this model's own upstream `defaultEffort`",
|
|
11410
|
+
* which every model in the live catalog declares (measured 2026-10-30: `high`
|
|
11411
|
+
* on thirteen of fifteen, `max` on the two five-tier ones). That is NOT the
|
|
11412
|
+
* same as "no opinion": the adapter must still pin SOMETHING for the request
|
|
11413
|
+
* paths that name no tier, and issue #83 is the record of what happens when
|
|
11414
|
+
* that is left to pi-ai's level map — it filled in `off`, the upstream answers
|
|
11415
|
+
* 400 / 11133, and `/compact` failed.
|
|
11416
|
+
*
|
|
11417
|
+
* `off` is deliberately absent from this field's vocabulary: whether a model
|
|
11418
|
+
* offers `off` is `offModelIds`' decision (issue #34), and letting both own it
|
|
11419
|
+
* would give one question two answers that can disagree.
|
|
11420
|
+
*/
|
|
11421
|
+
defaultThinkingEfforts: z.dict(z.string()).default({}),
|
|
11287
11422
|
pool: poolConfig.default({})
|
|
11288
11423
|
});
|
|
11289
11424
|
const accountSelectionConfig = z.object({
|
|
@@ -12223,6 +12358,7 @@ function apply(ctx, config) {
|
|
|
12223
12358
|
imageModelIds: (region) => [...effectiveImageIds(regionStateOf(current(), region), displayModels(current(), region))],
|
|
12224
12359
|
offModelIds: (region) => savedOffModelIds(regionStateOf(current(), region), displayModels(current(), region)),
|
|
12225
12360
|
contextBudgets: (region) => regionStateOf(current(), region).contextBudgets ?? {},
|
|
12361
|
+
defaultThinkingEfforts: (region) => regionStateOf(current(), region).defaultThinkingEfforts ?? {},
|
|
12226
12362
|
discoverModels,
|
|
12227
12363
|
/**
|
|
12228
12364
|
* This region's first-sighting readout, the data behind the composer pill's
|
|
@@ -13188,6 +13324,17 @@ function apply(ctx, config) {
|
|
|
13188
13324
|
displayName: WORKBUDDY_PROVIDER_DISPLAY_NAMES[region],
|
|
13189
13325
|
resolveAttachments: () => ctx.get("attachments"),
|
|
13190
13326
|
resolveContextRescue: () => contextRescue,
|
|
13327
|
+
/**
|
|
13328
|
+
* Issue #179: the per-model default tier, read LIVE for the same
|
|
13329
|
+
* reason as the switch below — the card saves it mid-session, and a
|
|
13330
|
+
* captured map would keep dispatching the old tier until a reload.
|
|
13331
|
+
*
|
|
13332
|
+
* `current()` rather than the captured `config` is what makes a save
|
|
13333
|
+
* take effect on the very next request; `applySelection` still runs
|
|
13334
|
+
* `invalidateCatalog()` on the same write, which is what re-publishes
|
|
13335
|
+
* the advertised `defaultEffort` to the host's model catalog.
|
|
13336
|
+
*/
|
|
13337
|
+
defaultThinkingEfforts: () => regionStateOf(current(), region).defaultThinkingEfforts ?? {},
|
|
13191
13338
|
dropForeignThinking: () => unwrapVolatile(current().dropForeignThinking) === true,
|
|
13192
13339
|
onForeignThinking: (blocks) => {
|
|
13193
13340
|
latestForeignThinking = {
|
package/locale/en.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
3
|
"title": "dsh-connect-workbuddy",
|
|
4
|
-
"description": "Connects locally signed-in WorkBuddy models, CN and international alike, to DSH, reading each model's credit multiplier from the upstream catalog on every refresh, with per-model image input toggles, account switching backed by an auto-failing-over pool, manual daily check-in, and read-only credit overviews shown as compact pills on the card and in the composer."
|
|
4
|
+
"description": "Connects locally signed-in WorkBuddy models, CN and international alike, to DSH, reading each model's credit multiplier from the upstream catalog on every refresh, with per-model image input toggles, a per-model default reasoning level, account switching backed by an auto-failing-over pool, manual daily check-in, and read-only credit overviews shown as compact pills on the card and in the composer."
|
|
5
5
|
}
|
|
6
6
|
}
|
package/locale/zh.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
3
|
"title": "dsh-connect-workbuddy",
|
|
4
|
-
"description": "将本机登录的 WorkBuddy 模型(国内版与国际版)接入 DSH
|
|
4
|
+
"description": "将本机登录的 WorkBuddy 模型(国内版与国际版)接入 DSH,每次刷新按上游目录自动读取每个模型的积分倍率,支持逐模型图片输入开关与逐模型默认推理强度、多账号切换与账号池自动换号,并提供每日签到、卡片与输入框里的胶囊积分读数等只读积分概览。"
|
|
5
5
|
}
|
|
6
6
|
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-connect-workbuddy",
|
|
3
3
|
"displayName": "DSH Connect WorkBuddy",
|
|
4
4
|
"description": "把本机登录的 WorkBuddy 模型(国内版与国际版)接入 DeepSeek Harness:每次刷新按上游目录自动读取每个模型的积分倍率,支持逐模型图片输入开关、多账号切换与账号池自动换号,并提供每日签到、卡片与输入框里的胶囊积分读数等只读积分概览。",
|
|
5
|
-
"version": "5.
|
|
5
|
+
"version": "5.15.1",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|