dsh-connect-workbuddy 5.13.0 → 5.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +161 -0
- package/README.en.md +14 -7
- package/README.md +16 -7
- package/lib/bin.js +1 -1
- package/lib/client.js +68 -6
- package/lib/{host-heartbeat-DLVGPY4k.js → host-heartbeat-DMC9icX8.js} +294 -22
- package/lib/index.d.ts +91 -16
- package/lib/index.js +165 -56
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,166 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.14.0 (2026-10-29)
|
|
4
|
+
|
|
5
|
+
> 一个「新模型发不了图」的**行为变更**,加三条独立缺陷:#167 补上 5.13.1 漏掉的第二句被禁指纹、
|
|
6
|
+
> #174 修掉三处「诊断与读取各说一套」并给临时文件与请求体兜底、#170 让失败的状态读取可恢复。
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- **新增的多模态模型不再被当成纯文本:广告值改为「用户勾选 ∪ 厂商核验表」(#168)**
|
|
11
|
+
- **现场**:`deepseek-v4.1-flash` 已勾选,发图仍被宿主拦下——`Model "…" does not
|
|
12
|
+
support image input`(`MODEL_DOES_NOT_SUPPORT_IMAGES`,宿主 `session/attachment-invalid`
|
|
13
|
+
的判据是 `resolveModelInfo().inputModalities` 不含 `image`)。报错来自宿主,但判据来自插件
|
|
14
|
+
广播的 `multimodal`。
|
|
15
|
+
- **根因**:`imageModelIds` 有两个写入者且语义不同——用户勾选(决定)与目录刷新(当天名册的
|
|
16
|
+
快照)。仅凭数组无法区分,于是「上次刷新之后才出现在名册里的模型」仅因**不在数组里**就被盖成
|
|
17
|
+
`multimodal: false`,`workBuddyModelInput` 答 `['text']`。本机实测复现:cn 的数组是
|
|
18
|
+
`deepseek-v4.1-flash / glm-5.3-flash / kimi-k3-1`,此后名册新增的
|
|
19
|
+
`minimax-m3.1-flash`、`kimi-k3.1-pro`、`kimi-k2.8-preview`、`hy3-x` 全部不可用。
|
|
20
|
+
- **修法**:`effectiveImageIds()` —— 保存的数组**优先**(用户勾过的照旧生效,反向勾选也尊重),
|
|
21
|
+
仅对数组**没有提到**的 id 用 `imageDefaultFor` 的厂商核验表补齐;`imageSelectionExplicit`
|
|
22
|
+
(卡片保存时置 `true`)一旦成立就完全以数组为准,使「显式取消勾选」不会被下一次读取悄悄改回去。
|
|
23
|
+
四个盖 `multimodal` 的地方(保存 / 发现 / 启动种子 / 卡片读数)统一走它。
|
|
24
|
+
- **没做的事**:不给未核验的 id 猜。`hy3-x` 仍是 `['text']`,守住 `docs/DESIGN.md` 那条
|
|
25
|
+
「不猜」的教训。
|
|
26
|
+
- **测试**:新增 `tests/issue168-image-seed.spec.ts`,经 `ctx.llm.resolveModelInfo`
|
|
27
|
+
(宿主自己的判据)断言广告值;含「不猜」「显式取消勾选仍有效」「显式反向勾选仍有效」三条对照。
|
|
28
|
+
对 5.13.1 代码跑该文件会红,即缺陷被真实钉住。
|
|
29
|
+
|
|
30
|
+
- **状态读取失败不再被当成终态(#170,合并 PR #171 by @aa52312300aa-sys)**
|
|
31
|
+
- 失败文档带 `accounts: []`,于是 `hasAccounts` 为 false,60s 轮询与 open effect 一起把自己
|
|
32
|
+
关掉;一次瞬时 `Failed to fetch` 会永久停在卡片上,只能关掉卡片再打开。
|
|
33
|
+
- 现在:错误横幅在**展开时**按 3s→30s 指数退避重试(以 `loaded` 文档身份为 key,折叠即解除武装),
|
|
34
|
+
横幅从裸的 `TypeError` 改成「读取账号状态失败:…」+「重试」按钮;新增
|
|
35
|
+
`row.usageErrorHint` / `row.usageRetry`(中英)与 `.dsm-workbuddy-usage-error-block`。
|
|
36
|
+
- **测试**:新增 `tests/issue170-status-retry.spec.tsx`,钉住退避序列(3s 后有请求、再 3s 没有、
|
|
37
|
+
再 3s 才有)、读到即停、「重试」按钮立即读取且不等待退避、空 message 回落到
|
|
38
|
+
`row.requestFailed`、以及折叠期间零请求。
|
|
39
|
+
|
|
40
|
+
- **Web GUI 段落的第二句也是独立指纹,改词表补齐(#167)**
|
|
41
|
+
- #167 用前缀二分测到最小失败窗口是 `er target, they mean this GUI.`:只改第一句(5.13.1 的做法)
|
|
42
|
+
仍 400,两句都改才 200。此前「第二句不是独立指纹」的结论被这次实测推翻,写入表中的注释与
|
|
43
|
+
`tests/issue163-sentence-rewrite.spec.ts` 里那条断言一并纠正。
|
|
44
|
+
- 新增两行长→短的表项(`without naming another target, …` 与 `they mean this GUI.`),因为门槛在
|
|
45
|
+
**截断形态**上也会触发;长行先跑,完整段落不会落到短行。
|
|
46
|
+
- **测试**:新增 `tests/issue167-second-gui-span.spec.ts`(两句都消失、截断窗口也消失、改写后
|
|
47
|
+
仍是可读指令、幂等、经 `prepareChatBody` 对 system 与 assistant 生效而对 user 不动、无关文本不变)。
|
|
48
|
+
|
|
49
|
+
- **心跳存活性不再把「进程启动时刻」和「心跳写入时刻」相比;auth 文件名接受 `-ai.info`(#174)**
|
|
50
|
+
- **① 误判为本机实测**:`isHeartbeatProcessAlive` 拿 `ps` 的进程启动时间与 `heartbeat.registeredAt`
|
|
51
|
+
(插件写入心跳那一刻)比,窗口 60s。两者的差是「宿主起来之后过了多久才挂载插件」,真实场景普遍
|
|
52
|
+
远超 60s——报告者实测进程 19:47:20、心跳 19:58:45,差 684,817 ms,于是 `doctor` / `status`
|
|
53
|
+
对一个刚写完心跳的活进程报 `hostBundle: "stale"`。既有测试没挡住,因为它把 `registeredAt`
|
|
54
|
+
**设成**进程启动时刻,等于断言了这个 bug 的前提。
|
|
55
|
+
修法:心跳新增 `hostStartedAt`(写心跳时取本进程启动时刻),比较改与**它**比;v1 心跳仍可读,
|
|
56
|
+
降级用 `registeredAt` 并按更宽的窗口判定(宁可漏判 PID 复用,也不把活宿主叫成死的)。
|
|
57
|
+
- **② 文件名**:`WORKBUDDY_LIVE_FILENAME` 只有 `workbuddy-desktop.info`,而 5.7.x 桌面端写的是
|
|
58
|
+
`workbuddy-desktop-ai.info`。后果是双重的:`fileRank` 的「live 永远优先」从不命中(排序退化为
|
|
59
|
+
按 `lastRefreshAtMs`,结果对只是运气),而 `doctor` 报 `desktopAuthFile.present: false` 并给出
|
|
60
|
+
「设置 WORKBUDDY_AUTH_FILE」的无效建议——同时账号列表是满的。修法:已知 live 名变成列表,
|
|
61
|
+
`fileRank` / 候选扫描 / `doctor` 统一走 `isLiveDesktopAuthFileName`。
|
|
62
|
+
- **顺带查出的第三处**:`resolveDesktopCandidates()`(`desktopAuthPath()`、`desktopFilePresent()`
|
|
63
|
+
的来源)原本**忽略 `authDirs`**,于是诊断路径与读取器实际扫描的路径不是同一组——与 ② 是同一种
|
|
64
|
+
「诊断与读取各说一套」的缺陷。现在两者同源。
|
|
65
|
+
- `diagnose()` 的 `missing` 改为**按目录**报一次、且只在目录里没有任何可读凭据时才报:否则在
|
|
66
|
+
用 `-ai.info` 的机器上会多出一行「另一个名字缺失」,而那条用户无法处理,也与已读到的账号相矛盾。
|
|
67
|
+
|
|
68
|
+
- **生成类临时文件不再只写不删(#174 ③)**
|
|
69
|
+
- 生图(`%TEMP%/workbuddy-image-gen`)与视频(`workbuddy-video-gen`)原来只 `mkdir` + `writeFile`,
|
|
70
|
+
仓库内无任何清理路径。新增 `src/temp-files.ts` 的 `pruneTempDir()`,**挂在写入之后**按 mtime
|
|
71
|
+
保留最新 20 个(不是启动时扫一遍:插件可能活在久不重启的宿主里,那时恰恰不生成)。
|
|
72
|
+
能力被吞掉——清理失败绝不能把一次成功的生成变成失败。
|
|
73
|
+
|
|
74
|
+
- **请求体超限回 413 而不是 500(#174 ④)**
|
|
75
|
+
- `readBody` 原来 reject 一个裸 `Error`,被外层 catch 写成 `500 internal`。改为具名
|
|
76
|
+
`RequestBodyTooLargeError` → 413 + `types: client`。
|
|
77
|
+
- 同时修掉一个连带缺陷:原来超限时 `req.destroy()` **销毁 socket**,那样 413 根本送不到客户端,
|
|
78
|
+
客户端只会看到连接被重置——比 500 更糟。现在改为停止读取(摘掉监听 + `pause()`),响应得以送达。
|
|
79
|
+
|
|
80
|
+
### Docs
|
|
81
|
+
|
|
82
|
+
- **市场条目已收录,但线上描述停在 2.0.15——修正为当前能力,并把「条目不会自己更新」写进文档(#164 的索引问题顺带查出)**
|
|
83
|
+
- **实际状态更正**:PR [#3812](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/3812) **已于 2026-08-30 合并**(merge `c079c705`),而两份 README 与本目录一直写着「等待维护者合并」。(与 #164 的「市场索引把 5.12.x 标成最新」是**两件独立的事**:条目里没有版本字段,索引给旧版是同步节奏问题,不是这句描述造成的。)
|
|
84
|
+
- **线上条目过期**:`data/plugins/dingminhua__dsh-connect-workbuddy.yml` 还是 2.0.15 时写的那句——没提国内版/国际版双供应商、没提每次刷新读取积分倍率、没提账号池自动换号,也没说明签到是**手动**的(会真实领取奖励,只在点按钮时执行)。已提更新 PR [#10281](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/10281),并补 `icon:` 指向本仓库 `icons/dsh-connect-workbuddy-128.png`。
|
|
85
|
+
- **写进文档的教训**:条目合并后不会自己更新;插件每次有大的能力变化,描述要另开一次 PR 跟上——注册表评审明确「描述会被当作声明,并对着代码核」。
|
|
86
|
+
- 同时修正两处过时规范:**没有提交数门槛**(对方 #4196 已废除),以及**只声明 `dsh.client` 是最常见的被拒原因**。
|
|
87
|
+
- 本目录的条目镜像补 `icon:` 字段;`description.en` / `description.zh` 仍与 `locale/en.json` / `locale/zh.json` 逐字一致,由 `tests/locale-meta.spec.ts` 钉住(该测试会拒绝任何一侧的重新翻译)。
|
|
88
|
+
|
|
89
|
+
- **两份 README 的「安装」维护一条版本下限:CN 区渠道校验 400 请确保 ≥ 5.14.0(#164、#167)**
|
|
90
|
+
- **为什么**:#164 的实测矩阵确认 5.13.1 的按句改词在同一份 body 上 **200**,而 5.12.1 / 5.12.4 的
|
|
91
|
+
原样、`prepareChatBody`、三级救援**全部 400**。停在旧版的用户会反复撞上这个已经修好的 400。
|
|
92
|
+
- **5.14.0 把下限从 5.13.1 提到 5.14.0**:5.13.1 只改了 Web GUI 段的第一句,#167 证明第二句是
|
|
93
|
+
独立指纹,所以停在 5.13.1 仍会有一部分人必现 400。下限跟着「第一个真正改完两句的版本」走,
|
|
94
|
+
否则这条警告反而会把人引到一个仍然会撞墙的版本上。
|
|
95
|
+
- **两个升级不到的原因也写进去了**:市场不同索引在同一时刻可能给出三个「最新」(#164 实测
|
|
96
|
+
5.12.3 / 5.12.4 / 5.13.1 并存),以及 pnpm 的 24 小时 `minimumReleaseAge` 会把浮动范围静默压回
|
|
97
|
+
上一版;需要立刻修复时给的是**精确版本号**命令。
|
|
98
|
+
|
|
99
|
+
### Tests
|
|
100
|
+
|
|
101
|
+
- **把 #164 测到的「最小触发跨度」钉成回归测试(`tests/issue163-sentence-rewrite.spec.ts`)**
|
|
102
|
+
- #164 二分出门槛比 #163 更短:`@` 路径段**前 112 字**(两句)、Web GUI 段**前 217 字**(两句)
|
|
103
|
+
就会触发;两句各自单发都是 200。改词表必须把这两个**截断形态**也清掉,否则宿主只发半段时
|
|
104
|
+
仍会被拒。测试断言两段改写后都不含指纹、且改写后仍是可读的指令。
|
|
105
|
+
- 同时记录一条与 #164 建议 2 对应的口径:第 0 级改词会让 system 略长(#164 实测 +32 字节 / 5 行),
|
|
106
|
+
自助对比正文长度时不要把它当成「扰动后长度」。
|
|
107
|
+
|
|
108
|
+
- **#174 四条各自钉住可失败点**:心跳(长运行宿主必须判活、`hostStartedAt` 与 `registeredAt`
|
|
109
|
+
冲突时以精确者为先、v1 心跳仍可读、容差常量本身)、临时文件(按 mtime 而非文件名、只清本类型、
|
|
110
|
+
目录不存在返 0、`keep` 是有限值)、413(真发一个超限流,断言状态码)、diagnose
|
|
111
|
+
(文件名命中时**不**报 missing、tried 与 failures 按目录对齐)。
|
|
112
|
+
- 对修复前代码跑:心跳 4 条红、413 1 条红、#174 临时文件与 diagnose 的新用例分别红。
|
|
113
|
+
|
|
114
|
+
- **#170 的退避是「有界」的,不只是「有重试」**:`tests/issue170-status-retry.spec.tsx` 断言第二次
|
|
115
|
+
尝试**不在** 3s 时发生(恒为 3s 的定频实现会让这条红),并且读到成功后才回到安静的 60s 轮询。
|
|
116
|
+
- **#168 断言的是宿主真正读的那个字段**:`inputModalities`,不是 `multimodal` 中间量;只测后者的话,
|
|
117
|
+
盖值的四个调用点全部传错数组也能绿。
|
|
118
|
+
|
|
119
|
+
## 5.13.1 (2026-10-29)
|
|
120
|
+
|
|
121
|
+
### Bug Fixes
|
|
122
|
+
|
|
123
|
+
- **按句改写网关固定指纹,落在 `neutralizeChannelFingerprints` 之前(#163,2026-10-29 实测)**
|
|
124
|
+
- **新测量**:CN 网关会在匹配前规范化 U+200B;system 开头插零宽仍 11128,`Note: ` 或真改词
|
|
125
|
+
才 200。`user` / `tool` 开头不扫(18:46 连测 3 次 200),所以不碰这两个角色。
|
|
126
|
+
- **第三处是整段组合指纹**:`@` 路径那 8 句逐句单独发 200,整段一起发 400。只改开头或整段
|
|
127
|
+
`replaceAll` 都不满足维护性要求;按句改才能既拆掉组合指纹,又覆盖宿主改一两个词的情况。
|
|
128
|
+
- **修法**:新增 `rewriteChannelFingerprintPhrases()` 与按句表(identity、coding agent、Web GUI、
|
|
129
|
+
`Agent Teams is available in this session`、`@` 路径整段逐句),在 `prepareChatBody()` 里
|
|
130
|
+
`keepPromptLast` 之后、`neutralizeChannelFingerprints` **之前**调用;只遍历 `system` /
|
|
131
|
+
`assistant` 的 string content 与 array text part,`user` / `tool`、工具调用、图片不动;无命中
|
|
132
|
+
逐字节不变,函数幂等。
|
|
133
|
+
- **测试**:新增 `tests/issue163-sentence-rewrite.spec.ts`(三处 span 消失、普通对话不变、
|
|
134
|
+
幂等、assistant 改写、array part、user/tool 不动、无命中 no-op);同步更新 `issue98*` /
|
|
135
|
+
`upstream` / `identity-clause-break` 里旧的「中性头 + 原文」断言。`pnpm run check` 通过。
|
|
136
|
+
|
|
137
|
+
- **自动「继续」降为最后兜底:默认 1 次,文案不再承诺有效(#163)**
|
|
138
|
+
- **证据**:固定 system 指纹每轮逐字节相同;同一 body 打上百次都是 400,只改三处才 200
|
|
139
|
+
(freedomx86 17:44);现场 3 次自动续发、Windpixy 反复继续/新老会话、QIYU 三级救援全跑完
|
|
140
|
+
仍 400;appulsexi-maker 19:12 的零历史新会话、只有 system 一条,仍 11128。新回合换不掉
|
|
141
|
+
固定 system 内容,自动续发对这一族无效。
|
|
142
|
+
- **修法**:`DEFAULT_MAX_CONSECUTIVE` 与 schema / 客户端默认 3 → **1**;卡片、配置描述、
|
|
143
|
+
README 与 `docs/PINNED-11128-RETRY.md` 改为「最后兜底,不是 11128 的修复;固定指纹由按句
|
|
144
|
+
改词处理,只有随轮次变化的内容才可能靠它碰过」。
|
|
145
|
+
- 仍然默认关闭;每次仍真实消耗一次请求。**行为面**未变(只认 `channel_reject`、不换号、
|
|
146
|
+
不碰子代理、成功/真人消息清零)。
|
|
147
|
+
|
|
148
|
+
- **换 provider 后,另一家的 thinking 不再被摊成 assistant 正文发给 WorkBuddy(#163 现场)**
|
|
149
|
+
- **现场**:`session-bb9396be` 在 02:03:43 从 `dsh-opencode-go/deepseek-v4.1-flash` 切到
|
|
150
|
+
`workbuddy/deepseek-v4.1-flash`,02:04:35 起连续 4 个回合 400/11128;错误里三条 assistant 的
|
|
151
|
+
14,410 / 4,225 / 5,138 字,逐字等于切换前 seq 98/109/114 的 reasoning 块。
|
|
152
|
+
- **机制**:宿主把另一家的 assistant 轮次重建为 `dsh-foreign`,pi-ai 把无签名的 thinking 块
|
|
153
|
+
改写成可见 `text`;WorkBuddy 网关扫描 assistant 消息,把它当成未授权渠道。`dropForeignThinking`
|
|
154
|
+
本来就是修这个的,但默认关闭,且卡片上没有开关。
|
|
155
|
+
- **修法**:`withLegacyContext` 在 `alignAssistantIdentities` 之后**无条件**丢弃**跨 provider** 的
|
|
156
|
+
foreign thinking(`api: dsh-foreign`,或 provider 不属于本 bundle 的两条路由);同一路由内换
|
|
157
|
+
模型(`hy3` → `deepseek-v4.1-flash`)仍由 `dropForeignThinking` 显式开关控制。跨 provider 的
|
|
158
|
+
思考块没有签名、本来就不可能作为 reasoning 重放,丢弃不改变语义;文本与工具调用不动。
|
|
159
|
+
- **诊断**:自动丢弃时写一条 warn(仅在开关关闭时),`latestForeignThinking` 仍照旧上报。
|
|
160
|
+
- **测试**:`tests/issue147-foreign-thinking.spec.ts` 新增三条(跨 provider 选中、同路由模型
|
|
161
|
+
切换不选、global 路由对齐后不误伤);`tests/issue147-model-leg-wiring.spec.ts` 新增两条(两个
|
|
162
|
+
generation × 两个入口的 `dsh-foreign` 回合在开关关闭时也被丢弃)。
|
|
163
|
+
|
|
3
164
|
## 5.13.0 (2026-10-28)
|
|
4
165
|
|
|
5
166
|
> 用户贴的那条失败:`本轮运行失败400: {"message":"workbuddy 上游失败:被网关的渠道校验拦截(不是账号、
|
package/README.en.md
CHANGED
|
@@ -52,12 +52,12 @@ It depends on the WorkBuddy client's own endpoints (not a public API), so the pl
|
|
|
52
52
|
- **Optional: make the usage page record these calls like the official client's (5.8.0, issue #132, OFF by default)** — with `usageIdentity` on, chat requests carry `X-IDE-Name: WorkBuddy` (plus `X-IDE-Type: CLI` and a version taken from the existing product UA, not a second hard-coded number), so the usage page's client column is no longer blank, and the conversation-identity triple becomes the official **32-character lower-case hex UUIDv7** with `X-Request-ID` alongside it — the gateway echoes that id as the SSE `id` and writes it into the usage page, so hyphenated v4 ids made one account's rows look like two different clients — while "fresh per request, all distinct" has not been relaxed. **`User-Agent` is untouched under either setting**: billing and rate limits recognise the CLI channel. The image endpoint does not follow this switch; it keeps the shape measured on 2026-10-21. It is off by default because the shape fixes nothing on its own (the three `11128` doors read message openings, a missing conversation id, and whether the first message is a system prompt — no header moves any of them) and the only measurement is a reporter's on an older build, so a purely cosmetic alignment does not get to put every account at risk first. There is also an opt-in `keepPromptLast` (**off by default**): the usage page takes the request text from the LAST `user` message, and the host appends its time samples, runtime context and `<system-reminder>` blocks AFTER what you actually typed, so that column recorded injected text; when enabled it moves those injected blocks **above the real prompt** — order only, except that the recorded prompt's own leading `<acp …>mNNNNN</acp>` marker is stripped (leave it and that column still opens with a marker, i.e. nothing was fixed); earlier messages **keep** theirs, because the model cites history by those ids and the only id that can never matter is the message being answered now. It stays off by default because the order is something the upstream counts and a few leading characters are removed from the real prompt. The move matches only the host's own injected prefixes, and a conversation with no injected tail is untouched byte for byte — marker included — pinned by tests written for both.
|
|
53
53
|
- **Optional: move each new session's title request onto the hidden lightweight model (issue #133, OFF by default)** — the DSH host fires one title-generation request per new session through the same provider as the conversation, so it is billed at the main paid model's rate. With `sessionTitle.enabled` on, a request the plugin recognises as a title (its only user message opens with the host's template `Generate the session title from this JSON array of human messages:`) is re-pointed at `sessionTitle.model` (default `wb-lite`). The reporter measured 0 credits for those calls, with three caveats: it is still a real upstream request and counts toward request volume; the measurement is the reporter's account, not reproduced here; detection is fail-closed — if the host rewords the template the rewrite silently stops and billing reverts, and ordinary conversations are never touched. If the upstream rejects the model, only that session's title fails once; the conversation is unaffected. Like `usageIdentity` / `keepPromptLast` / `extraInjectedPrefixes` in the bullets above, `sessionTitle` is read once when the plugin mounts: **after editing `cordis.patch.yml`, reload the plugin or restart DSH for the change to take effect** — hot edits do not apply.
|
|
54
54
|
- **Benefits: a marker for what is unclaimed, plus a per-account Claim button** — the card marks, account by account, anything in the upstream's benefit campaigns (compensation / gifts) that **has not been claimed yet**. The data comes from the upstream's own two status endpoints; in the marking stage the plugin is **read-only** and never calls the two write endpoints. Three display rules all follow the upstream's state: `active && !claimed` shows the row, `claimed` removes it entirely, and an expired campaign (upstream flips `active` to `false`) disappears by itself — the plugin **never compares `end_time` against the local clock**, so a skewed machine cannot argue with the server; `end_time` only drives the "three days left" highlight. `active` alone is not enough to decide: an already-claimed account still reports `active: true`, and reading it alone would leave a claimed campaign hanging there forever. The read reuses the check-in pipeline (once per account, cached for 30 minutes, no request while the account pool is off), and a failing endpoint counts as "unread" rather than caching "nobody has benefits". Each row ends in a solid amber **Claim pill**, and one click claims **one campaign of one account**: there is no "claim everything", because a click spends one irrevocable real entitlement — and the write route **requires `accountId`**, so the request itself cannot batch. Before writing it **re-reads** the status and decides again; anything already claimed or expired returns 409 without touching upstream. After a successful claim it answers with the upstream's **fresh reading** and refreshes the card, with no optimistic update — even a silent upstream failure cannot leave a false "claimed" behind — and it also drops that account's cached benefit state, so the pill cannot linger on a stale status.
|
|
55
|
-
- **Optional:
|
|
55
|
+
- **Optional: a LAST-RESORT 「继续」 when the gateway refuses the request (OFF by default, 1 send by default)** — when the upstream rejects with `400` and business code `11128` the turn simply ends: the plugin does not retry inside that request (its three-rung ladder already ran), it does not swap accounts (the gate matches message contents, and every account sends the same ones), and the host does not retry. **This is not a fix for 11128.** Fixed system fingerprints (the identity / Web GUI / @-path paragraph from #163) are byte-identical every turn, so resending only pays for the same 400 again; since 5.13.1 `prepareChatBody` rewrites known phrases sentence by sentence (5.14.0 adds the Web-GUI paragraph's second sentence, see #167). Only content that happens to change between turns can be helped by a new turn. Turning on the card's Capabilities switch makes the engine wait 300 ms and send `继续` through `agent.followup` (`source.kind` is `workbuddy-continue`, **not** `user`). **The cap counts CONSECUTIVE sends** (`channelRejectContinue.maxConsecutive`, default **1**; the old default of 3 is disproven — if one cannot help, three cannot): one successful turn, or one message you type yourself, resets it; the plugin's own message does not. Subagent sessions are never touched, and neither is a session already running or with something queued. **Every send spends a real request**, hence OFF by default; it is also the only upstream-shaped switch whose change **takes effect immediately**. It can be set directly in `cordis.patch.yml`:
|
|
56
56
|
|
|
57
57
|
```yaml
|
|
58
58
|
channelRejectContinue:
|
|
59
59
|
enabled: true
|
|
60
|
-
maxConsecutive:
|
|
60
|
+
maxConsecutive: 1
|
|
61
61
|
text: 继续
|
|
62
62
|
```
|
|
63
63
|
|
|
@@ -184,6 +184,10 @@ 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.0.** 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
|
+
>
|
|
189
|
+
> Two things stop people from actually upgrading: **① different marketplace indexes can report three different "latest" at the same moment** (5.12.3 / 5.12.4 / 5.13.1 were all live at once per #164), so "check for updates" on a stale index never shows the new version; **② 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.0`.
|
|
190
|
+
|
|
187
191
|
### Supported host range
|
|
188
192
|
|
|
189
193
|
DSH's shipped gate (`evaluatePluginCompatibility` in `packages/boot/app-boot/src/plugin-compatibility.ts`) checks every declared `dsh-*` peer range; **one unsatisfied peer skips the entire bundle** (it lands in `skippedBundles` and is only printed to stderr) — no provider is registered, no card appears, the model list goes empty, and the page shows no error at all.
|
|
@@ -454,12 +458,14 @@ else { await envelope.response.text() /* or consume it as SSE */ }
|
|
|
454
458
|
|
|
455
459
|
## Marketplace listing
|
|
456
460
|
|
|
457
|
-
The plugin ships an installable `dsh.bundle` manifest and is published to npm. Community marketplaces generally sync entries from the [Awesome DSH Plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin) registry; the
|
|
461
|
+
The plugin ships an installable `dsh.bundle` manifest and is published to npm. Community marketplaces generally sync entries from the [Awesome DSH Plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin) registry; the local mirror of the entry lives at [`awesome-dsh-plugin-submission/dingminhua__dsh-connect-workbuddy.yml`](awesome-dsh-plugin-submission/dingminhua__dsh-connect-workbuddy.yml). **The entry was merged and listed via PR [#3812](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/3812) (2026-08-30).**
|
|
462
|
+
|
|
463
|
+
> ⚠️ **A merged entry does not update itself.** #3812 was merged when the plugin was at 2.0.15, so the registry line kept describing that release: no CN/international dual providers, no per-refresh credit multipliers, no account pool with failover, and nothing saying check-in is **manual** (it claims a real reward and only runs on a button press). **Every meaningful capability change needs its own entry PR**; the registry review states plainly that a description is read as a claim and checked against the code. The current update is PR [#10281](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/10281).
|
|
458
464
|
|
|
459
465
|
**Listing directory rules** (per [contributing.md](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/blob/main/contributing.md)):
|
|
460
466
|
|
|
461
467
|
- One YAML per plugin: `data/plugins/<owner>__<repo>.yml` with `url` (exactly matching the repo), `name` (`owner/repo`), `category` (one of the valid values; this project uses `model`), and `description.en` (required, ending with a period) / `description.zh` (optional); a description containing `: ` must be quoted.
|
|
462
|
-
- The repo must declare a `dsh.bundle` manifest (here `dsh.bundle.patch: ./cordis.patch.yml`) and carry the `dsh-plugin` topic
|
|
468
|
+
- The repo must declare a `dsh.bundle` manifest (here `dsh.bundle.patch: ./cordis.patch.yml`) and carry the `dsh-plugin` topic — **declaring only `dsh.client` is the most common rejection**. The 1-day repo age is checked automatically by CI (there is **no commit-count bar**; that one was removed).
|
|
463
469
|
- Both READMEs (`README.md` / `README.zh.md`) are generated — never edit by hand; after changing the YAML, regenerate with `node scripts/generate-readme.mjs`.
|
|
464
470
|
- At most 3 entries per PR; change only your own entry, never touch other plugins.
|
|
465
471
|
|
|
@@ -467,7 +473,7 @@ Marketplace screenshots and GitHub README badges are two separate mechanisms:
|
|
|
467
473
|
|
|
468
474
|
- **GitHub badges** are produced by the Shields/dshfind image links at the top of this README.
|
|
469
475
|
- **Marketplace screenshots** follow the current convention and are declared in the plugin's own root [`screenshots.json`](screenshots.json) (this project declares its usage-card screenshot there). The registry's `data/screenshots.json` is a legacy fallback and **no longer accepts new keys** (this project removed its edit before submitting).
|
|
470
|
-
- **Marketplace icons
|
|
476
|
+
- **Marketplace icons** are declared with the entry's optional `icon:` field (this project points at `icons/dsh-connect-workbuddy-128.png`): it **must be a GitHub-hosted raster** (png/jpg/jpeg/webp) — SVG and third-party image hosts are refused — and storefronts fall back to the owner avatar when it is omitted. It is not a generic npm `package.json` field, nor a README badge.
|
|
471
477
|
|
|
472
478
|
**The title and description on the plugin card are not rendered by this plugin** — they are **package metadata** the DSH plugin manager reads: it takes its words from `meta.title` / `meta.description` in `locale/<lang>.json` (`locale/en.json` is the anchor, so without it `zh.json` is never read; without the `"./locale/*.json"` wildcard in `exports` the whole directory is skipped **silently**), and falls back to `package.json`'s `description` for English. The same sentence therefore lives in three places in this repository, and [`tests/locale-meta.spec.ts`](tests/locale-meta.spec.ts) pins them together: the card dictionary (`locale/en.json` / `locale/zh.json`), the English fallback (`package.json.description`) and the listing draft (`description.en` / `description.zh` in `awesome-dsh-plugin-submission/dingminhua__dsh-connect-workbuddy.yml`). **Edit one, edit all three.**
|
|
473
479
|
|
|
@@ -492,8 +498,9 @@ Marketplace screenshots and GitHub README badges are two separate mechanisms:
|
|
|
492
498
|
- **Switching region makes the context "grow", and can hit `code 11115` immediately — the gateway is not over-charging you.** The same history is counted under different buckets in the two regions, and the root cause is how the host's bundled `@earendil-works/pi-ai` replays history. It evaluates `isSameModel` for every assistant message (`transform-messages.js:68`, keyed on provider **and** api **and** model id all being equal): for a message that fails, its thinking is **downgraded to ordinary content** (`:88–96` — not dropped), and content is what the gateways count, while thinking goes out in a different field. This plugin registers the domestic and international editions as **two provider ids** (`workbuddy` / `workbuddy-global`, see [How it works](#how-it-works)) that carry the **same model id** — so one region switch moves **all the other side's thinking into the content bucket at once**. That is the cause of issue #59's "the same conversation history is charged 1.374M by the international gateway and 708K by the domestic one (1.94×)"; switching back is the same formula read the other way, 0.696×.
|
|
493
499
|
- Three consequences you can see: a region switch can move the token count by ±30%–94%, and **how far depends only on the other side's thinking character count, not on total history length**; **switching models inside one region** triggers the same downgrade (the model leg of `isSameModel`), so the count can move without a region switch at all — the downgrade itself cannot be removed, but **its becoming prose can**, see `dropForeignThinking` below; and that jump lands on top of the host's current compaction threshold (`min(0.8 × window, window − 128000 − 65536)`), so a session that was comfortably under the limit can hit `code 11115 prompt is too long` the moment you switch region.
|
|
494
500
|
- **The downgrade is not only a counting problem: the reasoning becomes the answer, and the model then copies that shape (issue #147).** The bullet above covers what the downgrade does to BILLING; this one covers what it does to CONTENT. For a message where `isSameModel` fails, pi-ai does not drop its thinking — it rewrites each block to `{type:'text'}` (`transform-messages.js:87–90`), i.e. the answer channel. So after a provider switch, **another model's internal working notes are presented as this turn's reply**, and a model that reads "all of my past reasoning was plain text" keeps writing its plan into the text channel from then on. The reporter measured one real turn: 56 of 80 assistant messages carried an answer while their own `reasoning` was ≤ 1 character, 20 of those held English working notes totalling 58,960 characters, and CJK fell to 1.37% of the 60,984 visible characters where the previous six turns had been entirely Chinese. It also pushed the session from 696,966 to 758,005 total tokens against an 800,000 compaction threshold.
|
|
495
|
-
-
|
|
496
|
-
-
|
|
501
|
+
- **Cross-provider foreign thinking is dropped unconditionally; the `dropForeignThinking` switch only covers a model switch inside one route (off by default)**: this plugin **removes** those foreign thinking blocks before the request goes out, instead of letting them be flattened into prose. Removal rather than preservation, because these blocks are built by the host's `foreignAssistant()` and carry **no `thinkingSignature`** — even claimed as our own turn they could never be replayed as signed reasoning, so their only possible contribution to the wire was the prose they became. Text blocks and tool calls are never touched, and neither is any message's identity.
|
|
502
|
+
- **No switch for the cross-provider leg**: after a provider switch the host rebuilds the other provider's assistant turns as `dsh-foreign`, and pi-ai flattens their thinking into assistant text; the WorkBuddy gateway scans assistant messages, so the whole turn is refused with `11128` (issue #163's measured scene: 14,410 / 4,225 / 5,138 characters of reasoning replayed as prose, with all three in-place rescue rungs and three auto-`继续` attempts failing). Those blocks could never be replayed as reasoning anyway, so dropping them is a correction rather than a preference and does not wait for an opt-in. `dropForeignThinking` only widens the same repair to a model switch **inside one route** (`hy3` → `deepseek-v4.1-flash`, both served by this bundle and stamped `provider: workbuddy`); for that leg the off path is still **byte-for-byte** what earlier builds sent.
|
|
503
|
+
- An automatic drop writes a warn (only while the switch is off) and still reports through `latestForeignThinking`, so the log names which blocks that request removed.
|
|
497
504
|
- The ORDER is load-bearing: the repair runs **after** `alignAssistantIdentities`. A global-region turn of this plugin's own arrives carrying `api: 'dsh-foreign'` too (the host's replay-degrade path), so diagnosing first would classify it as foreign and delete its reasoning — exactly issue #102 in reverse. `tests/issue147-foreign-thinking.spec.ts` pins the order through pi-ai's real `transformMessages`.
|
|
498
505
|
- Turns already recorded in history are **not** retroactively repaired: they are already stored as prose, and the switch governs later requests only. Starting a new session after the provider switch is the direct way out.
|
|
499
506
|
- **It also covers switching MODELS inside one region** (`hy3` → `deepseek-v4.1-flash`, both served by this plugin, both stamped `provider: workbuddy`). The provider and api legs are equal there, so only the `model` leg separates the turn from the request — and 5.12.0's predicate compared just the first two, so that switch detected **nothing** and the switch looked broken. The model id comes from the request entry points (`model.id` on `stream` / `streamSimple`); omitting it keeps 5.12.0's behaviour exactly, which is why the card's read-only path is unaffected (it reads `lastForeignThinking`, which the request path fills).
|
package/README.md
CHANGED
|
@@ -61,12 +61,12 @@
|
|
|
61
61
|
- **可选:让上游用量页把这笔记成官方客户端发的那笔(5.8.0,issue #132,默认关闭)** —— 开 `usageIdentity` 后 chat 请求带上 `X-IDE-Name: WorkBuddy`(连同 `X-IDE-Type: CLI` 与版本号;版本号取自现有的产品 UA,不留第二个硬编码数字),用量页的「使用端」由此不再空白;会话身份三件套同时改成官方客户端那种 **32 位十六进制 UUIDv7** 并补上 `X-Request-ID`——网关把这个 id 回显成 SSE 的 `id` 并写进用量页,此前带连字符的 v4 与官方记录摆在同一页上像两种客户端;**每请求现生成、互不相同**这条没有放松。**`User-Agent` 在两种取值下都一字未动**——账单与限流认的是 CLI 通道;生图路径也不跟着这个开关变,它保持自己 2026-10-21 实测过的形状。**默认关**的理由:形状本身不修任何东西(`11128` 的三道门读的是消息开头、会话 id 有没有、首条是不是 system,头改不动其中任何一条),而实测证据来自报告者的账号与旧版本、本插件未在真机复现——一次纯显示对齐不该让所有账号先承担风险。另有可选的 `keepPromptLast`(**默认关闭**):用量页的「请求」列取自 body 里**最后一条** `user` 消息,而宿主把时间采样、运行时上下文、`<system-reminder>` 追加在你真正输入的那句**之后**,那一列于是常年记的是注入块;开启后把这些注入块**搬到真实输入之前**(只变顺序;另外会剥掉**被记录那一条**真实输入前面的 `<acp …>mNNNNN</acp>` 标记,否则那一列仍然以标记开头而等于没修,更早的消息一律保留自己的标记,因为模型靠它们引用历史)。默认关的理由是它会改变上游会数到的消息顺序、并删掉真实输入的几个前导字符;搬移只匹配宿主自己的注入前缀,而普通对话在没有注入尾块时逐字节不变(连标记也不剥)——这两点各有专门测试钉住。
|
|
62
62
|
- **可选:把每个新会话的标题请求改到隐藏轻量模型(issue #133,默认关闭)** —— DSH 宿主每个新会话会发一次标题生成请求,走的是与对话相同的 provider,于是这笔按主付费模型的费率计费。开启 `sessionTitle.enabled` 后,插件把能按宿主标题模板(`Generate the session title from this JSON array of human messages:` 开头、整条 body 只有这一条 user 消息)识别出的标题请求改写到 `sessionTitle.model`(默认 `wb-lite`)。报告者实测该模型对这类请求 **0 积分**,但注意三点:这仍是**真实上游请求**,请求数照算;证据是报告者账号级的,本插件未复现;识别按 fail-closed 设计——宿主若改模板,改写静默停止、回到原计费,不会误伤普通对话。若上游拒绝该模型,只是该会话标题生成失败一次,不影响主对话。另外,`sessionTitle` 与本条及上一条的 `usageIdentity` / `keepPromptLast` / `extraInjectedPrefixes` 一样,都是插件挂载时读一次配置:**改完 `cordis.patch.yml` 后需要重载插件或重启 DSH 才生效**,热改不生效。
|
|
63
63
|
- **福利活动:待领取标记 + 单账号「领取」按钮** —— 卡片会逐账号标出上游活动(补偿 / 赠礼)里**还没领的那一份**,数据来自上游自己的两个状态接口,标记阶段**只读**、两个写接口一次都不调用。显示规则完全由上游状态决定:`active && !claimed` 才显示,`claimed` 整行消失,活动过期(`active` 翻 `false`)自动消失——**不用本地时钟去比 `end_time`**,本机时间偏了也不会与服务端打架;`end_time` 只用于 3 天内临期高亮。`active` 单独看不行:已领取的账号 `active` 仍为 `true`,只看它会让领过的账号永远挂着待领取。读取复用签到的管线(每账号一次、30 分钟缓存、账号池关闭时不发请求),任一接口失败按「未读」处理,不会把一次失败缓存成「全都没福利」。行尾是一个实心琥珀色的**胶囊按钮「领取」**,一次只领**一个账号的一个活动**:没有「全部领取」,因为一次点击花掉的是一份不可撤销的真实权益,写路由也**强制要求 `accountId`**,请求本身无法批量领取。领取前**重新读取**状态再判定,已领取或已过期一律 409 且不调用上游;领取后用上游的**新读数**作答并刷新卡片,不做乐观更新——即使上游静默失败也不会留下「已领取」的假象,成功后同时清掉该账号的福利缓存,按钮不会顶着旧状态赖着不走。
|
|
64
|
-
-
|
|
64
|
+
- **可选:被网关渠道校验拦截后的最后兜底「继续」(默认关闭,默认只发 1 次)** —— 上游用 `400` + 业务码 `11128` 拒绝时,这一轮就此结束:插件不会在原请求里重试(三级救援已跑完),不会换号(网关匹配的是消息内容,每个账号发出去都一样),宿主也不重试。**它不是 11128 的修复**:固定 system 指纹(#163 的 identity / Web GUI / @ 路径整段)每一轮逐字节相同,重发只会再付一次同一个 400;5.13.1 起这些由 `prepareChatBody` 的按句改词处理(5.14.0 又补上 Web GUI 段落的第二句,见 #167)。只有被拒内容刚好随轮次变化的窄情况,才可能靠新回合碰过去。开启卡片「能力」栏的开关后,插件检测到这一类失败、等 300ms、用 `agent.followup` 发一条 `继续`(`source.kind` 是 `workbuddy-continue`,**不是** `user`)。**上限数的是「连续」次数**(`channelRejectContinue.maxConsecutive`,默认 **1**;旧默认 3 已证伪——1 次救不了,3 次也救不了):成功一轮、或你自己发一条消息,计数清零;插件自己发的不清零。子代理会话一律不碰;会话正在跑、或收件箱已有排队内容时也不发。**每次都会真实消耗一次请求**,所以默认关闭;它也是本插件里**唯一一个改完即生效**的上游类开关。与其它上游开关一样,也能直接在 `cordis.patch.yml` 里写:
|
|
65
65
|
|
|
66
66
|
```yaml
|
|
67
67
|
channelRejectContinue:
|
|
68
68
|
enabled: true
|
|
69
|
-
maxConsecutive:
|
|
69
|
+
maxConsecutive: 1
|
|
70
70
|
text: 继续
|
|
71
71
|
```
|
|
72
72
|
|
|
@@ -194,6 +194,10 @@ DSH PiAiAdapter(每个 provider 一套)
|
|
|
194
194
|
|
|
195
195
|
> ⚠️ **版本要求:DSH 0.1.7-rc.1 及以上(当前窗口:`0.1.7-rc.1` ~ `0.2.x` 正式版)。** 自 v2.1.0 起本插件只支持 DSH 0.1.7-rc.1 及以上的宿主(旧版宿主无法通过依赖解析安装本版);0.1.5 及更早的宿主请停留在 v2.0.15。自 v2.1.2 起上界放宽为 **`<0.3.0-0`**,0.3.0 线被明确拒绝。详见下方[受支持的宿主范围](#受支持的宿主范围)。
|
|
196
196
|
|
|
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
|
+
>
|
|
199
|
+
> 两个会让人升级不到的原因:**① 插件市场的不同索引在同一时刻可能给出三个「最新」**([#164](https://github.com/dingminhua/dsh-connect-workbuddy/issues/164) 实测到 5.12.3 / 5.12.4 / 5.13.1 并存),在旧索引上点「检查更新」看不到新版;**② pnpm 的 24 小时新版本等待期**(`minimumReleaseAge`)会把浮动范围**静默压回上一个版本**。需要立刻修复时请写**精确版本号**:`dsh plugin --profile desktop add dsh-connect-workbuddy@5.14.0`(原因见下方「刚发布的版本请写精确版本号」那条)。
|
|
200
|
+
|
|
197
201
|
### 受支持的宿主范围
|
|
198
202
|
|
|
199
203
|
DSH 的随包门禁(`packages/boot/app-boot/src/plugin-compatibility.ts` 的 `evaluatePluginCompatibility`)会逐条核对本插件声明的 `dsh-*` peer 范围;**只要有一条不满足,整个组合包会被跳过**(进入 `skippedBundles`,仅打印到 stderr)——provider 不注册、卡片不出现、模型列表清空,而页面上没有任何报错。
|
|
@@ -321,6 +325,8 @@ dsh plugin --profile web exec dsh-connect-workbuddy status
|
|
|
321
325
|
|
|
322
326
|
**这条路到底绕开了什么,说清楚。** 提供方那条路要让请求看起来像 CodeBuddy 客户端才能过网关的渠道校验,4.15.x 起那条校验已经升级成「按提示词中段做精确子串匹配」,靠句读处插零宽空格躲开——这是军备竞赛,而且不再免费:issue #98 里已经出现**账号级**限制(`code 11140`,「账号存在异常行为,已被限制使用」)。
|
|
323
327
|
|
|
328
|
+
> **2026-10-29 的 #163 复测更新**:零宽这条路已经被网关堵死——CN 网关在匹配前会规范化 U+200B,system 开头每个词边界都插零宽仍然 11128。现在改为**按句改词**(`rewriteChannelFingerprintPhrases`,落在 `neutralizeChannelFingerprints` 之前),只改命中句、其余逐字节不动,也不再需要把提示词伪装成第三方 agent。细节见 CHANGELOG 的 Unreleased。
|
|
329
|
+
|
|
324
330
|
CLI 路线逃掉的是**客户端指纹层**,不是账号层。它用 `CODEBUDDY_AUTH_TOKEN` 把本插件 vault 里的同一个 WorkBuddy 账号交给 CLI,所以「跟网关说话的是真的 CodeBuddy CLI 进程、没有身份要伪造、因此账号不会被限制」这句话**不成立**:身份仍然是同一批账号,`11140` 是账号级的,同一批账号在 CLI 路线上一样受限。真正被绕开的只有「这条请求的客户端指纹像不像 CodeBuddy」——这一层是真实的收益(渠道校验不会再因为提示词里有句读而拒你),但它不等于「免封」。两条路是互补的容错,不是替代。
|
|
325
331
|
|
|
326
332
|
### 前提
|
|
@@ -467,12 +473,14 @@ else { await envelope.response.text() /* 或者按 SSE 消费 */ }
|
|
|
467
473
|
|
|
468
474
|
## 市场收录与展示
|
|
469
475
|
|
|
470
|
-
插件已包含可安装的 `dsh.bundle` manifest,并发布到 npm。社区市场通常从 [Awesome DSH Plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
|
|
476
|
+
插件已包含可安装的 `dsh.bundle` manifest,并发布到 npm。社区市场通常从 [Awesome DSH Plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin) 注册表同步条目;仓库内的条目镜像位于 [`awesome-dsh-plugin-submission/dingminhua__dsh-connect-workbuddy.yml`](awesome-dsh-plugin-submission/dingminhua__dsh-connect-workbuddy.yml)。**条目已于 PR [#3812](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/3812) 合并收录(2026-08-30)**。
|
|
477
|
+
|
|
478
|
+
> ⚠️ **条目合并之后不会自己更新。** #3812 收录时插件还在 2.0.15,注册表上挂的描述因此一直停在当时的能力:没提国内版/国际版双供应商、没提每次刷新读取积分倍率、没提账号池自动换号,也没说明签到是**手动**的(签到会真实领取奖励,只在用户点按钮时执行)。**插件每次有大的能力变化,条目也要另开一次 PR 跟上**——注册表评审明确「描述必须属实、会对着代码核」。当前那条更新是 PR [#10281](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/10281)。
|
|
471
479
|
|
|
472
480
|
**收录目录规范**(依据 [contributing.md](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/blob/main/contributing.md)):
|
|
473
481
|
|
|
474
482
|
- 每个插件一个 YAML:`data/plugins/<owner>__<repo>.yml`,字段为 `url`(与仓库地址完全一致)、`name`(`owner/repo`)、`category`(有效值之一,本项目为 `model`)、`description.en`(必填,以句号结尾)/ `description.zh`(可选);描述中含 `: ` 必须加引号。
|
|
475
|
-
- 仓库必须声明 `dsh.bundle` manifest(本项目 `dsh.bundle.patch: ./cordis.patch.yml`),且被 `dsh-plugin` topic
|
|
483
|
+
- 仓库必须声明 `dsh.bundle` manifest(本项目 `dsh.bundle.patch: ./cordis.patch.yml`),且被 `dsh-plugin` topic 标记;**只声明 `dsh.client` 是最常见的被拒原因**。仓库创建满 1 天由 CI 自动检查(**没有提交数门槛**,那条已废除)。
|
|
476
484
|
- 两个 README(`README.md` / `README.zh.md`)由脚本生成,不得手工编辑;修改 YAML 后执行 `node scripts/generate-readme.mjs` 重新生成。
|
|
477
485
|
- 一个 PR 最多收录 3 条;只改自己的条目,不动其他插件。
|
|
478
486
|
|
|
@@ -480,7 +488,7 @@ else { await envelope.response.text() /* 或者按 SSE 消费 */ }
|
|
|
480
488
|
|
|
481
489
|
- **GitHub 徽章**由本 README 顶部的 Shields/dshfind 图片链接生成。
|
|
482
490
|
- **市场截图**按当前约定由**插件自己仓库根目录的 `screenshots.json`** 声明(本项目声明了 [screenshots.json](screenshots.json),含使用界面截图),注册表 `data/screenshots.json` 是旧约定下的回退,**不再添加新键**(本项目已在提交前撤销该文件的改动)。
|
|
483
|
-
-
|
|
491
|
+
- **市场图标**在注册表条目里用可选的 `icon:` 字段声明(本项目指向 `icons/dsh-connect-workbuddy-128.png`):**必须是 GitHub 托管的位图**(png/jpg/jpeg/webp),SVG 与第三方图床会被拒;不写则回退到作者头像。它并不是 npm `package.json` 的通用字段,也不是 README 徽章。
|
|
484
492
|
|
|
485
493
|
**插件卡上那句标题与简介不是本插件渲染的**——那是 DSH 插件管理器读的**包元数据**:它按 `locale/<lang>.json` 的 `meta.title` / `meta.description` 取词(`locale/en.json` 是锚点,缺了它 `zh.json` 永远不会被读;`exports` 里缺 `"./locale/*.json"` 通配则整个目录被**静默**跳过),英文回退到 `package.json` 的 `description`。因此同一句话在本仓库写在三处,并由 [`tests/locale-meta.spec.ts`](tests/locale-meta.spec.ts) 钉死:卡面字典(`locale/en.json` / `locale/zh.json`)、英文回退(`package.json.description`)、市场条目草稿(`awesome-dsh-plugin-submission/dingminhua__dsh-connect-workbuddy.yml` 的 `description.en` / `description.zh`)。**改一处就要同改三处。**
|
|
486
494
|
|
|
@@ -507,8 +515,9 @@ else { await envelope.response.text() /* 或者按 SSE 消费 */ }
|
|
|
507
515
|
- **本插件已在 4.9.0 起从插件侧绕开它**:两条路由注册的 **model 身份统一为 `workbuddy`**(`src/adapter.ts` 的 `WORKBUDDY_MODEL_IDENTITY`),也就是模型自己声明的 `provider` 字段两边相同,于是 `isSameModel` 成立、换区不再把 thinking 降级成正文。**路由 id 没有合并**——它仍是 `workbuddy` / `workbuddy-global`,各自指向自己的 shim、目录与凭据,所以一侧故障不会带走另一侧,模型选择器也仍是两组。**4.9.0/4.9.1 的这版修复漏了一半,国际版整区不可用(issue #75)**:上面的说法只对 `getModel(provider, id)` 这一条按**参数**查表的路径成立;真正跑聊天的 `models.streamSimple(model, …)` 第一步是 `requireProvider(model)` = `providers.get(model.provider)`(`models.js:351–355`),查的是**模型上盖的那个字段**。于是国内版(路由 `workbuddy`、盖 `workbuddy`)自洽,国际版(路由 `workbuddy-global`、盖 `workbuddy`)在只注册了 `workbuddy-global` 的注册表里查不到,**每一个请求都在发出前就抛 `Unknown provider: workbuddy`**——一次也会失败,且报错里不带任何 WorkBuddy 字样,看起来像宿主坏了。**4.9.2 起改为:每条路由把自己的注册表同时登记上「路由 id」和「共享身份」两个键**(`src/adapter.ts` 的 `modelIdentityKeysOf`),模型上盖的身份仍然两边相同(#59 不回退),而 `requireProvider` 在国际版自己的注册表里也能查到、并拿到**国际版自己的** provider 对象(含它自己的 shim 地址与凭据)。pi-ai 的注册表本来就是 `Map`(`models.js:37` 的 `providers.set(provider.id, provider)`,键取的是 **provider 对象的 `id`**,不是 profiles 映射的键),所以这是注册表原生支持的事;**路由集合、模型选择器、目录与凭据都没有变化**,多出来的那个键不出现在任何 UI 里。回归测试 `tests/issue75-global-provider-dispatch.spec.ts` 走的是宿主的真实构造(`createModels` + `setProvider` + `streamSimple`),并保留一个反证用例固定住旧的失败文案。详见 `docs/audit/ISSUE59-OPTIONS.md` 的「#75 CORRECTION」一节。
|
|
508
516
|
- 升级后仍可能看到计数变化的部分:**在同一区域内换模型**仍会降级(`model` 那条腿是真的换了模型,thinking 签名未必对新模型有效,保留才是错的)——降级本身无法消除,但**它变成正文这件事可以**,见下面 `dropForeignThinking` 那一条;**升级之前就录在历史里的消息**带着旧的按区域 id,重放时仍按旧规则降级——这种历史只会越聊越少,不会反过来影响新录的消息。
|
|
509
517
|
- **降级不只是计数问题:思考会变成正文,模型随后照抄这个格式(issue #147)。** 上一条说的是「thinking 被降级」对计费的影响,这条说的是它对**内容**的影响。pi-ai 对 `isSameModel` 不成立的消息**不是丢弃** thinking,而是把它改写成 `{type:'text'}`(`transform-messages.js:87–90`)——也就是答案通道。于是跨 provider 切换后,**另一个模型的内部工作笔记会作为本轮回答呈现**,模型读到「我过去的思考都是正文」这个形状后,接下来的每一轮都继续把计划写进正文。报告者在一轮真实会话里量到:80 条 assistant 消息中 56 条带正文而自身 `reasoning` ≤ 1 字符,其中 20 条是英文工作笔记、合计 58,960 字符;整轮 60,984 字符正文里 CJK 只剩 1.37%,而此前 6 轮的可见回复全是中文。它同时把会话从 696,966 推到 758,005 total token(压缩阈值 800,000)。
|
|
510
|
-
-
|
|
511
|
-
-
|
|
518
|
+
- **跨 provider 的 foreign thinking 无条件丢弃;开关 `dropForeignThinking` 只负责同一路由内换模型(默认关闭)**:本插件在请求发出前**删掉**那些「本来会被压成正文」的外来 thinking 块。之所以是删而不是保留:这些块由宿主以 `foreignAssistant()` 构造,**不带 `thinkingSignature`**,即使被当成自己的轮次也无法作为带签名的 reasoning 重放——它们对线上唯一的贡献就是被压成的那段正文。正文块与工具调用一律不动,每条消息的身份也不动。
|
|
519
|
+
- **跨 provider 不设开关**:换 provider 后宿主把另一家的 assistant 轮次重建成 `dsh-foreign`,pi-ai 会把它们的 thinking 摊成 assistant 正文;WorkBuddy 网关正好扫描 assistant 消息,于是整轮以 `11128` 被拒(issue #163 的现场:14,410 / 4,225 / 5,138 字的 reasoning 被当成正文重放,三次原地救援与三轮自动「继续」全失败)。这些块本来就不可能作为 reasoning 重放,丢弃是纠正而不是偏好,所以不等待用户 opt-in。`dropForeignThinking` 只把同样的修复扩展到**同一路由内换模型**(`hy3` → `deepseek-v4.1-flash`,两边都由本插件提供、都盖 `provider: workbuddy`);那一条的关闭路径仍**一个字节都不变**。
|
|
520
|
+
- 自动丢弃时写一条 warn(仅在开关关闭时),`latestForeignThinking` 仍照旧上报,日志里能看出这次请求删掉了哪些块。
|
|
512
521
|
- 顺序很关键:修复跑在 `alignAssistantIdentities` **之后**。国际区自己的轮次到达时也带 `api: 'dsh-foreign'`(宿主的重放降级路径),先诊断会把它误判成外来轮次并删掉它自己的推理——那正是 #102 的反向回归。`tests/issue147-foreign-thinking.spec.ts` 用真实 pi-ai 的 `transformMessages` 钉住了这个顺序。
|
|
513
522
|
- 已经录进历史的坏轮次**不会**被追溯修复:它们已经作为正文存在会话里,开关只管之后的请求。想彻底摆脱,切换 provider 后新开会话是最直接的办法。
|
|
514
523
|
- **它同时覆盖「同一个区域内换模型」**(`hy3` → `deepseek-v4.1-flash`,两者都由本插件提供、都盖 `provider: workbuddy`)。这时 provider 与 api 两条腿都相等,判据只差 `model` 一条腿——5.12.0 的判定只比前两条,因此这种切换**一个块也检不出**,开关看起来无效。判断所需的 model id 由请求入口(`stream` / `streamSimple` 的 `model.id`)传入;不传时行为与 5.12.0 完全一致,卡片那条只读路径因此不受影响(它读的本来就是请求路径填好的 `lastForeignThinking`)。
|
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,
|
|
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-DMC9icX8.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
|
@@ -852,7 +852,7 @@ window.__ModuleLoader__.load({
|
|
|
852
852
|
*/
|
|
853
853
|
const CHANNEL_CONTINUE_DEFAULTS = {
|
|
854
854
|
enabled: false,
|
|
855
|
-
maxConsecutive:
|
|
855
|
+
maxConsecutive: 1,
|
|
856
856
|
text: "继续"
|
|
857
857
|
};
|
|
858
858
|
/**
|
|
@@ -5199,6 +5199,9 @@ window.__ModuleLoader__.load({
|
|
|
5199
5199
|
.dsm-workbuddy-searched-more{margin-top:6px;padding:0;border:0;background:none;color:var(--dsw-alias-brand-primary,#5686fe);font:inherit;font-size:12px;line-height:18px;cursor:pointer;text-align:left}
|
|
5200
5200
|
.dsm-workbuddy-searched-more:hover{text-decoration:underline}
|
|
5201
5201
|
.dsm-workbuddy-usage-error{margin:0;font-size:14px;line-height:22px;color:var(--dsw-alias-state-error-primary,#ef4444)}
|
|
5202
|
+
/* A failed status read is recoverable, so the banner carries an action: the
|
|
5203
|
+
message alone told the user nothing they could do but close the card. */
|
|
5204
|
+
.dsm-workbuddy-usage-error-block{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin-top:12px}
|
|
5202
5205
|
.dsm-workbuddy-usage-dot{width:9px;height:9px;border-radius:50%;flex:0 0 auto}
|
|
5203
5206
|
.dsm-workbuddy-models{display:flex;flex-direction:column;gap:10px;border-top:1px solid var(--dsw-alias-border-l2,#36373b);padding-top:14px}
|
|
5204
5207
|
.dsm-workbuddy-models-fold{display:flex;flex-direction:column;gap:10px}
|
|
@@ -6168,6 +6171,18 @@ window.__ModuleLoader__.load({
|
|
|
6168
6171
|
*/
|
|
6169
6172
|
const POLL_INTERVAL_MS = 6e4;
|
|
6170
6173
|
/**
|
|
6174
|
+
* Retry cadence for a status read that FAILED.
|
|
6175
|
+
*
|
|
6176
|
+
* The failed read used to be terminal: `status.status === 'error'` made
|
|
6177
|
+
* `hasAccounts` false, which gated both the 60s poll and the open effect, so a
|
|
6178
|
+
* single transient failure (a WebView fetch rejected while the DSH web process
|
|
6179
|
+
* was restarting) left "Failed to fetch" on screen until the card was closed
|
|
6180
|
+
* and reopened. Backoff doubles from BASE up to MAX and resets on the first
|
|
6181
|
+
* read that lands; nothing is armed while the card is collapsed.
|
|
6182
|
+
*/
|
|
6183
|
+
const STATUS_RETRY_BASE_MS = 3e3;
|
|
6184
|
+
const STATUS_RETRY_MAX_MS = 3e4;
|
|
6185
|
+
/**
|
|
6171
6186
|
* The reasoning tiers the local CodeBuddy CLI accepts for its `--effort` flag.
|
|
6172
6187
|
*
|
|
6173
6188
|
* Kept in sync with `EFFORT_LEVELS` in `src/cli-bridge/tool.ts` by hand: that
|
|
@@ -6937,6 +6952,36 @@ window.__ModuleLoader__.load({
|
|
|
6937
6952
|
status.status
|
|
6938
6953
|
]);
|
|
6939
6954
|
/**
|
|
6955
|
+
* Recover from a failed status read without a close/reopen.
|
|
6956
|
+
*
|
|
6957
|
+
* Keyed on the document identity (`loaded`), not on `status.status`: every
|
|
6958
|
+
* failed read writes a FRESH `{ status: 'error' }` object, so the identity
|
|
6959
|
+
* changes and the next attempt is armed. A successful read replaces it with a
|
|
6960
|
+
* new object too, which resets the counter through the branch below.
|
|
6961
|
+
*/
|
|
6962
|
+
const statusRetries = (0, react.useRef)(0);
|
|
6963
|
+
(0, react.useEffect)(() => {
|
|
6964
|
+
if (!open || status.status !== "error") {
|
|
6965
|
+
statusRetries.current = 0;
|
|
6966
|
+
return;
|
|
6967
|
+
}
|
|
6968
|
+
const delay = Math.min(STATUS_RETRY_MAX_MS, STATUS_RETRY_BASE_MS * 2 ** statusRetries.current);
|
|
6969
|
+
const controller = new AbortController();
|
|
6970
|
+
const timer = window.setTimeout(() => {
|
|
6971
|
+
statusRetries.current += 1;
|
|
6972
|
+
refreshUsage(activeRegion, controller.signal);
|
|
6973
|
+
}, delay);
|
|
6974
|
+
return () => {
|
|
6975
|
+
window.clearTimeout(timer);
|
|
6976
|
+
controller.abort();
|
|
6977
|
+
};
|
|
6978
|
+
}, [
|
|
6979
|
+
open,
|
|
6980
|
+
activeRegion,
|
|
6981
|
+
refreshUsage,
|
|
6982
|
+
loaded
|
|
6983
|
+
]);
|
|
6984
|
+
/**
|
|
6940
6985
|
* Re-detect the local sign-ins and refresh the panel.
|
|
6941
6986
|
*
|
|
6942
6987
|
* This deliberately writes NO account selection. It used to persist whatever
|
|
@@ -7833,6 +7878,7 @@ window.__ModuleLoader__.load({
|
|
|
7833
7878
|
lastCatalog: visibleModels.map(toPersistedWorkBuddyModel),
|
|
7834
7879
|
enabledModelIds: [...activeEnabledIds],
|
|
7835
7880
|
imageModelIds: [...activeImageIds],
|
|
7881
|
+
imageSelectionExplicit: true,
|
|
7836
7882
|
offModelIds: [...activeOffIds],
|
|
7837
7883
|
contextBudgets: activeContextBudgets
|
|
7838
7884
|
});
|
|
@@ -9118,9 +9164,21 @@ window.__ModuleLoader__.load({
|
|
|
9118
9164
|
t
|
|
9119
9165
|
}) : null
|
|
9120
9166
|
] }) : null,
|
|
9121
|
-
status.status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.
|
|
9122
|
-
className: "dsm-workbuddy-usage-error",
|
|
9123
|
-
children:
|
|
9167
|
+
status.status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9168
|
+
className: "dsm-workbuddy-usage-error-block",
|
|
9169
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
9170
|
+
className: "dsm-workbuddy-usage-error",
|
|
9171
|
+
role: "alert",
|
|
9172
|
+
children: t("row.usageErrorHint", { message: status.message === "" ? t("row.requestFailed") : status.message })
|
|
9173
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
9174
|
+
type: "button",
|
|
9175
|
+
className: "dsm-btn dsm-btn-outline",
|
|
9176
|
+
onClick: () => {
|
|
9177
|
+
statusRetries.current = 0;
|
|
9178
|
+
refreshUsage(activeRegion);
|
|
9179
|
+
},
|
|
9180
|
+
children: t("row.usageRetry")
|
|
9181
|
+
})]
|
|
9124
9182
|
}) : null
|
|
9125
9183
|
]
|
|
9126
9184
|
})
|
|
@@ -10842,6 +10900,8 @@ window.__ModuleLoader__.load({
|
|
|
10842
10900
|
"row.credentialRejectedChoose": "Other local sign-ins exist but none of them answered the upstream just now. Try switching to one above; if none works, sign in again in the desktop app.",
|
|
10843
10901
|
"row.requestFailed": "Request failed",
|
|
10844
10902
|
"row.requestFailedHint": "Refresh failed — showing the last loaded data: {message}",
|
|
10903
|
+
"row.usageErrorHint": "Could not read the account status: {message}. The local DSH service may have just restarted — retrying automatically.",
|
|
10904
|
+
"row.usageRetry": "Retry",
|
|
10845
10905
|
"row.poolErrUnavailable": "The account pool is not available in this build (or its batch route is missing). Restart the app; if it persists, the plugin needs reinstalling.",
|
|
10846
10906
|
"row.poolErrFailed": "The batch failed on the host side: {message}",
|
|
10847
10907
|
"row.poolErrHttp": "The batch was refused (HTTP {status}). Check the plugin is up to date and try again.",
|
|
@@ -11151,7 +11211,7 @@ window.__ModuleLoader__.load({
|
|
|
11151
11211
|
"row.sessionTitleModel": "Target model:",
|
|
11152
11212
|
"row.extraPrefixesCount": "Custom injected prefixes in effect:",
|
|
11153
11213
|
"row.channelRejectContinueLabel": "Resend 「继续」 after the gateway refuses the request",
|
|
11154
|
-
"row.channelRejectContinueHint": "When the gateway answers 400 with code 11128 (「被网关的渠道校验拦截」), the turn just fails: the plugin never retries it in place and never swaps accounts, because the gate matches the message
|
|
11214
|
+
"row.channelRejectContinueHint": "LAST RESORT, not a fix for 11128. When the gateway answers 400 with code 11128 (「被网关的渠道校验拦截」), the turn just fails: the plugin never retries it in place and never swaps accounts, because the gate matches the message contents and every account sends the same ones. Fixed system fingerprints are byte-identical every turn, so resending only pays for the same 400 again; since 5.13.1 `prepareChatBody` rewrites known phrases instead. The narrow case this can help is content that changes between turns. It sends the text below as a NEW turn and spends a real request each time. Stops after N CONSECUTIVE sends (default 1); one successful turn, or one message you type yourself, resets the count. Subagent sessions are never touched. Off by default. Takes effect immediately, no reload.",
|
|
11155
11215
|
"row.channelRejectContinueLimit": "Max consecutive sends:",
|
|
11156
11216
|
"row.channelRejectContinueText": "Text sent:",
|
|
11157
11217
|
"row.cliBridgeLabel": "Let the local CodeBuddy CLI do the work",
|
|
@@ -11276,6 +11336,8 @@ window.__ModuleLoader__.load({
|
|
|
11276
11336
|
"row.credentialRejectedChoose": "本机还有其他账号,但刚才都没能通过上游校验。可以在上方切换一个试试;如果都不行,请在桌面端重新登录。",
|
|
11277
11337
|
"row.requestFailed": "请求失败",
|
|
11278
11338
|
"row.requestFailedHint": "刷新失败——下方为上次加载的数据:{message}",
|
|
11339
|
+
"row.usageErrorHint": "读取账号状态失败:{message}。本地 DSH 服务可能刚重启过,正在自动重试。",
|
|
11340
|
+
"row.usageRetry": "重试",
|
|
11279
11341
|
"row.poolErrUnavailable": "本版本没有可用的账号池(或其批量接口缺失)。请重启应用;若仍不行,插件可能需要重新安装。",
|
|
11280
11342
|
"row.poolErrFailed": "批量操作在宿主侧失败:{message}",
|
|
11281
11343
|
"row.poolErrHttp": "批量操作被拒绝(HTTP {status})。请确认插件是最新版本后重试。",
|
|
@@ -11585,7 +11647,7 @@ window.__ModuleLoader__.load({
|
|
|
11585
11647
|
"row.sessionTitleModel": "目标模型:",
|
|
11586
11648
|
"row.extraPrefixesCount": "生效的自定义注入前缀:",
|
|
11587
11649
|
"row.channelRejectContinueLabel": "被网关渠道校验拦截后,自动再发一次「继续」",
|
|
11588
|
-
"row.channelRejectContinueHint": "
|
|
11650
|
+
"row.channelRejectContinueHint": "最后兜底,不是 11128 的修复。上游用 400 + code 11128(「被网关的渠道校验拦截」)拒绝时,这一轮就直接失败了:插件不会在原地重试,也不会换号——网关匹配的是消息内容,每个账号发出去都一样。固定 system 指纹每一轮逐字节相同,重发只会再付一次同一个 400;5.13.1 起由 `prepareChatBody` 按句改词处理。只有被拒内容刚好随轮次变化的窄情况才可能靠新回合碰过去。它会把下面这句作为新回合发出,每次真实消耗一次请求;默认只发 1 次、默认关闭;连续计数,成功一轮或你自己发一条消息即清零。子代理会话一律不碰,改完立即生效,不必重载插件。",
|
|
11589
11651
|
"row.channelRejectContinueLimit": "连续发送上限:",
|
|
11590
11652
|
"row.channelRejectContinueText": "发送的文本:",
|
|
11591
11653
|
"row.cliBridgeLabel": "允许调用本机 CodeBuddy CLI 干活",
|