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 +27 -0
- package/lib/bin.js +1 -1
- package/lib/client.js +122 -7
- package/lib/{host-heartbeat-D5ArLcFf.js → host-heartbeat-DTuwptkM.js} +1 -1
- package/lib/index.d.ts +112 -6
- package/lib/index.js +186 -12
- package/package.json +1 -1
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-
|
|
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
|
|
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
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
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)})`)))))
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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,
|
|
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.
|
|
5
|
+
"version": "3.4.4",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|