dsh-connect-workbuddy 3.4.2 → 3.4.4

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/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as readAtRestKey, A as CREDENTIAL_REJECTED_CODE, B as prepareChatBody, C as defaultDesktopAuthPath, D as parseWorkBuddyAuth, E as legacyWorkbuddyOwnAuthPath, F as declaredTools, G as deriveAtRestKeyId, H as WORKBUDDY_APP_EXECUTABLE_ENV, I as isCredentialRejectedError, J as isEncryptedFieldWrapper, K as fetchAtRestKeyPayload, L as parseCreditMultiplier, M as WorkBuddyCredentialRejectedError, N as WorkBuddyUpstreamClient, O as workbuddyAccountId, P as classifyUpstreamError, Q as openEncryptedField, R as parseReasoning, S as defaultDesktopAuthDirs, T as isEncryptedCredentialError, U as clearAtRestKeyCache, V as regionOf, W as deriveAtRestKey, X as macosBundleExecutable, Y as isWorkbuddyBundle, Z as macosNestedAppBundles, _ as WORKBUDDY_AUTH_FILE_ENV, a as readHostHeartbeat, b as authFileName, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, et as workbuddyAppExecutableCandidates, f as WorkBuddyCatalog, g as WORKBUDDY_AUTH_FILENAME, h as ENCRYPTED_CREDENTIAL_CODE, i as processStartTimeMs, j as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, k as workbuddyOwnAuthPath, l as piAiRuntimeInfo, m as fallbackModelsFor, n as clearHostHeartbeat, o as workbuddyHostHeartbeatPath, p as deriveCatalog, q as findWorkbuddyAppExecutable, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, u as FALLBACK_WORKBUDDY_MODELS, v as WorkBuddyCredentialStore, w as hasEncryptedCredentialFields, x as defaultDesktopAuthCandidates, y as WorkBuddyEncryptedCredentialError, z as parseUpstreamModel } from "./host-heartbeat-Sq5JwluI.js";
1
+ import { $ as openEncryptedField, A as CREDENTIAL_REJECTED_CODE, B as prepareChatBody, C as defaultDesktopAuthPath, D as parseWorkBuddyAuth, E as legacyWorkbuddyOwnAuthPath, F as declaredTools, G as deriveAtRestKeyId, H as WORKBUDDY_APP_EXECUTABLE_ENV, I as isCredentialRejectedError, K as fetchAtRestKeyPayload, L as parseCreditMultiplier, M as WorkBuddyCredentialRejectedError, N as WorkBuddyUpstreamClient, O as workbuddyAccountId, P as classifyUpstreamError, Q as macosNestedAppBundles, R as parseReasoning, S as defaultDesktopAuthDirs, T as isEncryptedCredentialError, U as clearAtRestKeyCache, V as regionOf, W as deriveAtRestKey, X as isWorkbuddyBundle, Y as isEncryptedFieldWrapper, Z as macosBundleExecutable, _ as WORKBUDDY_AUTH_FILE_ENV, a as readHostHeartbeat, b as authFileName, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, et as readAtRestKey, f as WorkBuddyCatalog, g as WORKBUDDY_AUTH_FILENAME, h as ENCRYPTED_CREDENTIAL_CODE, i as processStartTimeMs, j as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, k as workbuddyOwnAuthPath, l as piAiRuntimeInfo, m as fallbackModelsFor, n as clearHostHeartbeat, o as workbuddyHostHeartbeatPath, p as deriveCatalog, q as findWorkbuddyAppExecutable, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, tt as workbuddyAppExecutableCandidates, u as FALLBACK_WORKBUDDY_MODELS, v as WorkBuddyCredentialStore, w as hasEncryptedCredentialFields, x as defaultDesktopAuthCandidates, y as WorkBuddyEncryptedCredentialError, z as parseUpstreamModel } from "./host-heartbeat-DTuwptkM.js";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import { readFile } from "node:fs/promises";
4
4
  import { randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
@@ -1180,6 +1180,7 @@ const KIND_STATUS = {
1180
1180
  hard_credit: 402,
1181
1181
  soft_rate: 429,
1182
1182
  session_dead: 401,
1183
+ policy_reject: 403,
1183
1184
  not_found: 502,
1184
1185
  server: 502,
1185
1186
  client: 400
@@ -1212,6 +1213,16 @@ const KIND_STATUS = {
1212
1213
  * Every other kind already lands outside the retry set by its status alone
1213
1214
  * (`hard_credit` 402 → quota, `session_dead` 401 → auth, 502/400 → other), so
1214
1215
  * none of them is listed: a translation nobody needs is a lie waiting to drift.
1216
+ *
1217
+ * `policy_reject` is deliberately NOT listed either, for a sharper reason than
1218
+ * the others: the obvious candidate translation would be the upstream's own
1219
+ * numeric code (11140), but that is not a class the host knows, so it would take
1220
+ * the host's generic path and add nothing over `policy_reject`. It needs no
1221
+ * translation to stay outside the retry set (403), and it must NOT be translated
1222
+ * INTO the set — a content-policy refusal is deterministic, so retrying it in
1223
+ * place would spend the retry budget on a request the server will refuse again,
1224
+ * and cross-provider failover cannot help a policy decision either. The code the
1225
+ * host receives therefore names the class honestly.
1215
1226
  */
1216
1227
  const KIND_HOST_CODE = { soft_rate: "quota_exceeded" };
1217
1228
  /**
@@ -1331,10 +1342,49 @@ var RecoveryStream = class {
1331
1342
  */
1332
1343
  nativeCallsSeen = false;
1333
1344
  buffer;
1334
- constructor(res, gate, logger) {
1345
+ /**
1346
+ * A notice to place at the head of the answer, once, before any prose.
1347
+ *
1348
+ * Set when the request reached this stream only after the pool failed over, so
1349
+ * the user learns WHY the answer came from a different account. Without it a
1350
+ * failover is invisible: the reply looks like it came from the account they
1351
+ * picked, and the only trace is a `logger.warn` they never see. The notice is
1352
+ * emitted ahead of the model's own text so it cannot be mistaken for part of
1353
+ * the answer, and only once — a retry that never delivers text must not leave
1354
+ * a stray line behind.
1355
+ */
1356
+ notice;
1357
+ constructor(res, gate, logger, notice) {
1335
1358
  this.res = res;
1336
1359
  this.logger = logger;
1337
1360
  this.buffer = gate === void 0 ? void 0 : new DsmlStreamBuffer(gate);
1361
+ this.notice = notice;
1362
+ }
1363
+ /**
1364
+ * Emit the pending failover notice, if any, and forget it.
1365
+ *
1366
+ * Called immediately before the first content reaches the client. Placed
1367
+ * here rather than in the constructor because a stream whose write fails, or
1368
+ * whose attempt is discarded in favour of a retry, must not have already told
1369
+ * the user about a switch it never completed.
1370
+ */
1371
+ flushNotice() {
1372
+ const notice = this.notice;
1373
+ if (notice === void 0) return;
1374
+ this.notice = void 0;
1375
+ if (!this.res.writable) return;
1376
+ const chunk = { choices: [{
1377
+ index: 0,
1378
+ delta: {},
1379
+ finish_reason: ""
1380
+ }] };
1381
+ const choiceRecord = {
1382
+ index: 0,
1383
+ delta: {},
1384
+ finish_reason: ""
1385
+ };
1386
+ this.windowOpened = true;
1387
+ this.res.write(contentFrame(chunk, choiceRecord, notice));
1338
1388
  }
1339
1389
  /** True when a call or real prose has reached the client. */
1340
1390
  get delivered() {
@@ -1433,6 +1483,7 @@ var RecoveryStream = class {
1433
1483
  if (Array.isArray(nativeCalls) && nativeCalls.length > 0) {
1434
1484
  this.nativeCallsSeen = true;
1435
1485
  this.windowOpened = true;
1486
+ this.flushNotice();
1436
1487
  this.send(frame, false);
1437
1488
  this.wroteContent = true;
1438
1489
  this.closeWindow();
@@ -1447,6 +1498,7 @@ var RecoveryStream = class {
1447
1498
  }
1448
1499
  if (this.buffer === void 0 || this.nativeCallsSeen) {
1449
1500
  this.windowOpened = true;
1501
+ this.flushNotice();
1450
1502
  this.closeWindow();
1451
1503
  this.res.write(frame);
1452
1504
  this.wroteContent = true;
@@ -1455,6 +1507,7 @@ var RecoveryStream = class {
1455
1507
  this.windowOpened = true;
1456
1508
  const outcome = this.buffer.add(content);
1457
1509
  if (outcome.calls !== void 0 && outcome.calls.length > 0) {
1510
+ this.flushNotice();
1458
1511
  this.send(callsFrame(chunk, choiceRecord, outcome.calls), false);
1459
1512
  this.wroteContent = true;
1460
1513
  this.closeWindow();
@@ -1467,6 +1520,7 @@ var RecoveryStream = class {
1467
1520
  return;
1468
1521
  }
1469
1522
  if (outcome.prose) {
1523
+ this.flushNotice();
1470
1524
  this.send(contentFrame(chunk, choiceRecord, outcome.text), false);
1471
1525
  this.wroteContent = true;
1472
1526
  this.closeWindow();
@@ -1510,6 +1564,7 @@ var RecoveryStream = class {
1510
1564
  this.windowOpened = true;
1511
1565
  const frame = contentFrame(chunk, choiceRecord, text);
1512
1566
  if (text.trim() !== "") {
1567
+ this.flushNotice();
1513
1568
  this.send(frame, false);
1514
1569
  this.wroteContent = true;
1515
1570
  this.closeWindow();
@@ -1587,6 +1642,55 @@ function callsFrame(chunk, choice, calls) {
1587
1642
  };
1588
1643
  return `data: ${JSON.stringify(next)}\n\n`;
1589
1644
  }
1645
+ /**
1646
+ * How to name a pool account in a sentence the USER reads.
1647
+ *
1648
+ * Prefers the phone number (`uin`) because that is what the pool table shows and
1649
+ * what a person recognises; the nickname and the opaque account id are fallbacks
1650
+ * so this can never render an empty name. Deliberately NOT the hashed account id
1651
+ * on its own: a 24-character hex digest identifies an account to the plugin, but
1652
+ * to the person reading the answer it identifies nothing.
1653
+ */
1654
+ function describeAccount(credential) {
1655
+ if (credential.uin !== void 0 && credential.uin !== "") return credential.uin;
1656
+ if (credential.nickname !== void 0 && credential.nickname !== "") return credential.nickname;
1657
+ return workbuddyAccountId(credential);
1658
+ }
1659
+ /**
1660
+ * One short phrase for why an account was abandoned, for the in-reply notice.
1661
+ *
1662
+ * Kept to the distinction the user can act on rather than the internal kind
1663
+ * name: "rate limited" says wait or add an account, "rejected" says the sign-in
1664
+ * is the problem. The numeric kind would only send them to the log.
1665
+ */
1666
+ function failoverReasonText(failure) {
1667
+ if (failure.ok) return "unusable";
1668
+ switch (failure.kind) {
1669
+ case "soft_rate":
1670
+ case "hard_credit": return "rate limited";
1671
+ case "session_dead": return "signed out";
1672
+ case "policy_reject": return "rejected by policy";
1673
+ case "not_found": return "model unavailable";
1674
+ case "server": return "erroring upstream";
1675
+ default: return `unusable (http ${failure.status})`;
1676
+ }
1677
+ }
1678
+ /**
1679
+ * The line prepended to an answer that came from a failover account.
1680
+ *
1681
+ * Block-quoted and bracketed so it reads as machinery rather than as the model's
1682
+ * own words, and so a user skimming for the answer can skip it. It states the
1683
+ * reason AND the account left behind, because those are the two facts that make
1684
+ * an unexpected reply explicable.
1685
+ *
1686
+ * The wording avoids "switched TO <account>" on purpose: the account that took
1687
+ * over is the one the user is about to keep talking to, and naming it invites
1688
+ * the reading that they must now manage it. What they need is to know the one
1689
+ * they chose did not answer, and why.
1690
+ */
1691
+ function failoverNoticeText(from, reason) {
1692
+ return `\n\n> [dsh-connect-workbuddy] 「${from}」was ${reason} — this answer came from another account\n\n`;
1693
+ }
1590
1694
  /** One frame carrying replacement text for a frame whose content was consumed. */
1591
1695
  function contentFrame(chunk, choice, content) {
1592
1696
  const delta = {
@@ -1715,6 +1819,15 @@ function createWorkBuddyShim(options) {
1715
1819
  let result = await client.chatStream(credential, prepared, controller.signal);
1716
1820
  const triedAccountIds = [];
1717
1821
  /**
1822
+ * The account the request STARTED on, and the reason it was abandoned.
1823
+ *
1824
+ * `??=` on the first switch only: if three accounts fail in a row the user
1825
+ * cares that the answer did not come from the one they picked, not with a
1826
+ * blow-by-blow list of every candidate that was also rate limited.
1827
+ */
1828
+ let switchedFrom;
1829
+ let switchedBecause;
1830
+ /**
1718
1831
  * Tell the plugin about a failed attempt so it can store the measurement.
1719
1832
  *
1720
1833
  * Every failed attempt is reported exactly once, INCLUDING the last one — an
@@ -1769,6 +1882,8 @@ function createWorkBuddyShim(options) {
1769
1882
  });
1770
1883
  if (controller.signal.aborted) break;
1771
1884
  logger?.warn(`dsh-connect-workbuddy: retrying chat on another pool account after ${result.kind} (http ${result.status})`);
1885
+ switchedFrom ??= describeAccount(credential);
1886
+ switchedBecause ??= failoverReasonText(result);
1772
1887
  credential = next;
1773
1888
  result = await client.chatStream(credential, prepared, controller.signal);
1774
1889
  }
@@ -1777,6 +1892,16 @@ function createWorkBuddyShim(options) {
1777
1892
  if (!result.ok) {
1778
1893
  const attempts = triedAccountIds.length;
1779
1894
  const note = attempts > 1 ? ` (after trying ${attempts} accounts)` : "";
1895
+ if (result.kind === "policy_reject") {
1896
+ const detail = result.detail;
1897
+ const lead = detail?.displayMsg ?? result.message.slice(0, 200);
1898
+ const meta = ["服务端策略拒绝"];
1899
+ if (detail?.upstreamCode !== void 0) meta.push(`code ${detail.upstreamCode}`);
1900
+ if (detail?.requestId !== void 0) meta.push(`requestId ${detail.requestId}`);
1901
+ const message = `${lead}(${meta.join(",")})${note}。该请求被 WorkBuddy 服务端策略拒绝,重新登录不会解决;可在 WorkBuddy 桌面端用同一账号验证,或切换区域/账号后重试`;
1902
+ writeOpenAIError(res, KIND_STATUS[result.kind], result.kind, message, hostErrorCode(result.kind));
1903
+ return;
1904
+ }
1780
1905
  writeOpenAIError(res, KIND_STATUS[result.kind], result.kind, `workbuddy upstream ${result.kind} (http ${result.status})${note}: ${result.message.slice(0, 400)}`, hostErrorCode(result.kind));
1781
1906
  return;
1782
1907
  }
@@ -1792,7 +1917,8 @@ function createWorkBuddyShim(options) {
1792
1917
  requiredParameters: declared.requiredParameters,
1793
1918
  ...declared.pinnedToolName === void 0 ? {} : { pinnedToolName: declared.pinnedToolName }
1794
1919
  };
1795
- const writer = new RecoveryStream(res, gate, logger);
1920
+ const notice = switchedFrom === void 0 || switchedBecause === void 0 ? void 0 : failoverNoticeText(switchedFrom, switchedBecause);
1921
+ const writer = new RecoveryStream(res, gate, logger, notice);
1796
1922
  await writer.consume(result.response.body);
1797
1923
  if (gate !== void 0 && !writer.delivered && writer.residueSeen && !controller.signal.aborted) {
1798
1924
  logger?.warn("dsh-connect-workbuddy: the turn produced DSML markup only; retrying it once");
@@ -1847,7 +1973,7 @@ const PROBE_SYSTEM_PROMPT = "You are a connectivity check. Reply with a single w
1847
1973
  * Output cap for a probe. One token is enough to prove the model answers.
1848
1974
  *
1849
1975
  * The probe's cost comes from its INPUT, not its output (see
1850
- * {@link PROBE_INPUT_TOKENS}), so paying for a long completion would add money
1976
+ * {@link DEFAULT_PROBE_INPUT_TOKENS}), so paying for a long completion would add money
1851
1977
  * without adding signal.
1852
1978
  */
1853
1979
  const PROBE_MAX_TOKENS = 1;
@@ -1874,8 +2000,44 @@ const PROBE_MAX_TOKENS = 1;
1874
2000
  * A probe that sent a handful of tokens answered "usable" while every real
1875
2001
  * request in a long conversation was refused — the probe was asking too small a
1876
2002
  * question. 25k is sized to be a realistic long-conversation payload.
2003
+ *
2004
+ * Renamed from `PROBE_INPUT_TOKENS` to say what it now IS: the DEFAULT, not the
2005
+ * only possible size. The size is a property of the user's own traffic (see
2006
+ * {@link probeRequestBody}), so it became a setting; this constant is what an
2007
+ * unset setting, an older profile, and every existing test mean by "a probe".
2008
+ */
2009
+ const DEFAULT_PROBE_INPUT_TOKENS = 25e3;
2010
+ /**
2011
+ * The probe sizes the card offers, in tokens.
2012
+ *
2013
+ * A closed set, each step a meaningful position relative to the measured 20k~30k
2014
+ * threshold: below it (10k/20k) proves an account can serve SHORT requests and
2015
+ * is the cheap choice; at it (30k) is the size that finally covers the whole
2016
+ * measured window; above it (50k/100k) is for conversations that routinely run
2017
+ * long, where a smaller probe would report "usable" for an account that refuses
2018
+ * the very next message.
2019
+ *
2020
+ * Exported so the schema's description, the card's dropdown, and the coercion
2021
+ * below cannot drift apart into three different menus.
1877
2022
  */
1878
- const PROBE_INPUT_TOKENS = 25e3;
2023
+ const PROBE_INPUT_TOKEN_CHOICES = [
2024
+ 1e4,
2025
+ 2e4,
2026
+ 3e4,
2027
+ 5e4,
2028
+ 1e5
2029
+ ];
2030
+ /**
2031
+ * A requested probe size reduced to a size this build will actually send.
2032
+ *
2033
+ * Anything outside {@link PROBE_INPUT_TOKEN_CHOICES} — `undefined` from an older
2034
+ * profile, `0` from an unset field, a hand-edited odd number — becomes the
2035
+ * measured default. Coercing rather than rejecting keeps a hand-edited profile
2036
+ * working instead of turning one odd value into a failed test batch.
2037
+ */
2038
+ function resolveProbeInputTokens(requested) {
2039
+ return requested !== void 0 && PROBE_INPUT_TOKEN_CHOICES.includes(requested) ? requested : DEFAULT_PROBE_INPUT_TOKENS;
2040
+ }
1879
2041
  /**
1880
2042
  * Characters per token for the filler text, measured.
1881
2043
  *
@@ -1894,11 +2056,14 @@ const FILLER_LINE = "The quick brown fox jumps over the lazy dog. ";
1894
2056
  * `prepareChatBody` enforces that too, but sending it explicitly keeps this
1895
2057
  * function's output valid on its own.
1896
2058
  *
1897
- * Measured on the built artifact: 112,713 bytes of body, and the upstream
1898
- * counted `prompt_tokens: 25023` at a cost of `credit: 0.72`.
2059
+ * Measured on the built artifact at the DEFAULT size: 112,713 bytes of body, and
2060
+ * the upstream counted `prompt_tokens: 25023` at a cost of `credit: 0.72`. The
2061
+ * body bytes and the credit both scale with the size asked for, which is why the
2062
+ * size is a user setting: a larger probe answers more truthfully for someone
2063
+ * whose conversations are long, and costs proportionally more to ask.
1899
2064
  */
1900
- function probeRequestBody(modelId) {
1901
- const targetChars = Math.ceil(PROBE_INPUT_TOKENS * CHARS_PER_TOKEN);
2065
+ function probeRequestBody(modelId, inputTokens = DEFAULT_PROBE_INPUT_TOKENS) {
2066
+ const targetChars = Math.ceil(resolveProbeInputTokens(inputTokens) * CHARS_PER_TOKEN);
1902
2067
  const filler = FILLER_LINE.repeat(Math.ceil(targetChars / 45));
1903
2068
  return JSON.stringify({
1904
2069
  model: modelId,
@@ -2026,12 +2191,16 @@ function cooldownOf(input) {
2026
2191
  *
2027
2192
  * Built on `classifyUpstreamError` rather than re-testing status codes, so a
2028
2193
  * probe and a real chat request can never disagree about what the same upstream
2029
- * answer means. The extra split here is `credential-rejected`, which the
2194
+ * answer means. The extra splits here are `credential-rejected`, which the
2030
2195
  * upstream signals with a non-JSON 401/403 edge page and which needs completely
2031
- * different advice (re-auth, not "wait").
2196
+ * different advice (re-auth, not "wait"), and `policy-rejected`, a JSON 403
2197
+ * content-policy refusal where re-auth advice would be actively misleading.
2032
2198
  */
2033
2199
  function outcomeOfFailure(status, body) {
2034
- if (status === 401 || status === 403) return "credential-rejected";
2200
+ if (status === 401 || status === 403) {
2201
+ if (classifyUpstreamError(status, body) === "policy_reject") return "policy-rejected";
2202
+ return "credential-rejected";
2203
+ }
2035
2204
  switch (classifyUpstreamError(status, body)) {
2036
2205
  case "soft_rate": return "rate-limited";
2037
2206
  case "hard_credit": return "out-of-credit";
@@ -2092,7 +2261,7 @@ async function probeModel(input) {
2092
2261
  let answer;
2093
2262
  const startedAt = Date.now();
2094
2263
  try {
2095
- answer = await input.client.probeChat(input.credential, probeRequestBody(input.modelId), input.signal);
2264
+ answer = await input.client.probeChat(input.credential, probeRequestBody(input.modelId, input.inputTokens), input.signal);
2096
2265
  } catch (error) {
2097
2266
  return {
2098
2267
  modelId: input.modelId,
@@ -2285,6 +2454,7 @@ function probeUpdatesOf(rows) {
2285
2454
  updates[row.accountId] = {
2286
2455
  outcome: row.result.outcome,
2287
2456
  atMs,
2457
+ source: "test-batch",
2288
2458
  ...row.result.retryAtMs === void 0 ? {} : { retryAtMs: row.result.retryAtMs },
2289
2459
  ...row.result.message === void 0 ? {} : { message: row.result.message }
2290
2460
  };
@@ -2362,6 +2532,7 @@ function exclusionOf(probe, nowMs) {
2362
2532
  case "failed": return retryDue(probe, nowMs) ? void 0 : "failed";
2363
2533
  case "rate-limited": return retryDue(probe, nowMs) ? void 0 : "rate-limited";
2364
2534
  case "out-of-credit": return retryDue(probe, nowMs) ? void 0 : "out-of-credit";
2535
+ case "policy-rejected": return;
2365
2536
  default: return "unusable";
2366
2537
  }
2367
2538
  }
@@ -2548,7 +2719,7 @@ const POOL_STORE_PREFIX = ".workbuddy-pool";
2548
2719
  function isProbe(value) {
2549
2720
  if (typeof value !== "object" || value === null) return false;
2550
2721
  const candidate = value;
2551
- return typeof candidate["outcome"] === "string" && typeof candidate["atMs"] === "number" && (candidate["retryAtMs"] === void 0 || typeof candidate["retryAtMs"] === "number") && (candidate["message"] === void 0 || typeof candidate["message"] === "string");
2722
+ return typeof candidate["outcome"] === "string" && typeof candidate["atMs"] === "number" && (candidate["retryAtMs"] === void 0 || typeof candidate["retryAtMs"] === "number") && (candidate["message"] === void 0 || typeof candidate["message"] === "string") && (candidate["source"] === void 0 || typeof candidate["source"] === "string");
2552
2723
  }
2553
2724
  /**
2554
2725
  * One region's path for measured pool facts.
@@ -2595,6 +2766,7 @@ async function readPoolProbes(region) {
2595
2766
  probes[accountId] = {
2596
2767
  outcome: probe.outcome,
2597
2768
  atMs: probe.atMs,
2769
+ ...knownProbeSource(probe.source) === void 0 ? {} : { source: knownProbeSource(probe.source) },
2598
2770
  ...probe.retryAtMs === void 0 ? {} : { retryAtMs: probe.retryAtMs },
2599
2771
  ...probe.message === void 0 ? {} : { message: probe.message }
2600
2772
  };
@@ -2602,6 +2774,17 @@ async function readPoolProbes(region) {
2602
2774
  return probes;
2603
2775
  }
2604
2776
  /**
2777
+ * A stored source string reduced to a source this build understands.
2778
+ *
2779
+ * `isProbe` accepts any string so a newer build's label cannot destroy the
2780
+ * measurement it describes; this narrows it at the point of use. An unknown
2781
+ * value becomes `undefined`, which the card renders as "unknown" — the honest
2782
+ * answer, rather than mislabelling it as one of the two kinds we do know.
2783
+ */
2784
+ function knownProbeSource(value) {
2785
+ return value === "test-batch" || value === "live-request" ? value : void 0;
2786
+ }
2787
+ /**
2605
2788
  * Merge measurements into one region's store and write it atomically.
2606
2789
  *
2607
2790
  * A MERGE rather than a replace, because batches measure one account at a time
@@ -3101,6 +3284,7 @@ async function workBuddyWebPool(deps, region) {
3101
3284
  ...measured?.probe === void 0 ? {} : { probe: {
3102
3285
  outcome: measured.probe.outcome,
3103
3286
  atMs: measured.probe.atMs,
3287
+ ...measured.probe.source === void 0 ? {} : { source: measured.probe.source },
3104
3288
  ...measured.probe.retryAtMs === void 0 ? {} : { retryAtMs: measured.probe.retryAtMs },
3105
3289
  ...measured.probe.message === void 0 ? {} : { message: measured.probe.message }
3106
3290
  } },
@@ -3117,6 +3301,7 @@ async function workBuddyWebPool(deps, region) {
3117
3301
  ...target.staleModelId === void 0 ? {} : { staleTargetModelId: target.staleModelId },
3118
3302
  targetModelSource: target.source,
3119
3303
  memberAccountIds: preferences.memberAccountIds,
3304
+ ...preferences.probeInputTokens === void 0 ? {} : { probeInputTokens: preferences.probeInputTokens },
3120
3305
  effectiveMemberAccountIds: effectiveMembersOf(accounts.map((account) => account.accountId), new Set(preferences.memberAccountIds)),
3121
3306
  accounts,
3122
3307
  catalog: deps.displayModels(region).map((model) => ({
@@ -3496,6 +3681,16 @@ const modelConfig = z.object({
3496
3681
  const poolConfig = z.object({
3497
3682
  enabled: z.boolean().default(false).description("Whether this region's account pool is active (opt-in). While on, a failed chat request is retried against the other usable pool members before the error is reported."),
3498
3683
  targetModelId: z.string().default("").description("Model id to test; empty means pick a zero-multiplier model from this region's catalog"),
3684
+ /**
3685
+ * Sizing of the test probe, as a token count the user picks from a fixed set.
3686
+ *
3687
+ * A closed set rather than a free number: each step is a meaningful position
3688
+ * relative to the measured 20k~30k threshold, and an arbitrary value would
3689
+ * invite a size that is neither safely under nor clearly over it. `0` (and any
3690
+ * unknown value) falls back to the measured default, so a profile written by
3691
+ * an older build — and one hand-edited to something odd — behaves identically.
3692
+ */
3693
+ probeInputTokens: z.number().step(1).default(0).description("Input tokens each test probe sends: 10000 / 20000 / 30000 / 50000 / 100000. Bigger probes catch a rate limit real conversations would hit, but cost more credit per test and may mark an account unusable for large requests only. 0 uses the measured default (25000)"),
3499
3694
  memberAccountIds: z.array(z.string()).default([]).description("Account ids checked into this pool (opt-in; empty means the pool covers nothing)")
3500
3695
  });
3501
3696
  const regionStateConfig = z.object({
@@ -4112,6 +4307,7 @@ function apply(ctx, config) {
4112
4307
  await writePoolProbes(region, { [accountId]: {
4113
4308
  outcome,
4114
4309
  atMs: Date.now(),
4310
+ source: "live-request",
4115
4311
  ...retryAtMs === void 0 ? {} : { retryAtMs },
4116
4312
  ...message === "" ? {} : { message }
4117
4313
  } });
@@ -4202,9 +4398,11 @@ function apply(ctx, config) {
4202
4398
  /** One region's pool preferences, with the schema's defaults applied. */
4203
4399
  const poolPreferencesOf = (config, region) => {
4204
4400
  const pool = regionStateOf(config, region).pool;
4401
+ const probeInputTokens = pool?.probeInputTokens;
4205
4402
  return {
4206
4403
  enabled: pool?.enabled === true,
4207
4404
  targetModelId: pool?.targetModelId ?? "",
4405
+ ...probeInputTokens === void 0 ? {} : { probeInputTokens },
4208
4406
  memberAccountIds: pool?.memberAccountIds ?? []
4209
4407
  };
4210
4408
  };
@@ -4250,11 +4448,13 @@ function apply(ctx, config) {
4250
4448
  claimDailyCheckin: (credential) => client.claimDailyCheckin(credential),
4251
4449
  probe: async (credential, modelId) => {
4252
4450
  const quotaRefreshAtMs = await quotaRefreshOf(region, credential);
4451
+ const inputTokens = resolveProbeInputTokens(poolPreferencesOf(current(), region).probeInputTokens);
4253
4452
  return probeModel({
4254
4453
  client,
4255
4454
  credential,
4256
4455
  modelId,
4257
4456
  nowMs: Date.now(),
4457
+ inputTokens,
4258
4458
  ...quotaRefreshAtMs === void 0 ? {} : { quotaRefreshAtMs }
4259
4459
  });
4260
4460
  }
@@ -4376,4 +4576,4 @@ function apply(ctx, config) {
4376
4576
  });
4377
4577
  }
4378
4578
  //#endregion
4379
- export { CREDENTIAL_REJECTED_CODE, Config, ENCRYPTED_CREDENTIAL_CODE, FALLBACK_WORKBUDDY_MODELS, FALLBACK_WORKBUDDY_MODELS_GLOBAL, NATIVE_MODALITY_BY_MODEL_ID, PROBE_INPUT_TOKENS, PROBE_MAX_TOKENS, PROBE_SYSTEM_PROMPT, WORKBUDDY_ACCOUNTS_REFRESH_PATH, WORKBUDDY_APP_EXECUTABLE_ENV, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_CHECKIN_PATH, WORKBUDDY_CONNECT_VERSION, WORKBUDDY_GLOBAL_PROVIDER, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_MODELS_REFRESH_PATH, WORKBUDDY_PROBE_PATH, WORKBUDDY_PROVIDER, WORKBUDDY_PROVIDERS, WORKBUDDY_PROVIDER_DISPLAY_NAMES, WORKBUDDY_REGIONS, WORKBUDDY_REGION_PARAM, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, WORKBUDDY_USAGE_PATH, WorkBuddyCatalog, WorkBuddyCredentialRejectedError, WorkBuddyCredentialStore, WorkBuddyEncryptedCredentialError, WorkBuddyUpstreamClient, apply, asVolatile, authFileName, classifyUpstreamError, clearAtRestKeyCache, clearHostHeartbeat, cooldownOf, createAccountUsabilityProbe, createWorkBuddyAdapter, createWorkBuddyShim, creditOfStream, defaultDesktopAuthCandidates, defaultDesktopAuthDirs, defaultDesktopAuthPath, deriveAtRestKey, deriveAtRestKeyId, deriveCatalog, fallbackModelsFor, fetchAtRestKeyPayload, findWorkbuddyAppExecutable, hasEncryptedCredentialFields, imageDefaultFor, inject, isCredentialRejectedError, isEncryptedCredentialError, isEncryptedFieldWrapper, isHeartbeatProcessAlive, isWorkbuddyBundle, legacyAttributionRegion, legacyWorkbuddyOwnAuthPath, macosBundleExecutable, macosNestedAppBundles, name, nativeModalityOf, nextRegionEnabled, nextRegionSlots, openEncryptedField, outcomeOfFailure, parseCreditMultiplier, parseReasoning, parseRetryAfter, parseUpstreamModel, parseWorkBuddyAuth, prepareChatBody, probeModel, probeRequestBody, probeSucceeded, processStartTimeMs, readAtRestKey, readHostHeartbeat, regionCleared, regionEnabled, regionEnabledOf, regionOf, regionOfProvider, regionOfStatusUrl, regionStateOf, registerWorkBuddyStatusRoute, resolveCredentialRecovery, selectAccountFor, settingsNamespaceOf, toPersistedWorkBuddyModel, unwrapVolatileDeep, withWorkBuddyRegion, workBuddyDisplayName, workBuddyModelInput, workBuddyThinkingLevelMap, workBuddyWebStatus, workbuddyAccountId, workbuddyAppExecutableCandidates, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, writeHostHeartbeat };
4579
+ export { CREDENTIAL_REJECTED_CODE, Config, DEFAULT_PROBE_INPUT_TOKENS, ENCRYPTED_CREDENTIAL_CODE, FALLBACK_WORKBUDDY_MODELS, FALLBACK_WORKBUDDY_MODELS_GLOBAL, NATIVE_MODALITY_BY_MODEL_ID, PROBE_INPUT_TOKEN_CHOICES, PROBE_MAX_TOKENS, PROBE_SYSTEM_PROMPT, WORKBUDDY_ACCOUNTS_REFRESH_PATH, WORKBUDDY_APP_EXECUTABLE_ENV, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_CHECKIN_PATH, WORKBUDDY_CONNECT_VERSION, WORKBUDDY_GLOBAL_PROVIDER, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_MODELS_REFRESH_PATH, WORKBUDDY_PROBE_PATH, WORKBUDDY_PROVIDER, WORKBUDDY_PROVIDERS, WORKBUDDY_PROVIDER_DISPLAY_NAMES, WORKBUDDY_REGIONS, WORKBUDDY_REGION_PARAM, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, WORKBUDDY_USAGE_PATH, WorkBuddyCatalog, WorkBuddyCredentialRejectedError, WorkBuddyCredentialStore, WorkBuddyEncryptedCredentialError, WorkBuddyUpstreamClient, apply, asVolatile, authFileName, classifyUpstreamError, clearAtRestKeyCache, clearHostHeartbeat, cooldownOf, createAccountUsabilityProbe, createWorkBuddyAdapter, createWorkBuddyShim, creditOfStream, defaultDesktopAuthCandidates, defaultDesktopAuthDirs, defaultDesktopAuthPath, deriveAtRestKey, deriveAtRestKeyId, deriveCatalog, fallbackModelsFor, fetchAtRestKeyPayload, findWorkbuddyAppExecutable, hasEncryptedCredentialFields, imageDefaultFor, inject, isCredentialRejectedError, isEncryptedCredentialError, isEncryptedFieldWrapper, isHeartbeatProcessAlive, isWorkbuddyBundle, legacyAttributionRegion, legacyWorkbuddyOwnAuthPath, macosBundleExecutable, macosNestedAppBundles, name, nativeModalityOf, nextRegionEnabled, nextRegionSlots, openEncryptedField, outcomeOfFailure, parseCreditMultiplier, parseReasoning, parseRetryAfter, parseUpstreamModel, parseWorkBuddyAuth, prepareChatBody, probeModel, probeRequestBody, probeSucceeded, processStartTimeMs, readAtRestKey, readHostHeartbeat, regionCleared, regionEnabled, regionEnabledOf, regionOf, regionOfProvider, regionOfStatusUrl, regionStateOf, registerWorkBuddyStatusRoute, resolveCredentialRecovery, selectAccountFor, settingsNamespaceOf, toPersistedWorkBuddyModel, unwrapVolatileDeep, withWorkBuddyRegion, workBuddyDisplayName, workBuddyModelInput, workBuddyThinkingLevelMap, workBuddyWebStatus, workbuddyAccountId, workbuddyAppExecutableCandidates, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, writeHostHeartbeat };
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": "3.4.2",
5
+ "version": "3.4.4",
6
6
  "author": "dingminhua",
7
7
  "copyright": "Copyright (c) 2026 LaoDing",
8
8
  "license": "MIT",