dsh-connect-workbuddy 5.8.0 → 5.8.2
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 +51 -0
- package/lib/bin.js +1 -1
- package/lib/client.js +77 -3
- package/lib/{host-heartbeat-BiVFMXGE.js → host-heartbeat-C9BFJEBM.js} +93 -7
- package/lib/index.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.8.2 (2026-10-24)
|
|
4
|
+
|
|
5
|
+
> 适配版本:跟随宿主 `0.2.1-rc.3`。rc.2 在 web-app 捆绑里新增了官方插件 `@deepseek-ai/dsh-client-ui-deepseek-model-label-1017`(给 DeepSeek 官方 Flash 行画「1017」发布徽章),它与本插件注册**同一个 `single` 槽**、**同一个默认优先级 0**——于是本插件的注册**抛错**,模型选择器里的负载徽章**静默消失**。本版修掉这一处,其余全部兼容。
|
|
6
|
+
|
|
7
|
+
### Fixes
|
|
8
|
+
|
|
9
|
+
- **模型选择器的标签槽位不再与宿主自带的 1017 徽章插件抢同一格(0.2.1-rc.2+)。**
|
|
10
|
+
- 该槽是 `kind: "single"`,`SlotCore.register()` 对同一优先级上的第二次注册**直接抛错**:`single slot "conversation.input.model.label" already has a registration at priority 0 … (lowest renders)`。两个插件都用默认 `0`,谁后注册谁抛。实测是本插件抛——应用自己的崩溃日志里逐字记着这句,以及本插件的 `model label slot failed to register`。因为 `catch` 只 `console.error`(刻意的:标签失注册不该让整页挂掉),页面上**只是徽章不见了**,没有任何可见报错,所以一直没被发现。
|
|
11
|
+
- 修法只做一件事:**换一个不碰撞的优先级**。`-0.5`(分数优先级合法)与宿主的默认 `0`、以及 `dsh-connect-qoder` 占用的 `-1` 都不碰撞,三个插件于是排成 `-1`(qoder)→ `-0.5`(本插件)→ `0`(宿主 1017)。取值只要求「不与别人碰撞」,不需要知道别人用了几。
|
|
12
|
+
- **刻意不引入插件之间的联动**(项目不变量,见 [`docs/DESIGN.md`](docs/DESIGN.md) §零):不读 `slots.entries(...)` 枚举同槽的其它注册、不把别家的行「转交」给别的插件的组件渲染、不为对方合成 `t` 席位、不依赖别人的优先级。曾经的转交方案能算「更客气」,但它要代渲染器合成对方的 props(`t` 来自对方注册时声明的 `locale:` 命名空间),还得约定一条跨插件转交方向否则递归——实测两个插件都转交时同一行被来回传递,直接 `RangeError: Maximum call stack size exceeded`。这不是本插件该承担的责任,整体撤除。
|
|
13
|
+
- **代价(已接受并记录)**:该槽是 `single`,一格只有一个赢家。「最低优先级渲染」意味着 `-0.5` 实际上**赢过**宿主的 `0`,因此本插件赢下该格时,别的插件在**它自己的行**上的装饰不会被合成(宿主的「1017」徽章不会出现在本插件渲染的那一行)。不是自己的模型行,一律给宿主契约的下限 `model.name`。这是**放弃联动换来的确定性**,不是待修缺陷;要改成联动,先改 `DESIGN.md` §零。
|
|
14
|
+
- **取值 `-0.5` 的定夺依据(不需要用户来选)**:宿主一方早有书面立场——`dsh-client-ui-deepseek-model-label-1017` 的 README「Known Limitations and Deferred Work」逐字写着「the two presentations do not compose automatically」,并把处置办法写成**在 profile patch 里禁用它**(配置层,不是运行时协商)。所以「两个徽标不合成」是宿主的官方立场,本项目的 §零 不变量与之一致。其余三条:1017 的 package description 就是「**Temporary** 1017 release badge」,README 承诺结构化发布标签上线后移除该包;它只装饰**一个**模型(`DeepSeek-V4.1-Flash`,限 `deepseek-official`/`deepseek-account` 两条线路),而本插件装饰**自己全部 WorkBuddy 模型行**(负载等级、促销价、夜间折扣、新模型标记);`single` 槽「最低优先级渲染」,`-0.5` 实际赢过宿主的 `0`——但选它是为了**不碰撞**而非谦让,且 1017 被移除后本插件自动成为该格唯一占用者。
|
|
15
|
+
|
|
16
|
+
### Docs
|
|
17
|
+
|
|
18
|
+
- 新增 [`docs/DSH-0.2.1-RC3-FOLLOWUP.md`](docs/DSH-0.2.1-RC3-FOLLOWUP.md):rc.3 复核全文,含逐面包比对与本次回归的完整证据链。
|
|
19
|
+
- [`docs/DSH-0.2.1-RC1-FOLLOWUP.md`](docs/DSH-0.2.1-RC1-FOLLOWUP.md) 补 §2.4 **订正**:原文把「rc 宿主上照常加载」归因为「闸门只校验 `@deepseek-ai/dsh*` + profile 用 `link:`」,**这是错的**——闸门确实校验,只是用 `semver.satisfies(rt, range, { includePrerelease: true })` 放行。范围本身无需改(裸 semver 判 false 的事实仍成立)。
|
|
20
|
+
|
|
21
|
+
### Tests
|
|
22
|
+
|
|
23
|
+
- 新增 [`tests/model-label-slot-coexistence.spec.tsx`](tests/model-label-slot-coexistence.spec.tsx)(6 条),跑**真实的 `SlotCore`**:宿主 1017 在场时注册不抛错、优先级与 `-1`/`0` 均不碰撞、自家行仍有徽章;外加「不做联动」一组——别家行只给纯目录名(**即使**另一个贡献者就在账本上)、源码不得出现 `slots.entries(...)` / 渲染别家组件 / `locale.bind`、注册只声明自家常量。
|
|
24
|
+
- 变异验证:把 `MODEL_LABEL_PRIORITY` 改回 `0` → 多条红并**复现同一句崩溃原文**;把注册换成会读账本的写法 → 「不做联动」那组红。
|
|
25
|
+
- `tests/dsh-line.spec.ts` 里「已知预发布悬崖」的注释按实测订正归因(断言本身不变,仍钉「裸 semver 判 false」)。
|
|
26
|
+
- 完整套件 **2225 通过 / 2 条按平台跳过**(此前 2219),`typecheck` 与 `build` 通过。
|
|
27
|
+
|
|
28
|
+
## 5.8.1 (2026-10-24)
|
|
29
|
+
|
|
30
|
+
> 修复版本:解决 DSH 会话在国内区**每一步**都 `400 / 11128` 的死循环。根因不是账号、额度、网络,也不是「宿主提示词要你改」——是插件的救援梯子对网关的**精确子串门**有一个可证明的盲区:`You are a coding agent powered by <model> model.` 这条 agent 身份从句,稀疏零宽扰动打不到,于是每次请求都逐字节原样上线被拒。**凭据、计费、provider 注册均未改动**;请求路径新增的定向破坏只插不可见字符(实测真实提示 **+0.26% 字节**),不改模型读到的任何一个词。
|
|
31
|
+
|
|
32
|
+
### Fixes
|
|
33
|
+
|
|
34
|
+
- **DSH 提示词里的 agent 身份从句现在在请求路径就被破坏,不再等被拒(#125 / `workbuddy2api-hub` #533)。**
|
|
35
|
+
- 两条独立实测指向同一机制:网关对被扫的 `system` / `assistant` 消息做的是**大小写不敏感的精确子串匹配**(不只是开头前缀),活的 DSH 提示词里有**三处**这样的 span——`You are an AI agent powered by DeepSeek Harness.`、`You are a coding agent powered by the <model> model.`、`You are interacting with the user through the DeepSeek Harness Web GUI …`——且**三处全改才 200**(「只改任意一两处不够」)。
|
|
36
|
+
- 盲区在第二处:它**没有相邻大写词对**(`WORD_INTERNAL_BREAK` 打不到)、**没有逗号/分号**、句末那个 `.` 又落在被禁子串**之后**(句读规则也打不到)。所以 5.7.0 的稀疏扰动清掉了第一、三处,却把第二处原样发出——那条请求于是一直 400,而错误回显把它标成「不在插件已知名单里」,反而把人引去改一条根本没问题的宿主提示。
|
|
37
|
+
- 修法是在 `prepareChatBody()` 里新增定向破坏 `breakIdentityClauses()`:在身份从句的**每个内部词界**后插一个 U+200B。实测三处 span 全部失配、**模型读到的每个词都不变**(与 #533 那条把 `powered by` 改写成 `built on` 的做法不同——那个会动措辞),真实 10,818 字提示只 **+0.26% 字节**(~28 个字符),足够便宜到无条件跑。前缀名单(`UNAPPROVED_CHANNEL_OPENINGS`)与它各司其职:名单管 offset 0 的前缀门,从句破坏管任意位置的子串门;顺序是**先补中性头、再破坏从句**,否则从句里的 U+200B 会让前缀谓词匹配不上、连累中性头这级防线(有测试钉住这个顺序)。
|
|
38
|
+
- **删掉 `perturbChannelFingerprints()` 的粗粒度幂等保护,改由各规则自身幂等(同上)。**
|
|
39
|
+
- 那句 `if (text.includes(U+200B)) return undefined` 原本是防重试叠加零宽。但请求路径现在**本来就会**先放几个 U+200B 进去,这句粗判会让整个梯子从第二级起**永久空转**——连 dense 兜底都被它挡掉,等于救援彻底失效。
|
|
40
|
+
- 幂等改由各规则自己保证:拉丁句读/逗号规则锚在尾随 `(\s+)` 上(U+200B 不是空白,已断的句子自然不再匹配);CJK 句读规则没有尾随空白可锚,**补 `(?!\u200b)` lookahead**,否则每次重试都会在 `。!` 后叠一个零宽;`breakIdentityClauses()` 靠 `\s+` 不匹配 U+200B 而自然幂等。三条各有测试。
|
|
41
|
+
- **`channelPerturbation: "always"` 从推荐手段降级为边缘手段。** 定向破坏已用 +0.26% 覆盖三处已知 span,密集扰动(+70.7%)只剩「出现定向破坏与稀疏都打不到的**未知** prose 指纹」这一种理由。默认仍是 `failure`,README 与 `docs/DESIGN.md` 的措辞同步改了,不再建议普通用户开 `always`。
|
|
42
|
+
|
|
43
|
+
### Docs
|
|
44
|
+
|
|
45
|
+
- [`docs/DESIGN.md`](docs/DESIGN.md) 渠道门一节新增两条要点(定向破坏的动机与实测成本、幂等保护的取舍),并订正「`always` 是唯一可靠手段」这句已过时的说法。
|
|
46
|
+
- [`docs/CHANNEL-FINGERPRINT-RESEARCH.md`](docs/CHANNEL-FINGERPRINT-RESEARCH.md) 补 `§6.2`,记录本次落地如何比原建议 1/3(直接上密集扰动)更省,以及为何 `always` 降级。
|
|
47
|
+
|
|
48
|
+
### Tests
|
|
49
|
+
|
|
50
|
+
- 新增 [`tests/identity-clause-break.spec.ts`](tests/identity-clause-break.spec.ts)(13 条):三处 span 全部失配、词不变、成本上界(<40 字符)、大小写/换行/任意空白、自然幂等、普通对话逐字节不变、assistant 历史也被破坏、`user`/`tool` 不动、以及移除粗判后三条扰动规则各自的幂等(含 CJK lookahead)。
|
|
51
|
+
- 因身份从句现在在请求路径就被破坏,`issue98` / `issue98-shim` / `upstream` 里几条钉「旧字节形态」的断言改为钉**不变量**(去掉 U+200B 后与宿主原文一致、前缀门仍补中性头、扰动仍是纯附加),并逐条加注释说明旧断言为何不再成立——包括 `upstream.spec.ts` 那条曾断言「中段指纹逐字节不变」的测试,它编码的正是 #125 证伪的那个信念。
|
|
52
|
+
- 完整套件 **2218 通过 / 2 条按平台跳过**(此前 2205),`typecheck` 与 `build` 通过。构建产物用「故意破坏源码、只留 `lib/`」的方式独立复验,确认定向破坏确实进了包(运行时对真实形状 prompt 报 0 处 span 存活)。
|
|
53
|
+
|
|
3
54
|
## 5.8.0 (2026-10-24)
|
|
4
55
|
|
|
5
56
|
> 特性版本:把 `11128` 渠道拒绝与 `11140` 内容/账号限制**真正分开**(此前整族被误判成同一件事),补上企业/教育账号在账号池里丢失的身份标记,修掉「设置已落盘、按钮却永远卡在保存中」,并按 #132 提供与官方客户端一致的请求标识。**凭据、计费、provider 注册与默认发出的字节均未改动**:#132 的两项(`usageIdentity`、`keepPromptLast`)都是**默认关闭的开关**——前者只改显示用的请求标识,后者会改变上游会数到的消息顺序,而它们的实测证据来自报告者的账号与 5.3.1 构建、本插件未在真机复现,因此不以任何形式静默上线。
|
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, It as hostPeersInfo, M as legacyWorkbuddyOwnAuthPath, Ot as findWorkbuddyAppExecutableWithSource, S as WORKBUDDY_AUTH_FILE_ENV, St as WORKBUDDY_APP_EXECUTABLE_ENV, V as WorkBuddyUpstreamClient, 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, It as hostPeersInfo, M as legacyWorkbuddyOwnAuthPath, Ot as findWorkbuddyAppExecutableWithSource, S as WORKBUDDY_AUTH_FILE_ENV, St as WORKBUDDY_APP_EXECUTABLE_ENV, V as WorkBuddyUpstreamClient, 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-C9BFJEBM.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
|
@@ -9416,8 +9416,64 @@ window.__ModuleLoader__.load({
|
|
|
9416
9416
|
});
|
|
9417
9417
|
}
|
|
9418
9418
|
/**
|
|
9419
|
+
* The priority this plugin registers the model-label slot at.
|
|
9420
|
+
*
|
|
9421
|
+
* WHY A DISTINCT PRIORITY AT ALL. `conversation.input.model.label` is a
|
|
9422
|
+
* `single` slot: it holds ONE cell, and `SlotCore.register()` THROWS when a
|
|
9423
|
+
* second registration claims one priority — "single slot … already has a
|
|
9424
|
+
* registration at priority 0 — register at a different priority to shadow it
|
|
9425
|
+
* (lowest renders)". The default priority is `0`, which is also what the host's
|
|
9426
|
+
* own shipped contributor uses
|
|
9427
|
+
* (`@deepseek-ai/dsh-client-ui-deepseek-model-label-1017`, in the web-app
|
|
9428
|
+
* bundle from 0.2.1-rc.2 on), so registering at `0` made the two race. Measured
|
|
9429
|
+
* on 0.2.1-rc.3 that is US: the app's own crash log records
|
|
9430
|
+
* `single slot "conversation.input.model.label" already has a registration at
|
|
9431
|
+
* priority 0 (registered by Kb)` and this entry's catch-all line
|
|
9432
|
+
* `model label slot failed to register`, and because that catch only logs, the
|
|
9433
|
+
* badge simply vanished from the picker with no visible error.
|
|
9434
|
+
*
|
|
9435
|
+
* WHY NOT `-1`. That is `dsh-connect-qoder`'s slot priority, and two plugins on
|
|
9436
|
+
* one priority collide for exactly the same reason `0` did.
|
|
9437
|
+
*
|
|
9438
|
+
* WHY `-0.5` IS SAFE AND SELF-CONTAINED. The value only has to be distinct from
|
|
9439
|
+
* whatever else claims the cell; `-0.5` is legal, sorts without collision
|
|
9440
|
+
* against both `-1` and `0`, and requires knowing nothing about the other
|
|
9441
|
+
* plugins. See the "不做插件联动" invariant in `docs/DESIGN.md`.
|
|
9442
|
+
*
|
|
9443
|
+
* WHAT THIS PLUGIN DELIBERATELY DOES NOT DO. It does not read the slot ledger,
|
|
9444
|
+
* does not look for another plugin's registration, and does not render another
|
|
9445
|
+
* plugin's component. Foreign rows get the plain catalog name, which is exactly
|
|
9446
|
+
* the slot's documented minimum. Inter-plugin chaining is out of scope by
|
|
9447
|
+
* project policy (`docs/DESIGN.md` §零) — and that policy is NOT stricter than
|
|
9448
|
+
* the Host's own: the shipped `dsh-client-ui-deepseek-model-label-1017` README
|
|
9449
|
+
* lists, under "Known Limitations and Deferred Work", that "another model-label
|
|
9450
|
+
* plugin at the same priority conflicts; the two presentations do not compose
|
|
9451
|
+
* automatically", and the remedy it documents is DISABLING it in the profile
|
|
9452
|
+
* patch — a configuration decision, not a runtime negotiation. A consequence is
|
|
9453
|
+
* that when this plugin wins the cell, another contributor's decoration on ITS
|
|
9454
|
+
* rows is not composed; that is an accepted, documented trade-off rather than
|
|
9455
|
+
* something to work around at runtime.
|
|
9456
|
+
*
|
|
9457
|
+
* WHY WINNING IS THE RIGHT SIDE OF THAT TRADE. 1017 is a TEMPORARY marker
|
|
9458
|
+
* (its own package description: "Temporary 1017 release badge …"; its README
|
|
9459
|
+
* says to remove the package and its bundle row once structured release labels
|
|
9460
|
+
* replace it) and it decorates exactly ONE model, on two provider routes. This
|
|
9461
|
+
* plugin decorates every WorkBuddy model row with load level, promo price and
|
|
9462
|
+
* the new-model mark — its core surface. Trading a long-lived feature for a
|
|
9463
|
+
* temporary marker is the wrong way round, and when 1017 is removed this
|
|
9464
|
+
* plugin is already the only occupant of the cell.
|
|
9465
|
+
*
|
|
9466
|
+
* Exported so the test asserts against the value that actually ships rather
|
|
9467
|
+
* than a copy of it.
|
|
9468
|
+
*/
|
|
9469
|
+
const MODEL_LABEL_PRIORITY = -.5;
|
|
9470
|
+
/**
|
|
9419
9471
|
* The label for one model row in the host's picker.
|
|
9420
9472
|
*
|
|
9473
|
+
* Self-contained by design: a model this plugin owns gets the full label, and
|
|
9474
|
+
* anything else gets the plain catalog name. There is no forwarding and no
|
|
9475
|
+
* consultation of other plugins' registrations — see `MODEL_LABEL_PRIORITY`.
|
|
9476
|
+
*
|
|
9421
9477
|
* @param props - The host's model identity and the placement of this label.
|
|
9422
9478
|
* @returns The catalog name, the promotion chip when one holds, and the load
|
|
9423
9479
|
* badge when the probe reported one.
|
|
@@ -10775,12 +10831,30 @@ window.__ModuleLoader__.load({
|
|
|
10775
10831
|
* example is the official DeepSeek "1017" release badge, which is the same
|
|
10776
10832
|
* job — a coloured marker beside a model name.
|
|
10777
10833
|
*
|
|
10778
|
-
*
|
|
10779
|
-
*
|
|
10834
|
+
* WHY A NEGATIVE PRIORITY (host 0.2.1-rc.2 and later). This is a `single`
|
|
10835
|
+
* slot: it holds ONE cell, and `SlotCore.register()` THROWS when a second
|
|
10836
|
+
* registration claims the same priority — "single slot … already has a
|
|
10837
|
+
* registration at priority 0 — register at a different priority to shadow
|
|
10838
|
+
* it (lowest renders)". The host itself now ships a contributor here
|
|
10839
|
+
* (`@deepseek-ai/dsh-client-ui-deepseek-model-label-1017`, in the web-app
|
|
10840
|
+
* bundle from rc.2 on) that registers at the default 0, so registering at
|
|
10841
|
+
* 0 makes the two race: whichever lands second throws. Measured on
|
|
10842
|
+
* 0.2.1-rc.3 that is US — the app's own crash log records, verbatim,
|
|
10843
|
+
* "single slot \"conversation.input.model.label\" already has a
|
|
10844
|
+
* registration at priority 0 (registered by Kb)" plus this entry's
|
|
10845
|
+
* catch-all line "model label slot failed to register" twice per page
|
|
10846
|
+
* load, which is the badge silently disappearing from the picker.
|
|
10847
|
+
*
|
|
10848
|
+
* See `MODEL_LABEL_PRIORITY` in `./WorkBuddyModelLabel.tsx` for the full
|
|
10849
|
+
* ordering, the measured evidence, and the deliberate absence of any
|
|
10850
|
+
* inter-plugin coordination (project invariant, `docs/DESIGN.md` §六).
|
|
10780
10851
|
*/
|
|
10781
10852
|
const registerModelLabel = () => {
|
|
10782
10853
|
try {
|
|
10783
|
-
return ctx.slots.inject("conversation.input.model.label", () => ctx.slots.register({
|
|
10854
|
+
return ctx.slots.inject("conversation.input.model.label", () => ctx.slots.register({
|
|
10855
|
+
name: "conversation.input.model.label",
|
|
10856
|
+
priority: MODEL_LABEL_PRIORITY
|
|
10857
|
+
}, WorkBuddyModelLabel));
|
|
10784
10858
|
} catch (error) {
|
|
10785
10859
|
console.error("[dsh-connect-workbuddy] model label slot failed to register (host provider unaffected):", error);
|
|
10786
10860
|
return;
|
|
@@ -2791,6 +2791,68 @@ const WORKBUDDY_FALLBACK_SYSTEM_PROMPT = "You are a helpful assistant.";
|
|
|
2791
2791
|
*/
|
|
2792
2792
|
const UNAPPROVED_CHANNEL_OPENINGS = ["you are an ai agent powered by deepseek harness", "you are claude code, anthropic's official cli for claude"];
|
|
2793
2793
|
/**
|
|
2794
|
+
* The identity clauses an agent prompt introduces itself with, matched so the
|
|
2795
|
+
* plugin can break them INSIDE rather than in front of.
|
|
2796
|
+
*
|
|
2797
|
+
* WHY {@link UNAPPROVED_CHANNEL_OPENINGS} DOES NOT COVER THESE. That list is a
|
|
2798
|
+
* PREFIX rule, and it is the right rule for the gate the plugin measured on
|
|
2799
|
+
* 2026-10-21. It is the wrong rule for the gate the upstream has been running
|
|
2800
|
+
* since: a case-insensitive EXACT SUBSTRING match ANYWHERE in a scanned
|
|
2801
|
+
* message, which is what this plugin's own issue #125 recorded (three sites in
|
|
2802
|
+
* the live DSH prompt, "只改任意一两处不够,三处全改才 200") and what
|
|
2803
|
+
* `workbuddy2api-hub` #533 independently bisected to the byte on a real 124 KB
|
|
2804
|
+
* request. A prefix prepend cannot break a substring match, because the banned
|
|
2805
|
+
* span is still in the text, intact, one line lower.
|
|
2806
|
+
*
|
|
2807
|
+
* THE THREE MEASURED SITES, and why each needs its own alternative:
|
|
2808
|
+
*
|
|
2809
|
+
* You are an AI agent powered by DeepSeek Harness.
|
|
2810
|
+
* You are a coding agent powered by the <model> model.
|
|
2811
|
+
* You are interacting with the user through the DeepSeek Harness Web GUI …
|
|
2812
|
+
*
|
|
2813
|
+
* The first is on the prefix list. The other two are NOT, and the second is the
|
|
2814
|
+
* one that survives every rung this plugin had: it carries no adjacent
|
|
2815
|
+
* capitalised word pair (so {@link WORD_INTERNAL_BREAK} misses it), no comma or
|
|
2816
|
+
* clause punctuation, and its terminal full stop lands AFTER the banned span (so
|
|
2817
|
+
* the sentence rules miss it too). Measured against a real DSH prompt: sparse
|
|
2818
|
+
* perturbation clears sites 1 and 3 and leaves site 2 byte-identical, which is
|
|
2819
|
+
* exactly the request that kept answering 11128.
|
|
2820
|
+
*
|
|
2821
|
+
* The model name in site 2 is not stable (`qmodel_38max`, `deepseek-v4.1-flash`,
|
|
2822
|
+
* …), so the clause is matched up to `powered by` and the variable tail is left
|
|
2823
|
+
* to the break rule itself.
|
|
2824
|
+
*/
|
|
2825
|
+
const IDENTITY_CLAUSE = /(you)(\s+are\s+(?:(?:an?\s+)?[\w-]*\s*agent\s+powered\s+by|interacting\s+with\s+the\s+user\s+through\s+the\s+deepseek\s+harness))/giu;
|
|
2826
|
+
/**
|
|
2827
|
+
* Break an identity clause at EVERY word boundary inside it.
|
|
2828
|
+
*
|
|
2829
|
+
* NOT one break, and that is deliberate. The plugin does not know which span the
|
|
2830
|
+
* upstream listed: #533's minimal failing sample is the whole sentence, but the
|
|
2831
|
+
* gate is a substring match, so the listed span could equally be a shorter piece
|
|
2832
|
+
* of it (`a coding agent powered by`). One break at the front defeats only the
|
|
2833
|
+
* spans that start there. Breaking at every internal word boundary defeats every
|
|
2834
|
+
* span inside the clause, whatever its endpoints, for about ten invisible
|
|
2835
|
+
* characters.
|
|
2836
|
+
*
|
|
2837
|
+
* The cost is why this can run on EVERY request instead of only in a rescue:
|
|
2838
|
+
* measured on a real 10,818-character DSH system prompt it adds ~28 characters
|
|
2839
|
+
* (+0.26%), against the dense rescue's 7,742 (+70.7%). Both break all three
|
|
2840
|
+
* sites; only this one is cheap enough to send unconditionally.
|
|
2841
|
+
*
|
|
2842
|
+
* The break goes AFTER each whitespace run, so the text keeps its own spacing
|
|
2843
|
+
* and a reader (or a tokenizer) sees the same words in the same order.
|
|
2844
|
+
*
|
|
2845
|
+
* Naturally idempotent: once a clause carries a break, `IDENTITY_CLAUSE` no
|
|
2846
|
+
* longer matches it — `\s+` does not match U+200B, so the second alternative
|
|
2847
|
+
* fails at the inserted character and the clause is left alone on every later
|
|
2848
|
+
* pass. That is a property this function has to have on its own, because
|
|
2849
|
+
* {@link perturbChannelFingerprints} can no longer refuse to touch text that
|
|
2850
|
+
* already carries an invisible break.
|
|
2851
|
+
*/
|
|
2852
|
+
function breakIdentityClauses(text) {
|
|
2853
|
+
return text.replace(IDENTITY_CLAUSE, (clause) => clause.replace(/(\s+)/gu, `$1${ZERO_WIDTH_SPACE}`));
|
|
2854
|
+
}
|
|
2855
|
+
/**
|
|
2794
2856
|
* Whether this text OPENS with an opening the gateways refuse.
|
|
2795
2857
|
*
|
|
2796
2858
|
* The gate is a PREFIX match on the START of a `system` message, so this is
|
|
@@ -2883,8 +2945,8 @@ function neutralizeChannelFingerprints(obj) {
|
|
|
2883
2945
|
if (!scansOpening(message["role"])) continue;
|
|
2884
2946
|
const content = message["content"];
|
|
2885
2947
|
if (typeof content === "string") {
|
|
2886
|
-
|
|
2887
|
-
message["content"] =
|
|
2948
|
+
const next = neutralizeScannedText(content);
|
|
2949
|
+
if (next !== void 0) message["content"] = next;
|
|
2888
2950
|
continue;
|
|
2889
2951
|
}
|
|
2890
2952
|
if (!Array.isArray(content)) continue;
|
|
@@ -2894,12 +2956,37 @@ function neutralizeChannelFingerprints(obj) {
|
|
|
2894
2956
|
const type = record["type"];
|
|
2895
2957
|
if (typeof type !== "string" || type.trim().toLowerCase() !== "text") continue;
|
|
2896
2958
|
const text = record["text"];
|
|
2897
|
-
if (typeof text !== "string"
|
|
2898
|
-
|
|
2959
|
+
if (typeof text !== "string") continue;
|
|
2960
|
+
const next = neutralizeScannedText(text);
|
|
2961
|
+
if (next !== void 0) record["text"] = next;
|
|
2899
2962
|
}
|
|
2900
2963
|
}
|
|
2901
2964
|
}
|
|
2902
2965
|
/**
|
|
2966
|
+
* Both neutralising transforms, applied to one scanned text part.
|
|
2967
|
+
*
|
|
2968
|
+
* `undefined` means the text goes on the wire byte-for-byte, which is the case
|
|
2969
|
+
* for every ordinary message: an ordinary conversation contains no identity
|
|
2970
|
+
* clause, so this changes nothing at all and the body is not re-serialised.
|
|
2971
|
+
*
|
|
2972
|
+
* THE ORDER IS LOAD-BEARING. The prefix rule reads the START of the text, so it
|
|
2973
|
+
* has to look at the text the HOST wrote; running {@link breakIdentityClauses}
|
|
2974
|
+
* first would insert an invisible break inside `You are an AI agent powered by
|
|
2975
|
+
* DeepSeek Harness`, and {@link isUnapprovedChannelOpening} would then no longer
|
|
2976
|
+
* match it — silently dropping the neutral head the prefix gate still needs. So
|
|
2977
|
+
* the head goes on first, and the clause breaks land in the text underneath it.
|
|
2978
|
+
*
|
|
2979
|
+
* BOTH RUN, because they defend different gates. The head defeats the prefix
|
|
2980
|
+
* match at offset 0; the clause breaks defeat the exact-substring match
|
|
2981
|
+
* anywhere inside. A body that needs only one of them gets only that one:
|
|
2982
|
+
* the Claude Code opening on the prefix list carries no identity clause, and an
|
|
2983
|
+
* identity clause nobody put on the prefix list needs no head.
|
|
2984
|
+
*/
|
|
2985
|
+
function neutralizeScannedText(text) {
|
|
2986
|
+
const broken = breakIdentityClauses(isUnapprovedChannelOpening(text) ? `${WORKBUDDY_FALLBACK_SYSTEM_PROMPT}\n\n${text}` : text);
|
|
2987
|
+
return broken === text ? void 0 : broken;
|
|
2988
|
+
}
|
|
2989
|
+
/**
|
|
2903
2990
|
* A body in which EVERY scanned opening (`system` and `assistant`) is moved
|
|
2904
2991
|
* behind the neutral head.
|
|
2905
2992
|
*
|
|
@@ -3072,8 +3159,7 @@ function perturbChannelFingerprints(bodyJson, dense = false) {
|
|
|
3072
3159
|
if (!Array.isArray(messages)) return void 0;
|
|
3073
3160
|
let changed = false;
|
|
3074
3161
|
const perturb = (text) => {
|
|
3075
|
-
|
|
3076
|
-
const boundaries = text.replace(/([.!?])(\s+)/gu, `$1${ZERO_WIDTH_SPACE}$2`).replace(/([。!?])/gu, `$1${ZERO_WIDTH_SPACE}`).replace(/([,;])(\s+)/gu, `$1${ZERO_WIDTH_SPACE}$2`).replace(WORD_INTERNAL_BREAK, (whole, head, tail) => `${head}${ZERO_WIDTH_SPACE}${tail}`);
|
|
3162
|
+
const boundaries = text.replace(/([.!?])(\s+)/gu, `$1${ZERO_WIDTH_SPACE}$2`).replace(/([。!?])(?!\u200b)/gu, `$1${ZERO_WIDTH_SPACE}`).replace(/([,;])(\s+)/gu, `$1${ZERO_WIDTH_SPACE}$2`).replace(WORD_INTERNAL_BREAK, (whole, head, tail) => `${head}${ZERO_WIDTH_SPACE}${tail}`);
|
|
3077
3163
|
if (!dense) return boundaries === text ? void 0 : boundaries;
|
|
3078
3164
|
const denseText = boundaries.replace(/(?=\S)/gu, (whole, offset, source) => {
|
|
3079
3165
|
if (source.slice(Math.max(0, offset - 1), offset + 1).includes(ZERO_WIDTH_SPACE)) return "";
|
|
@@ -6720,7 +6806,7 @@ function describeRosterSeenPayload(raw) {
|
|
|
6720
6806
|
//#endregion
|
|
6721
6807
|
//#region src/version.ts
|
|
6722
6808
|
/** The npm package version this build was produced from. */
|
|
6723
|
-
const WORKBUDDY_CONNECT_VERSION = "5.8.
|
|
6809
|
+
const WORKBUDDY_CONNECT_VERSION = "5.8.2";
|
|
6724
6810
|
//#endregion
|
|
6725
6811
|
//#region src/host-heartbeat.ts
|
|
6726
6812
|
/**
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as parseCreditMultiplier, A as isEncryptedCredentialError, At as isWorkbuddyBundle, B as WorkBuddyCredentialRejectedError, C as WorkBuddyCredentialStore, Ct as clearAtRestKeyCache, D as defaultDesktopAuthDirs, Dt as findWorkbuddyAppExecutable, E as defaultDesktopAuthCandidates, Et as fetchAtRestKeyPayload, F as workbuddyAccountId, Ft as workbuddyAppExecutableCandidates, G as forceNeutralHead, H as channelGateMemberOf, I as workbuddyOwnAuthPath, J as isCredentialRejectedError, K as isContextTooLongBody, L as CONTEXT_OVERFLOW_MARKER, Lt as resolveDshHome, M as legacyWorkbuddyOwnAuthPath, Mt as macosNestedAppBundles, N as parseWorkBuddyAuth, Nt as openEncryptedField, O as defaultDesktopAuthPath, P as preferredAccountName, Pt as readAtRestKey, Q as openingEvidenceOfBody, R as CREDENTIAL_REJECTED_CODE, Rt as withFileLock, S as WORKBUDDY_AUTH_FILE_ENV, St as WORKBUDDY_APP_EXECUTABLE_ENV, T as authFileName, Tt as deriveAtRestKeyId, U as classifyUpstreamError, V as WorkBuddyUpstreamClient, W as declaredTools, X as knownRegionOf, Y as isFirstMessageGateBody, Z as modelIdOfBody, _ as deriveCatalog, _t as parseQueueAnswer, a as readHostHeartbeat, at as requestEvidenceOf, b as ENCRYPTED_CREDENTIAL_CODE, bt as imageDefaultFor, c as WORKBUDDY_CONNECT_VERSION, ct as defaultOffModelIds, dt as offRefused, et as parseReasoning, f as piAiRuntimeInfo, ft as recordRefusal, g as applyContextBudgets, gt as pickImageModel, h as WorkBuddyCatalog, ht as WorkBuddyImageError, i as processStartTimeMs, it as regionOf, j as isVaultAccountId, jt as macosBundleExecutable, k as hasEncryptedCredentialFields, kt as isEncryptedFieldWrapper, l as createFileRosterSeenStore, lt as lastRefusalFor, m as FALLBACK_WORKBUDDY_MODELS_GLOBAL, mt as DEFAULT_IMAGE_SIZE, n as clearHostHeartbeat, nt as perturbChannelFingerprints, o as workbuddyHostHeartbeatPath, ot as attachBusyLevels, p as FALLBACK_WORKBUDDY_MODELS, pt as withEffectiveOff, q as isConversationIdGateBody, r as isHeartbeatProcessAlive, rt as prepareChatBody, s as writeHostHeartbeat, st as REFUSES_OFF_MODEL_IDS, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, tt as parseUpstreamModel, ut as offDefaultFor, v as fallbackModelsFor, vt as queueNoticeText, w as WorkBuddyEncryptedCredentialError, wt as deriveAtRestKey, x as WORKBUDDY_AUTH_FILENAME, xt as nativeModalityOf, y as withoutRouters, yt as NATIVE_MODALITY_BY_MODEL_ID, z as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, zt as writeFileAtomic } from "./host-heartbeat-
|
|
1
|
+
import { $ as parseCreditMultiplier, A as isEncryptedCredentialError, At as isWorkbuddyBundle, B as WorkBuddyCredentialRejectedError, C as WorkBuddyCredentialStore, Ct as clearAtRestKeyCache, D as defaultDesktopAuthDirs, Dt as findWorkbuddyAppExecutable, E as defaultDesktopAuthCandidates, Et as fetchAtRestKeyPayload, F as workbuddyAccountId, Ft as workbuddyAppExecutableCandidates, G as forceNeutralHead, H as channelGateMemberOf, I as workbuddyOwnAuthPath, J as isCredentialRejectedError, K as isContextTooLongBody, L as CONTEXT_OVERFLOW_MARKER, Lt as resolveDshHome, M as legacyWorkbuddyOwnAuthPath, Mt as macosNestedAppBundles, N as parseWorkBuddyAuth, Nt as openEncryptedField, O as defaultDesktopAuthPath, P as preferredAccountName, Pt as readAtRestKey, Q as openingEvidenceOfBody, R as CREDENTIAL_REJECTED_CODE, Rt as withFileLock, S as WORKBUDDY_AUTH_FILE_ENV, St as WORKBUDDY_APP_EXECUTABLE_ENV, T as authFileName, Tt as deriveAtRestKeyId, U as classifyUpstreamError, V as WorkBuddyUpstreamClient, W as declaredTools, X as knownRegionOf, Y as isFirstMessageGateBody, Z as modelIdOfBody, _ as deriveCatalog, _t as parseQueueAnswer, a as readHostHeartbeat, at as requestEvidenceOf, b as ENCRYPTED_CREDENTIAL_CODE, bt as imageDefaultFor, c as WORKBUDDY_CONNECT_VERSION, ct as defaultOffModelIds, dt as offRefused, et as parseReasoning, f as piAiRuntimeInfo, ft as recordRefusal, g as applyContextBudgets, gt as pickImageModel, h as WorkBuddyCatalog, ht as WorkBuddyImageError, i as processStartTimeMs, it as regionOf, j as isVaultAccountId, jt as macosBundleExecutable, k as hasEncryptedCredentialFields, kt as isEncryptedFieldWrapper, l as createFileRosterSeenStore, lt as lastRefusalFor, m as FALLBACK_WORKBUDDY_MODELS_GLOBAL, mt as DEFAULT_IMAGE_SIZE, n as clearHostHeartbeat, nt as perturbChannelFingerprints, o as workbuddyHostHeartbeatPath, ot as attachBusyLevels, p as FALLBACK_WORKBUDDY_MODELS, pt as withEffectiveOff, q as isConversationIdGateBody, r as isHeartbeatProcessAlive, rt as prepareChatBody, s as writeHostHeartbeat, st as REFUSES_OFF_MODEL_IDS, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, tt as parseUpstreamModel, ut as offDefaultFor, v as fallbackModelsFor, vt as queueNoticeText, w as WorkBuddyEncryptedCredentialError, wt as deriveAtRestKey, x as WORKBUDDY_AUTH_FILENAME, xt as nativeModalityOf, y as withoutRouters, yt as NATIVE_MODALITY_BY_MODEL_ID, z as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, zt as writeFileAtomic } from "./host-heartbeat-C9BFJEBM.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";
|
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.8.
|
|
5
|
+
"version": "5.8.2",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|