dsh-connect-workbuddy 5.6.3 → 5.8.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 +74 -0
- package/README.en.md +4 -1
- package/README.md +4 -1
- package/docs/WINDOWS.md +6 -0
- package/lib/bin.js +58 -3
- package/lib/client.js +800 -142
- package/lib/{host-heartbeat-r_icwyw3.js → host-heartbeat-BiVFMXGE.js} +626 -27
- package/lib/index.d.ts +297 -4
- package/lib/index.js +110 -26
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
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-BiVFMXGE.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";
|
|
@@ -3280,6 +3280,7 @@ const KIND_STATUS = {
|
|
|
3280
3280
|
model_busy: 429,
|
|
3281
3281
|
session_dead: 401,
|
|
3282
3282
|
policy_reject: 403,
|
|
3283
|
+
account_restricted: 403,
|
|
3283
3284
|
channel_reject: 400,
|
|
3284
3285
|
model_queue: 429,
|
|
3285
3286
|
not_found: 502,
|
|
@@ -3955,6 +3956,7 @@ function failoverReasonText(failure) {
|
|
|
3955
3956
|
case "model_busy": return "该模型当前繁忙(不是账号问题)";
|
|
3956
3957
|
case "session_dead": return "登录已失效";
|
|
3957
3958
|
case "policy_reject": return "被服务端策略拒绝";
|
|
3959
|
+
case "account_restricted": return "账号被上游限制使用";
|
|
3958
3960
|
case "channel_reject": return "被网关的渠道校验拦截(不是账号、额度或网络问题)";
|
|
3959
3961
|
case "not_found": return "模型不可用";
|
|
3960
3962
|
case "server": return "上游异常";
|
|
@@ -4020,7 +4022,8 @@ function channelEvidenceText(bodyJson) {
|
|
|
4020
4022
|
const shown = entries.slice(0, CHANNEL_EVIDENCE_LIMIT);
|
|
4021
4023
|
const rendered = shown.map((entry) => {
|
|
4022
4024
|
const label = entry.neutralized ? "(插件已加中性头)" : "(不在插件已知名单里,网关可能新增了词条)";
|
|
4023
|
-
|
|
4025
|
+
const span = entry.closing === void 0 ? `,共 ${entry.textLength} 字` : `,首尾为「${entry.closing}」,共 ${entry.textLength} 字`;
|
|
4026
|
+
return `「${entry.role}:${entry.opening}${span}」${label}`;
|
|
4024
4027
|
}).join("、");
|
|
4025
4028
|
const hidden = entries.length - shown.length;
|
|
4026
4029
|
return `请求里被网关扫描的消息开头:${rendered}${hidden > 0 ? `……另有 ${hidden} 条未列出。` : "。"}`;
|
|
@@ -4217,6 +4220,15 @@ function createWorkBuddyShim(options) {
|
|
|
4217
4220
|
const onAccountFailure = options.onAccountFailure;
|
|
4218
4221
|
const onRequestBody = options.onRequestBody;
|
|
4219
4222
|
const onUpstreamUsage = options.onUpstreamUsage;
|
|
4223
|
+
const channelPerturbation = options.channelPerturbation ?? "failure";
|
|
4224
|
+
/**
|
|
4225
|
+
* Whether the harness's injected `user` blocks are moved above the real prompt.
|
|
4226
|
+
*
|
|
4227
|
+
* OFF by default (issue #132): the reorder changes the message ORDER the
|
|
4228
|
+
* upstream counts, so it must be the user's deliberate choice rather than a
|
|
4229
|
+
* silent difference after an upgrade.
|
|
4230
|
+
*/
|
|
4231
|
+
const keepPromptLastEnabled = options.keepPromptLast === true;
|
|
4220
4232
|
const SHARED_SECRET = randomBytes(32).toString("base64url");
|
|
4221
4233
|
/**
|
|
4222
4234
|
* Whether this process's prompts have been PROVEN to need the invisible
|
|
@@ -4370,7 +4382,7 @@ function createWorkBuddyShim(options) {
|
|
|
4370
4382
|
return;
|
|
4371
4383
|
}
|
|
4372
4384
|
const raw = (await readBody(req)).toString("utf8");
|
|
4373
|
-
const prepared = prepareChatBody(raw);
|
|
4385
|
+
const prepared = prepareChatBody(raw, { keepPromptLast: keepPromptLastEnabled });
|
|
4374
4386
|
const requestModelId = modelIdOfBody(prepared);
|
|
4375
4387
|
if (onRequestBody !== void 0) try {
|
|
4376
4388
|
onRequestBody(prepared);
|
|
@@ -4379,19 +4391,7 @@ function createWorkBuddyShim(options) {
|
|
|
4379
4391
|
}
|
|
4380
4392
|
const controller = new AbortController();
|
|
4381
4393
|
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;
|
|
4394
|
+
const preferred = channelPerturbation === "always" || channelNeedsPerturbation ? perturbChannelFingerprints(prepared, channelPerturbation === "always") ?? prepared : prepared;
|
|
4395
4395
|
let result = await client.chatStream(credential, preferred, controller.signal);
|
|
4396
4396
|
if (channelNeedsPerturbation && !result.ok && result.kind === "channel_reject") {
|
|
4397
4397
|
channelNeedsPerturbation = false;
|
|
@@ -4432,6 +4432,7 @@ function createWorkBuddyShim(options) {
|
|
|
4432
4432
|
*/
|
|
4433
4433
|
let retriedWithForcedHead = false;
|
|
4434
4434
|
let retriedWithPerturbation = false;
|
|
4435
|
+
let retriedWithDensePerturbation = false;
|
|
4435
4436
|
if (!result.ok && result.kind === "channel_reject" && !controller.signal.aborted) {
|
|
4436
4437
|
const forced = forceNeutralHead(prepared);
|
|
4437
4438
|
if (forced !== void 0) {
|
|
@@ -4448,6 +4449,15 @@ function createWorkBuddyShim(options) {
|
|
|
4448
4449
|
if (result.ok) channelNeedsPerturbation = true;
|
|
4449
4450
|
}
|
|
4450
4451
|
}
|
|
4452
|
+
if (!result.ok && result.kind === "channel_reject" && !controller.signal.aborted) {
|
|
4453
|
+
const dense = perturbChannelFingerprints(forced ?? prepared, true);
|
|
4454
|
+
if (dense !== void 0) {
|
|
4455
|
+
logger?.warn("dsh-connect-workbuddy: the channel gate refused the sparse perturbation too; retrying once with a dense word-boundary perturbation");
|
|
4456
|
+
result = await client.chatStream(credential, dense, controller.signal);
|
|
4457
|
+
retriedWithDensePerturbation = true;
|
|
4458
|
+
if (result.ok && channelPerturbation === "failure") channelNeedsPerturbation = true;
|
|
4459
|
+
}
|
|
4460
|
+
}
|
|
4451
4461
|
}
|
|
4452
4462
|
const triedAccountIds = [];
|
|
4453
4463
|
/**
|
|
@@ -4534,13 +4544,22 @@ function createWorkBuddyShim(options) {
|
|
|
4534
4544
|
writeOpenAIError(res, 429, "model_queue", `${queueNoticeText(queue)}(上游 code ${queue.code})${note}`, void 0, NO_RETRY_HEADERS);
|
|
4535
4545
|
return;
|
|
4536
4546
|
}
|
|
4547
|
+
if (result.kind === "account_restricted") {
|
|
4548
|
+
const detail = result.detail;
|
|
4549
|
+
const meta = ["账号被上游限制使用"];
|
|
4550
|
+
if (detail?.upstreamCode !== void 0) meta.push(`code ${detail.upstreamCode}`);
|
|
4551
|
+
if (detail?.requestId !== void 0) meta.push(`requestId ${detail.requestId}`);
|
|
4552
|
+
const message = `${detail?.displayMsg ?? clippedUpstreamText(result.message, 200)}(${meta.join(",")})${note}${why}。该账号已被 WorkBuddy 上游按账号限制,与本次请求内容、网络和额度无关;插件会记录该测量并改用其他可用账号。该限制是上游对该账号的判定:重新登录不改变插件认的这个账号,需要 WorkBuddy 官方渠道解除后,在卡片上手动「测试」该账号才会回池`;
|
|
4553
|
+
writeOpenAIError(res, KIND_STATUS[result.kind], result.kind, message, hostErrorCode(result.kind));
|
|
4554
|
+
return;
|
|
4555
|
+
}
|
|
4537
4556
|
if (result.kind === "policy_reject") {
|
|
4538
4557
|
const detail = result.detail;
|
|
4539
4558
|
const lead = detail?.displayMsg ?? clippedUpstreamText(result.message, 200);
|
|
4540
4559
|
const meta = ["服务端策略拒绝"];
|
|
4541
4560
|
if (detail?.upstreamCode !== void 0) meta.push(`code ${detail.upstreamCode}`);
|
|
4542
4561
|
if (detail?.requestId !== void 0) meta.push(`requestId ${detail.requestId}`);
|
|
4543
|
-
const message = `${lead}(${meta.join(",")})${note}。该请求被 WorkBuddy
|
|
4562
|
+
const message = `${lead}(${meta.join(",")})${note}。该请求被 WorkBuddy 服务端策略拒绝;若上游的说明指向内容,重新登录不会解决,可在 WorkBuddy 桌面端用同一账号验证;若它指向账号本身,则需要走官方渠道处理,插件会在遇到该措辞时另行标明`;
|
|
4544
4563
|
writeOpenAIError(res, KIND_STATUS[result.kind], result.kind, message, hostErrorCode(result.kind));
|
|
4545
4564
|
return;
|
|
4546
4565
|
}
|
|
@@ -4564,6 +4583,7 @@ function createWorkBuddyShim(options) {
|
|
|
4564
4583
|
const retries = [];
|
|
4565
4584
|
if (retriedWithForcedHead) retries.push("强制中性头");
|
|
4566
4585
|
if (retriedWithPerturbation) retries.push("一次不可见扰动");
|
|
4586
|
+
if (retriedWithDensePerturbation) retries.push("一次密集不可见扰动");
|
|
4567
4587
|
const retryNote = retries.length === 0 ? "" : `,并已用${retries.join("与")}重试过`;
|
|
4568
4588
|
const member = channelGateMemberOf(result.message, requestEvidenceOf(prepared));
|
|
4569
4589
|
const said = detail?.rawMsg ?? detail?.displayMsg;
|
|
@@ -7059,7 +7079,9 @@ function cooldownOf(input) {
|
|
|
7059
7079
|
*/
|
|
7060
7080
|
function outcomeOfFailure(status, body) {
|
|
7061
7081
|
if (status === 401 || status === 403) {
|
|
7062
|
-
|
|
7082
|
+
const kind = classifyUpstreamError(status, body);
|
|
7083
|
+
if (kind === "policy_reject") return "policy-rejected";
|
|
7084
|
+
if (kind === "account_restricted") return "account-restricted";
|
|
7063
7085
|
return "credential-rejected";
|
|
7064
7086
|
}
|
|
7065
7087
|
switch (classifyUpstreamError(status, body)) {
|
|
@@ -7067,6 +7089,7 @@ function outcomeOfFailure(status, body) {
|
|
|
7067
7089
|
case "model_busy": return "model-busy";
|
|
7068
7090
|
case "hard_credit": return "out-of-credit";
|
|
7069
7091
|
case "session_dead": return "credential-rejected";
|
|
7092
|
+
case "account_restricted": return "account-restricted";
|
|
7070
7093
|
case "channel_reject": return "channel-rejected";
|
|
7071
7094
|
case "not_found": return "not-found";
|
|
7072
7095
|
default: return status === 0 || status >= 500 ? "unavailable" : "failed";
|
|
@@ -7607,6 +7630,7 @@ function exclusionOf(probe, nowMs) {
|
|
|
7607
7630
|
switch (probe.outcome) {
|
|
7608
7631
|
case "ok": return;
|
|
7609
7632
|
case "credential-rejected": return "credential-rejected";
|
|
7633
|
+
case "account-restricted": return "account-restricted";
|
|
7610
7634
|
case "not-found": return "not-found";
|
|
7611
7635
|
case "unavailable": return retryDue(probe, nowMs) ? void 0 : "unavailable";
|
|
7612
7636
|
case "failed": return retryDue(probe, nowMs) ? void 0 : "failed";
|
|
@@ -8648,7 +8672,8 @@ function toWebAccount(account) {
|
|
|
8648
8672
|
domain: account.domain,
|
|
8649
8673
|
source: account.source,
|
|
8650
8674
|
tokenExpiresAtMs: account.tokenExpiresAtMs,
|
|
8651
|
-
selected: account.selected
|
|
8675
|
+
selected: account.selected,
|
|
8676
|
+
...account.enterprise === true ? { enterprise: true } : {}
|
|
8652
8677
|
};
|
|
8653
8678
|
}
|
|
8654
8679
|
/**
|
|
@@ -8758,6 +8783,9 @@ async function workBuddyWebStatus(deps, region, options = {}) {
|
|
|
8758
8783
|
const creditPosition = deps.creditPosition?.(region) ?? "composer";
|
|
8759
8784
|
const imageToolEnabled = deps.imageToolEnabled?.(region) ?? false;
|
|
8760
8785
|
const cliBridgeEnabled = deps.cliBridgeEnabled?.() === true;
|
|
8786
|
+
const cliBridgeModel = deps.cliBridgeModel?.() ?? "";
|
|
8787
|
+
const cliBridgeEffort = deps.cliBridgeEffort?.() ?? "";
|
|
8788
|
+
const cliBridgeRegionOrder = deps.cliBridgeRegionOrder?.() ?? "";
|
|
8761
8789
|
const busyLevels = deps.busyLevels?.(region) ?? {};
|
|
8762
8790
|
const authStatus = await store.status();
|
|
8763
8791
|
const selectionExplicit = store.hasExplicitSelection();
|
|
@@ -8769,6 +8797,9 @@ async function workBuddyWebStatus(deps, region, options = {}) {
|
|
|
8769
8797
|
showCreditsInMainUi,
|
|
8770
8798
|
imageToolEnabled,
|
|
8771
8799
|
cliBridgeEnabled,
|
|
8800
|
+
cliBridgeModel,
|
|
8801
|
+
cliBridgeEffort,
|
|
8802
|
+
cliBridgeRegionOrder,
|
|
8772
8803
|
busyLevels
|
|
8773
8804
|
};
|
|
8774
8805
|
let credential;
|
|
@@ -8787,6 +8818,9 @@ async function workBuddyWebStatus(deps, region, options = {}) {
|
|
|
8787
8818
|
creditPosition,
|
|
8788
8819
|
imageToolEnabled,
|
|
8789
8820
|
cliBridgeEnabled,
|
|
8821
|
+
cliBridgeModel,
|
|
8822
|
+
cliBridgeEffort,
|
|
8823
|
+
cliBridgeRegionOrder,
|
|
8790
8824
|
busyLevels,
|
|
8791
8825
|
...await store.selectionLost() ? { selectionLost: true } : {},
|
|
8792
8826
|
...webAccounts.length === 0 ? await searchedPaths(store) : {}
|
|
@@ -8795,7 +8829,7 @@ async function workBuddyWebStatus(deps, region, options = {}) {
|
|
|
8795
8829
|
const selected = accounts.find((account) => account.selected);
|
|
8796
8830
|
const account = {
|
|
8797
8831
|
accountId: selected?.id ?? "",
|
|
8798
|
-
accountName: credential
|
|
8832
|
+
accountName: preferredAccountName(credential),
|
|
8799
8833
|
...credential.domain === "" ? {} : { domain: credential.domain },
|
|
8800
8834
|
region,
|
|
8801
8835
|
source: credential.source,
|
|
@@ -8806,6 +8840,9 @@ async function workBuddyWebStatus(deps, region, options = {}) {
|
|
|
8806
8840
|
creditPosition,
|
|
8807
8841
|
imageToolEnabled,
|
|
8808
8842
|
cliBridgeEnabled,
|
|
8843
|
+
cliBridgeModel,
|
|
8844
|
+
cliBridgeEffort,
|
|
8845
|
+
cliBridgeRegionOrder,
|
|
8809
8846
|
busyLevels,
|
|
8810
8847
|
accounts: accounts.map(toWebAccount),
|
|
8811
8848
|
models: deps.displayModels(region).map((model) => toWebModel(model, deps.contextBudgets(region))).map((model) => model.reasoning === void 0 || lastRefusalFor(model.id) === void 0 ? model : {
|
|
@@ -8820,6 +8857,7 @@ async function workBuddyWebStatus(deps, region, options = {}) {
|
|
|
8820
8857
|
offModelIds: [...deps.offModelIds(region)]
|
|
8821
8858
|
};
|
|
8822
8859
|
const figures = fast ? {} : await liveFigures(deps, region, credential, selected);
|
|
8860
|
+
const rosterSeen = deps.rosterSeen === void 0 ? void 0 : await deps.rosterSeen(region);
|
|
8823
8861
|
return {
|
|
8824
8862
|
status: "signed-in",
|
|
8825
8863
|
contextBudgets: Object.fromEntries(Object.entries(deps.contextBudgets(region)).filter(([, value]) => typeof value === "number")),
|
|
@@ -8829,6 +8867,10 @@ async function workBuddyWebStatus(deps, region, options = {}) {
|
|
|
8829
8867
|
authFile: volatileFlagOf("authFile")
|
|
8830
8868
|
},
|
|
8831
8869
|
...account,
|
|
8870
|
+
...rosterSeen === void 0 ? {} : {
|
|
8871
|
+
newModelIds: [...rosterSeen.newModelIds],
|
|
8872
|
+
...rosterSeen.versionNote === void 0 ? {} : { rosterSeenVersionNote: rosterSeen.versionNote }
|
|
8873
|
+
},
|
|
8832
8874
|
...figures,
|
|
8833
8875
|
...await poolSectionOf(deps, region, fast)
|
|
8834
8876
|
};
|
|
@@ -8901,6 +8943,7 @@ async function workBuddyWebPool(deps, region, fast = false) {
|
|
|
8901
8943
|
return {
|
|
8902
8944
|
accountId: account.id,
|
|
8903
8945
|
accountName: account.accountName,
|
|
8946
|
+
...account.enterprise === true ? { enterprise: true } : {},
|
|
8904
8947
|
...measured?.credits === void 0 ? {} : {
|
|
8905
8948
|
credits: measured.credits.total,
|
|
8906
8949
|
expiringSoon: measured.credits.expiringSoon,
|
|
@@ -9083,6 +9126,7 @@ function registerWorkBuddyStatusRoute(ctx, deps) {
|
|
|
9083
9126
|
try {
|
|
9084
9127
|
const credential = await deps.store(region).credentialFor(accountId);
|
|
9085
9128
|
if (credential === void 0) return json(res, 404, { error: "no credential for this account" });
|
|
9129
|
+
if (credential.enterpriseId !== void 0 && credential.enterpriseId !== "") return json(res, 409, { error: "enterprise-account-unsupported" });
|
|
9086
9130
|
const target = (await deps.client.fetchBenefits(credential)).find((benefit) => benefit.kind === kind);
|
|
9087
9131
|
if (target === void 0) return json(res, 404, { error: "benefit not available on this account" });
|
|
9088
9132
|
if (target.claimed) return json(res, 409, {
|
|
@@ -9695,7 +9739,14 @@ const Config = z.object({
|
|
|
9695
9739
|
enabledModelIds: z.array(z.string()).default([]).description("Deprecated: pre-region-split CN selection"),
|
|
9696
9740
|
imageModelIds: z.array(z.string()).default([]).description("Deprecated: pre-region-split CN image opt-in"),
|
|
9697
9741
|
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"))
|
|
9742
|
+
cliBridge: asVolatile(cliBridgeConfig.default({}).description("Optional CodeBuddy CLI route; off by default and independent of the providers")),
|
|
9743
|
+
channelPerturbation: asVolatile(z.union([
|
|
9744
|
+
z.const("off"),
|
|
9745
|
+
z.const("failure"),
|
|
9746
|
+
z.const("always")
|
|
9747
|
+
]).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.")),
|
|
9748
|
+
usageIdentity: asVolatile(z.boolean().default(false).description("Make the upstream's usage page record these calls like the official client's: add the X-IDE-* identity headers (which fill the 使用端 column) and send the conversation ids as 32-hex UUIDv7. Off by default — it is a display alignment measured by a reporter, not by this plugin, and the chat channel identity (User-Agent) and request body never change.")),
|
|
9749
|
+
keepPromptLast: asVolatile(z.boolean().default(false).description("Move the harness-injected runtime context above the real prompt, so the WorkBuddy usage page records what you actually typed instead of the injected block. Message count and text are preserved; the one edit is the recorded prompt's own leading <acp> marker. Off by default because it changes the message order the upstream counts."))
|
|
9699
9750
|
});
|
|
9700
9751
|
/**
|
|
9701
9752
|
* One region's saved model state. A config written before the region split has
|
|
@@ -9798,7 +9849,7 @@ async function legacyAttributionRegion(value, accountsFor) {
|
|
|
9798
9849
|
function apply(ctx, config) {
|
|
9799
9850
|
const client = new WorkBuddyUpstreamClient((message) => {
|
|
9800
9851
|
ctx.logger.warn(`dsh-connect-workbuddy: ${message}`);
|
|
9801
|
-
});
|
|
9852
|
+
}, { usageIdentity: unwrapVolatile(config.usageIdentity) === true });
|
|
9802
9853
|
/**
|
|
9803
9854
|
* How long a check-in reading is reused before asking the upstream again.
|
|
9804
9855
|
*
|
|
@@ -9836,6 +9887,7 @@ function apply(ctx, config) {
|
|
|
9836
9887
|
refresh: (credential) => client.refreshToken(credential)
|
|
9837
9888
|
});
|
|
9838
9889
|
const catalog = new WorkBuddyCatalog(region);
|
|
9890
|
+
const rosterSeen = createFileRosterSeenStore(region);
|
|
9839
9891
|
/**
|
|
9840
9892
|
* Where the next upstream `usage` should be written.
|
|
9841
9893
|
*
|
|
@@ -9855,6 +9907,8 @@ function apply(ctx, config) {
|
|
|
9855
9907
|
client,
|
|
9856
9908
|
catalog,
|
|
9857
9909
|
logger: ctx.logger,
|
|
9910
|
+
channelPerturbation: unwrapVolatile(config.channelPerturbation) ?? "failure",
|
|
9911
|
+
keepPromptLast: unwrapVolatile(config.keepPromptLast) === true,
|
|
9858
9912
|
/**
|
|
9859
9913
|
* Pool routing, bound to THIS region.
|
|
9860
9914
|
*
|
|
@@ -9887,7 +9941,8 @@ function apply(ctx, config) {
|
|
|
9887
9941
|
onAccountFailure: (accountId, failure) => {
|
|
9888
9942
|
recordAccountFailure(region, accountId, failure);
|
|
9889
9943
|
}
|
|
9890
|
-
})
|
|
9944
|
+
}),
|
|
9945
|
+
rosterSeen
|
|
9891
9946
|
};
|
|
9892
9947
|
}
|
|
9893
9948
|
const accountUsabilityProbe = createAccountUsabilityProbe({
|
|
@@ -10303,6 +10358,15 @@ function apply(ctx, config) {
|
|
|
10303
10358
|
offModelIds: (region) => savedOffModelIds(regionStateOf(current(), region), displayModels(current(), region)),
|
|
10304
10359
|
contextBudgets: (region) => regionStateOf(current(), region).contextBudgets ?? {},
|
|
10305
10360
|
discoverModels,
|
|
10361
|
+
/**
|
|
10362
|
+
* This region's first-sighting readout, the data behind the composer pill's
|
|
10363
|
+
* "· N 新模型" segment.
|
|
10364
|
+
*
|
|
10365
|
+
* READ-ONLY here: only the reconciler's `note` writes first-sighting
|
|
10366
|
+
* history, so no amount of polling by the pill or the card can change who
|
|
10367
|
+
* counts as new — the one property that keeps the marker honest.
|
|
10368
|
+
*/
|
|
10369
|
+
rosterSeen: (region) => stacks[region].rosterSeen.readout(),
|
|
10306
10370
|
regionEnabled: (region) => regionEnabled(current(), region),
|
|
10307
10371
|
/**
|
|
10308
10372
|
* The region's committed sidebar-credit flag, read from the same config the
|
|
@@ -10328,6 +10392,16 @@ function apply(ctx, config) {
|
|
|
10328
10392
|
*/
|
|
10329
10393
|
cliBridgeEnabled: () => unwrapVolatile(current().cliBridge)?.enabled === true,
|
|
10330
10394
|
/**
|
|
10395
|
+
* The model / reasoning effort the optional CodeBuddy CLI route runs on.
|
|
10396
|
+
*
|
|
10397
|
+
* `cliBridge` is volatile, hence the unwrap; absent keys read as `''` —
|
|
10398
|
+
* "the CLI's own default" — because an empty pin is a COMMITTED value the
|
|
10399
|
+
* card's pickers can render, not a missing one.
|
|
10400
|
+
*/
|
|
10401
|
+
cliBridgeModel: () => unwrapVolatile(current().cliBridge)?.model ?? "",
|
|
10402
|
+
cliBridgeEffort: () => unwrapVolatile(current().cliBridge)?.effort ?? "",
|
|
10403
|
+
cliBridgeRegionOrder: () => unwrapVolatile(current().cliBridge)?.regionOrder ?? "",
|
|
10404
|
+
/**
|
|
10331
10405
|
* WHERE the readout sits, resolved here rather than read from the browser
|
|
10332
10406
|
* settings mirror, for the same measured reason as the flag above: on the
|
|
10333
10407
|
* affected 0.1.7 deployments that mirror never picks up this plugin's
|
|
@@ -10491,7 +10565,8 @@ function apply(ctx, config) {
|
|
|
10491
10565
|
const wanted = new Set(poolPreferencesOf(current(), region).memberAccountIds);
|
|
10492
10566
|
return (await stacks[region].store.accounts()).filter((account) => !wanted.has(account.id)).map((account) => ({
|
|
10493
10567
|
id: account.id,
|
|
10494
|
-
accountName: account.accountName
|
|
10568
|
+
accountName: account.accountName,
|
|
10569
|
+
...account.enterprise === true ? { enterprise: true } : {}
|
|
10495
10570
|
}));
|
|
10496
10571
|
},
|
|
10497
10572
|
/**
|
|
@@ -10564,6 +10639,7 @@ function apply(ctx, config) {
|
|
|
10564
10639
|
if (cached !== void 0 && now - cached.atMs < 18e5) return [account.id, cached.views];
|
|
10565
10640
|
const credential = await store.credentialFor(account.id).catch(() => void 0);
|
|
10566
10641
|
if (credential === void 0) return void 0;
|
|
10642
|
+
if (credential.enterpriseId !== void 0 && credential.enterpriseId !== "") return [account.id, []];
|
|
10567
10643
|
const read = await client.fetchBenefits(credential).catch(() => void 0);
|
|
10568
10644
|
if (read === void 0) return void 0;
|
|
10569
10645
|
const views = claimableBenefits(read, now);
|
|
@@ -10875,6 +10951,7 @@ function apply(ctx, config) {
|
|
|
10875
10951
|
case "out-of-credit": return "账号积分耗尽";
|
|
10876
10952
|
case "model-busy": return "账号撞上模型繁忙";
|
|
10877
10953
|
case "credential-rejected": return "账号登录已失效";
|
|
10954
|
+
case "account-restricted": return "账号被上游限制使用";
|
|
10878
10955
|
case "not-found": return "账号已不存在";
|
|
10879
10956
|
case "unusable": return "账号不可用";
|
|
10880
10957
|
default: return "账号不可用";
|
|
@@ -10914,7 +10991,8 @@ function apply(ctx, config) {
|
|
|
10914
10991
|
return {
|
|
10915
10992
|
account: {
|
|
10916
10993
|
id: account.id,
|
|
10917
|
-
accountName: account.accountName
|
|
10994
|
+
accountName: account.accountName,
|
|
10995
|
+
...account.enterprise === true ? { enterprise: true } : {}
|
|
10918
10996
|
},
|
|
10919
10997
|
...reading === void 0 ? {} : { credits: reading },
|
|
10920
10998
|
...probes[account.id] === void 0 ? {} : { probe: probes[account.id] },
|
|
@@ -11054,7 +11132,8 @@ function apply(ctx, config) {
|
|
|
11054
11132
|
return {
|
|
11055
11133
|
account: {
|
|
11056
11134
|
id: account.id,
|
|
11057
|
-
accountName: account.accountName
|
|
11135
|
+
accountName: account.accountName,
|
|
11136
|
+
...account.enterprise === true ? { enterprise: true } : {}
|
|
11058
11137
|
},
|
|
11059
11138
|
...credits === void 0 ? {} : { credits: {
|
|
11060
11139
|
total: credits.total,
|
|
@@ -11272,6 +11351,11 @@ function apply(ctx, config) {
|
|
|
11272
11351
|
const state = regionStateOf(current(), region);
|
|
11273
11352
|
stacks[region].catalog.set(withEffectiveOff(withImageSelection(deriveCatalog(models, new Set(state.enabledModelIds ?? []), state.contextBudgets ?? {}), new Set(state.imageModelIds ?? [])), new Set(savedOffModelIds(state, models))));
|
|
11274
11353
|
adapters[region].invalidate();
|
|
11354
|
+
try {
|
|
11355
|
+
await stacks[region].rosterSeen.note(models.map((model) => model.id));
|
|
11356
|
+
} catch (error) {
|
|
11357
|
+
ctx.logger.warn(`dsh-connect-workbuddy: ${region} first-sighting history not updated`, error);
|
|
11358
|
+
}
|
|
11275
11359
|
await regionsFieldHealed;
|
|
11276
11360
|
await repairRegionCatalog(region, models);
|
|
11277
11361
|
} catch (error) {
|
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.8.0",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|