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 +26 -0
- package/lib/bin.js +1 -1
- package/lib/{host-heartbeat-r_icwyw3.js → host-heartbeat-CVz2J0Xt.js} +34 -4
- package/lib/index.d.ts +19 -0
- package/lib/index.js +23 -16
- package/package.json +1 -1
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-
|
|
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
|
|
2857
|
-
return
|
|
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.
|
|
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-
|
|
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
|
-
|
|
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.
|
|
5
|
+
"version": "5.7.0",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|