dsh-connect-workbuddy 5.6.3 → 5.7.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 CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.7.0 (2026-10-23)
4
+
5
+ > 特性版本:加强 WorkBuddy 渠道校验(`11128`)的三级自愈救援,并改善失败证据回显。**凭据、计费、provider 注册与普通请求路径均未改动**;默认策略仍只在渠道校验拒绝后启用,可选 `channelPerturbation: "always"` 让已确认普通请求必失败的环境跳过无效首轮。
6
+
7
+ ### Features
8
+
9
+ - **渠道校验新增密集零宽救援与可配置扰动策略。**
10
+ - 渠道门现在按「强制中性头 → 稀疏零宽扰动 → 密集零宽扰动」逐级重试;密集模式在被扫描的 `system` / `assistant` 文本字符边界插入 U+200B,能覆盖没有标点的纯 prose 指纹。
11
+ - 新增全局 `channelPerturbation` 配置:`off` 只保留失败后救援,`failure`(默认)从成功救援中学习形状,`always` 每个请求都从密集救援形状起步。CN 与 global 两个 provider 均由同一配置驱动。
12
+ - 普通请求仍然逐字节保持原样(除已有的已知指纹归一化);任何渠道救援都使用同一账号,不触发账号池换号,也不记为账号失败。
13
+
14
+ ### Fixes
15
+
16
+ - **渠道失败证据不再只显示被扫描消息的首行。**
17
+ - 证据现在显示首行、尾行(若文本有多行)与总字数,帮助区分「已知开头命中」与「assistant 历史中段存在精确子串」;这避免把插件已中性化的开头误报为网关新增词条。
18
+ - `OpeningEvidence` 增加 `textLength` 与可选 `closing`,失败回复仍保持原有的 `workbuddy 上游失败:…(http N)` 报告形状。
19
+
20
+ ### Docs
21
+
22
+ - 新增 [`docs/CHANNEL-FINGERPRINT-RESEARCH.md`](docs/CHANNEL-FINGERPRINT-RESEARCH.md),整理主流网关、中转站与适配层对客户端指纹的处理方式,并记录本次实现取舍。
23
+ - 更新 [`docs/DESIGN.md`](docs/DESIGN.md),补充三级渠道救援、扰动策略与证据回显设计。
24
+
25
+ ### Tests
26
+
27
+ - 新增密集扰动、`always` / `off` 策略、证据回显字段与既有渠道门回归测试;渠道相关测试 80 条全部通过,完整套件 2137 条通过、2 条按平台跳过。
28
+
3
29
  ## 5.6.3 (2026-10-23)
4
30
 
5
31
  > 补丁版本:修「免费模型测出来可用,却显示 8 天后才能用」。**请求路径、provider 路由、计费与凭据一律未变。**
package/lib/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { C as defaultDesktopAuthCandidates, L as WorkBuddyUpstreamClient, M as workbuddyOwnAuthPath, Mt as hostPeersInfo, a as readHostHeartbeat, b as WorkBuddyCredentialStore, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, k as legacyWorkbuddyOwnAuthPath, l as piAiRuntimeInfo, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS, vt as WORKBUDDY_APP_EXECUTABLE_ENV, w as defaultDesktopAuthDirs, wt as findWorkbuddyAppExecutableWithSource, y as WORKBUDDY_AUTH_FILE_ENV } from "./host-heartbeat-r_icwyw3.js";
2
+ import { C as defaultDesktopAuthCandidates, L as WorkBuddyUpstreamClient, M as workbuddyOwnAuthPath, Mt as hostPeersInfo, a as readHostHeartbeat, b as WorkBuddyCredentialStore, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, k as legacyWorkbuddyOwnAuthPath, l as piAiRuntimeInfo, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS, vt as WORKBUDDY_APP_EXECUTABLE_ENV, w as defaultDesktopAuthDirs, wt as findWorkbuddyAppExecutableWithSource, y as WORKBUDDY_AUTH_FILE_ENV } from "./host-heartbeat-CVz2J0Xt.js";
3
3
  import { realpathSync } from "node:fs";
4
4
  import { fileURLToPath } from "node:url";
5
5
  //#region src/bin.ts
@@ -2832,6 +2832,11 @@ const WORD_INTERNAL_BREAK = /([A-Z][A-Za-z0-9]*)(\s+)(?=[A-Z][A-Za-z0-9])/gu;
2832
2832
  * the request AND the neutral head could not change anything. Requests the
2833
2833
  * gateway answers keep their bytes untouched.
2834
2834
  *
2835
+ * `dense` adds word-boundary breaks for the last-resort rescue. The default
2836
+ * sparse form is still used first because it is the least invasive and has been
2837
+ * measured; dense mode exists for the refusal both the neutral head and the
2838
+ * sparse form could not clear.
2839
+ *
2835
2840
  * Idempotent: a body that already carries U+200B in a scanned message is left
2836
2841
  * alone, so a retry never stacks perturbations. Returns `undefined` when
2837
2842
  * nothing would change, which is the caller's signal not to spend a request.
@@ -2839,7 +2844,7 @@ const WORD_INTERNAL_BREAK = /([A-Z][A-Za-z0-9]*)(\s+)(?=[A-Z][A-Za-z0-9])/gu;
2839
2844
  * Never throws: a malformed body is the upstream's to reject, and a parse error
2840
2845
  * must not turn a rescue into a crash.
2841
2846
  */
2842
- function perturbChannelFingerprints(bodyJson) {
2847
+ function perturbChannelFingerprints(bodyJson, dense = false) {
2843
2848
  let parsed;
2844
2849
  try {
2845
2850
  parsed = JSON.parse(bodyJson);
@@ -2853,8 +2858,14 @@ function perturbChannelFingerprints(bodyJson) {
2853
2858
  let changed = false;
2854
2859
  const perturb = (text) => {
2855
2860
  if (text.includes(ZERO_WIDTH_SPACE)) return void 0;
2856
- const next = 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}`);
2857
- return next === text ? void 0 : next;
2861
+ 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}`);
2862
+ if (!dense) return boundaries === text ? void 0 : boundaries;
2863
+ const denseText = boundaries.replace(/(?=\S)/gu, (whole, offset, source) => {
2864
+ if (source.slice(Math.max(0, offset - 1), offset + 1).includes(ZERO_WIDTH_SPACE)) return "";
2865
+ const previous = source.slice(0, offset).match(/\S(?=\S*$)/u)?.[0];
2866
+ return previous !== void 0 && /[A-Za-z0-9぀-ヿ㐀-鿿]/u.test(previous) ? ZERO_WIDTH_SPACE : "";
2867
+ });
2868
+ return denseText === text ? void 0 : denseText;
2858
2869
  };
2859
2870
  for (const value of messages) {
2860
2871
  if (typeof value !== "object" || value === null || Array.isArray(value)) continue;
@@ -2937,8 +2948,11 @@ function openingEvidenceOfBody(bodyJson) {
2937
2948
  if (evidence.length >= SYSTEM_EVIDENCE_LIMIT) break;
2938
2949
  const opening = firstNonEmptyLineOf(text).replace(/\s+/gu, " ").trim();
2939
2950
  if (opening === "") continue;
2951
+ const closing = lastNonEmptyLineOf(text).replace(/\s+/gu, " ").trim();
2940
2952
  evidence.push({
2941
2953
  opening: clip(opening, SYSTEM_OPENING_LIMIT),
2954
+ textLength: text.length,
2955
+ ...closing === "" || closing === opening ? {} : { closing: clip(closing, SYSTEM_OPENING_LIMIT) },
2942
2956
  role: roleOfEvidence,
2943
2957
  neutralized: startsWithNeutralHead(opening) || isUnapprovedChannelOpening(opening),
2944
2958
  messageIndex: index
@@ -2973,6 +2987,22 @@ function firstNonEmptyLineOf(content) {
2973
2987
  return "";
2974
2988
  }
2975
2989
  /**
2990
+ * The last line of a text that carries something.
2991
+ *
2992
+ * The gate has also been measured to match an exact substring INSIDE a scanned
2993
+ * message, so a first line alone can point at the wrong evidence: a neutralised
2994
+ * opening may sit above a fingerprint deeper in the same assistant history
2995
+ * message. The closing line is the cheapest observable other end of that text.
2996
+ */
2997
+ function lastNonEmptyLineOf(content) {
2998
+ const lines = content.split("\n");
2999
+ for (let index = lines.length - 1; index >= 0; index--) {
3000
+ const line = lines[index];
3001
+ if (line.trim() !== "") return line;
3002
+ }
3003
+ return "";
3004
+ }
3005
+ /**
2976
3006
  * Make the conversation open with a system message, prepending the fallback
2977
3007
  * when nothing else supplies one.
2978
3008
  *
@@ -6121,7 +6151,7 @@ function generationOf(version) {
6121
6151
  //#endregion
6122
6152
  //#region src/version.ts
6123
6153
  /** The npm package version this build was produced from. */
6124
- const WORKBUDDY_CONNECT_VERSION = "5.6.3";
6154
+ const WORKBUDDY_CONNECT_VERSION = "5.7.0";
6125
6155
  //#endregion
6126
6156
  //#region src/host-heartbeat.ts
6127
6157
  /**
package/lib/index.d.ts CHANGED
@@ -1864,6 +1864,16 @@ interface WorkBuddyShimOptions {
1864
1864
  client: Pick<WorkBuddyUpstreamClient, 'chatStream'>;
1865
1865
  catalog: WorkBuddyCatalog;
1866
1866
  logger?: ShimLogger;
1867
+ /**
1868
+ * When to perturb scanned messages with invisible characters.
1869
+ *
1870
+ * `off` keeps the ordinary fail-then-rescue ladder; `failure` (the default)
1871
+ * remembers a successful rescue and starts later requests with it; `always`
1872
+ * starts every request with the dense rescue. `always` is for users whose
1873
+ * gateway has already made the plain attempt a guaranteed 11128 — it trades a
1874
+ * little prompt byte-level purity for avoiding that doomed round trip.
1875
+ */
1876
+ channelPerturbation?: 'off' | 'failure' | 'always';
1867
1877
  /**
1868
1878
  * Pool failover for chat requests. Absent means no failover at all — the
1869
1879
  * pre-pool behaviour, where the selected account serves and its failure is
@@ -4532,6 +4542,15 @@ interface Config {
4532
4542
  * client-fingerprint workaround, not an account one — see `cli-bridge/runner`.
4533
4543
  */
4534
4544
  cliBridge?: CliBridgeSettings;
4545
+ /**
4546
+ * When scanned messages are perturbed with invisible characters.
4547
+ *
4548
+ * `failure` (default) learns from a successful channel-gate rescue and starts
4549
+ * later requests with the learned form; `always` starts every request with
4550
+ * the dense form for a gateway whose plain attempt is a guaranteed refusal;
4551
+ * `off` keeps only the post-failure rescue ladder.
4552
+ */
4553
+ channelPerturbation?: 'off' | 'failure' | 'always';
4535
4554
  }
4536
4555
  /**
4537
4556
  * Mark a schema's field as volatile on the DSH lines that support it.
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as prepareChatBody, A as parseWorkBuddyAuth, At as readAtRestKey, B as declaredTools, C as defaultDesktopAuthCandidates, Ct as findWorkbuddyAppExecutable, D as isEncryptedCredentialError, Dt as macosBundleExecutable, E as hasEncryptedCredentialFields, Et as isWorkbuddyBundle, F as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, Ft as writeFileAtomic, G as isFirstMessageGateBody, H as isContextTooLongBody, I as WorkBuddyCredentialRejectedError, J as openingEvidenceOfBody, K as knownRegionOf, L as WorkBuddyUpstreamClient, M as workbuddyOwnAuthPath, N as CONTEXT_OVERFLOW_MARKER, Nt as resolveDshHome, O as isVaultAccountId, Ot as macosNestedAppBundles, P as CREDENTIAL_REJECTED_CODE, Pt as withFileLock, Q as perturbChannelFingerprints, R as channelGateMemberOf, S as authFileName, St as fetchAtRestKeyPayload, T as defaultDesktopAuthPath, Tt as isEncryptedFieldWrapper, U as isConversationIdGateBody, V as forceNeutralHead, W as isCredentialRejectedError, X as parseReasoning, Y as parseCreditMultiplier, Z as parseUpstreamModel, _ as ENCRYPTED_CREDENTIAL_CODE, _t as nativeModalityOf, a as readHostHeartbeat, at as lastRefusalFor, b as WorkBuddyCredentialStore, bt as deriveAtRestKey, c as WORKBUDDY_CONNECT_VERSION, ct as recordRefusal, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, dt as WorkBuddyImageError, et as regionOf, f as WorkBuddyCatalog, ft as pickImageModel, g as withoutRouters, gt as imageDefaultFor, h as fallbackModelsFor, ht as NATIVE_MODALITY_BY_MODEL_ID, i as processStartTimeMs, it as defaultOffModelIds, j as workbuddyAccountId, jt as workbuddyAppExecutableCandidates, k as legacyWorkbuddyOwnAuthPath, kt as openEncryptedField, l as piAiRuntimeInfo, lt as withEffectiveOff, m as deriveCatalog, mt as queueNoticeText, n as clearHostHeartbeat, nt as attachBusyLevels, o as workbuddyHostHeartbeatPath, ot as offDefaultFor, p as applyContextBudgets, pt as parseQueueAnswer, q as modelIdOfBody, r as isHeartbeatProcessAlive, rt as REFUSES_OFF_MODEL_IDS, s as writeHostHeartbeat, st as offRefused, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, tt as requestEvidenceOf, u as FALLBACK_WORKBUDDY_MODELS, ut as DEFAULT_IMAGE_SIZE, v as WORKBUDDY_AUTH_FILENAME, vt as WORKBUDDY_APP_EXECUTABLE_ENV, w as defaultDesktopAuthDirs, x as WorkBuddyEncryptedCredentialError, xt as deriveAtRestKeyId, y as WORKBUDDY_AUTH_FILE_ENV, yt as clearAtRestKeyCache, z as classifyUpstreamError } from "./host-heartbeat-r_icwyw3.js";
1
+ import { $ as prepareChatBody, A as parseWorkBuddyAuth, At as readAtRestKey, B as declaredTools, C as defaultDesktopAuthCandidates, Ct as findWorkbuddyAppExecutable, D as isEncryptedCredentialError, Dt as macosBundleExecutable, E as hasEncryptedCredentialFields, Et as isWorkbuddyBundle, F as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, Ft as writeFileAtomic, G as isFirstMessageGateBody, H as isContextTooLongBody, I as WorkBuddyCredentialRejectedError, J as openingEvidenceOfBody, K as knownRegionOf, L as WorkBuddyUpstreamClient, M as workbuddyOwnAuthPath, N as CONTEXT_OVERFLOW_MARKER, Nt as resolveDshHome, O as isVaultAccountId, Ot as macosNestedAppBundles, P as CREDENTIAL_REJECTED_CODE, Pt as withFileLock, Q as perturbChannelFingerprints, R as channelGateMemberOf, S as authFileName, St as fetchAtRestKeyPayload, T as defaultDesktopAuthPath, Tt as isEncryptedFieldWrapper, U as isConversationIdGateBody, V as forceNeutralHead, W as isCredentialRejectedError, X as parseReasoning, Y as parseCreditMultiplier, Z as parseUpstreamModel, _ as ENCRYPTED_CREDENTIAL_CODE, _t as nativeModalityOf, a as readHostHeartbeat, at as lastRefusalFor, b as WorkBuddyCredentialStore, bt as deriveAtRestKey, c as WORKBUDDY_CONNECT_VERSION, ct as recordRefusal, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, dt as WorkBuddyImageError, et as regionOf, f as WorkBuddyCatalog, ft as pickImageModel, g as withoutRouters, gt as imageDefaultFor, h as fallbackModelsFor, ht as NATIVE_MODALITY_BY_MODEL_ID, i as processStartTimeMs, it as defaultOffModelIds, j as workbuddyAccountId, jt as workbuddyAppExecutableCandidates, k as legacyWorkbuddyOwnAuthPath, kt as openEncryptedField, l as piAiRuntimeInfo, lt as withEffectiveOff, m as deriveCatalog, mt as queueNoticeText, n as clearHostHeartbeat, nt as attachBusyLevels, o as workbuddyHostHeartbeatPath, ot as offDefaultFor, p as applyContextBudgets, pt as parseQueueAnswer, q as modelIdOfBody, r as isHeartbeatProcessAlive, rt as REFUSES_OFF_MODEL_IDS, s as writeHostHeartbeat, st as offRefused, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, tt as requestEvidenceOf, u as FALLBACK_WORKBUDDY_MODELS, ut as DEFAULT_IMAGE_SIZE, v as WORKBUDDY_AUTH_FILENAME, vt as WORKBUDDY_APP_EXECUTABLE_ENV, w as defaultDesktopAuthDirs, x as WorkBuddyEncryptedCredentialError, xt as deriveAtRestKeyId, y as WORKBUDDY_AUTH_FILE_ENV, yt as clearAtRestKeyCache, z as classifyUpstreamError } from "./host-heartbeat-CVz2J0Xt.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";
@@ -4020,7 +4020,8 @@ function channelEvidenceText(bodyJson) {
4020
4020
  const shown = entries.slice(0, CHANNEL_EVIDENCE_LIMIT);
4021
4021
  const rendered = shown.map((entry) => {
4022
4022
  const label = entry.neutralized ? "(插件已加中性头)" : "(不在插件已知名单里,网关可能新增了词条)";
4023
- return `「${entry.role}:${entry.opening}」${label}`;
4023
+ const span = entry.closing === void 0 ? `,共 ${entry.textLength} 字` : `,首尾为「${entry.closing}」,共 ${entry.textLength} 字`;
4024
+ return `「${entry.role}:${entry.opening}${span}」${label}`;
4024
4025
  }).join("、");
4025
4026
  const hidden = entries.length - shown.length;
4026
4027
  return `请求里被网关扫描的消息开头:${rendered}${hidden > 0 ? `……另有 ${hidden} 条未列出。` : "。"}`;
@@ -4217,6 +4218,7 @@ function createWorkBuddyShim(options) {
4217
4218
  const onAccountFailure = options.onAccountFailure;
4218
4219
  const onRequestBody = options.onRequestBody;
4219
4220
  const onUpstreamUsage = options.onUpstreamUsage;
4221
+ const channelPerturbation = options.channelPerturbation ?? "failure";
4220
4222
  const SHARED_SECRET = randomBytes(32).toString("base64url");
4221
4223
  /**
4222
4224
  * Whether this process's prompts have been PROVEN to need the invisible
@@ -4379,19 +4381,7 @@ function createWorkBuddyShim(options) {
4379
4381
  }
4380
4382
  const controller = new AbortController();
4381
4383
  res.on("close", () => controller.abort());
4382
- /**
4383
- * The body to send FIRST, which is the perturbed form once this process has
4384
- * proven the gateway demands it.
4385
- *
4386
- * `perturbChannelFingerprints` is consulted only when the flag is set, so a
4387
- * fresh process still pays the plain attempt — that is what discovers
4388
- * whether the gate is on at all (see `channelNeedsPerturbation`).
4389
- *
4390
- * The perturbed form is a purely additive transform: it inserts zero-width
4391
- * spaces and changes no word the model reads, so starting from it is not a
4392
- * behaviour change, only a saved round trip.
4393
- */
4394
- const preferred = channelNeedsPerturbation ? perturbChannelFingerprints(prepared) ?? prepared : prepared;
4384
+ const preferred = channelPerturbation === "always" || channelNeedsPerturbation ? perturbChannelFingerprints(prepared, channelPerturbation === "always") ?? prepared : prepared;
4395
4385
  let result = await client.chatStream(credential, preferred, controller.signal);
4396
4386
  if (channelNeedsPerturbation && !result.ok && result.kind === "channel_reject") {
4397
4387
  channelNeedsPerturbation = false;
@@ -4432,6 +4422,7 @@ function createWorkBuddyShim(options) {
4432
4422
  */
4433
4423
  let retriedWithForcedHead = false;
4434
4424
  let retriedWithPerturbation = false;
4425
+ let retriedWithDensePerturbation = false;
4435
4426
  if (!result.ok && result.kind === "channel_reject" && !controller.signal.aborted) {
4436
4427
  const forced = forceNeutralHead(prepared);
4437
4428
  if (forced !== void 0) {
@@ -4448,6 +4439,15 @@ function createWorkBuddyShim(options) {
4448
4439
  if (result.ok) channelNeedsPerturbation = true;
4449
4440
  }
4450
4441
  }
4442
+ if (!result.ok && result.kind === "channel_reject" && !controller.signal.aborted) {
4443
+ const dense = perturbChannelFingerprints(forced ?? prepared, true);
4444
+ if (dense !== void 0) {
4445
+ logger?.warn("dsh-connect-workbuddy: the channel gate refused the sparse perturbation too; retrying once with a dense word-boundary perturbation");
4446
+ result = await client.chatStream(credential, dense, controller.signal);
4447
+ retriedWithDensePerturbation = true;
4448
+ if (result.ok && channelPerturbation === "failure") channelNeedsPerturbation = true;
4449
+ }
4450
+ }
4451
4451
  }
4452
4452
  const triedAccountIds = [];
4453
4453
  /**
@@ -4564,6 +4564,7 @@ function createWorkBuddyShim(options) {
4564
4564
  const retries = [];
4565
4565
  if (retriedWithForcedHead) retries.push("强制中性头");
4566
4566
  if (retriedWithPerturbation) retries.push("一次不可见扰动");
4567
+ if (retriedWithDensePerturbation) retries.push("一次密集不可见扰动");
4567
4568
  const retryNote = retries.length === 0 ? "" : `,并已用${retries.join("与")}重试过`;
4568
4569
  const member = channelGateMemberOf(result.message, requestEvidenceOf(prepared));
4569
4570
  const said = detail?.rawMsg ?? detail?.displayMsg;
@@ -9695,7 +9696,12 @@ const Config = z.object({
9695
9696
  enabledModelIds: z.array(z.string()).default([]).description("Deprecated: pre-region-split CN selection"),
9696
9697
  imageModelIds: z.array(z.string()).default([]).description("Deprecated: pre-region-split CN image opt-in"),
9697
9698
  contextBudgets: z.dict(z.number().step(1).min(1)).default({}).description("Deprecated: pre-region-split CN context budgets"),
9698
- cliBridge: asVolatile(cliBridgeConfig.default({}).description("Optional CodeBuddy CLI route; off by default and independent of the providers"))
9699
+ cliBridge: asVolatile(cliBridgeConfig.default({}).description("Optional CodeBuddy CLI route; off by default and independent of the providers")),
9700
+ channelPerturbation: asVolatile(z.union([
9701
+ z.const("off"),
9702
+ z.const("failure"),
9703
+ z.const("always")
9704
+ ]).default("failure").description("When to perturb scanned messages with invisible characters: \"off\" only rescues after a channel-gate refusal, \"failure\" (default) learns from a successful rescue and starts later requests perturbed, \"always\" starts every request with the dense rescue for a gateway whose plain attempt is guaranteed to fail."))
9699
9705
  });
9700
9706
  /**
9701
9707
  * One region's saved model state. A config written before the region split has
@@ -9855,6 +9861,7 @@ function apply(ctx, config) {
9855
9861
  client,
9856
9862
  catalog,
9857
9863
  logger: ctx.logger,
9864
+ channelPerturbation: unwrapVolatile(config.channelPerturbation) ?? "failure",
9858
9865
  /**
9859
9866
  * Pool routing, bound to THIS region.
9860
9867
  *
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.6.3",
5
+ "version": "5.7.0",
6
6
  "author": "dingminhua",
7
7
  "copyright": "Copyright (c) 2026 LaoDing",
8
8
  "license": "MIT",