dsh-connect-workbuddy 3.4.3 → 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/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.4.4 (2026-10-03)
4
+
5
+ ### Features
6
+
7
+ - **测试请求体积可选(1 万 / 2 万 / 3 万 / 5 万 / 10 万 token),不再固定 2.5 万。**
8
+ - **背景**:上游的频率限制(6004)按**请求体积**触发——实测阈值落在 2 万~3 万 token 之间,**单次请求就足以触顶**(不是累计)。而账号池的「测试」探针此前固定发 25,000 token,于是真实对话体积在 2.5 万~3 万之间时会出现「测试测出可用、一发聊天立刻被限」。
9
+ - **为什么不是把固定值调大**:探针只测一个点,而阈值是一个区间、真实对话体积是连续的——把 2.5 万改成 3 万只是把缝隙上移(3 万以上漏判),且成本线性上升(2.5 万 ≈ 0.72 积分,5 万 ≈ 1.4+)。**体积是用户流量的属性,因此交给用户选。**
10
+ - 新增池子设置「测试请求体积」,默认仍是实测标定的 25K(`0` = 默认);改动立即生效,无需重载。
11
+ - 相应用户可见的说明写在设置项旁:解释 2 万~3 万阈值与「测出可用、一发就被限」的成因。
12
+
13
+ - **换号时在回复正文里说明原因,不再只有日志。**
14
+ - **背景**:池子的 failover 会给同一个请求依次试多个账号,此前只写一行 `logger.warn`——用户完全看不到。于是一次切换过的回复,和「你选的那个账号正常回答」**在界面上完全一样**,而且事后无法从记录区分「刚才真有请求失败」和「状态莫名其妙退回了旧值」。
15
+ - 现在回答开头会附一行(仅当真的发生了切换、且确实有内容送达时):
16
+
17
+ > [dsh-connect-workbuddy] 「13046625578」was rate limited — this answer came from another account
18
+
19
+ - 只报**第一个**被放弃的账号与原因(连续试 4 个账号时不刷屏),原因按用户能采取的行动措辞(`rate limited` / `signed out` / `rejected by policy` / …),而不是内部 kind 名。
20
+
21
+ - **池子记录标注来源与时间,让「结果变了」可解释。**
22
+ - 每条测量现在记录它是**谁写的**:`测得`(用户点「测试」的批测)或 `聊天撞上`(实时聊天失败被记录),以及**多久前**更新。
23
+ - 卡片上分两行显示:主行是结果/恢复时间/原因,次行是来源胶囊 + 相对时间。此前只能靠时间戳分布**猜**来源(批测写同一时间戳、单账号写入时间戳各不同)。
24
+ - 「来源未知」用于本次升级之前写入、没有来源字段的旧记录——不猜,如实标注。
25
+
26
+ ### Fixes
27
+
28
+ - **「来源未知」胶囊太淡看不清。** 旧样式同时做了四处削弱(`background:transparent`、`1px dashed`、`label-dimmed #9aa0a6`、`font-weight:400`);实测对比度发现**字色不是主因**(`#9aa0a6` 5.28:1,反而比正常胶囊的 `#999` 4.89:1 更亮),主因是 10px 小字配 400 字重。已改为实底 + 实线边框 + `label-secondary`(8.41:1),并去掉字重覆盖以继承基类的 600。
29
+
3
30
  ## 3.4.3 (2026-10-03)
4
31
 
5
32
  ### Features
package/lib/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { E as legacyWorkbuddyOwnAuthPath, H as WORKBUDDY_APP_EXECUTABLE_ENV, J as findWorkbuddyAppExecutableWithSource, N as WorkBuddyUpstreamClient, S as defaultDesktopAuthDirs, _ as WORKBUDDY_AUTH_FILE_ENV, a as readHostHeartbeat, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, k as workbuddyOwnAuthPath, l as piAiRuntimeInfo, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS, v as WorkBuddyCredentialStore, x as defaultDesktopAuthCandidates } from "./host-heartbeat-D5ArLcFf.js";
2
+ import { E as legacyWorkbuddyOwnAuthPath, H as WORKBUDDY_APP_EXECUTABLE_ENV, J as findWorkbuddyAppExecutableWithSource, N as WorkBuddyUpstreamClient, S as defaultDesktopAuthDirs, _ as WORKBUDDY_AUTH_FILE_ENV, a as readHostHeartbeat, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, k as workbuddyOwnAuthPath, l as piAiRuntimeInfo, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS, v as WorkBuddyCredentialStore, x as defaultDesktopAuthCandidates } from "./host-heartbeat-DTuwptkM.js";
3
3
  import { realpathSync } from "node:fs";
4
4
  import { fileURLToPath } from "node:url";
5
5
  //#region src/bin.ts
package/lib/client.js CHANGED
@@ -513,11 +513,19 @@ window.__ModuleLoader__.load({
513
513
  * @throws {WorkBuddySettingsWriteError} when neither writer persists the value.
514
514
  */
515
515
  async function writePoolPreferences(scope, region, preferences) {
516
- const next = nextRegionPool(scope.getSnapshot().value, region, preferences)[region];
516
+ const slot = {
517
+ enabled: preferences.enabled,
518
+ targetModelId: preferences.targetModelId,
519
+ memberAccountIds: preferences.memberAccountIds
520
+ };
521
+ if (preferences.probeInputTokens !== void 0) slot["probeInputTokens"] = preferences.probeInputTokens;
522
+ const next = nextRegionPool(scope.getSnapshot().value, region, slot)[region];
517
523
  await writeField(scope, "regions", region, next, (readBack) => {
518
524
  const pool = readBack?.pool;
519
525
  if (pool === void 0 || pool === null) return false;
520
526
  if (pool["enabled"] !== preferences.enabled || pool["targetModelId"] !== preferences.targetModelId) return false;
527
+ const wantedTokens = preferences.probeInputTokens ?? 0;
528
+ if ((pool["probeInputTokens"] ?? 0) !== wantedTokens) return false;
521
529
  const stored = pool["memberAccountIds"];
522
530
  if (!Array.isArray(stored) || stored.length !== preferences.memberAccountIds.length) return false;
523
531
  const wanted = new Set(preferences.memberAccountIds);
@@ -619,6 +627,53 @@ window.__ModuleLoader__.load({
619
627
  return message.replace(/^transport error:\s*/u, "");
620
628
  }
621
629
  //#endregion
630
+ //#region src/client/probe-age.ts
631
+ const MINUTE_MS$1 = 6e4;
632
+ const HOUR_MS$1 = 60 * MINUTE_MS$1;
633
+ const DAY_MS$1 = 24 * HOUR_MS$1;
634
+ /**
635
+ * How long ago a measurement was taken, coarsely: "刚刚", "N 分钟前",
636
+ * "N 小时前", "N 天前".
637
+ *
638
+ * Coarse on purpose, for the same reason `remainingText` rounds: a figure that
639
+ * changes every second reads as live telemetry rather than as "how stale is
640
+ * this", which is the only question being asked here.
641
+ *
642
+ * A clock skew that puts the measurement in the FUTURE (a stored `atMs` ahead
643
+ * of `nowMs`, from a machine whose clock moved backwards) is reported as
644
+ * "刚刚" rather than as a negative age: the record is real, and the card must
645
+ * not print "-3 分钟前".
646
+ */
647
+ function probeAgeText(t, atMs, nowMs) {
648
+ const age = nowMs - atMs;
649
+ if (age < MINUTE_MS$1) return t("row.poolProbeJustNow");
650
+ if (age < HOUR_MS$1) return t("row.poolProbeMinutesAgo", { count: Math.round(age / MINUTE_MS$1) });
651
+ if (age < 2 * DAY_MS$1) return t("row.poolProbeHoursAgo", { count: Math.round(age / HOUR_MS$1) });
652
+ return t("row.poolProbeDaysAgo", { count: Math.round(age / DAY_MS$1) });
653
+ }
654
+ //#endregion
655
+ //#region src/probe.ts
656
+ /**
657
+ * The probe sizes the card offers, in tokens.
658
+ *
659
+ * A closed set, each step a meaningful position relative to the measured 20k~30k
660
+ * threshold: below it (10k/20k) proves an account can serve SHORT requests and
661
+ * is the cheap choice; at it (30k) is the size that finally covers the whole
662
+ * measured window; above it (50k/100k) is for conversations that routinely run
663
+ * long, where a smaller probe would report "usable" for an account that refuses
664
+ * the very next message.
665
+ *
666
+ * Exported so the schema's description, the card's dropdown, and the coercion
667
+ * below cannot drift apart into three different menus.
668
+ */
669
+ const PROBE_INPUT_TOKEN_CHOICES = [
670
+ 1e4,
671
+ 2e4,
672
+ 3e4,
673
+ 5e4,
674
+ 1e5
675
+ ];
676
+ //#endregion
622
677
  //#region src/client/remaining.ts
623
678
  const MINUTE_MS = 6e4;
624
679
  const HOUR_MS = 60 * MINUTE_MS;
@@ -989,10 +1044,11 @@ window.__ModuleLoader__.load({
989
1044
  const saved = {
990
1045
  enabled: pool?.enabled ?? false,
991
1046
  targetModelId: pool?.targetModelId || pool?.staleTargetModelId || "",
1047
+ probeInputTokens: pool?.probeInputTokens ?? 0,
992
1048
  memberAccountIds: pool?.memberAccountIds ?? []
993
1049
  };
994
1050
  const active = draftRegion === region && draft !== null ? draft : saved;
995
- const dirty = draftRegion === region && draft !== null && (draft.enabled !== saved.enabled || draft.targetModelId !== saved.targetModelId || !sameIds(draft.memberAccountIds, saved.memberAccountIds));
1051
+ const dirty = draftRegion === region && draft !== null && (draft.enabled !== saved.enabled || draft.targetModelId !== saved.targetModelId || draft.probeInputTokens !== saved.probeInputTokens || !sameIds(draft.memberAccountIds, saved.memberAccountIds));
996
1052
  /**
997
1053
  * Membership that actually resolves to a local sign-in — what a batch runs on.
998
1054
  *
@@ -1376,10 +1432,24 @@ window.__ModuleLoader__.load({
1376
1432
  const name = account.accountName === "" ? t("row.accountUnnamed") : account.accountName;
1377
1433
  const excluded = exclusionText(t, account);
1378
1434
  const probe = account.probe;
1435
+ const outcomeLine = probe === void 0 ? t("row.poolNeverTested") : probe.retryAtMs !== void 0 ? `${outcomeText(t, probe.outcome)} · ${t("row.poolRetryAt", { at: formatShort(probe.retryAtMs) })}` : probe.outcome === "rate-limited" || probe.outcome === "out-of-credit" ? `${outcomeText(t, probe.outcome)} · ${t("row.poolRetryUnknown")}` : outcomeText(t, probe.outcome);
1436
+ const remaining = probe?.retryAtMs === void 0 ? "" : remainingText(t, probe.retryAtMs, Date.now());
1437
+ const reason = probe === void 0 ? void 0 : inlineProbeReason(probe.outcome, probe.message);
1438
+ const age = probe === void 0 ? void 0 : probeAgeText(t, probe.atMs, Date.now());
1439
+ const probeLabel = probe === void 0 ? void 0 : probe.source === void 0 ? {
1440
+ text: t("row.poolProbeSourceUnknown"),
1441
+ className: "dsm-workbuddy-pool-prov-tag dsm-workbuddy-pool-prov-unknown"
1442
+ } : probe.source === "live-request" ? {
1443
+ text: t("row.poolProbeSourceLive"),
1444
+ className: "dsm-workbuddy-pool-prov-tag dsm-workbuddy-pool-prov-live"
1445
+ } : {
1446
+ text: t("row.poolProbeSourceTest"),
1447
+ className: "dsm-workbuddy-pool-prov-tag"
1448
+ };
1379
1449
  const probeLine = [
1380
- probe === void 0 ? t("row.poolNeverTested") : probe.retryAtMs !== void 0 ? `${outcomeText(t, probe.outcome)} · ${t("row.poolRetryAt", { at: formatShort(probe.retryAtMs) })}` : probe.outcome === "rate-limited" || probe.outcome === "out-of-credit" ? `${outcomeText(t, probe.outcome)} · ${t("row.poolRetryUnknown")}` : outcomeText(t, probe.outcome),
1381
- probe?.retryAtMs === void 0 ? "" : remainingText(t, probe.retryAtMs, Date.now()),
1382
- probe === void 0 ? void 0 : inlineProbeReason(probe.outcome, probe.message)
1450
+ outcomeLine,
1451
+ remaining,
1452
+ reason
1383
1453
  ].filter((part) => part !== void 0 && part !== "").join(" · ");
1384
1454
  return (0, react.createElement)("div", {
1385
1455
  className: `dsm-workbuddy-pool-row${account.current ? " dsm-workbuddy-pool-row-current" : ""}`,
@@ -1396,7 +1466,7 @@ window.__ModuleLoader__.load({
1396
1466
  }), (0, react.createElement)("span", { className: "dsm-workbuddy-pool-account-name" }, (0, react.createElement)("b", null, name), account.current ? (0, react.createElement)("span", { className: "dsm-workbuddy-pool-current-tag" }, t("row.poolCurrentBadge")) : null, (0, react.createElement)("span", { className: checked ? "dsm-workbuddy-pool-member" : "dsm-workbuddy-pool-not-member" }, checked ? t("row.poolMember") : t("row.poolNotMember")), excluded === void 0 ? probe === void 0 ? null : (0, react.createElement)("span", null, t("row.poolTestedAt", { at: formatShort(probe.atMs) })) : (0, react.createElement)("span", { className: "dsm-workbuddy-pool-excluded" }, excluded))), (0, react.createElement)("span", { className: "dsm-workbuddy-pool-credits" }, account.credits === void 0 ? (0, react.createElement)("span", null, "—") : (0, react.createElement)("b", null, formatNumber$1(account.credits)), account.expiringSoon !== void 0 && account.expiringSoon > 0 ? (0, react.createElement)("span", { className: "dsm-workbuddy-pool-soon" }, t("row.poolCreditSoon", { count: formatNumber$1(account.expiringSoon) })) : account.nearestExpiryMs === void 0 ? null : (0, react.createElement)("span", { className: "dsm-workbuddy-pool-soon-plain" }, t("row.poolCreditNearest", { at: formatShort(account.nearestExpiryMs) }))), (0, react.createElement)("span", {
1397
1467
  className: "dsm-workbuddy-pool-probe",
1398
1468
  ...probe?.message === void 0 ? {} : { title: probe.message }
1399
- }, probeLine), checkinSupported ? (0, react.createElement)("span", { className: "dsm-workbuddy-pool-checkin" }, account.checkedInToday === void 0 ? (0, react.createElement)("span", {
1469
+ }, (0, react.createElement)("span", { className: "dsm-workbuddy-pool-probe-main" }, probeLine), probeLabel === void 0 && age === void 0 ? null : (0, react.createElement)("span", { className: "dsm-workbuddy-pool-probe-prov" }, probeLabel === void 0 ? null : (0, react.createElement)("span", { className: probeLabel.className }, probeLabel.text), probeLabel === void 0 || age === void 0 ? null : (0, react.createElement)("span", { className: "dsm-workbuddy-pool-prov-sep" }, "·"), age === void 0 ? null : (0, react.createElement)("span", { className: "dsm-workbuddy-pool-prov-age" }, age))), checkinSupported ? (0, react.createElement)("span", { className: "dsm-workbuddy-pool-checkin" }, account.checkedInToday === void 0 ? (0, react.createElement)("span", {
1400
1470
  className: "dsm-workbuddy-pool-unknown",
1401
1471
  title: t("row.poolNeverTested")
1402
1472
  }, t("row.poolCheckinUnknown")) : account.checkedInToday ? (0, react.createElement)("span", { className: "dsm-workbuddy-pool-checked" }, t("row.poolCheckedIn")) : t("row.poolNotCheckedIn")) : null);
@@ -1431,7 +1501,22 @@ window.__ModuleLoader__.load({
1431
1501
  }, t("row.poolTargetStaleOption", { model: active.targetModelId })) : null, ...(pool.catalog ?? []).map((model) => (0, react.createElement)("option", {
1432
1502
  value: model.id,
1433
1503
  key: model.id
1434
- }, model.creditMultiplier === void 0 ? model.name : `${model.name} (x${model.creditMultiplier.toFixed(2)})`)))))), (0, react.createElement)("div", { className: "dsm-workbuddy-pool-save-bar" }, (0, react.createElement)("a", {
1504
+ }, model.creditMultiplier === void 0 ? model.name : `${model.name} (x${model.creditMultiplier.toFixed(2)})`))))), (0, react.createElement)("div", { className: "dsm-workbuddy-pool-set" }, (0, react.createElement)("span", { className: "dsm-workbuddy-pool-set-copy" }, (0, react.createElement)("b", null, t("row.poolProbeSizeLabel")), (0, react.createElement)("span", null, t("row.poolProbeSizeHint"))), (0, react.createElement)("span", { className: "dsm-workbuddy-pool-set-ctl" }, (0, react.createElement)("select", {
1505
+ className: "dsm-workbuddy-pool-select",
1506
+ value: String(active.probeInputTokens),
1507
+ disabled: !canEdit,
1508
+ onChange: (event) => {
1509
+ const parsed = Number.parseInt(event.currentTarget.value, 10);
1510
+ const next = Number.isFinite(parsed) ? parsed : 0;
1511
+ onEdit((current) => ({
1512
+ ...current,
1513
+ probeInputTokens: next
1514
+ }));
1515
+ }
1516
+ }, (0, react.createElement)("option", { value: "0" }, t("row.poolProbeSizeDefault")), ...PROBE_INPUT_TOKEN_CHOICES.map((tokens) => (0, react.createElement)("option", {
1517
+ value: String(tokens),
1518
+ key: String(tokens)
1519
+ }, t("row.poolProbeSizeOption", { tokens: String(tokens / 1e3) }))))))), (0, react.createElement)("div", { className: "dsm-workbuddy-pool-save-bar" }, (0, react.createElement)("a", {
1435
1520
  className: "dsm-workbuddy-usage-cheer",
1436
1521
  href: WORKBUDDY_GITHUB_URL,
1437
1522
  target: "_blank",
@@ -1705,6 +1790,14 @@ window.__ModuleLoader__.load({
1705
1790
  .dsm-workbuddy-pool-soon{font-size:11px;line-height:16px;color:var(--dsw-alias-state-warn-primary,#f59e0b)}
1706
1791
  .dsm-workbuddy-pool-soon-plain{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary,#999)}
1707
1792
  .dsm-workbuddy-pool-probe,.dsm-workbuddy-pool-checkin{font-size:12px;line-height:17px;color:var(--dsw-alias-label-tertiary,#999);min-width:0;overflow-wrap:anywhere}
1793
+ .dsm-workbuddy-pool-probe{display:flex;flex-direction:column;gap:4px}
1794
+ .dsm-workbuddy-pool-probe-main{color:var(--dsw-alias-label-secondary,#c6c9d0)}
1795
+ .dsm-workbuddy-pool-probe-prov{display:flex;align-items:center;gap:5px;font-size:11px;line-height:15px;color:var(--dsw-alias-label-tertiary,#999);min-width:0;flex-wrap:wrap}
1796
+ .dsm-workbuddy-pool-prov-tag{padding:1px 6px;border-radius:999px;background:var(--dsw-alias-bg-layer-3,#2a2c33);color:var(--dsw-alias-label-tertiary,#999);font-size:10px;font-weight:600;line-height:15px;white-space:nowrap}
1797
+ .dsm-workbuddy-pool-prov-live{background:rgba(245,158,11,.16);color:var(--dsw-alias-state-warn-primary,#f59e0b)}
1798
+ .dsm-workbuddy-pool-prov-unknown{background:var(--dsw-alias-bg-layer-3,#2a2c33);border:1px solid var(--dsw-alias-border-l1,#2c2d31);color:var(--dsw-alias-label-secondary,#c6c9d0)}
1799
+ .dsm-workbuddy-pool-prov-sep{color:var(--dsw-alias-label-dimmed,#9aa0a6);opacity:.7}
1800
+ .dsm-workbuddy-pool-prov-age{color:var(--dsw-alias-label-tertiary,#999);white-space:nowrap}
1708
1801
  .dsm-workbuddy-pool-members{display:flex;flex-direction:column;gap:10px}
1709
1802
  .dsm-workbuddy-pool-members-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap}
1710
1803
  .dsm-workbuddy-pool-members-title{margin:0 0 3px;font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e6e6)}
@@ -3077,6 +3170,13 @@ window.__ModuleLoader__.load({
3077
3170
  "row.remainingMinutes": "in about {count} min",
3078
3171
  "row.remainingHours": "in about {count} h",
3079
3172
  "row.remainingDays": "in about {count} d",
3173
+ "row.poolProbeJustNow": "just now",
3174
+ "row.poolProbeMinutesAgo": "{count} min ago",
3175
+ "row.poolProbeHoursAgo": "{count} h ago",
3176
+ "row.poolProbeDaysAgo": "{count} d ago",
3177
+ "row.poolProbeSourceTest": "tested",
3178
+ "row.poolProbeSourceLive": "hit by a live request",
3179
+ "row.poolProbeSourceUnknown": "source unknown",
3080
3180
  "row.poolSettingsTitle": "Account pool settings",
3081
3181
  "row.poolSettingsHint": "These are preferences: they take effect when you save (same as the model list). Test results are written automatically and are not affected by this button.",
3082
3182
  "row.poolEnabled": "Enable the account pool",
@@ -3087,6 +3187,10 @@ window.__ModuleLoader__.load({
3087
3187
  "row.poolTargetLabel": "Target test model",
3088
3188
  "row.poolTargetHint": "Auto (default) picks a zero-multiplier model from this region's catalog, preferring the largest context window. The two regions can pick different models. A manual choice always wins.",
3089
3189
  "row.poolTargetAutoOption": "Auto — pick a free model",
3190
+ "row.poolProbeSizeLabel": "Test request size",
3191
+ "row.poolProbeSizeHint": "How much text a test sends. The upstream refuses a request once it grows past roughly 20K–30K tokens, so a test smaller than your real conversations answers \"usable\" for an account that would refuse them. Bigger tests are more truthful but cost more credit each.",
3192
+ "row.poolProbeSizeDefault": "Default (25K)",
3193
+ "row.poolProbeSizeOption": "{tokens}K tokens",
3090
3194
  "row.poolSaved": "Save",
3091
3195
  "row.poolSaving": "Saving…",
3092
3196
  "row.poolSaveFailed": "Could not save: {message}",
@@ -3254,6 +3358,13 @@ window.__ModuleLoader__.load({
3254
3358
  "row.remainingMinutes": "约 {count} 分钟后",
3255
3359
  "row.remainingHours": "约 {count} 小时后",
3256
3360
  "row.remainingDays": "约 {count} 天后",
3361
+ "row.poolProbeJustNow": "刚刚",
3362
+ "row.poolProbeMinutesAgo": "{count} 分钟前",
3363
+ "row.poolProbeHoursAgo": "{count} 小时前",
3364
+ "row.poolProbeDaysAgo": "{count} 天前",
3365
+ "row.poolProbeSourceTest": "测得",
3366
+ "row.poolProbeSourceLive": "聊天撞上",
3367
+ "row.poolProbeSourceUnknown": "来源未知",
3257
3368
  "row.poolSettingsTitle": "账号池设置",
3258
3369
  "row.poolSettingsHint": "下面是偏好设置:点保存才生效(与「模型管理」一致)。测试结果由插件自动写入,不受这个保存按钮影响。",
3259
3370
  "row.poolEnabled": "启用账号池",
@@ -3264,6 +3375,10 @@ window.__ModuleLoader__.load({
3264
3375
  "row.poolTargetLabel": "目标测试模型",
3265
3376
  "row.poolTargetHint": "「自动」(默认)会从本区域目录里挑一个倍率为 0 的免费模型,多个候选取上下文窗口最大的。国内版与国际版可能挑到不同的模型。手动指定优先于自动。",
3266
3377
  "row.poolTargetAutoOption": "自动 —— 挑一个免费模型",
3378
+ "row.poolProbeSizeLabel": "测试请求体积",
3379
+ "row.poolProbeSizeHint": "测试时发送多少文本。上游在请求大约超过 2 万~3 万 token 时会开始拒绝,所以测试体积若小于你真实对话的体积,就会出现「测出可用、实际一发就被限」。体积越大测得越准,但每次测试消耗的积分也越多。",
3380
+ "row.poolProbeSizeDefault": "默认(2.5 万)",
3381
+ "row.poolProbeSizeOption": "{tokens} 万 token",
3267
3382
  "row.poolSaved": "保存",
3268
3383
  "row.poolSaving": "保存中…",
3269
3384
  "row.poolSaveFailed": "保存失败:{message}",
@@ -2791,7 +2791,7 @@ function generationOf(version) {
2791
2791
  //#endregion
2792
2792
  //#region src/version.ts
2793
2793
  /** The npm package version this build was produced from. */
2794
- const WORKBUDDY_CONNECT_VERSION = "3.4.3";
2794
+ const WORKBUDDY_CONNECT_VERSION = "3.4.4";
2795
2795
  //#endregion
2796
2796
  //#region src/host-heartbeat.ts
2797
2797
  /**
package/lib/index.d.ts CHANGED
@@ -1351,7 +1351,7 @@ declare const PROBE_SYSTEM_PROMPT = "You are a connectivity check. Reply with a
1351
1351
  * Output cap for a probe. One token is enough to prove the model answers.
1352
1352
  *
1353
1353
  * The probe's cost comes from its INPUT, not its output (see
1354
- * {@link PROBE_INPUT_TOKENS}), so paying for a long completion would add money
1354
+ * {@link DEFAULT_PROBE_INPUT_TOKENS}), so paying for a long completion would add money
1355
1355
  * without adding signal.
1356
1356
  */
1357
1357
  declare const PROBE_MAX_TOKENS = 1;
@@ -1378,8 +1378,27 @@ declare const PROBE_MAX_TOKENS = 1;
1378
1378
  * A probe that sent a handful of tokens answered "usable" while every real
1379
1379
  * request in a long conversation was refused — the probe was asking too small a
1380
1380
  * question. 25k is sized to be a realistic long-conversation payload.
1381
+ *
1382
+ * Renamed from `PROBE_INPUT_TOKENS` to say what it now IS: the DEFAULT, not the
1383
+ * only possible size. The size is a property of the user's own traffic (see
1384
+ * {@link probeRequestBody}), so it became a setting; this constant is what an
1385
+ * unset setting, an older profile, and every existing test mean by "a probe".
1386
+ */
1387
+ declare const DEFAULT_PROBE_INPUT_TOKENS = 25000;
1388
+ /**
1389
+ * The probe sizes the card offers, in tokens.
1390
+ *
1391
+ * A closed set, each step a meaningful position relative to the measured 20k~30k
1392
+ * threshold: below it (10k/20k) proves an account can serve SHORT requests and
1393
+ * is the cheap choice; at it (30k) is the size that finally covers the whole
1394
+ * measured window; above it (50k/100k) is for conversations that routinely run
1395
+ * long, where a smaller probe would report "usable" for an account that refuses
1396
+ * the very next message.
1397
+ *
1398
+ * Exported so the schema's description, the card's dropdown, and the coercion
1399
+ * below cannot drift apart into three different menus.
1381
1400
  */
1382
- declare const PROBE_INPUT_TOKENS = 25000;
1401
+ declare const PROBE_INPUT_TOKEN_CHOICES: readonly number[];
1383
1402
  /**
1384
1403
  * Build the request body for one probe.
1385
1404
  *
@@ -1387,10 +1406,13 @@ declare const PROBE_INPUT_TOKENS = 25000;
1387
1406
  * `prepareChatBody` enforces that too, but sending it explicitly keeps this
1388
1407
  * function's output valid on its own.
1389
1408
  *
1390
- * Measured on the built artifact: 112,713 bytes of body, and the upstream
1391
- * counted `prompt_tokens: 25023` at a cost of `credit: 0.72`.
1409
+ * Measured on the built artifact at the DEFAULT size: 112,713 bytes of body, and
1410
+ * the upstream counted `prompt_tokens: 25023` at a cost of `credit: 0.72`. The
1411
+ * body bytes and the credit both scale with the size asked for, which is why the
1412
+ * size is a user setting: a larger probe answers more truthfully for someone
1413
+ * whose conversations are long, and costs proportionally more to ask.
1392
1414
  */
1393
- declare function probeRequestBody(modelId: string): string;
1415
+ declare function probeRequestBody(modelId: string, inputTokens?: number): string;
1394
1416
  /**
1395
1417
  * How one probe ended.
1396
1418
  *
@@ -1509,12 +1531,35 @@ declare function probeModel(input: {
1509
1531
  credential: WorkBuddyCredential;
1510
1532
  modelId: string;
1511
1533
  nowMs: number;
1534
+ /**
1535
+ * How much input this probe sends, in tokens.
1536
+ *
1537
+ * Omitted means {@link DEFAULT_PROBE_INPUT_TOKENS}, so every existing caller
1538
+ * keeps the measured 25k and only the pool's user-facing "test" passes a
1539
+ * chosen size through.
1540
+ */
1541
+ inputTokens?: number;
1512
1542
  /** The region's next monthly quota refresh, when known. */
1513
1543
  quotaRefreshAtMs?: number;
1514
1544
  signal?: AbortSignal;
1515
1545
  }): Promise<WorkBuddyProbeResult>;
1516
1546
  //#endregion
1517
1547
  //#region src/account-pool.d.ts
1548
+ /**
1549
+ * Which of the pool's writers produced a measurement.
1550
+ *
1551
+ * `test-batch` — the user pressed "test" (or the check-in batch), so the
1552
+ * outcome is a deliberate measurement of the target model.
1553
+ * `live-request` — a real chat request failed and its failure was recorded so
1554
+ * the NEXT request starts from a measurement instead of rediscovering the
1555
+ * same wall.
1556
+ *
1557
+ * The distinction is diagnostic, not behavioural: both feed the same routing
1558
+ * and cooling logic. It exists so a user who watched an account measure `ok`
1559
+ * and later saw it limited can tell whether that was a later live failure
1560
+ * (expected) or an older value winning (a bug).
1561
+ */
1562
+ type WorkBuddyPoolProbeSource = 'test-batch' | 'live-request';
1518
1563
  /** One account's pool entry: what the picker needs, plus its last measurement. */
1519
1564
  interface WorkBuddyPoolAccount {
1520
1565
  /** Stable account id (the same one `WorkBuddyAccountChoice` carries). */
@@ -1534,6 +1579,26 @@ interface WorkBuddyPoolProbe {
1534
1579
  outcome: WorkBuddyProbeOutcome;
1535
1580
  /** When this measurement was taken, in epoch ms. */
1536
1581
  atMs: number;
1582
+ /**
1583
+ * WHAT WROTE THIS MEASUREMENT.
1584
+ *
1585
+ * Two sites write probes for the same account, and before this field they
1586
+ * were indistinguishable in the file — which made a real confusion
1587
+ * unanswerable: the user presses "test", sees an account measured `ok`, and
1588
+ * later finds it `rate-limited`. Nothing in the record said whether that was
1589
+ * a NEWER measurement (a live request really did hit the limit) or an OLDER
1590
+ * one somehow winning, and the file keeps no history to tell them apart.
1591
+ *
1592
+ * With the source recorded, that question has an answer on sight: a
1593
+ * `live-request` entry that replaced a `test-batch` result is the pool
1594
+ * working as designed (a live failure steers the next request away from the
1595
+ * account); two entries of the SAME source with the OLDER one winning would
1596
+ * be a genuine bug.
1597
+ *
1598
+ * ABSENT for any measurement written before this field existed, so old probe
1599
+ * files stay readable — an absent source means "unknown", never a guess.
1600
+ */
1601
+ source?: WorkBuddyPoolProbeSource;
1537
1602
  /**
1538
1603
  * When this account may be usable again, when the upstream stated a time.
1539
1604
  *
@@ -1961,6 +2026,14 @@ interface WorkBuddyWebPoolAccount {
1961
2026
  probe?: {
1962
2027
  outcome: WorkBuddyWebProbeOutcome;
1963
2028
  atMs: number;
2029
+ /**
2030
+ * What wrote this measurement, so the card can explain a change the user
2031
+ * watched happen: `test-batch` is a deliberate test, `live-request` is a
2032
+ * real request that failed and overwrote it. ABSENT for records written
2033
+ * before the field existed — the card must then say "unknown" rather than
2034
+ * guess which of the two it was.
2035
+ */
2036
+ source?: 'test-batch' | 'live-request';
1964
2037
  /** Only when the upstream named a time; never invented locally. */
1965
2038
  retryAtMs?: number;
1966
2039
  /**
@@ -2030,6 +2103,14 @@ interface WorkBuddyWebPool {
2030
2103
  targetModelSource: 'preferred' | 'free' | 'none' | 'stale';
2031
2104
  /** The account ids the user has checked into this region's pool. */
2032
2105
  memberAccountIds?: readonly string[];
2106
+ /**
2107
+ * Tokens each test probe sends, as the user saved it; `0`/absent = default.
2108
+ *
2109
+ * The SAVED value, not the resolved one: the card needs to tell "the user
2110
+ * never chose a size" apart from "the user chose 25000", because the first
2111
+ * must keep showing the default option rather than a number they never picked.
2112
+ */
2113
+ probeInputTokens?: number;
2033
2114
  /**
2034
2115
  * The subset of {@link memberAccountIds} that resolves to a real local
2035
2116
  * sign-in — i.e. what a batch actually runs on.
@@ -2188,6 +2269,14 @@ interface WorkBuddyPoolDeps {
2188
2269
  preferences(region: WorkBuddyRegion): {
2189
2270
  enabled: boolean;
2190
2271
  targetModelId: string;
2272
+ /**
2273
+ * Tokens each test probe sends as saved; `0` when the user never chose.
2274
+ *
2275
+ * Optional so a caller that has not learnt about the setting still
2276
+ * satisfies this interface, and so its absence reads as "default" — the
2277
+ * same meaning the host gives an older profile.
2278
+ */
2279
+ probeInputTokens?: number;
2191
2280
  memberAccountIds: readonly string[];
2192
2281
  };
2193
2282
  /** One region's CHECKED accounts, plus their credits and last measurements. */
@@ -2344,6 +2433,23 @@ interface WorkBuddyPoolPreferences {
2344
2433
  enabled?: boolean;
2345
2434
  /** Model id to test; `''` means auto-pick a free model from this region. */
2346
2435
  targetModelId?: string;
2436
+ /**
2437
+ * How much input a probe sends, in tokens — the pool's biggest single knob.
2438
+ *
2439
+ * The upstream's rate limit (6004) fires on request SIZE, and the threshold
2440
+ * was measured to sit between 20k and 30k tokens. The probe has to be sized
2441
+ * near where the user's REAL conversations land: too small and it reports
2442
+ * "usable" while every long conversation is refused (the exact false positive
2443
+ * that made this knob necessary), too large and it costs real credit on every
2444
+ * batch AND excludes accounts that would have served the shorter requests the
2445
+ * user actually sends.
2446
+ *
2447
+ * A fixed value therefore cannot be right for everyone — the right size is a
2448
+ * property of the user's own traffic, which only they know. `undefined` keeps
2449
+ * the measured default ({@link DEFAULT_PROBE_INPUT_TOKENS}), so an existing
2450
+ * profile is unaffected.
2451
+ */
2452
+ probeInputTokens?: number;
2347
2453
  /**
2348
2454
  * The account ids checked into this region's pool.
2349
2455
  *
@@ -2498,4 +2604,4 @@ declare function legacyAttributionRegion(value: Config, accountsFor: (region: Wo
2498
2604
  */
2499
2605
  declare function apply(ctx: Context, config: Config): void;
2500
2606
  //#endregion
2501
- 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, type UpstreamErrorKind, 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, type WorkBuddyAccountChoice, type WorkBuddyAdapter, type WorkBuddyAuthStatus, type WorkBuddyCandidateFailure, WorkBuddyCatalog, type WorkBuddyChatResult, type WorkBuddyCredential, type WorkBuddyCredentialRecovery, WorkBuddyCredentialRejectedError, WorkBuddyCredentialStore, type WorkBuddyCreditPackage, type WorkBuddyCredits, WorkBuddyEncryptedCredentialError, type WorkBuddyEncryptedField, type WorkBuddyHostHeartbeat, type WorkBuddyModelInfo, type WorkBuddyNativeModality, WorkBuddyPoolPreferences, type WorkBuddyProbeClient, type WorkBuddyProbeOutcome, type WorkBuddyProbeResult, type WorkBuddyProbeStore, type WorkBuddyReasoning, type WorkBuddyRecoveryCandidate, type WorkBuddyRefreshOutcome, WorkBuddyRegionState, type WorkBuddyShim, type WorkBuddyStatusRouteOptions, type WorkBuddyStoreOptions, WorkBuddyUpstreamClient, type WorkBuddyUpstreamModel, type WorkBuddyUsabilityProbeOptions, 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 };
2607
+ 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, type UpstreamErrorKind, 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, type WorkBuddyAccountChoice, type WorkBuddyAdapter, type WorkBuddyAuthStatus, type WorkBuddyCandidateFailure, WorkBuddyCatalog, type WorkBuddyChatResult, type WorkBuddyCredential, type WorkBuddyCredentialRecovery, WorkBuddyCredentialRejectedError, WorkBuddyCredentialStore, type WorkBuddyCreditPackage, type WorkBuddyCredits, WorkBuddyEncryptedCredentialError, type WorkBuddyEncryptedField, type WorkBuddyHostHeartbeat, type WorkBuddyModelInfo, type WorkBuddyNativeModality, WorkBuddyPoolPreferences, type WorkBuddyProbeClient, type WorkBuddyProbeOutcome, type WorkBuddyProbeResult, type WorkBuddyProbeStore, type WorkBuddyReasoning, type WorkBuddyRecoveryCandidate, type WorkBuddyRefreshOutcome, WorkBuddyRegionState, type WorkBuddyShim, type WorkBuddyStatusRouteOptions, type WorkBuddyStoreOptions, WorkBuddyUpstreamClient, type WorkBuddyUpstreamModel, type WorkBuddyUsabilityProbeOptions, 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/lib/index.js CHANGED
@@ -1,4 +1,4 @@
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-D5ArLcFf.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";
@@ -1342,10 +1342,49 @@ var RecoveryStream = class {
1342
1342
  */
1343
1343
  nativeCallsSeen = false;
1344
1344
  buffer;
1345
- 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) {
1346
1358
  this.res = res;
1347
1359
  this.logger = logger;
1348
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));
1349
1388
  }
1350
1389
  /** True when a call or real prose has reached the client. */
1351
1390
  get delivered() {
@@ -1444,6 +1483,7 @@ var RecoveryStream = class {
1444
1483
  if (Array.isArray(nativeCalls) && nativeCalls.length > 0) {
1445
1484
  this.nativeCallsSeen = true;
1446
1485
  this.windowOpened = true;
1486
+ this.flushNotice();
1447
1487
  this.send(frame, false);
1448
1488
  this.wroteContent = true;
1449
1489
  this.closeWindow();
@@ -1458,6 +1498,7 @@ var RecoveryStream = class {
1458
1498
  }
1459
1499
  if (this.buffer === void 0 || this.nativeCallsSeen) {
1460
1500
  this.windowOpened = true;
1501
+ this.flushNotice();
1461
1502
  this.closeWindow();
1462
1503
  this.res.write(frame);
1463
1504
  this.wroteContent = true;
@@ -1466,6 +1507,7 @@ var RecoveryStream = class {
1466
1507
  this.windowOpened = true;
1467
1508
  const outcome = this.buffer.add(content);
1468
1509
  if (outcome.calls !== void 0 && outcome.calls.length > 0) {
1510
+ this.flushNotice();
1469
1511
  this.send(callsFrame(chunk, choiceRecord, outcome.calls), false);
1470
1512
  this.wroteContent = true;
1471
1513
  this.closeWindow();
@@ -1478,6 +1520,7 @@ var RecoveryStream = class {
1478
1520
  return;
1479
1521
  }
1480
1522
  if (outcome.prose) {
1523
+ this.flushNotice();
1481
1524
  this.send(contentFrame(chunk, choiceRecord, outcome.text), false);
1482
1525
  this.wroteContent = true;
1483
1526
  this.closeWindow();
@@ -1521,6 +1564,7 @@ var RecoveryStream = class {
1521
1564
  this.windowOpened = true;
1522
1565
  const frame = contentFrame(chunk, choiceRecord, text);
1523
1566
  if (text.trim() !== "") {
1567
+ this.flushNotice();
1524
1568
  this.send(frame, false);
1525
1569
  this.wroteContent = true;
1526
1570
  this.closeWindow();
@@ -1598,6 +1642,55 @@ function callsFrame(chunk, choice, calls) {
1598
1642
  };
1599
1643
  return `data: ${JSON.stringify(next)}\n\n`;
1600
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
+ }
1601
1694
  /** One frame carrying replacement text for a frame whose content was consumed. */
1602
1695
  function contentFrame(chunk, choice, content) {
1603
1696
  const delta = {
@@ -1726,6 +1819,15 @@ function createWorkBuddyShim(options) {
1726
1819
  let result = await client.chatStream(credential, prepared, controller.signal);
1727
1820
  const triedAccountIds = [];
1728
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
+ /**
1729
1831
  * Tell the plugin about a failed attempt so it can store the measurement.
1730
1832
  *
1731
1833
  * Every failed attempt is reported exactly once, INCLUDING the last one — an
@@ -1780,6 +1882,8 @@ function createWorkBuddyShim(options) {
1780
1882
  });
1781
1883
  if (controller.signal.aborted) break;
1782
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);
1783
1887
  credential = next;
1784
1888
  result = await client.chatStream(credential, prepared, controller.signal);
1785
1889
  }
@@ -1813,7 +1917,8 @@ function createWorkBuddyShim(options) {
1813
1917
  requiredParameters: declared.requiredParameters,
1814
1918
  ...declared.pinnedToolName === void 0 ? {} : { pinnedToolName: declared.pinnedToolName }
1815
1919
  };
1816
- 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);
1817
1922
  await writer.consume(result.response.body);
1818
1923
  if (gate !== void 0 && !writer.delivered && writer.residueSeen && !controller.signal.aborted) {
1819
1924
  logger?.warn("dsh-connect-workbuddy: the turn produced DSML markup only; retrying it once");
@@ -1868,7 +1973,7 @@ const PROBE_SYSTEM_PROMPT = "You are a connectivity check. Reply with a single w
1868
1973
  * Output cap for a probe. One token is enough to prove the model answers.
1869
1974
  *
1870
1975
  * The probe's cost comes from its INPUT, not its output (see
1871
- * {@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
1872
1977
  * without adding signal.
1873
1978
  */
1874
1979
  const PROBE_MAX_TOKENS = 1;
@@ -1895,8 +2000,44 @@ const PROBE_MAX_TOKENS = 1;
1895
2000
  * A probe that sent a handful of tokens answered "usable" while every real
1896
2001
  * request in a long conversation was refused — the probe was asking too small a
1897
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.
1898
2022
  */
1899
- 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
+ }
1900
2041
  /**
1901
2042
  * Characters per token for the filler text, measured.
1902
2043
  *
@@ -1915,11 +2056,14 @@ const FILLER_LINE = "The quick brown fox jumps over the lazy dog. ";
1915
2056
  * `prepareChatBody` enforces that too, but sending it explicitly keeps this
1916
2057
  * function's output valid on its own.
1917
2058
  *
1918
- * Measured on the built artifact: 112,713 bytes of body, and the upstream
1919
- * 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.
1920
2064
  */
1921
- function probeRequestBody(modelId) {
1922
- 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);
1923
2067
  const filler = FILLER_LINE.repeat(Math.ceil(targetChars / 45));
1924
2068
  return JSON.stringify({
1925
2069
  model: modelId,
@@ -2117,7 +2261,7 @@ async function probeModel(input) {
2117
2261
  let answer;
2118
2262
  const startedAt = Date.now();
2119
2263
  try {
2120
- 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);
2121
2265
  } catch (error) {
2122
2266
  return {
2123
2267
  modelId: input.modelId,
@@ -2310,6 +2454,7 @@ function probeUpdatesOf(rows) {
2310
2454
  updates[row.accountId] = {
2311
2455
  outcome: row.result.outcome,
2312
2456
  atMs,
2457
+ source: "test-batch",
2313
2458
  ...row.result.retryAtMs === void 0 ? {} : { retryAtMs: row.result.retryAtMs },
2314
2459
  ...row.result.message === void 0 ? {} : { message: row.result.message }
2315
2460
  };
@@ -2574,7 +2719,7 @@ const POOL_STORE_PREFIX = ".workbuddy-pool";
2574
2719
  function isProbe(value) {
2575
2720
  if (typeof value !== "object" || value === null) return false;
2576
2721
  const candidate = value;
2577
- 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");
2578
2723
  }
2579
2724
  /**
2580
2725
  * One region's path for measured pool facts.
@@ -2621,6 +2766,7 @@ async function readPoolProbes(region) {
2621
2766
  probes[accountId] = {
2622
2767
  outcome: probe.outcome,
2623
2768
  atMs: probe.atMs,
2769
+ ...knownProbeSource(probe.source) === void 0 ? {} : { source: knownProbeSource(probe.source) },
2624
2770
  ...probe.retryAtMs === void 0 ? {} : { retryAtMs: probe.retryAtMs },
2625
2771
  ...probe.message === void 0 ? {} : { message: probe.message }
2626
2772
  };
@@ -2628,6 +2774,17 @@ async function readPoolProbes(region) {
2628
2774
  return probes;
2629
2775
  }
2630
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
+ /**
2631
2788
  * Merge measurements into one region's store and write it atomically.
2632
2789
  *
2633
2790
  * A MERGE rather than a replace, because batches measure one account at a time
@@ -3127,6 +3284,7 @@ async function workBuddyWebPool(deps, region) {
3127
3284
  ...measured?.probe === void 0 ? {} : { probe: {
3128
3285
  outcome: measured.probe.outcome,
3129
3286
  atMs: measured.probe.atMs,
3287
+ ...measured.probe.source === void 0 ? {} : { source: measured.probe.source },
3130
3288
  ...measured.probe.retryAtMs === void 0 ? {} : { retryAtMs: measured.probe.retryAtMs },
3131
3289
  ...measured.probe.message === void 0 ? {} : { message: measured.probe.message }
3132
3290
  } },
@@ -3143,6 +3301,7 @@ async function workBuddyWebPool(deps, region) {
3143
3301
  ...target.staleModelId === void 0 ? {} : { staleTargetModelId: target.staleModelId },
3144
3302
  targetModelSource: target.source,
3145
3303
  memberAccountIds: preferences.memberAccountIds,
3304
+ ...preferences.probeInputTokens === void 0 ? {} : { probeInputTokens: preferences.probeInputTokens },
3146
3305
  effectiveMemberAccountIds: effectiveMembersOf(accounts.map((account) => account.accountId), new Set(preferences.memberAccountIds)),
3147
3306
  accounts,
3148
3307
  catalog: deps.displayModels(region).map((model) => ({
@@ -3522,6 +3681,16 @@ const modelConfig = z.object({
3522
3681
  const poolConfig = z.object({
3523
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."),
3524
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)"),
3525
3694
  memberAccountIds: z.array(z.string()).default([]).description("Account ids checked into this pool (opt-in; empty means the pool covers nothing)")
3526
3695
  });
3527
3696
  const regionStateConfig = z.object({
@@ -4138,6 +4307,7 @@ function apply(ctx, config) {
4138
4307
  await writePoolProbes(region, { [accountId]: {
4139
4308
  outcome,
4140
4309
  atMs: Date.now(),
4310
+ source: "live-request",
4141
4311
  ...retryAtMs === void 0 ? {} : { retryAtMs },
4142
4312
  ...message === "" ? {} : { message }
4143
4313
  } });
@@ -4228,9 +4398,11 @@ function apply(ctx, config) {
4228
4398
  /** One region's pool preferences, with the schema's defaults applied. */
4229
4399
  const poolPreferencesOf = (config, region) => {
4230
4400
  const pool = regionStateOf(config, region).pool;
4401
+ const probeInputTokens = pool?.probeInputTokens;
4231
4402
  return {
4232
4403
  enabled: pool?.enabled === true,
4233
4404
  targetModelId: pool?.targetModelId ?? "",
4405
+ ...probeInputTokens === void 0 ? {} : { probeInputTokens },
4234
4406
  memberAccountIds: pool?.memberAccountIds ?? []
4235
4407
  };
4236
4408
  };
@@ -4276,11 +4448,13 @@ function apply(ctx, config) {
4276
4448
  claimDailyCheckin: (credential) => client.claimDailyCheckin(credential),
4277
4449
  probe: async (credential, modelId) => {
4278
4450
  const quotaRefreshAtMs = await quotaRefreshOf(region, credential);
4451
+ const inputTokens = resolveProbeInputTokens(poolPreferencesOf(current(), region).probeInputTokens);
4279
4452
  return probeModel({
4280
4453
  client,
4281
4454
  credential,
4282
4455
  modelId,
4283
4456
  nowMs: Date.now(),
4457
+ inputTokens,
4284
4458
  ...quotaRefreshAtMs === void 0 ? {} : { quotaRefreshAtMs }
4285
4459
  });
4286
4460
  }
@@ -4402,4 +4576,4 @@ function apply(ctx, config) {
4402
4576
  });
4403
4577
  }
4404
4578
  //#endregion
4405
- 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.3",
5
+ "version": "3.4.4",
6
6
  "author": "dingminhua",
7
7
  "copyright": "Copyright (c) 2026 LaoDing",
8
8
  "license": "MIT",