dsh-connect-workbuddy 4.4.5 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/lib/bin.js +1 -1
- package/lib/client.js +273 -12
- package/lib/{host-heartbeat-Bj7JvSYJ.js → host-heartbeat-DvcOd-3C.js} +20 -2
- package/lib/index.d.ts +159 -3
- package/lib/index.js +352 -14
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.5.0 (2026-10-14)
|
|
4
|
+
|
|
5
|
+
> 版本号是 **minor** 而不是 patch:这一版新增了一个**用户可见的设置**(积分读数的位置)和一个**新的渲染席位**,而且配置 schema 多了一个字段 —— 按语义化版本这是特性,不是修错。`4.4.6` 之后没有任何以 4.4.7 发布过的产物或 Release(tag 已撤回重打)。
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- **积分读数只有一个固定位置:装了别的插件后它就不显示了 —— 现在位置可选(输入框工具栏 / 输入框下方)**(issue #62 报告者)
|
|
10
|
+
- **现象**:报告者装了第三方插件(`dsh-auto-continue`)之后,对话输入框里的 WorkBuddy 积分余额消失。根因是那个位置本身:`conversation.input.left`(输入框工具栏)是一个**共享的 list 插槽**,shell 自己的控件和任何其他 bundle 都注册在那里 —— 而 list 插槽无法保证每个占用者都可见,别人挤进来就可能把本插件的读数挤没。原注释里其实已经记着同类问题(更早放在侧边栏底部时,两个连接器插件互相挤压,才迁到输入框这一行)。
|
|
11
|
+
- **修法**:把「位置」做成**每区域一个选项**,两个席位都注册、由 gate 决定在哪个席位渲染:
|
|
12
|
+
- `composer`(默认)—— 输入框工具栏 `conversation.input.left`,与模型选择同一行;
|
|
13
|
+
- `stats` —— 输入框下方 `conversation.composer.dock`,shell 自己渲染「轮次 / 步数 · tok/s · token · 缓存命中」那一行;`order: 10` 排在 shell 的统计之后,所以官方那几颗统计仍在最左,读数是**追加**而不是顶替。
|
|
14
|
+
- 选默认值是为了**升级不动任何人的界面**:设置在 schema 里默认 `composer`,升级前写下的 profile 读出来仍是 `composer`。
|
|
15
|
+
- **为什么两个席位都注册**:位置这种东西用户是「一边看着结果一边改」的,注册哪个席位取决于设置就意味着每次改设置都要**重新注册插槽**;而本仓库的注释里已经写明这种绑定是脆的(晚到的 settings 命名空间必须产生新的注册而不是改闭包)。现在**决定权在 gate 里一处**:席位不符就 `return null`,另一个席位照常渲染。
|
|
16
|
+
- **判定的顺序**:先归属(是不是本插件的 provider)、再两个开关(区域是否启用、读数是否显示)、**最后**才是席位。所以一个被关掉的区域在**两个席位都不渲染**,而不是先在非当前席位画一帧再被拒。
|
|
17
|
+
- **卡片新增一个两选一控件**(就在「对话框显示积分」那个勾选旁边,只作用于当前标签页的区域):读取走 Host 的 usage 应答(与那个勾选同源,因为浏览器 settings 镜像在本命名空间上是过期的那份),写完**重新读一次 Host** —— 否则控件会一直显示旧席位,看起来像没生效。
|
|
18
|
+
- **守卫**:`tests/issue62-credit-position.spec.tsx`(9 例)覆盖:默认 `composer`(含升级前的 profile 与手改的未知值)、按区域而非全局读取、写入只改该区域该字段(模型列表/预算/兄弟区域都不动)、席位与显示开关相互独立、gate 在**指定席位渲染 / 另一席位渲染 0 个 / 两个席位都不渲染(隐藏或区域关闭)/ 别的 provider 两个席位都不渲染**。**变异验证**:把 gate 的席位判定删掉(两个席位都渲染)→ 2 例红;把读取的默认值去掉(升级前的 profile 读成 stats)→ 2 例红。
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
- **新增「上游请求体构成」诊断(`WORKBUDDY_BODY_REPORT`),用于定案 issue #59:两份请求体到底是不是同一份历史。**(issue #59)
|
|
22
|
+
- **为什么需要**:报告里同一份历史被国内网关算约 70.8 万 token、国际网关算约 137.4 万(1.94 倍),切区域即 `code 11115 prompt is too long`。两条独立测量已经把范围压到唯一一个问题:报告者在**两个区域**跑本仓库的内容类别探针,逐字节相同的散文/代码/JSON 六格比值全在 **0.98~1.06**(同一段字节两边计价一致);代码链路核对又表明**历史 reasoning 根本没进请求体**(pi-ai 只在 `compat.requiresThinkingAsText` 为真时把 `thinking` 当文本转发,本插件从未设置;`reasoning_details` 那条路需要流式解析从不写出的签名)。**剩下唯一可能是两份请求体不同**,而这件事**不需要任何区域有积分**就能查:请求体是本地拼好的,`14018 Credits exhausted` 是发出去之后才可能发生的。
|
|
23
|
+
- **做法**:`src/body-report.ts` 在 shim 把 body 交给上游**之前**记录它的**构成** —— 每条消息的 `role`、逐块的类型(`system`/`text`/`tool-call`/`tool-result`/`reasoning`/`image`)、字符数与其中**中日韩字符**数(实测中文 0.669、英文 0.224 token/字符,差 3 倍,混在一起看不出来)、`tools` 数组、`messages.length` 与 body 总字节数。按区域写 `<dir>/<region>.jsonl`,一次请求一行(一轮对话有很多次上游调用,**最后一次**才是要比的那份)。
|
|
24
|
+
- **只记计数、不记文本**:不写任何消息内容、账号名或令牌 —— 一个要被贴进公开 issue 的诊断不能有泄漏用户会话的能力。`tests/issue59-body-report.spec.ts` 里有一条专门的断言:用带特征串的 body 生成报告后,序列化结果**不含**该特征串(变异验证:让 `block()` 把 `text` 一起带上 → 该例与 reasoning 那例同时变红)。
|
|
25
|
+
- **开关**:环境变量 `WORKBUDDY_BODY_REPORT` 未设置或为空白即**关闭**;`1`/`true` 写到 `<DSH home>/body-reports/`;其他值按**目录路径**解释。写文件失败只走日志,**绝不影响聊天请求**(有专门断言)。
|
|
26
|
+
- **守卫**:`tests/issue59-body-report.spec.ts`(16 例)。
|
|
27
|
+
- **配套**:`scripts/probe-region-token-accounting.mjs` 新增 `--language en|zh` 与 `--kind prose|code|json`(六种组合确定性填充、跨区域逐字节相同),并补上国内侧实测基线:prose 214.0 / code 287.0 / json 288.3(en),prose 671.0 / code 480.7 / json 397.3(zh),单位 tokens/1k chars。
|
|
28
|
+
|
|
29
|
+
## 4.4.6 (2026-10-14)
|
|
30
|
+
|
|
31
|
+
### Fixes
|
|
32
|
+
|
|
33
|
+
- **面板的「测试」只测一个模型,却把结果当成账号级结论:账号池说「可用」,一切到实际在用的模型就撞 `429 / 6004`。**(issue #61 报告者报告,维护者在 issue 下确认「测试的是某一个模型……不能换其他模型」)
|
|
34
|
+
- **现象与证据**:报告者同一时刻、同一脚本测两个国内账号 —— `hy4-preview-f`(当时的 `pool.targetModelId`,面板测的就是它)两个号都可用,而 `step-5-preview`(当时实际在用的模型)两个号都 `429 / 6004`。池文件里这两个号还被更早的一次批量测试记成 `outcome=ok source=test-batch`,而池开着时那份「ok」正是选号依据。**`6004` 是「账号 × 模型」维度的条件**(本项目 10-13 也在三个国际账号上量到 `deepseek-v4.1-flash` 挂、其余模型全通),所以单一模型探测在结构上无法回答「这个账号能不能服务」。
|
|
35
|
+
- **根因(报告者给出代码位置,本地逐条复核属实)**:`resolveTargetModel()` 只解析出**一个** `modelId`(来自区域级 `pool.targetModelId`),一条路径同时服务批量与逐行测试;而 `probeModel()` 的三个返回分支**早就带着 `modelId`**,落盘时被丢掉了 —— 记录里只有 `outcome/atMs/source/retryAtMs/message`,没有任何字段说明这条 `ok` 是**哪个模型**上的。
|
|
36
|
+
- **修法(三处,各自成立)**:
|
|
37
|
+
1. **测量记录记住模型**:`WorkBuddyPoolProbe` 新增可选 `modelId`,两个写入方(批量测试、实时聊天失败)在拿到模型的那一刻写下它。
|
|
38
|
+
2. **每个账号可单独指定测试模型**:新增设置 `pool.targetModelIdByAccount`(账号 id → 模型 id),读取时先查 map、查不到回落 `targetModelId`。**批量测试与逐行测试走同一条解析**(报告者指出这正是「改一处两处同时生效」的地方),所以「测试所有账号」也会按每个号自己的模型去测。解析仍然过 `resolveTargetModel`:指向本区域已下架模型时**报 `stale`、绝不静默换成另一个模型**(换一个等于花积分测一个用户没选的模型,还把它当作这个号的结论)。
|
|
39
|
+
3. **卡片显示适用范围**:池表每行的测量结论旁写上「模型 {model}」(记录里没有该字段的旧数据写「未记录模型」,不猜);每行新增一个测试模型下拉框(留空 = 跟随区域目标),随「保存」一起写入。
|
|
40
|
+
- **为什么 `modelId` 落盘时不继承**:`accountName` 与 `retryAtMs` 描述的是**账号**(后来者看不到的名字仍然是真的,未来的刷新时刻仍然在未来),而模型描述的是**这一次测量**。把旧的 model id 继承到一次新测量上,等于用不是它的模型给它贴标签 —— 正是这个字段要终结的那种混淆。所以**缺失即「范围未知」,不是猜测**。
|
|
41
|
+
- **配置向后兼容**:`targetModelIdByAccount` 默认空对象,且**只有非空时才写入** settings(与 `probeInputTokens` 同一条规则)—— 从未用过这个设置的 profile 写出来的槽位与升级前逐字节相同,升级不会看起来像用户改过设置。
|
|
42
|
+
- **守卫**:`tests/issue61-probe-model-scope.spec.ts`(9 例)覆盖:逐账号优先/回落、空串视为「跟随区域目标」、override 下架时解析为 `stale` 且不带 `modelId`、批量按各账号自己的模型探测、单一模型的旧调用形态不变、解析不出模型时产出 `unavailable` 行并指名那个模型(**没有**回退到别的模型)、批量写入 `modelId`、**不继承**、新测量覆盖旧模型。`tests/pool-route.spec.ts` 新增一条路由级断言:批量路由交给 Host 的是**逐账号解析器**(`a` 用区域目标、`b` 用 `b` 自己的)。**变异验证**:记录不再写 `modelId` → 1 例红;合并改成继承 → 1 例红;批量改回「整批一个模型」→ 1 例红;路由改回传单一 modelId → 路由断言红。
|
|
43
|
+
|
|
44
|
+
### Features
|
|
45
|
+
|
|
3
46
|
## 4.4.5 (2026-10-14)
|
|
4
47
|
|
|
5
48
|
### Fixes
|
package/lib/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as defaultDesktopAuthDirs, F as WorkBuddyUpstreamClient,
|
|
2
|
+
import { C as defaultDesktopAuthDirs, F as WorkBuddyUpstreamClient, K as WORKBUDDY_APP_EXECUTABLE_ENV, O as legacyWorkbuddyOwnAuthPath, Q as findWorkbuddyAppExecutableWithSource, S as defaultDesktopAuthCandidates, a as readHostHeartbeat, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, j as workbuddyOwnAuthPath, l as piAiRuntimeInfo, o as workbuddyHostHeartbeatPath, ot as hostPeersInfo, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS, v as WORKBUDDY_AUTH_FILE_ENV, y as WorkBuddyCredentialStore } from "./host-heartbeat-DvcOd-3C.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
|
@@ -233,6 +233,23 @@ window.__ModuleLoader__.load({
|
|
|
233
233
|
function regionCreditsShownOf(value, region) {
|
|
234
234
|
return regionSlotOf(value, region)["showCreditsInMainUi"] !== false;
|
|
235
235
|
}
|
|
236
|
+
/** The seat a region's readout uses, defaulting to the composer row. */
|
|
237
|
+
function regionCreditPositionOf(value, region) {
|
|
238
|
+
return regionSlotOf(value, region)["creditPosition"] === "stats" ? "stats" : "composer";
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Build the next `regions` settings value for one region's credit seat.
|
|
242
|
+
*
|
|
243
|
+
* Same merge rule as {@link nextRegionCreditsShown}: only the target region's
|
|
244
|
+
* field changes, so every other field of that slot and every other region's slot
|
|
245
|
+
* survive the write.
|
|
246
|
+
*/
|
|
247
|
+
function nextRegionCreditPosition(value, region, position) {
|
|
248
|
+
return nextRegionSlots(regionsMapOf(value), region, {
|
|
249
|
+
...regionSlotOf(value, region),
|
|
250
|
+
creditPosition: position
|
|
251
|
+
});
|
|
252
|
+
}
|
|
236
253
|
/**
|
|
237
254
|
* Build the next `regions` settings value for the sidebar-credit on/off toggle.
|
|
238
255
|
*
|
|
@@ -632,6 +649,20 @@ window.__ModuleLoader__.load({
|
|
|
632
649
|
});
|
|
633
650
|
}
|
|
634
651
|
/**
|
|
652
|
+
* Write one region's credit SEAT (issue #62), verifying it landed.
|
|
653
|
+
*
|
|
654
|
+
* Sibling of {@link writeRegionCreditsShown}: same whole-slot merge, same
|
|
655
|
+
* landed check, same reason for both. The seat decides only WHERE the readout
|
|
656
|
+
* renders, so a lost write would leave the user looking at the old position
|
|
657
|
+
* while the control claimed the new one.
|
|
658
|
+
*/
|
|
659
|
+
async function writeRegionCreditPosition(scope, region, position, slot) {
|
|
660
|
+
await writeField(scope, "regions", region, slot, (readBack) => {
|
|
661
|
+
const stored = readBack;
|
|
662
|
+
return stored !== null && stored !== void 0 && stored["creditPosition"] === position;
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
635
666
|
* Write one region's account-pool PREFERENCES, verifying they landed.
|
|
636
667
|
*
|
|
637
668
|
* Routed through {@link writeField} like every other settings write, so it gets
|
|
@@ -661,6 +692,8 @@ window.__ModuleLoader__.load({
|
|
|
661
692
|
targetModelId: preferences.targetModelId,
|
|
662
693
|
memberAccountIds: preferences.memberAccountIds
|
|
663
694
|
};
|
|
695
|
+
const overrides = Object.entries(preferences.targetModelIdByAccount ?? {}).filter(([, modelId]) => modelId !== "");
|
|
696
|
+
if (overrides.length > 0) slot["targetModelIdByAccount"] = Object.fromEntries(overrides);
|
|
664
697
|
if (preferences.probeInputTokens !== void 0) slot["probeInputTokens"] = preferences.probeInputTokens;
|
|
665
698
|
const next = nextRegionPool(scope.getSnapshot().value, region, slot)[region];
|
|
666
699
|
await writeField(scope, "regions", region, next, (readBack) => {
|
|
@@ -672,7 +705,12 @@ window.__ModuleLoader__.load({
|
|
|
672
705
|
const stored = pool["memberAccountIds"];
|
|
673
706
|
if (!Array.isArray(stored) || stored.length !== preferences.memberAccountIds.length) return false;
|
|
674
707
|
const wanted = new Set(preferences.memberAccountIds);
|
|
675
|
-
|
|
708
|
+
if (!stored.every((id) => typeof id === "string" && wanted.has(id))) return false;
|
|
709
|
+
const storedModels = pool["targetModelIdByAccount"];
|
|
710
|
+
if (overrides.length === 0) return storedModels === void 0 || typeof storedModels === "object" && storedModels !== null && !Array.isArray(storedModels) && Object.keys(storedModels).length === 0;
|
|
711
|
+
if (typeof storedModels !== "object" || storedModels === null || Array.isArray(storedModels)) return false;
|
|
712
|
+
const storedMap = storedModels;
|
|
713
|
+
return Object.keys(storedMap).length === overrides.length && overrides.every(([accountId, modelId]) => storedMap[accountId] === modelId);
|
|
676
714
|
});
|
|
677
715
|
}
|
|
678
716
|
//#endregion
|
|
@@ -1259,6 +1297,21 @@ window.__ModuleLoader__.load({
|
|
|
1259
1297
|
const wanted = new Set(right);
|
|
1260
1298
|
return left.every((id) => wanted.has(id));
|
|
1261
1299
|
}
|
|
1300
|
+
/**
|
|
1301
|
+
* Whether two per-account test-model maps are equal (issue #61).
|
|
1302
|
+
*
|
|
1303
|
+
* By CONTENT, not by size: moving an account from one model to another keeps the
|
|
1304
|
+
* count and changes the meaning, and clearing an entry is a change a size
|
|
1305
|
+
* comparison would miss entirely when another account was added at the same
|
|
1306
|
+
* time. Order is irrelevant for the same reason it is in {@link sameIds} — the
|
|
1307
|
+
* map is written by key, not by click sequence.
|
|
1308
|
+
*/
|
|
1309
|
+
function sameModelOverrides(left, right) {
|
|
1310
|
+
const leftKeys = Object.keys(left);
|
|
1311
|
+
const rightKeys = Object.keys(right);
|
|
1312
|
+
if (leftKeys.length !== rightKeys.length) return false;
|
|
1313
|
+
return leftKeys.every((key) => left[key] === right[key]);
|
|
1314
|
+
}
|
|
1262
1315
|
/** Whether a probe outcome means the model answered. */
|
|
1263
1316
|
function outcomeOk(outcome) {
|
|
1264
1317
|
return outcome === "ok";
|
|
@@ -1345,7 +1398,8 @@ window.__ModuleLoader__.load({
|
|
|
1345
1398
|
enabled: pool?.enabled ?? false,
|
|
1346
1399
|
targetModelId: pool?.targetModelId || pool?.staleTargetModelId || "",
|
|
1347
1400
|
probeInputTokens: pool?.probeInputTokens ?? 0,
|
|
1348
|
-
memberAccountIds: pool?.memberAccountIds ?? []
|
|
1401
|
+
memberAccountIds: pool?.memberAccountIds ?? [],
|
|
1402
|
+
targetModelIdByAccount: { ...pool?.targetModelIdByAccount }
|
|
1349
1403
|
};
|
|
1350
1404
|
const active = draftRegion === region && draft !== null ? draft : saved;
|
|
1351
1405
|
/**
|
|
@@ -1366,7 +1420,7 @@ window.__ModuleLoader__.load({
|
|
|
1366
1420
|
* the pending edit.
|
|
1367
1421
|
*/
|
|
1368
1422
|
const poolRunning = saved.enabled;
|
|
1369
|
-
const dirty = draftRegion === region && draft !== null && (draft.enabled !== saved.enabled || draft.targetModelId !== saved.targetModelId || draft.probeInputTokens !== saved.probeInputTokens || !sameIds(draft.memberAccountIds, saved.memberAccountIds));
|
|
1423
|
+
const dirty = draftRegion === region && draft !== null && (draft.enabled !== saved.enabled || draft.targetModelId !== saved.targetModelId || draft.probeInputTokens !== saved.probeInputTokens || !sameModelOverrides(draft.targetModelIdByAccount, saved.targetModelIdByAccount) || !sameIds(draft.memberAccountIds, saved.memberAccountIds));
|
|
1370
1424
|
/**
|
|
1371
1425
|
* Membership that actually resolves to a local sign-in — what a batch runs on.
|
|
1372
1426
|
*
|
|
@@ -1527,6 +1581,25 @@ window.__ModuleLoader__.load({
|
|
|
1527
1581
|
};
|
|
1528
1582
|
});
|
|
1529
1583
|
}, [editDraft]);
|
|
1584
|
+
/**
|
|
1585
|
+
* Set ONE account's test model (issue #61).
|
|
1586
|
+
*
|
|
1587
|
+
* `''` REMOVES the entry rather than storing an empty string: "follow the
|
|
1588
|
+
* region target" is the absence of a choice, which is the same shape the host
|
|
1589
|
+
* reads and the same shape an untouched profile has — so nothing about an
|
|
1590
|
+
* older profile changes when this setting is never used.
|
|
1591
|
+
*/
|
|
1592
|
+
const setRowModel = (0, react.useCallback)((accountId, modelId) => {
|
|
1593
|
+
editDraft((current) => {
|
|
1594
|
+
const next = { ...current.targetModelIdByAccount };
|
|
1595
|
+
if (modelId === "") delete next[accountId];
|
|
1596
|
+
else next[accountId] = modelId;
|
|
1597
|
+
return {
|
|
1598
|
+
...current,
|
|
1599
|
+
targetModelIdByAccount: next
|
|
1600
|
+
};
|
|
1601
|
+
});
|
|
1602
|
+
}, [editDraft]);
|
|
1530
1603
|
/** Check every listed account, or none of them. */
|
|
1531
1604
|
const selectAll = (0, react.useCallback)((next) => {
|
|
1532
1605
|
editDraft((current) => ({
|
|
@@ -1847,7 +1920,10 @@ window.__ModuleLoader__.load({
|
|
|
1847
1920
|
},
|
|
1848
1921
|
onRemove: () => {
|
|
1849
1922
|
runRowRemove(account.accountId, account.accountName);
|
|
1850
|
-
}
|
|
1923
|
+
},
|
|
1924
|
+
modelOverride: active.targetModelIdByAccount[account.accountId] ?? "",
|
|
1925
|
+
catalog: pool.catalog ?? [],
|
|
1926
|
+
onModelChange: (modelId) => setRowModel(account.accountId, modelId)
|
|
1851
1927
|
}))), effectiveMembers.length === 0 ? (0, react.createElement)("p", {
|
|
1852
1928
|
className: "dsm-workbuddy-pool-warn",
|
|
1853
1929
|
role: "status"
|
|
@@ -1915,6 +1991,7 @@ window.__ModuleLoader__.load({
|
|
|
1915
1991
|
/** One account's row. */
|
|
1916
1992
|
function renderAccountRow(input) {
|
|
1917
1993
|
const { t, account, checked, canEdit, checkinSupported, onToggle, busy, testDisabled, targetHint, onTest, onRemove } = input;
|
|
1994
|
+
const { modelOverride, catalog, onModelChange } = input;
|
|
1918
1995
|
const name = account.accountName === "" ? t("row.accountUnnamed") : account.accountName;
|
|
1919
1996
|
const excluded = exclusionText(t, account.excludedBy);
|
|
1920
1997
|
const probe = account.probe;
|
|
@@ -1952,10 +2029,23 @@ window.__ModuleLoader__.load({
|
|
|
1952
2029
|
}), (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", {
|
|
1953
2030
|
className: "dsm-workbuddy-pool-probe",
|
|
1954
2031
|
...probe?.message === void 0 ? {} : { title: probe.message }
|
|
1955
|
-
}, (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", {
|
|
2032
|
+
}, (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), probe === void 0 ? null : (0, react.createElement)("span", { className: "dsm-workbuddy-pool-prov-model" }, probe.modelId === void 0 ? t("row.poolProbeModelUnknown") : t("row.poolProbeModel", { model: probe.modelId })))), checkinSupported ? (0, react.createElement)("span", { className: "dsm-workbuddy-pool-checkin" }, account.checkedInToday === void 0 ? (0, react.createElement)("span", {
|
|
1956
2033
|
className: "dsm-workbuddy-pool-unknown",
|
|
1957
2034
|
title: t("row.poolNeverTested")
|
|
1958
|
-
}, t("row.poolCheckinUnknown")) : account.checkedInToday ? (0, react.createElement)("span", { className: "dsm-workbuddy-pool-checked" }, t("row.poolCheckedIn")) : t("row.poolNotCheckedIn")) : null, (0, react.createElement)("span", { className: "dsm-workbuddy-pool-row-actions" }, (0, react.createElement)("
|
|
2035
|
+
}, t("row.poolCheckinUnknown")) : account.checkedInToday ? (0, react.createElement)("span", { className: "dsm-workbuddy-pool-checked" }, t("row.poolCheckedIn")) : t("row.poolNotCheckedIn")) : null, (0, react.createElement)("span", { className: "dsm-workbuddy-pool-row-actions" }, (0, react.createElement)("select", {
|
|
2036
|
+
className: "dsm-workbuddy-pool-select dsm-workbuddy-pool-row-model",
|
|
2037
|
+
value: modelOverride,
|
|
2038
|
+
disabled: !canEdit,
|
|
2039
|
+
title: t("row.poolRowModelHint"),
|
|
2040
|
+
"aria-label": t("row.poolRowModelAria", { accountName: name }),
|
|
2041
|
+
onChange: (event) => onModelChange(event.currentTarget.value)
|
|
2042
|
+
}, (0, react.createElement)("option", { value: "" }, t("row.poolRowModelInherit")), modelOverride !== "" && !catalog.some((model) => model.id === modelOverride) ? (0, react.createElement)("option", {
|
|
2043
|
+
value: modelOverride,
|
|
2044
|
+
disabled: true
|
|
2045
|
+
}, t("row.poolTargetStaleOption", { model: modelOverride })) : null, ...catalog.map((model) => (0, react.createElement)("option", {
|
|
2046
|
+
value: model.id,
|
|
2047
|
+
key: model.id
|
|
2048
|
+
}, model.name))), (0, react.createElement)("button", {
|
|
1959
2049
|
type: "button",
|
|
1960
2050
|
className: "dsm-btn dsm-btn-outline dsm-workbuddy-pool-small-btn",
|
|
1961
2051
|
disabled: busy || testDisabled,
|
|
@@ -2985,6 +3075,15 @@ window.__ModuleLoader__.load({
|
|
|
2985
3075
|
render (no Host pool state), so there is no heading row to share. This is the
|
|
2986
3076
|
spacing the control carried before it moved beside that heading. */
|
|
2987
3077
|
.dsm-workbuddy-credits-switch-row{display:flex;align-items:center;margin:8px 0 2px}
|
|
3078
|
+
/* The credit readout's seat picker (issue #62). Narrower than the pool's model
|
|
3079
|
+
select so the show/hide box and this control share one heading row, and it
|
|
3080
|
+
wraps onto its own line rather than squashing the pool title. */
|
|
3081
|
+
/* Its OWN label class, deliberately not the switch's: a query for "the
|
|
3082
|
+
sidebar-credit switch" must not be able to pick this control up, and the two
|
|
3083
|
+
are different widgets (a checkbox and a two-way picker). */
|
|
3084
|
+
.dsm-workbuddy-credit-position-switch{display:inline-flex;align-items:center;gap:6px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary,#c6c9d0)}
|
|
3085
|
+
.dsm-workbuddy-credit-position{max-width:150px;padding:2px 6px;font-size:12px}
|
|
3086
|
+
.dsm-workbuddy-credits-switch-row .dsm-workbuddy-credit-position-switch{margin-left:12px}
|
|
2988
3087
|
.dsm-workbuddy-credits-switch input{margin:0;cursor:pointer;accent-color:var(--dsw-alias-brand-primary,#5686fe)}
|
|
2989
3088
|
.dsm-workbuddy-credits-switch input:disabled{opacity:.4;cursor:default}
|
|
2990
3089
|
.dsm-workbuddy-tab-off{opacity:.55}
|
|
@@ -3140,6 +3239,10 @@ window.__ModuleLoader__.load({
|
|
|
3140
3239
|
.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)}
|
|
3141
3240
|
.dsm-workbuddy-pool-prov-sep{color:var(--dsw-alias-label-dimmed,#9aa0a6);opacity:.7}
|
|
3142
3241
|
.dsm-workbuddy-pool-prov-age{color:var(--dsw-alias-label-tertiary,#999);white-space:nowrap}
|
|
3242
|
+
/* WHICH model a verdict is about (issue #61). Deliberately quiet: it is scope
|
|
3243
|
+
information on the provenance line, not a status of its own, so an ok row and
|
|
3244
|
+
a rate-limited row must not look different because of it. */
|
|
3245
|
+
.dsm-workbuddy-pool-prov-model{color:var(--dsw-alias-label-tertiary,#999);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:190px}
|
|
3143
3246
|
.dsm-workbuddy-pool-members{display:flex;flex-direction:column;gap:10px}
|
|
3144
3247
|
.dsm-workbuddy-pool-members-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap}
|
|
3145
3248
|
.dsm-workbuddy-pool-members-title{margin:0 0 3px;font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e6e6)}
|
|
@@ -3193,6 +3296,9 @@ window.__ModuleLoader__.load({
|
|
|
3193
3296
|
/* Per-row actions (test / remove), right side of each pool row. */
|
|
3194
3297
|
.dsm-workbuddy-pool-row-actions{display:flex;gap:6px;align-items:center;justify-content:flex-end;flex:none}
|
|
3195
3298
|
.dsm-workbuddy-pool-row-actions .dsm-workbuddy-pool-small-btn{padding:2px 10px;font-size:12px}
|
|
3299
|
+
/* This account's own test model (issue #61). Narrower than the section-level
|
|
3300
|
+
picker so a long model name cannot push the two buttons out of the row. */
|
|
3301
|
+
.dsm-workbuddy-pool-row-model{max-width:150px;padding:2px 6px;font-size:12px}
|
|
3196
3302
|
@media (max-width:760px){.dsm-workbuddy-pool-row{grid-template-columns:1fr 1fr;row-gap:6px}.dsm-workbuddy-pool-select{max-width:180px}}
|
|
3197
3303
|
/* Pool switch, namespaced under .dsm-workbuddy-pool so it cannot collide with host styles. */
|
|
3198
3304
|
.dsm-workbuddy-pool .sw{position:relative;display:inline-block;width:38px;height:22px;flex:none;cursor:pointer}
|
|
@@ -4074,6 +4180,40 @@ window.__ModuleLoader__.load({
|
|
|
4074
4180
|
* hide never drops the directory, model picks, budgets or pool preferences
|
|
4075
4181
|
* that live alongside it.
|
|
4076
4182
|
*/
|
|
4183
|
+
/**
|
|
4184
|
+
* WHERE the active region's credit readout sits (issue #62).
|
|
4185
|
+
*
|
|
4186
|
+
* Read off the Host's usage answer, like {@link creditsShownOn} and for the
|
|
4187
|
+
* same measured reason: the browser's settings mirror for this namespace is
|
|
4188
|
+
* documented as stale on the affected 0.1.7 deployments, so a control reading
|
|
4189
|
+
* it would keep showing the previous seat after a successful write. Absent
|
|
4190
|
+
* means `composer`, which is what the Host resolves for a profile written
|
|
4191
|
+
* before the setting existed.
|
|
4192
|
+
*/
|
|
4193
|
+
const creditPositionOn = (item) => {
|
|
4194
|
+
return statusByRegion[item]?.creditPosition === "stats" ? "stats" : "composer";
|
|
4195
|
+
};
|
|
4196
|
+
/**
|
|
4197
|
+
* Move one region's credit readout between its two seats.
|
|
4198
|
+
*
|
|
4199
|
+
* Same shape as {@link toggleCredits}: whole region slot in, landed check,
|
|
4200
|
+
* then a Host re-read. The re-read is load-bearing here for the same reason —
|
|
4201
|
+
* the control renders the Host's answer, so without it the picker keeps
|
|
4202
|
+
* showing the seat it had and the change looks like it did nothing.
|
|
4203
|
+
*/
|
|
4204
|
+
const changeCreditPosition = async (item, position) => {
|
|
4205
|
+
if (settingsScope === void 0) return;
|
|
4206
|
+
setTogglingCredits(item);
|
|
4207
|
+
try {
|
|
4208
|
+
const slot = nextRegionCreditPosition(settingsScope.getSnapshot().value, item, position)[item];
|
|
4209
|
+
await writeRegionCreditPosition(settingsScope, item, position, typeof slot === "object" && slot !== null ? slot : {});
|
|
4210
|
+
await refreshUsage(item);
|
|
4211
|
+
} catch (error) {
|
|
4212
|
+
if (mounted.current) setAccountError(error instanceof Error ? error.message : t("row.requestFailed"));
|
|
4213
|
+
} finally {
|
|
4214
|
+
if (mounted.current) setTogglingCredits(void 0);
|
|
4215
|
+
}
|
|
4216
|
+
};
|
|
4077
4217
|
const toggleCredits = async (item, shown) => {
|
|
4078
4218
|
if (settingsScope === void 0) return;
|
|
4079
4219
|
setTogglingCredits(item);
|
|
@@ -4380,6 +4520,40 @@ window.__ModuleLoader__.load({
|
|
|
4380
4520
|
}
|
|
4381
4521
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: activeRegion === "cn" ? t("row.showCreditsCn") : t("row.showCreditsGlobal") })]
|
|
4382
4522
|
});
|
|
4523
|
+
/**
|
|
4524
|
+
* WHERE the readout sits — a two-way choice, active tab's region only
|
|
4525
|
+
* (issue #62).
|
|
4526
|
+
*
|
|
4527
|
+
* It sits directly under the show/hide switch because the two answer one
|
|
4528
|
+
* question together ("show it, and where"), and because a user only reaches
|
|
4529
|
+
* for this when the default seat is unavailable — typically after installing
|
|
4530
|
+
* another plugin that occupies the composer row. Rendered as a `<select>`
|
|
4531
|
+
* rather than a pair of radios so it stays one compact row on the pool
|
|
4532
|
+
* heading, matching the switch beside it.
|
|
4533
|
+
*
|
|
4534
|
+
* Disabled with the region, exactly like the switch: a region that is off has
|
|
4535
|
+
* no readout to place.
|
|
4536
|
+
*/
|
|
4537
|
+
const creditPositionPicker = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
4538
|
+
className: "dsm-workbuddy-credit-position-switch",
|
|
4539
|
+
title: t("row.creditPositionHint"),
|
|
4540
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
4541
|
+
className: "dsm-workbuddy-pool-select dsm-workbuddy-credit-position",
|
|
4542
|
+
value: activeRegionOn ? creditPositionOn(activeRegion) : "composer",
|
|
4543
|
+
disabled: !activeRegionOn || togglingCredits === activeRegion || !canWrite || saving || poolBusy,
|
|
4544
|
+
"aria-label": t("row.creditPositionAria", { region: activeRegion === "cn" ? t("row.tabCn") : t("row.tabGlobal") }),
|
|
4545
|
+
onChange: (event) => {
|
|
4546
|
+
changeCreditPosition(activeRegion, event.currentTarget.value === "stats" ? "stats" : "composer");
|
|
4547
|
+
},
|
|
4548
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
4549
|
+
value: "composer",
|
|
4550
|
+
children: t("row.creditPositionComposer")
|
|
4551
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
4552
|
+
value: "stats",
|
|
4553
|
+
children: t("row.creditPositionStats")
|
|
4554
|
+
})]
|
|
4555
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.creditPositionLabel") })]
|
|
4556
|
+
});
|
|
4383
4557
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
4384
4558
|
className: `dsm-plugin-card${open ? " dsm-plugin-card-open" : ""}`,
|
|
4385
4559
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
@@ -4520,15 +4694,15 @@ window.__ModuleLoader__.load({
|
|
|
4520
4694
|
},
|
|
4521
4695
|
selectingAccount: switchingAccount,
|
|
4522
4696
|
accountTools,
|
|
4523
|
-
...status.pool === void 0 ? {} : { headerExtra: creditsSwitch },
|
|
4697
|
+
...status.pool === void 0 ? {} : { headerExtra: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [creditsSwitch, creditPositionPicker] }) },
|
|
4524
4698
|
siblingBusy: saving || togglingRegion !== void 0,
|
|
4525
4699
|
onBusyChange: setPoolBusy,
|
|
4526
4700
|
onSaved: async () => await refreshUsage(activeRegion) !== void 0,
|
|
4527
4701
|
onRefresh: () => refreshUsage(activeRegion)
|
|
4528
4702
|
}),
|
|
4529
|
-
status.pool === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.
|
|
4703
|
+
status.pool === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4530
4704
|
className: "dsm-workbuddy-credits-switch-row",
|
|
4531
|
-
children: creditsSwitch
|
|
4705
|
+
children: [creditsSwitch, creditPositionPicker]
|
|
4532
4706
|
}) : null,
|
|
4533
4707
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
4534
4708
|
className: "dsm-workbuddy-models",
|
|
@@ -5314,6 +5488,17 @@ window.__ModuleLoader__.load({
|
|
|
5314
5488
|
* rows). The composer row is per-session and provider-scoped, so exactly one
|
|
5315
5489
|
* plugin's credits are relevant at a time and nothing overlaps.
|
|
5316
5490
|
*
|
|
5491
|
+
* WHY THERE IS A SECOND SEAT (issue #62). The composer tool row is a LIST seat,
|
|
5492
|
+
* so it is shared not only with the shell's own controls but with any other
|
|
5493
|
+
* plugin that registers there — and one report is exactly that: with a
|
|
5494
|
+
* third-party plugin installed the readout stopped appearing at all. A list seat
|
|
5495
|
+
* cannot promise that every occupant is visible, so the seat is the USER's
|
|
5496
|
+
* choice: `composer` (default, unchanged for anyone who does not choose) or
|
|
5497
|
+
* `stats`, the ambient dock below the composer card where the shell renders its
|
|
5498
|
+
* own round/token pills. The gate is registered in BOTH seats and renders in the
|
|
5499
|
+
* one the setting names, which keeps the decision in one function instead of
|
|
5500
|
+
* re-registering slots when the setting changes.
|
|
5501
|
+
*
|
|
5317
5502
|
* Kept separate from {@link ComposerCredits} (and from the browser-plugin
|
|
5318
5503
|
* entry) so the decision can be unit-tested without a Host: the entry imports
|
|
5319
5504
|
* browser-only DSH packages the test environment cannot load, while this module
|
|
@@ -5350,6 +5535,16 @@ window.__ModuleLoader__.load({
|
|
|
5350
5535
|
* the authority; the scope is the fallback for a Host line that predates the
|
|
5351
5536
|
* field.
|
|
5352
5537
|
*/
|
|
5538
|
+
/**
|
|
5539
|
+
* The settings tree this plugin writes, unwrapped to a plain object.
|
|
5540
|
+
*
|
|
5541
|
+
* `unwrapVolatileDeep` first: the scope's value is a live cosmokit reference on
|
|
5542
|
+
* the 0.1.7 line, and reading fields off it without unwrapping yields proxies
|
|
5543
|
+
* whose `===` comparisons against a string are false.
|
|
5544
|
+
*/
|
|
5545
|
+
function scopeValueOf(scope) {
|
|
5546
|
+
return scope === void 0 ? void 0 : unwrapVolatileDeep(scope.getSnapshot().value);
|
|
5547
|
+
}
|
|
5353
5548
|
function composerCreditsRegionOf(projection, scope, hostValue) {
|
|
5354
5549
|
const provider = selectedProviderOf(projection);
|
|
5355
5550
|
const region = provider === void 0 ? void 0 : WORKBUDDY_COMPOSER_PROVIDERS[provider];
|
|
@@ -5361,7 +5556,7 @@ window.__ModuleLoader__.load({
|
|
|
5361
5556
|
return region;
|
|
5362
5557
|
}
|
|
5363
5558
|
function ComposerCreditsGate(props) {
|
|
5364
|
-
const { t, settingsScope, useProjection } = props;
|
|
5559
|
+
const { t, position, settingsScope, useProjection } = props;
|
|
5365
5560
|
const [, setRevision] = (0, react.useState)(0);
|
|
5366
5561
|
(0, react.useEffect)(() => settingsScope?.subscribe(() => {
|
|
5367
5562
|
setRevision((value) => value + 1);
|
|
@@ -5369,6 +5564,7 @@ window.__ModuleLoader__.load({
|
|
|
5369
5564
|
const projection = useProjection === void 0 ? void 0 : useProjection("modelSelection");
|
|
5370
5565
|
const region = composerCreditsRegionOf(projection, settingsScope);
|
|
5371
5566
|
if (region === void 0) return null;
|
|
5567
|
+
if (regionCreditPositionOf(scopeValueOf(settingsScope), region) !== (position ?? "composer")) return null;
|
|
5372
5568
|
const provider = selectedProviderOf(projection);
|
|
5373
5569
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ComposerCredits, {
|
|
5374
5570
|
t,
|
|
@@ -5571,6 +5767,11 @@ window.__ModuleLoader__.load({
|
|
|
5571
5767
|
"row.poolProbeSourceTest": "tested",
|
|
5572
5768
|
"row.poolProbeSourceLive": "hit by a live request",
|
|
5573
5769
|
"row.poolProbeSourceUnknown": "source unknown",
|
|
5770
|
+
"row.poolProbeModel": "model {model}",
|
|
5771
|
+
"row.poolProbeModelUnknown": "model not recorded",
|
|
5772
|
+
"row.poolRowModelHint": "Which model this account's test uses. Leave it on \"region target\" to follow the pool's model; pick one when this account must be tested on the model you actually chat with.",
|
|
5773
|
+
"row.poolRowModelAria": "Test model for {accountName}",
|
|
5774
|
+
"row.poolRowModelInherit": "region target",
|
|
5574
5775
|
"row.poolSettingsTitle": "Account pool settings",
|
|
5575
5776
|
"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.",
|
|
5576
5777
|
"row.poolEnabled": "Enable the account pool",
|
|
@@ -5633,6 +5834,11 @@ window.__ModuleLoader__.load({
|
|
|
5633
5834
|
"row.showCreditsCn": "WB CN credits in composer",
|
|
5634
5835
|
"row.showCreditsGlobal": "WB AI credits in composer",
|
|
5635
5836
|
"row.showCreditsCnHint": "Show the domestic (WB CN) credit balance in the composer toolbar, but only while this session uses a WB CN model. Click it for per-account balances and a manual refresh (refreshes by itself every 5 minutes).",
|
|
5837
|
+
"row.creditPositionLabel": "position",
|
|
5838
|
+
"row.creditPositionComposer": "composer toolbar",
|
|
5839
|
+
"row.creditPositionStats": "below the input box",
|
|
5840
|
+
"row.creditPositionAria": "Credit readout position for {region}",
|
|
5841
|
+
"row.creditPositionHint": "Where the credit readout sits. \"composer toolbar\" is the default (the row with the model picker). Choose \"below the input box\" — the row carrying the round / step / token statistics — if another plugin occupies the composer toolbar and the readout does not appear.",
|
|
5636
5842
|
"row.showCreditsGlobalHint": "Show the international (WB AI) credit balance in the composer toolbar, but only while this session uses a WB AI model. Click it for per-account balances and a manual refresh (refreshes by itself every 5 minutes).",
|
|
5637
5843
|
"composer.pointsCn": "WB CN",
|
|
5638
5844
|
"composer.pointsGlobal": "WB AI",
|
|
@@ -5837,6 +6043,11 @@ window.__ModuleLoader__.load({
|
|
|
5837
6043
|
"row.poolProbeSourceTest": "测得",
|
|
5838
6044
|
"row.poolProbeSourceLive": "聊天撞上",
|
|
5839
6045
|
"row.poolProbeSourceUnknown": "来源未知",
|
|
6046
|
+
"row.poolProbeModel": "模型 {model}",
|
|
6047
|
+
"row.poolProbeModelUnknown": "未记录模型",
|
|
6048
|
+
"row.poolRowModelHint": "这个账号测试用哪个模型。留「区域目标」就跟随账号池的目标模型;当某个号必须用你实际在聊的模型来测时,在这里单独指定。",
|
|
6049
|
+
"row.poolRowModelAria": "{accountName} 的测试模型",
|
|
6050
|
+
"row.poolRowModelInherit": "区域目标",
|
|
5840
6051
|
"row.poolSettingsTitle": "账号池设置",
|
|
5841
6052
|
"row.poolSettingsHint": "下面是偏好设置:点保存才生效(与「模型管理」一致)。测试结果由插件自动写入,不受这个保存按钮影响。",
|
|
5842
6053
|
"row.poolEnabled": "启用账号池",
|
|
@@ -5899,6 +6110,11 @@ window.__ModuleLoader__.load({
|
|
|
5899
6110
|
"row.showCreditsCn": "对话框显示 WB CN 积分",
|
|
5900
6111
|
"row.showCreditsGlobal": "对话框显示 WB AI 积分",
|
|
5901
6112
|
"row.showCreditsCnHint": "在对话输入框工具栏显示国内版(WB CN)剩余积分,仅当本会话使用 WB CN 模型时出现。点击可看各账号余额并手动刷新(每 5 分钟也会自动刷新)。",
|
|
6113
|
+
"row.creditPositionLabel": "位置",
|
|
6114
|
+
"row.creditPositionComposer": "输入框工具栏",
|
|
6115
|
+
"row.creditPositionStats": "输入框下方",
|
|
6116
|
+
"row.creditPositionAria": "{region} 的积分显示位置",
|
|
6117
|
+
"row.creditPositionHint": "积分显示在哪里。「输入框工具栏」是默认位置(与模型选择同一行)。如果装了别的插件后读数不显示,改成「输入框下方」—— 那是「轮次 / 步数 / token」统计所在的那一行。",
|
|
5902
6118
|
"row.showCreditsGlobalHint": "在对话输入框工具栏显示国际版(WB AI)剩余积分,仅当本会话使用 WB AI 模型时出现。点击可看各账号余额并手动刷新(每 5 分钟也会自动刷新)。",
|
|
5903
6119
|
"composer.pointsCn": "WB CN",
|
|
5904
6120
|
"composer.pointsGlobal": "WB AI",
|
|
@@ -6022,8 +6238,12 @@ window.__ModuleLoader__.load({
|
|
|
6022
6238
|
id: "dsh-connect-workbuddy-composer-credits",
|
|
6023
6239
|
order: 4,
|
|
6024
6240
|
locale: namespace,
|
|
6025
|
-
inject: () => settingsScope === void 0 ? {
|
|
6241
|
+
inject: () => settingsScope === void 0 ? {
|
|
6026
6242
|
t,
|
|
6243
|
+
position: "composer"
|
|
6244
|
+
} : {
|
|
6245
|
+
t,
|
|
6246
|
+
position: "composer",
|
|
6027
6247
|
settingsScope
|
|
6028
6248
|
}
|
|
6029
6249
|
}, ComposerCreditsGate));
|
|
@@ -6033,6 +6253,46 @@ window.__ModuleLoader__.load({
|
|
|
6033
6253
|
}
|
|
6034
6254
|
};
|
|
6035
6255
|
/**
|
|
6256
|
+
* The SAME readout in the ambient dock below the composer card (issue #62).
|
|
6257
|
+
*
|
|
6258
|
+
* Registered unconditionally, like the composer seat, because the CHOICE
|
|
6259
|
+
* between the two seats lives in the gate: it renders in the seat the
|
|
6260
|
+
* setting names and returns null in the other. Registering only the chosen
|
|
6261
|
+
* seat would mean re-registering on every settings change, and the seat is
|
|
6262
|
+
* exactly the kind of thing a user flips once while looking at the result.
|
|
6263
|
+
*
|
|
6264
|
+
* WHY THIS SEAT EXISTS: the composer tool row is a list seat shared with any
|
|
6265
|
+
* other plugin that registers there, and issue #62 is one report of the
|
|
6266
|
+
* readout disappearing when another plugin was installed. This row is where
|
|
6267
|
+
* the shell renders its own round/step/token pills, so it is a place a user
|
|
6268
|
+
* already reads session numbers from.
|
|
6269
|
+
*
|
|
6270
|
+
* `order` places it after the shell's own pills (`stats` registers at 0), so
|
|
6271
|
+
* the shipped statistics stay leftmost and the readout reads as an addition
|
|
6272
|
+
* rather than a replacement.
|
|
6273
|
+
*/
|
|
6274
|
+
const registerStatsCredits = () => {
|
|
6275
|
+
try {
|
|
6276
|
+
return ctx.slots.inject("conversation.composer.dock", () => ctx.slots.register({
|
|
6277
|
+
name: "conversation.composer.dock",
|
|
6278
|
+
id: "dsh-connect-workbuddy-stats-credits",
|
|
6279
|
+
order: 10,
|
|
6280
|
+
locale: namespace,
|
|
6281
|
+
inject: () => settingsScope === void 0 ? {
|
|
6282
|
+
t,
|
|
6283
|
+
position: "stats"
|
|
6284
|
+
} : {
|
|
6285
|
+
t,
|
|
6286
|
+
position: "stats",
|
|
6287
|
+
settingsScope
|
|
6288
|
+
}
|
|
6289
|
+
}, ComposerCreditsGate));
|
|
6290
|
+
} catch (error) {
|
|
6291
|
+
console.error("[dsh-connect-workbuddy] stats credits slot failed to register (host provider unaffected):", error);
|
|
6292
|
+
return;
|
|
6293
|
+
}
|
|
6294
|
+
};
|
|
6295
|
+
/**
|
|
6036
6296
|
* Every slot this plugin contributes, and how to (re)create them.
|
|
6037
6297
|
*
|
|
6038
6298
|
* Kept as one list because the binding of `settingsScope` and the slot
|
|
@@ -6050,7 +6310,8 @@ window.__ModuleLoader__.load({
|
|
|
6050
6310
|
const registerAllSlots = () => [
|
|
6051
6311
|
...WORKBUDDY_CARD_KEYS.map((key) => registerCard("plugins.bundle.config", key)),
|
|
6052
6312
|
...WORKBUDDY_CARD_KEYS.map((key) => registerCard("plugins.row.config", `${key}#${key}`)),
|
|
6053
|
-
registerComposerCredits()
|
|
6313
|
+
registerComposerCredits(),
|
|
6314
|
+
registerStatsCredits()
|
|
6054
6315
|
].filter((disposer) => disposer !== void 0);
|
|
6055
6316
|
/**
|
|
6056
6317
|
* Disposers of the slots registered for the CURRENT binding.
|
|
@@ -1220,6 +1220,24 @@ function ensureSystemHead(obj) {
|
|
|
1220
1220
|
* `tool_choice` (the upstream's field is a string; object forms return 400),
|
|
1221
1221
|
* and guarantee a leading system message.
|
|
1222
1222
|
*/
|
|
1223
|
+
/**
|
|
1224
|
+
* The model id a prepared body asks for, when it carries one (issue #61).
|
|
1225
|
+
*
|
|
1226
|
+
* Lives beside {@link prepareChatBody} because it reads the same shape: the
|
|
1227
|
+
* serialized OpenAI body, after this plugin's rewrites. Never throws — a
|
|
1228
|
+
* malformed body is the upstream's to reject, not a reason to lose the failure
|
|
1229
|
+
* measurement that this id is needed for.
|
|
1230
|
+
*/
|
|
1231
|
+
function modelIdOfBody(bodyJson) {
|
|
1232
|
+
try {
|
|
1233
|
+
const parsed = JSON.parse(bodyJson);
|
|
1234
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return void 0;
|
|
1235
|
+
const model = parsed["model"];
|
|
1236
|
+
return typeof model === "string" && model !== "" ? model : void 0;
|
|
1237
|
+
} catch {
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1223
1241
|
function prepareChatBody(source) {
|
|
1224
1242
|
let body;
|
|
1225
1243
|
try {
|
|
@@ -3807,7 +3825,7 @@ function generationOf(version) {
|
|
|
3807
3825
|
//#endregion
|
|
3808
3826
|
//#region src/version.ts
|
|
3809
3827
|
/** The npm package version this build was produced from. */
|
|
3810
|
-
const WORKBUDDY_CONNECT_VERSION = "4.
|
|
3828
|
+
const WORKBUDDY_CONNECT_VERSION = "4.5.0";
|
|
3811
3829
|
//#endregion
|
|
3812
3830
|
//#region src/host-heartbeat.ts
|
|
3813
3831
|
/**
|
|
@@ -3965,4 +3983,4 @@ async function clearHostHeartbeat() {
|
|
|
3965
3983
|
await rm(workbuddyHostHeartbeatPath(), { force: true });
|
|
3966
3984
|
}
|
|
3967
3985
|
//#endregion
|
|
3968
|
-
export {
|
|
3986
|
+
export { isEncryptedFieldWrapper as $, workbuddyAccountId as A, modelIdOfBody as B, defaultDesktopAuthDirs as C, isVaultAccountId as D, isEncryptedCredentialError as E, WorkBuddyUpstreamClient as F, regionOf as G, parseReasoning as H, classifyUpstreamError as I, deriveAtRestKey as J, WORKBUDDY_APP_EXECUTABLE_ENV as K, declaredTools as L, CREDENTIAL_REJECTED_CODE as M, WORKBUDDY_FALLBACK_SYSTEM_PROMPT as N, legacyWorkbuddyOwnAuthPath as O, WorkBuddyCredentialRejectedError as P, findWorkbuddyAppExecutableWithSource as Q, isCredentialRejectedError as R, defaultDesktopAuthCandidates as S, hasEncryptedCredentialFields as T, parseUpstreamModel as U, parseCreditMultiplier as V, prepareChatBody as W, fetchAtRestKeyPayload as X, deriveAtRestKeyId as Y, findWorkbuddyAppExecutable as Z, WORKBUDDY_AUTH_FILENAME as _, readHostHeartbeat as a, workbuddyAppExecutableCandidates as at, WorkBuddyEncryptedCredentialError as b, WORKBUDDY_CONNECT_VERSION as c, withFileLock as ct, FALLBACK_WORKBUDDY_MODELS_GLOBAL as d, isWorkbuddyBundle as et, WorkBuddyCatalog as f, ENCRYPTED_CREDENTIAL_CODE as g, fallbackModelsFor as h, processStartTimeMs as i, readAtRestKey as it, workbuddyOwnAuthPath as j, parseWorkBuddyAuth as k, piAiRuntimeInfo as l, writeFileAtomic as lt, deriveCatalog as m, clearHostHeartbeat as n, macosNestedAppBundles as nt, workbuddyHostHeartbeatPath as o, hostPeersInfo as ot, applyContextBudgets as p, clearAtRestKeyCache as q, isHeartbeatProcessAlive as r, openEncryptedField as rt, writeHostHeartbeat as s, resolveDshHome as st, WORKBUDDY_HOST_HEARTBEAT_FILENAME as t, macosBundleExecutable as tt, FALLBACK_WORKBUDDY_MODELS as u, WORKBUDDY_AUTH_FILE_ENV as v, defaultDesktopAuthPath as w, authFileName as x, WorkBuddyCredentialStore as y, knownRegionOf as z };
|