dsh-xdpool 2.4.1 → 2.4.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 CHANGED
@@ -1,6 +1,39 @@
1
1
  # Changelog
2
2
  本项目的所有重要变更都会记录在此文件。
3
3
  版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
4
+ ## 2.4.2 — 第二道内容指纹:`Web GUI` 那句,以及 assistant 历史
5
+ > 2.4.1 之后仍有 11128。这一版补上第二道内容指纹,并把改写范围扩到 assistant 历史。修复来自社区 PR [#62](https://github.com/XDTrees/dsh-xdpool/pull/62)([@jmglsi](https://github.com/jmglsi)),实测复核与发布由维护者完成。
6
+ ### 同一条提示词里还有第二道指纹
7
+ 2.4.1 只改写了 `You are an AI agent powered by DeepSeek Harness` 那一句的开头。把这一句改掉之后,请求仍然 `400 / 11128`,因为网关还在看提示词的另一段——介绍 Web GUI 的那一对句子:
8
+
9
+ ```
10
+ You are interacting with the user through the DeepSeek Harness Web GUI
11
+ whose URL the runtime context names. When the user refers to "this page",
12
+ "this GUI", or "this app" without naming another target, they mean this GUI.
13
+ ```
14
+
15
+ 这两句是**合起来**才算的:单独发任一句都 `200`,两句凑齐就 `400`;在这对句子里做任何小改动都放行。所以这是第二道**内容指纹**,不是请求长度限制,也不是别的头。
16
+
17
+ 修法是把产品名后的 `Web GUI` 改称 `web interface`——保住原意的最小改动。改写清单仍是数组,这一条只是新的一行。
18
+
19
+ ### 被扫的是 system 和 assistant,user/tool 不扫
20
+ 同一句话放进 `assistant` 消息同样 `400`,改写后 `200`;放进 `user` 或 `tool` 则从来不管。这一条很重要:**只要历史里有一条助手回复带着这段文字,之后这个会话的每一次请求都会失败**。所以改写范围从「只改 system」扩到 system + assistant。
21
+
22
+ ### 实测(真实国内网关,改写前 → 改写后)
23
+ | 请求体 | 修复前 | 修复后 |
24
+ |---|---|---|
25
+ | 主 agent 提示词(含 Web GUI 那对句子) | `400 / 11128` | `200` |
26
+ | 同一对句子放在 `assistant` 历史里 | `400 / 11128` | `200` |
27
+ | 两句合一的提示词(两道指纹同时在) | `400 / 11128` | `200` |
28
+ | 埋进数百字长提示词中间 | `400 / 11128` | `200` |
29
+ | 只有无关填充的长提示词(基线) | `200` | `200` |
30
+
31
+ 维护者复核时额外确认了一件事:DSH 当前版本真正发出的那几句(`The runtime context names the DeepSeek Harness implementation checkout…`、`… the DeepSeek Harness Web GUI at <url>.`、`Current DeepSeek Harness Web GUI URL: …`)**原样发送就是 `200`**,不触发任何检查——它们含产品名却不是模板命中的句式。也就是说命中的是句式,不是「出现产品名」;这也是为什么没有采用「把 `DeepSeek Harness` 全局替换成 `DSH`」那种更宽的改法:它会连 `D:\DeepSeek Harness\resources\app\dsh\` 这类要求模型逐字使用的路径一起改掉,而换不来任何额外的放行。
32
+ ### 同时并入的两个社区修复
33
+ - **PR [#49](https://github.com/XDTrees/dsh-xdpool/pull/49)**([@GIGIGIGIbaby](https://github.com/GIGIGIGIbaby)):模型连通性探测的 `content` 改用内容块数组。宿主按 `content.some(...)` 校验每条出站消息,裸字符串没有 `.some`,探测在进程内就炸了,卡片上显示成 `content.some is not a function`——一个形状错误伪装成模型坏了。
34
+ - **PR [#50](https://github.com/XDTrees/dsh-xdpool/pull/50)**([@GIGIGIGIbaby](https://github.com/GIGIGIGIbaby)):禁用按钮划过时会「消失」。`.dsm-btn:disabled:hover` 里的 `background:transparent` 优先级 (0,3,0),压过了 `.dsm-btn-primary` 的填充色 (0,1,0);主按钮文字是深色、为浅底设计的,底一变透明文字就看不见了。现在这条规则只保留 `transform` 部分。
35
+ ### 其他
36
+ - 测试:新增指纹改写与被扫角色 9 例 + 运行时上下文指纹 5 例 + 探测请求形状 4 例 + 禁用按钮悬停 6 例,全套 72 文件 947 用例。
4
37
  ## 2.4.1 — 11128 的真正成因:system 内容指纹
5
38
  > 2.4.0 之后仍有用户撞上 `400 / 11128 Illegal API invocation from an unapproved channel`。定位下来,2.4.0 的归因是错的,这一版修的是真原因。
6
39
  ### 11128 不是身份头的问题,是 system 内容里的自报句
package/lib/bin.js CHANGED
@@ -300,8 +300,11 @@ const CHAT_IDENTITY_HEADERS = Object.freeze({
300
300
  * it" above is the same finding from the other side).
301
301
  * - it is case-insensitive (`you are an ai agent powered by deepseek harness`
302
302
  * is blocked too).
303
- * - only `role: "system"` is inspected: the same sentence in a `user` or
304
- * `tool` message passes, and it is blocked in a second system message too.
303
+ * - the scanned roles are `system` and `assistant`: the same sentence in a
304
+ * `user` or `tool` message passes, and it is blocked in a second system
305
+ * message too. 2.4.1 recorded this as system-only; re-measured
306
+ * 2026-10-21, an `assistant` message carrying the sentence 400s as well,
307
+ * so `prepareChatBody` rewrites both roles.
305
308
  * - `deepseek harness` alone, or `powered by deepseek` alone, passes — the
306
309
  * whole phrase is what matches.
307
310
  * - replacing `powered by` with `running on` passes, which is the rewrite
@@ -313,14 +316,38 @@ const CHAT_IDENTITY_HEADERS = Object.freeze({
313
316
  * lose its anchor tomorrow. Rewriting the phrase is what the report's repro
314
317
  * verified, so that is what this does.
315
318
  *
319
+ * A SECOND FINGERPRINT (measured against the live domestic gateway 2026-10-21)
320
+ *
321
+ * Once the sentence above was rewritten, requests still 400/11128'd, and the
322
+ * blocker turned out to be another region of the same DSH system prompt: the
323
+ * runtime-context sentence pair
324
+ *
325
+ * You are interacting with the user through the DeepSeek Harness Web GUI
326
+ * whose URL the runtime context names. When the user refers to "this page",
327
+ * "this GUI", or "this app" without naming another target, they mean this GUI.
328
+ *
329
+ * The pair is load-bearing: either sentence alone 200s, and so does the pair
330
+ * with any small edit inside it — so this is a second CONTENT fingerprint, not
331
+ * a prompt-length rule and not another header rule. Renaming the product's
332
+ * "Web GUI" to "web interface" is the smallest edit that keeps the meaning for
333
+ * the model and clears the check; it was verified at 200 against both a main
334
+ * agent system prompt and a subagent system prompt, on the same account that
335
+ * reproduced the 400.
336
+ *
337
+ * The same window in an `assistant` message also answers 400, and 200 after
338
+ * this rewrite; `user` and `tool` messages are not scanned. That is why the
339
+ * rewrite is applied to assistant history too — one past reply carrying a
340
+ * fingerprint would otherwise fail every later request in the conversation.
341
+ *
316
342
  * The list is an ARRAY because these fingerprints are appended to over time; a
317
343
  * future rejection should be one more row here, not a new code path.
318
344
  */
319
- const BANNED_SYSTEM_PHRASES = Object.freeze([[/powered by DeepSeek Harness/gi, "running on DeepSeek Harness"]]);
345
+ const BANNED_SYSTEM_PHRASES = Object.freeze([[/powered by DeepSeek Harness/gi, "running on DeepSeek Harness"], [/DeepSeek Harness Web GUI/gi, "DeepSeek Harness web interface"]]);
320
346
  /**
321
- * Rewrite fingerprinted system content, leaving everything else byte-identical.
347
+ * Rewrite fingerprinted system/assistant content, leaving everything else
348
+ * byte-identical.
322
349
  *
323
- * System content arrives as either a plain string or an array of content blocks
350
+ * Message content arrives as either a plain string or an array of content blocks
324
351
  * (an OpenAI-compatible body allows both), so both shapes are handled — fixing
325
352
  * only the string form would miss every request that carries an image or a
326
353
  * cache-control block.
@@ -962,9 +989,9 @@ var WorkBuddyUpstreamClient = class {
962
989
  * Normalize an OpenAI chat-completions body for the WorkBuddy upstream:
963
990
  * force `stream: true` (the upstream rejects non-streaming), convert the
964
991
  * DSH `developer` role into `system` (upstream rejects `developer` with
965
- * business code 11128), rewrite system content the gateway fingerprints as a
966
- * non-official client (also 11128), and flatten `tool_choice` into its string
967
- * form.
992
+ * business code 11128), rewrite fingerprinted system/assistant content the
993
+ * gateway reads as a non-official client (also 11128), and flatten
994
+ * `tool_choice` into its string form.
968
995
  *
969
996
  * This is the REGION-BLIND half. Everything here is measured good on both
970
997
  * gateways, so it runs before the region is known — the pool lane picks its
@@ -989,7 +1016,7 @@ var WorkBuddyUpstreamClient = class {
989
1016
  if (typeof value !== "object" || value === null || Array.isArray(value)) continue;
990
1017
  const message = value;
991
1018
  if (message["role"] === "developer") message["role"] = "system";
992
- if (message["role"] === "system") message["content"] = sanitizeSystemContent(message["content"]);
1019
+ if (message["role"] === "system" || message["role"] === "assistant") message["content"] = sanitizeSystemContent(message["content"]);
993
1020
  }
994
1021
  const choice = obj["tool_choice"];
995
1022
  if (typeof choice === "string") {
package/lib/client.js CHANGED
@@ -1340,7 +1340,14 @@ window.__ModuleLoader__.load({
1340
1340
  .dsm-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid transparent;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}
1341
1341
  .dsm-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
1342
1342
  .dsm-btn:disabled{opacity:.4;cursor:default;filter:saturate(.35);transform:none}
1343
- .dsm-btn:disabled:hover{transform:none;background:transparent}
1343
+ /* Only the transform is neutralised here. Every variant's hover background is
1344
+ already gated with ":not(:disabled)", so a disabled button never acquires one
1345
+ and this rule has nothing to undo — but background:transparent used to sit
1346
+ beside it, and at specificity (0,3,0) it beat .dsm-btn-primary's (0,1,0)
1347
+ fill. The primary label is a dark colour designed for a light background, so
1348
+ on a dark panel the fill went transparent, the text stayed dark, and the
1349
+ button appeared to vanish the moment the cursor reached it. */
1350
+ .dsm-btn:disabled:hover{transform:none}
1344
1351
  .dsm-btn-outline{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:transparent;font-weight:500}
1345
1352
  .dsm-btn-outline:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed);background:rgba(255,255,255,.04)}
1346
1353
  .dsm-btn-primary{background:var(--dsw-alias-label-primary,#e6e6e6);color:var(--dsw-alias-bg-layer-3,#202126)}
@@ -12188,7 +12195,7 @@ window.__ModuleLoader__.load({
12188
12195
  *
12189
12196
  * @module dsh-xdpool/version
12190
12197
  */
12191
- const POOL_VERSION = "2.4.1";
12198
+ const POOL_VERSION = "2.4.2";
12192
12199
  //#endregion
12193
12200
  //#region src/client/PoolCard.tsx
12194
12201
  /**
package/lib/index.d.ts CHANGED
@@ -469,9 +469,9 @@ export declare class WorkBuddyUpstreamClient {
469
469
  * Normalize an OpenAI chat-completions body for the WorkBuddy upstream:
470
470
  * force `stream: true` (the upstream rejects non-streaming), convert the
471
471
  * DSH `developer` role into `system` (upstream rejects `developer` with
472
- * business code 11128), rewrite system content the gateway fingerprints as a
473
- * non-official client (also 11128), and flatten `tool_choice` into its string
474
- * form.
472
+ * business code 11128), rewrite fingerprinted system/assistant content the
473
+ * gateway reads as a non-official client (also 11128), and flatten
474
+ * `tool_choice` into its string form.
475
475
  *
476
476
  * This is the REGION-BLIND half. Everything here is measured good on both
477
477
  * gateways, so it runs before the region is known — the pool lane picks its
package/lib/index.js CHANGED
@@ -351,8 +351,11 @@ const CHAT_IDENTITY_HEADERS = Object.freeze({
351
351
  * it" above is the same finding from the other side).
352
352
  * - it is case-insensitive (`you are an ai agent powered by deepseek harness`
353
353
  * is blocked too).
354
- * - only `role: "system"` is inspected: the same sentence in a `user` or
355
- * `tool` message passes, and it is blocked in a second system message too.
354
+ * - the scanned roles are `system` and `assistant`: the same sentence in a
355
+ * `user` or `tool` message passes, and it is blocked in a second system
356
+ * message too. 2.4.1 recorded this as system-only; re-measured
357
+ * 2026-10-21, an `assistant` message carrying the sentence 400s as well,
358
+ * so `prepareChatBody` rewrites both roles.
356
359
  * - `deepseek harness` alone, or `powered by deepseek` alone, passes — the
357
360
  * whole phrase is what matches.
358
361
  * - replacing `powered by` with `running on` passes, which is the rewrite
@@ -364,14 +367,38 @@ const CHAT_IDENTITY_HEADERS = Object.freeze({
364
367
  * lose its anchor tomorrow. Rewriting the phrase is what the report's repro
365
368
  * verified, so that is what this does.
366
369
  *
370
+ * A SECOND FINGERPRINT (measured against the live domestic gateway 2026-10-21)
371
+ *
372
+ * Once the sentence above was rewritten, requests still 400/11128'd, and the
373
+ * blocker turned out to be another region of the same DSH system prompt: the
374
+ * runtime-context sentence pair
375
+ *
376
+ * You are interacting with the user through the DeepSeek Harness Web GUI
377
+ * whose URL the runtime context names. When the user refers to "this page",
378
+ * "this GUI", or "this app" without naming another target, they mean this GUI.
379
+ *
380
+ * The pair is load-bearing: either sentence alone 200s, and so does the pair
381
+ * with any small edit inside it — so this is a second CONTENT fingerprint, not
382
+ * a prompt-length rule and not another header rule. Renaming the product's
383
+ * "Web GUI" to "web interface" is the smallest edit that keeps the meaning for
384
+ * the model and clears the check; it was verified at 200 against both a main
385
+ * agent system prompt and a subagent system prompt, on the same account that
386
+ * reproduced the 400.
387
+ *
388
+ * The same window in an `assistant` message also answers 400, and 200 after
389
+ * this rewrite; `user` and `tool` messages are not scanned. That is why the
390
+ * rewrite is applied to assistant history too — one past reply carrying a
391
+ * fingerprint would otherwise fail every later request in the conversation.
392
+ *
367
393
  * The list is an ARRAY because these fingerprints are appended to over time; a
368
394
  * future rejection should be one more row here, not a new code path.
369
395
  */
370
- const BANNED_SYSTEM_PHRASES = Object.freeze([[/powered by DeepSeek Harness/gi, "running on DeepSeek Harness"]]);
396
+ const BANNED_SYSTEM_PHRASES = Object.freeze([[/powered by DeepSeek Harness/gi, "running on DeepSeek Harness"], [/DeepSeek Harness Web GUI/gi, "DeepSeek Harness web interface"]]);
371
397
  /**
372
- * Rewrite fingerprinted system content, leaving everything else byte-identical.
398
+ * Rewrite fingerprinted system/assistant content, leaving everything else
399
+ * byte-identical.
373
400
  *
374
- * System content arrives as either a plain string or an array of content blocks
401
+ * Message content arrives as either a plain string or an array of content blocks
375
402
  * (an OpenAI-compatible body allows both), so both shapes are handled — fixing
376
403
  * only the string form would miss every request that carries an image or a
377
404
  * cache-control block.
@@ -1122,9 +1149,9 @@ var WorkBuddyUpstreamClient = class {
1122
1149
  * Normalize an OpenAI chat-completions body for the WorkBuddy upstream:
1123
1150
  * force `stream: true` (the upstream rejects non-streaming), convert the
1124
1151
  * DSH `developer` role into `system` (upstream rejects `developer` with
1125
- * business code 11128), rewrite system content the gateway fingerprints as a
1126
- * non-official client (also 11128), and flatten `tool_choice` into its string
1127
- * form.
1152
+ * business code 11128), rewrite fingerprinted system/assistant content the
1153
+ * gateway reads as a non-official client (also 11128), and flatten
1154
+ * `tool_choice` into its string form.
1128
1155
  *
1129
1156
  * This is the REGION-BLIND half. Everything here is measured good on both
1130
1157
  * gateways, so it runs before the region is known — the pool lane picks its
@@ -1149,7 +1176,7 @@ var WorkBuddyUpstreamClient = class {
1149
1176
  if (typeof value !== "object" || value === null || Array.isArray(value)) continue;
1150
1177
  const message = value;
1151
1178
  if (message["role"] === "developer") message["role"] = "system";
1152
- if (message["role"] === "system") message["content"] = sanitizeSystemContent(message["content"]);
1179
+ if (message["role"] === "system" || message["role"] === "assistant") message["content"] = sanitizeSystemContent(message["content"]);
1153
1180
  }
1154
1181
  const choice = obj["tool_choice"];
1155
1182
  if (typeof choice === "string") {
@@ -17624,7 +17651,10 @@ async function probeModelConnectivity(llm, provider, model, options = {}) {
17624
17651
  system: "You are being called by a connection test. Answer in one short word.",
17625
17652
  messages: [{
17626
17653
  role: "user",
17627
- content: PROBE_PROMPT
17654
+ content: [{
17655
+ type: "text",
17656
+ text: PROBE_PROMPT
17657
+ }]
17628
17658
  }],
17629
17659
  maxTokens: 24,
17630
17660
  signal: timeout.signal
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-xdpool",
3
3
  "displayName": "DSH XD Pool",
4
4
  "description": "One DSH model pool for every free quota you have: merge all locally signed-in WorkBuddy accounts with auto-failover, sign in to 13 vendors by QR/web, aggregate 76 vendors' free tiers, plus a usage dashboard, model routing, a local OpenAI-compatible gateway and a points-automation suite.",
5
- "version": "2.4.1",
5
+ "version": "2.4.2",
6
6
  "license": "MIT",
7
7
  "author": "XDTrees",
8
8
  "homepage": "https://github.com/XDTrees/dsh-xdpool#readme",