dsh-connect-workbuddy 5.5.0 → 5.5.1
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 +11 -1
- package/lib/bin.js +1 -1
- package/lib/client.js +166 -7
- package/lib/{host-heartbeat-CVnH8S3J.js → host-heartbeat-BhgyeZWV.js} +1 -1
- package/lib/index.d.ts +52 -1
- package/lib/index.js +263 -23
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 5.5.1 (2026-10-23)
|
|
4
|
+
|
|
5
|
+
> 补丁版本:修「账号池状态重启就失效」的根因,以及同一症状的另外三处(手动排序箭头、`regionsMapOf` 跳层读旧副本、卡片保存把脏数据带回去)。**没有新增任何功能**:请求路径、provider 路由、模型目录、计费与凭据一律未变,5.5.0 的全部能力原样保留。
|
|
6
|
+
> **本版会主动改写一个设置字段的形状**:被 5.4.0–5.5.0 写脏的 `regions` 字段会在**开机时**被清洗一次(只动形状,不动任何区域内容,干净 profile 不写)。如果你正是「账号池重启就失效」的受害者,升级后重启一次即可恢复,**不需要手动改 profile**;国际版整槽沉到第 4 层的情况也会被捞回来(本机实测 3 个成员与 27 个模型目录)。
|
|
4
7
|
|
|
5
8
|
### Fixes
|
|
6
9
|
|
|
7
10
|
- **排序箭头在「账号池还没保存开启」时不出现 —— 先勾选再排序这条自然路径被堵死。** `AccountPool.tsx` 的 `moveEnabled` 读的是**已保存**的 `pool.enabled`,而同一行旁边的 `isMember` 读的却是**草稿** `active.memberAccountIds`。于是:勾选账号入池(草稿变了)→ 想去排序 → **箭头根本不存在**,因为开关还没保存;用户既看不到箭头,也就没有理由去按保存,下一次保存自然「什么都没写」——这正是「手动排序没保存」的实际形态。改成读草稿的 `active.enabled`:箭头描述的是草稿,就该跟着草稿走(报告运行中的池仍然是 `poolRunning`,那个值没有动)。变异验证:改回 `pool.enabled`,新增用例立刻红(期望 4 个箭头、实际 0 个)。
|
|
8
11
|
- **`regionsMapOf()` 会跳到更深一层,读到同名区域的过期副本。** 旧规则是「只要有 `regions` 键就往下跳」,理由是可以同时接受「整份 settings」和「regions map」两种入参。但 regions **map 自己也可能有 `regions` 键** —— 本机真实 profile 里 `config.regions` 下就躺着 `accounts` / `regions` / `cliBridge` 三个旧写入留下的兄弟键。一旦跳下去,读到的就是**另一份更旧的同名区域**:实测同一份文档,「按 section 读」得到 `['c','a','b']`,「按 map 读」得到 `[]`(正是「排序明明存了却读不出来」)。现在改为**按形状判定**:外层对象只要含有区域 id(`cn` / `global`)就是 map,直接用它;否则才是 section,才往里跳。变异验证:把判定换回无条件跳,新增用例立刻红。
|
|
12
|
+
- **「账号池状态重启就失效」的根因:目录修复把整份 settings 写进了 `regions` 字段。** 上一条修的是**读**,这一条修的是**造出那份脏数据的写**。`writeRegionCatalog()` 用 `{ op: 'set', path: ['regions'] }` 写回目录,所以它的值必须是 `regions` **字段**;但它把 `describe().value`(**整份 section**:`{authFile, accounts, regions, cliBridge, …}`)交给了 `mergeRegionCatalog()`,而后者把入参当字段读、又原样展开写回。结果每次开机目录修复都把 section 写进字段:真正的区域表被推到下一层 `regions` 键里,`accounts` / `cliBridge` 成了两个假区域槽,于是宿主 `regionStateOf(config, 'cn')`(只读一层)读到的是**空** —— 账号池开关、成员、手动排序、目标模型、模型选择全部「看起来没保存」,下次保存再把这份空写回去。每次开机多一层:本机实测 5 层、字段 103 KB(其中 3 层是纯拷贝)。修法:`mergeRegionCatalog()` 统一走 `regionsMapOf()`(接受 section 或字段),返回值恒为字段本身;`regionStateOf()`、设置保存端点 `__save`、卡片各写路径的合并基点、以及 `readOrderAccountIds()` 全部改走同一个窄化函数。
|
|
13
|
+
- **脏 profile 不靠人手修**:`regionsMapOf()` 现在同时做三件事 —— 丢掉 section/槽位的兄弟键名拷贝;把只活在下层的区域**捞回来**(本机 `global` 就是这种情况:外层 `cn` 还在、国际版整槽只在下一层,不捞就等于让第一次写入删掉用户的国际版池);「在表里的区域」仍然以外层为准,且当多个深层副本都有同一区域时按**用户配置量**取舍而不是按深浅(本机就有 `{lastCatalog}` 的空壳副本躺在一个仍有 3 个成员的真副本上面 —— 按深浅会捞回空壳)。修复写仍只在目录指纹变化时触发,但**字段脏时也写一次**,否则「目录恰好一致」的 profile 永远等不到打扫。
|
|
14
|
+
- **修复写不再「只在目录变了才写」**:目录指纹相同时,只要 `Config['regions']` 还带兄弟键名就照写一次(`regionsFieldIsCorrupt()`),否则「目录恰好与上游一致」的 profile 永远等不到打扫 —— 那种 profile 的脏数据会活得比产生它的 bug 还久。写一次即净,之后这条分支不再触发。
|
|
15
|
+
- **开机再补一次独立清洗**(`healRegionsField()`):上面那条挂在「拿到实时模型目录」之后,于是关掉的区域、断网启动、上游空回答这三种情况都不会清洗。新增一次纯形状写入 —— 不需要上游、不碰任何区域内容、干净 profile 直接返回不写(只扫一遍键名),失败只记 warn(读侧本来就已是对的)。
|
|
16
|
+
- **代价控制在请求路径上**:`regionStateOf()` 现在是每次请求都会走的读(池开关、模型选择、额度门控),而 heal 会深拷贝整份字段(每区一份多模型目录)。实测 28 模型槽位:不做快路径是 **11 µs/次**(原来裸读 0.05 µs),加「干净字段原样返回」的快路径后回到 **0.09 µs**;脏字段 0.86 µs 且只到 profile 被写入修复为止。快路径的成立条件写在代码注释里(只有**字段本身**是 volatile,槽内是普通数据),并用同引用断言(`toBe`)钉住,防止以后被顺手删掉。
|
|
17
|
+
- **实测(本机真实 profile)**:修复前该字段 5 层嵌套、103 KB;用新 `mergeRegionCatalog()` 对同一份 section 重算,输出恰为 `['cn','global']`,13 KB;`cn` 保住 `pool.enabled=true` / `step-5-preview` / **8 个成员**,`global` 从下层捞回 `pool.enabled=true` / `deepseek-v4.1-flash` / **3 个成员**与 27 个模型目录。另有「主动清空」的保护:外层已显式写成 `null`(schemastery 把 `{}` 简化成 `null`)时不再从下层捞,否则会把用户清掉的池子复活。
|
|
18
|
+
- **变异验证(六处,逐个改回旧行为都必红)**:①`mergeRegionCatalog()` 改回「把入参当字段」→ `catalog-write.spec.ts` 红 4 条;②`regionStateOf()` 改回单层裸读 → `region.spec.ts` 红(`global` 读成空);③`__save` 改回对原始字段合并 → `web-status.spec.ts` 红(写回的值里出现 `regions` 兄弟键、`global` 消失);④卡片写路径改回直接展开原始快照 → `client-account-selection.spec.ts` 红;⑤删掉快路径 → `region.spec.ts` 的同引用断言红;⑥删掉开机清洗的脏判断 → `catalog-write.spec.ts` 的「干净字段不写」红。新增 24 条测试(`catalog-write.spec.ts` 13 条、`region.spec.ts` 8 条、`web-status.spec.ts` / `client-account-selection.spec.ts` / `manual-order.spec.ts` 各 1 条)。
|
|
9
19
|
|
|
10
20
|
### Docs
|
|
11
21
|
|
package/lib/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as defaultDesktopAuthCandidates, L as WorkBuddyUpstreamClient, M as workbuddyOwnAuthPath, Mt as hostPeersInfo, a as readHostHeartbeat, b as WorkBuddyCredentialStore, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, k as legacyWorkbuddyOwnAuthPath, l as piAiRuntimeInfo, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS, vt as WORKBUDDY_APP_EXECUTABLE_ENV, w as defaultDesktopAuthDirs, wt as findWorkbuddyAppExecutableWithSource, y as WORKBUDDY_AUTH_FILE_ENV } from "./host-heartbeat-
|
|
2
|
+
import { C as defaultDesktopAuthCandidates, L as WorkBuddyUpstreamClient, M as workbuddyOwnAuthPath, Mt as hostPeersInfo, a as readHostHeartbeat, b as WorkBuddyCredentialStore, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, k as legacyWorkbuddyOwnAuthPath, l as piAiRuntimeInfo, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS, vt as WORKBUDDY_APP_EXECUTABLE_ENV, w as defaultDesktopAuthDirs, wt as findWorkbuddyAppExecutableWithSource, y as WORKBUDDY_AUTH_FILE_ENV } from "./host-heartbeat-BhgyeZWV.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
|
@@ -204,15 +204,166 @@ window.__ModuleLoader__.load({
|
|
|
204
204
|
* counts as the whole section only when it shows a key that is neither a region
|
|
205
205
|
* id nor a known sibling — i.e. it looks like a settings document. A plain (or
|
|
206
206
|
* dirty) `cn` / `global` map is returned as-is.
|
|
207
|
+
*
|
|
208
|
+
* THE RESULT IS HEALED, not merely narrowed — see {@link healedRegionsMap}.
|
|
209
|
+
* Returning the map "as-is" is what let a corrupted document keep reading as
|
|
210
|
+
* half-empty: the field held `cn` at level 0 and `global` only one level down,
|
|
211
|
+
* so every `global` question answered "never configured" even though the file
|
|
212
|
+
* still had the answer. Reads and writes both come through here, so healing
|
|
213
|
+
* here fixes both halves at once (the Host's `regionStateOf`, the card's
|
|
214
|
+
* `nextRegion*` write bases, and the catalog repair's write).
|
|
207
215
|
*/
|
|
208
216
|
function regionsMapOf(value) {
|
|
217
|
+
const peeled = peelLiveReference(value);
|
|
218
|
+
if (typeof peeled === "object" && peeled !== null && !Array.isArray(peeled)) {
|
|
219
|
+
const candidate = peeled;
|
|
220
|
+
if (!isRecord(candidate["regions"]) && !carriesForeignKey(candidate)) return candidate;
|
|
221
|
+
}
|
|
209
222
|
const unwrapped = unwrapVolatileDeep(value);
|
|
210
223
|
if (typeof unwrapped !== "object" || unwrapped === null || Array.isArray(unwrapped)) return {};
|
|
211
224
|
const record = unwrapped;
|
|
212
225
|
const nested = record["regions"];
|
|
213
|
-
if (typeof nested !== "object" || nested === null || Array.isArray(nested)) return record;
|
|
214
|
-
|
|
215
|
-
|
|
226
|
+
if (typeof nested !== "object" || nested === null || Array.isArray(nested)) return healedRegionsMap(record);
|
|
227
|
+
return healedRegionsMap(hasRegionKey(record) ? record : nested);
|
|
228
|
+
}
|
|
229
|
+
/** `value` with ONE `{get(): T}` live reference peeled, exactly like a read. */
|
|
230
|
+
function peelLiveReference(value) {
|
|
231
|
+
if (value === null || typeof value !== "object") return value;
|
|
232
|
+
const get = value.get;
|
|
233
|
+
return typeof get === "function" ? get() : value;
|
|
234
|
+
}
|
|
235
|
+
/** Whether the object carries a sibling-field name where a region id belongs. */
|
|
236
|
+
function carriesForeignKey(record) {
|
|
237
|
+
return Object.keys(record).some((key) => REGION_MAP_FOREIGN_KEYS.has(key));
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Names that belong to the settings SECTION (`Config` in `src/index.ts`) or to
|
|
241
|
+
* one region SLOT (`regionStateConfig`) — never to the `regions` map.
|
|
242
|
+
*
|
|
243
|
+
* They are the signature of a data-loss bug and the list that undoes it. From
|
|
244
|
+
* 5.4.0 to 5.5.0 `mergeRegionCatalog` was handed the WHOLE settings section but
|
|
245
|
+
* read it as the `regions` field, so every boot's directory repair wrote
|
|
246
|
+
* `{ accounts, regions, cliBridge }` INTO the field — replacing the region map
|
|
247
|
+
* and burying the real one under a `regions` key. A live profile ended up five
|
|
248
|
+
* levels deep (one per startup) and 258 KB of duplicated slots.
|
|
249
|
+
*
|
|
250
|
+
* Neither a region id (`cn` / `global`) nor any future one can collide with
|
|
251
|
+
* these names, so removing them from the map is safe, idempotent, and does not
|
|
252
|
+
* discard a region that a newer build might add.
|
|
253
|
+
*/
|
|
254
|
+
const REGION_MAP_FOREIGN_KEYS = /* @__PURE__ */ new Set([
|
|
255
|
+
"authFile",
|
|
256
|
+
"accountId",
|
|
257
|
+
"accounts",
|
|
258
|
+
"regions",
|
|
259
|
+
"cliBridge",
|
|
260
|
+
"enabled",
|
|
261
|
+
"showCreditsInMainUi",
|
|
262
|
+
"creditPosition",
|
|
263
|
+
"lastCatalog",
|
|
264
|
+
"enabledModelIds",
|
|
265
|
+
"imageModelIds",
|
|
266
|
+
"imageToolEnabled",
|
|
267
|
+
"offModelIds",
|
|
268
|
+
"contextBudgets",
|
|
269
|
+
"pool"
|
|
270
|
+
]);
|
|
271
|
+
/** A non-null, non-array object: the only shape a slot or a level can have. */
|
|
272
|
+
function isRecord(value) {
|
|
273
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
274
|
+
}
|
|
275
|
+
/** Whether a map carries at least one real region id. */
|
|
276
|
+
function hasRegionKey(map) {
|
|
277
|
+
return Object.keys(map).some((key) => WORKBUDDY_REGIONS.includes(key));
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* How much USER CONFIG one candidate slot actually carries.
|
|
281
|
+
*
|
|
282
|
+
* Used only when the map lost a region and the copies left at different depths
|
|
283
|
+
* disagree — and they do: the bug's own directory repair wrote a slot that held
|
|
284
|
+
* nothing but `lastCatalog` (it started from a map where the region was already
|
|
285
|
+
* missing), while the copy further down still holds the pool the user had
|
|
286
|
+
* checked in. "Shallowest wins" would therefore resurrect the EMPTY one and
|
|
287
|
+
* throw away the real configuration. Depth is not age in this wreckage; CONTENT
|
|
288
|
+
* is the only usable signal.
|
|
289
|
+
*
|
|
290
|
+
* Deliberately counts only non-default, user-authored values: resolved slots
|
|
291
|
+
* carry every schema default, so a slot's key count says nothing.
|
|
292
|
+
*/
|
|
293
|
+
function slotConfigWeight(slot) {
|
|
294
|
+
let weight = 0;
|
|
295
|
+
const pool = slot["pool"];
|
|
296
|
+
if (isRecord(pool)) {
|
|
297
|
+
if (pool["enabled"] === true) weight += 4;
|
|
298
|
+
if (Array.isArray(pool["memberAccountIds"]) && pool["memberAccountIds"].length > 0) weight += 4;
|
|
299
|
+
if (Array.isArray(pool["orderAccountIds"]) && pool["orderAccountIds"].length > 0) weight += 2;
|
|
300
|
+
if (typeof pool["targetModelId"] === "string" && pool["targetModelId"] !== "") weight += 2;
|
|
301
|
+
if (typeof pool["probeInputTokens"] === "number" && pool["probeInputTokens"] > 0) weight += 1;
|
|
302
|
+
}
|
|
303
|
+
for (const key of [
|
|
304
|
+
"enabledModelIds",
|
|
305
|
+
"imageModelIds",
|
|
306
|
+
"offModelIds"
|
|
307
|
+
]) {
|
|
308
|
+
const list = slot[key];
|
|
309
|
+
if (Array.isArray(list) && list.length > 0) weight += 1;
|
|
310
|
+
}
|
|
311
|
+
const budgets = slot["contextBudgets"];
|
|
312
|
+
if (isRecord(budgets) && Object.keys(budgets).length > 0) weight += 1;
|
|
313
|
+
if (slot["enabled"] === false) weight += 1;
|
|
314
|
+
if (slot["showCreditsInMainUi"] === false) weight += 1;
|
|
315
|
+
if (slot["imageToolEnabled"] === true) weight += 1;
|
|
316
|
+
return weight;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* The `regions` map with a corrupted document's wreckage removed.
|
|
320
|
+
*
|
|
321
|
+
* Two repairs, both keyed on the bug's signature (a foreign key or a nested
|
|
322
|
+
* `regions` copy) so a HEALTHY map passes through untouched:
|
|
323
|
+
*
|
|
324
|
+
* 1. DROP every key that names a section field or a slot field. Those are the
|
|
325
|
+
* copies the bug wrote; keeping them means the next save carries them
|
|
326
|
+
* forward forever.
|
|
327
|
+
* 2. RECOVER a region this level does not have from the copies buried beneath
|
|
328
|
+
* it. A region that IS here is never replaced — the outer copy is the one
|
|
329
|
+
* the surviving writers have been updating, and re-reading a stale inner
|
|
330
|
+
* copy over it is exactly the defect the shape rule above was fixed for.
|
|
331
|
+
*
|
|
332
|
+
* Recovery exists because `global` in the measured profile lived ONLY one level
|
|
333
|
+
* down: without it, the host reads that region as "never configured" and the
|
|
334
|
+
* first write after the fix would delete the user's international pool outright.
|
|
335
|
+
*
|
|
336
|
+
* Among the buried copies of a MISSING region, the one carrying the most user
|
|
337
|
+
* configuration wins rather than the shallowest — see {@link slotConfigWeight}:
|
|
338
|
+
* the bug's own directory repair left `{ lastCatalog }` slots that look newer
|
|
339
|
+
* but hold nothing, one level above the copy with the pool still in it.
|
|
340
|
+
*/
|
|
341
|
+
function healedRegionsMap(map) {
|
|
342
|
+
const healed = {};
|
|
343
|
+
for (const [key, slot] of Object.entries(map)) {
|
|
344
|
+
if (REGION_MAP_FOREIGN_KEYS.has(key)) continue;
|
|
345
|
+
healed[key] = slot;
|
|
346
|
+
}
|
|
347
|
+
const recovered = {};
|
|
348
|
+
let deeper = map;
|
|
349
|
+
for (let depth = 0; depth < 8; depth += 1) {
|
|
350
|
+
const nested = deeper["regions"];
|
|
351
|
+
if (!isRecord(nested)) break;
|
|
352
|
+
for (const region of WORKBUDDY_REGIONS) {
|
|
353
|
+
if (region in map) continue;
|
|
354
|
+
const candidate = nested[region];
|
|
355
|
+
if (!isRecord(candidate)) continue;
|
|
356
|
+
const weight = slotConfigWeight(candidate);
|
|
357
|
+
const best = recovered[region];
|
|
358
|
+
if (best === void 0 || weight > best.weight) recovered[region] = {
|
|
359
|
+
slot: candidate,
|
|
360
|
+
weight
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
deeper = nested;
|
|
364
|
+
}
|
|
365
|
+
for (const [region, entry] of Object.entries(recovered)) healed[region] = entry.slot;
|
|
366
|
+
return healed;
|
|
216
367
|
}
|
|
217
368
|
/** One region's stored slot as a plain object; any other shape reads as empty. */
|
|
218
369
|
function regionSlotOf(value, region) {
|
|
@@ -537,7 +688,9 @@ window.__ModuleLoader__.load({
|
|
|
537
688
|
* value, so reading it wrong silently deleted the untouched region.
|
|
538
689
|
*/
|
|
539
690
|
function fieldSnapshotOf(scope, field) {
|
|
540
|
-
const
|
|
691
|
+
const stored = scope.getSnapshot().value?.[field];
|
|
692
|
+
if (field === "regions") return regionsMapOf(stored);
|
|
693
|
+
const value = unwrapVolatileDeep(stored);
|
|
541
694
|
return typeof value === "object" && value !== null ? value : {};
|
|
542
695
|
}
|
|
543
696
|
/**
|
|
@@ -884,12 +1037,18 @@ window.__ModuleLoader__.load({
|
|
|
884
1037
|
* `orderAccountIds` may be anything at all. Every shape that is not a list of
|
|
885
1038
|
* strings means "no manual order", which is also an older profile's spelling —
|
|
886
1039
|
* never a crash.
|
|
1040
|
+
*
|
|
1041
|
+
* The map comes from `regionsMapOf` rather than a bare `value.regions` lookup:
|
|
1042
|
+
* it accepts the section OR the field and heals a field an older build
|
|
1043
|
+
* corrupted (the "restart reset" report). That is not cosmetic here — on the
|
|
1044
|
+
* measured profile the INTERNATIONAL slot survived only one level down, so a
|
|
1045
|
+
* direct lookup reported "no manual order" for the region whose order the user
|
|
1046
|
+
* had actually saved, and the credit panel listed those accounts in the Host's
|
|
1047
|
+
* order instead.
|
|
887
1048
|
*/
|
|
888
1049
|
function readOrderAccountIds(value, region) {
|
|
889
1050
|
if (region === void 0) return [];
|
|
890
|
-
const
|
|
891
|
-
if (typeof regions !== "object" || regions === null) return [];
|
|
892
|
-
const slot = regions[region];
|
|
1051
|
+
const slot = regionsMapOf(value)[region];
|
|
893
1052
|
if (typeof slot !== "object" || slot === null) return [];
|
|
894
1053
|
const order = slot.pool;
|
|
895
1054
|
if (typeof order !== "object" || order === null) return [];
|
|
@@ -5966,7 +5966,7 @@ function generationOf(version) {
|
|
|
5966
5966
|
//#endregion
|
|
5967
5967
|
//#region src/version.ts
|
|
5968
5968
|
/** The npm package version this build was produced from. */
|
|
5969
|
-
const WORKBUDDY_CONNECT_VERSION = "5.5.
|
|
5969
|
+
const WORKBUDDY_CONNECT_VERSION = "5.5.1";
|
|
5970
5970
|
//#endregion
|
|
5971
5971
|
//#region src/host-heartbeat.ts
|
|
5972
5972
|
/**
|
package/lib/index.d.ts
CHANGED
|
@@ -2911,6 +2911,57 @@ declare function withWorkBuddyRegion(path: string, region: WorkBuddyWebRegion):
|
|
|
2911
2911
|
* answer 400 instead of guessing.
|
|
2912
2912
|
*/
|
|
2913
2913
|
declare function regionOfStatusUrl(url: string): WorkBuddyWebRegion | undefined;
|
|
2914
|
+
/**
|
|
2915
|
+
* Narrow a settings value to the `regions` map. Accepts EITHER the whole
|
|
2916
|
+
* settings section (the Host's resolved `Config`) OR the `regions` map itself,
|
|
2917
|
+
* and unwraps the former. This tolerance is deliberate: passing the whole
|
|
2918
|
+
* section where the map was expected was a real shipped bug in the sibling
|
|
2919
|
+
* project — the lookup then read `section['cn']` (absent), so the card's
|
|
2920
|
+
* checkbox reported `true` forever and clicking it appeared to do nothing even
|
|
2921
|
+
* though the write succeeded.
|
|
2922
|
+
*
|
|
2923
|
+
* The resolved section delivers `regions` as a `{get(): T}` LIVE reference, and
|
|
2924
|
+
* a live reference is `typeof === 'object'` and not an array — so it passes a
|
|
2925
|
+
* naive object check and gets returned as if it were the map. Every lookup on
|
|
2926
|
+
* it is then `undefined`: the enabled flag reads back as "on" forever (the very
|
|
2927
|
+
* symptom described above), and a merge that spreads this map silently drops
|
|
2928
|
+
* the region it was not editing. Unwrap before narrowing.
|
|
2929
|
+
*
|
|
2930
|
+
* THE HOP IS CONDITIONAL, and that is the whole point (a saved manual pool
|
|
2931
|
+
* order "disappearing"). "Has a `regions` key" is NOT sufficient evidence that
|
|
2932
|
+
* the caller passed the whole section: the `regions` MAP can legitimately carry
|
|
2933
|
+
* a `regions` key of its own, because an earlier build could write a sibling
|
|
2934
|
+
* field name into it — a live profile was found with `accounts`, `regions` and
|
|
2935
|
+
* `cliBridge` sitting beside `cn` / `global`. Hopping there means reading a
|
|
2936
|
+
* DIFFERENT, older copy of the same region: measured, the same document returned
|
|
2937
|
+
* `['c','a','b']` when read as a section and `[]` when read as a map. So a value
|
|
2938
|
+
* counts as the whole section only when it shows a key that is neither a region
|
|
2939
|
+
* id nor a known sibling — i.e. it looks like a settings document. A plain (or
|
|
2940
|
+
* dirty) `cn` / `global` map is returned as-is.
|
|
2941
|
+
*
|
|
2942
|
+
* THE RESULT IS HEALED, not merely narrowed — see {@link healedRegionsMap}.
|
|
2943
|
+
* Returning the map "as-is" is what let a corrupted document keep reading as
|
|
2944
|
+
* half-empty: the field held `cn` at level 0 and `global` only one level down,
|
|
2945
|
+
* so every `global` question answered "never configured" even though the file
|
|
2946
|
+
* still had the answer. Reads and writes both come through here, so healing
|
|
2947
|
+
* here fixes both halves at once (the Host's `regionStateOf`, the card's
|
|
2948
|
+
* `nextRegion*` write bases, and the catalog repair's write).
|
|
2949
|
+
*/
|
|
2950
|
+
declare function regionsMapOf(value: unknown): Record<string, unknown>;
|
|
2951
|
+
/**
|
|
2952
|
+
* Whether the stored `regions` FIELD still carries the debris described at
|
|
2953
|
+
* {@link REGION_MAP_FOREIGN_KEYS}.
|
|
2954
|
+
*
|
|
2955
|
+
* Pass the field itself (`Config['regions']`), not the whole section: the
|
|
2956
|
+
* section legitimately has `accounts` / `cliBridge` beside `regions`, so asking
|
|
2957
|
+
* that question of a section would always answer "corrupt".
|
|
2958
|
+
*
|
|
2959
|
+
* The catalog repair uses it to write even when the model directory already
|
|
2960
|
+
* matches: without that, a profile whose directory is in agreement would never
|
|
2961
|
+
* be cleaned — the very shape that made the pool look unsaved would outlive the
|
|
2962
|
+
* fix that stops producing it.
|
|
2963
|
+
*/
|
|
2964
|
+
declare function regionsFieldIsCorrupt(value: unknown): boolean;
|
|
2914
2965
|
/**
|
|
2915
2966
|
* Whether one region's provider is switched on. Opt-out semantics: only an
|
|
2916
2967
|
* explicit `false` disables it, so a config written before this switch existed
|
|
@@ -4446,4 +4497,4 @@ declare function legacyAttributionRegion(value: Config, accountsFor: (region: Wo
|
|
|
4446
4497
|
*/
|
|
4447
4498
|
declare function apply(ctx: Context, config: Config): void;
|
|
4448
4499
|
//#endregion
|
|
4449
|
-
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, REFUSES_OFF_MODEL_IDS, 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_TPS_PATH, 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, defaultOffModelIds, deriveAtRestKey, deriveAtRestKeyId, deriveCatalog, fallbackModelsFor, fetchAtRestKeyPayload, findWorkbuddyAppExecutable, hasEncryptedCredentialFields, imageDefaultFor, inject, isCredentialRejectedError, isEncryptedCredentialError, isEncryptedFieldWrapper, isHeartbeatProcessAlive, isWorkbuddyBundle, knownRegionOf, legacyAttributionRegion, legacyWorkbuddyOwnAuthPath, macosBundleExecutable, macosNestedAppBundles, modelIdOfBody, name, nativeModalityOf, nextRegionEnabled, nextRegionSlots, offDefaultFor, 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, withEffectiveOff, withWorkBuddyRegion, workBuddyDisplayName, workBuddyModelInput, workBuddyThinkingLevelMap, workBuddyWebStatus, workbuddyAccountId, workbuddyAppExecutableCandidates, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, writeHostHeartbeat };
|
|
4500
|
+
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, REFUSES_OFF_MODEL_IDS, 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_TPS_PATH, 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, defaultOffModelIds, deriveAtRestKey, deriveAtRestKeyId, deriveCatalog, fallbackModelsFor, fetchAtRestKeyPayload, findWorkbuddyAppExecutable, hasEncryptedCredentialFields, imageDefaultFor, inject, isCredentialRejectedError, isEncryptedCredentialError, isEncryptedFieldWrapper, isHeartbeatProcessAlive, isWorkbuddyBundle, knownRegionOf, legacyAttributionRegion, legacyWorkbuddyOwnAuthPath, macosBundleExecutable, macosNestedAppBundles, modelIdOfBody, name, nativeModalityOf, nextRegionEnabled, nextRegionSlots, offDefaultFor, openEncryptedField, outcomeOfFailure, parseCreditMultiplier, parseReasoning, parseRetryAfter, parseUpstreamModel, parseWorkBuddyAuth, prepareChatBody, probeModel, probeRequestBody, probeSucceeded, processStartTimeMs, readAtRestKey, readHostHeartbeat, regionCleared, regionEnabled, regionEnabledOf, regionOf, regionOfProvider, regionOfStatusUrl, regionStateOf, regionsFieldIsCorrupt, regionsMapOf, registerWorkBuddyStatusRoute, resolveCredentialRecovery, selectAccountFor, settingsNamespaceOf, toPersistedWorkBuddyModel, unwrapVolatileDeep, withEffectiveOff, withWorkBuddyRegion, workBuddyDisplayName, workBuddyModelInput, workBuddyThinkingLevelMap, workBuddyWebStatus, workbuddyAccountId, workbuddyAppExecutableCandidates, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, writeHostHeartbeat };
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as prepareChatBody, A as parseWorkBuddyAuth, At as readAtRestKey, B as declaredTools, C as defaultDesktopAuthCandidates, Ct as findWorkbuddyAppExecutable, D as isEncryptedCredentialError, Dt as macosBundleExecutable, E as hasEncryptedCredentialFields, Et as isWorkbuddyBundle, F as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, Ft as writeFileAtomic, G as isFirstMessageGateBody, H as isContextTooLongBody, I as WorkBuddyCredentialRejectedError, J as openingEvidenceOfBody, K as knownRegionOf, L as WorkBuddyUpstreamClient, M as workbuddyOwnAuthPath, N as CONTEXT_OVERFLOW_MARKER, Nt as resolveDshHome, O as isVaultAccountId, Ot as macosNestedAppBundles, P as CREDENTIAL_REJECTED_CODE, Pt as withFileLock, Q as perturbChannelFingerprints, R as channelGateMemberOf, S as authFileName, St as fetchAtRestKeyPayload, T as defaultDesktopAuthPath, Tt as isEncryptedFieldWrapper, U as isConversationIdGateBody, V as forceNeutralHead, W as isCredentialRejectedError, X as parseReasoning, Y as parseCreditMultiplier, Z as parseUpstreamModel, _ as ENCRYPTED_CREDENTIAL_CODE, _t as nativeModalityOf, a as readHostHeartbeat, at as lastRefusalFor, b as WorkBuddyCredentialStore, bt as deriveAtRestKey, c as WORKBUDDY_CONNECT_VERSION, ct as recordRefusal, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, dt as WorkBuddyImageError, et as regionOf, f as WorkBuddyCatalog, ft as pickImageModel, g as withoutRouters, gt as imageDefaultFor, h as fallbackModelsFor, ht as NATIVE_MODALITY_BY_MODEL_ID, i as processStartTimeMs, it as defaultOffModelIds, j as workbuddyAccountId, jt as workbuddyAppExecutableCandidates, k as legacyWorkbuddyOwnAuthPath, kt as openEncryptedField, l as piAiRuntimeInfo, lt as withEffectiveOff, m as deriveCatalog, mt as queueNoticeText, n as clearHostHeartbeat, nt as attachBusyLevels, o as workbuddyHostHeartbeatPath, ot as offDefaultFor, p as applyContextBudgets, pt as parseQueueAnswer, q as modelIdOfBody, r as isHeartbeatProcessAlive, rt as REFUSES_OFF_MODEL_IDS, s as writeHostHeartbeat, st as offRefused, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, tt as requestEvidenceOf, u as FALLBACK_WORKBUDDY_MODELS, ut as DEFAULT_IMAGE_SIZE, v as WORKBUDDY_AUTH_FILENAME, vt as WORKBUDDY_APP_EXECUTABLE_ENV, w as defaultDesktopAuthDirs, x as WorkBuddyEncryptedCredentialError, xt as deriveAtRestKeyId, y as WORKBUDDY_AUTH_FILE_ENV, yt as clearAtRestKeyCache, z as classifyUpstreamError } from "./host-heartbeat-
|
|
1
|
+
import { $ as prepareChatBody, A as parseWorkBuddyAuth, At as readAtRestKey, B as declaredTools, C as defaultDesktopAuthCandidates, Ct as findWorkbuddyAppExecutable, D as isEncryptedCredentialError, Dt as macosBundleExecutable, E as hasEncryptedCredentialFields, Et as isWorkbuddyBundle, F as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, Ft as writeFileAtomic, G as isFirstMessageGateBody, H as isContextTooLongBody, I as WorkBuddyCredentialRejectedError, J as openingEvidenceOfBody, K as knownRegionOf, L as WorkBuddyUpstreamClient, M as workbuddyOwnAuthPath, N as CONTEXT_OVERFLOW_MARKER, Nt as resolveDshHome, O as isVaultAccountId, Ot as macosNestedAppBundles, P as CREDENTIAL_REJECTED_CODE, Pt as withFileLock, Q as perturbChannelFingerprints, R as channelGateMemberOf, S as authFileName, St as fetchAtRestKeyPayload, T as defaultDesktopAuthPath, Tt as isEncryptedFieldWrapper, U as isConversationIdGateBody, V as forceNeutralHead, W as isCredentialRejectedError, X as parseReasoning, Y as parseCreditMultiplier, Z as parseUpstreamModel, _ as ENCRYPTED_CREDENTIAL_CODE, _t as nativeModalityOf, a as readHostHeartbeat, at as lastRefusalFor, b as WorkBuddyCredentialStore, bt as deriveAtRestKey, c as WORKBUDDY_CONNECT_VERSION, ct as recordRefusal, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, dt as WorkBuddyImageError, et as regionOf, f as WorkBuddyCatalog, ft as pickImageModel, g as withoutRouters, gt as imageDefaultFor, h as fallbackModelsFor, ht as NATIVE_MODALITY_BY_MODEL_ID, i as processStartTimeMs, it as defaultOffModelIds, j as workbuddyAccountId, jt as workbuddyAppExecutableCandidates, k as legacyWorkbuddyOwnAuthPath, kt as openEncryptedField, l as piAiRuntimeInfo, lt as withEffectiveOff, m as deriveCatalog, mt as queueNoticeText, n as clearHostHeartbeat, nt as attachBusyLevels, o as workbuddyHostHeartbeatPath, ot as offDefaultFor, p as applyContextBudgets, pt as parseQueueAnswer, q as modelIdOfBody, r as isHeartbeatProcessAlive, rt as REFUSES_OFF_MODEL_IDS, s as writeHostHeartbeat, st as offRefused, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, tt as requestEvidenceOf, u as FALLBACK_WORKBUDDY_MODELS, ut as DEFAULT_IMAGE_SIZE, v as WORKBUDDY_AUTH_FILENAME, vt as WORKBUDDY_APP_EXECUTABLE_ENV, w as defaultDesktopAuthDirs, x as WorkBuddyEncryptedCredentialError, xt as deriveAtRestKeyId, y as WORKBUDDY_AUTH_FILE_ENV, yt as clearAtRestKeyCache, z as classifyUpstreamError } from "./host-heartbeat-BhgyeZWV.js";
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
4
|
import { randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
@@ -451,15 +451,184 @@ function regionOfStatusUrl(url) {
|
|
|
451
451
|
* counts as the whole section only when it shows a key that is neither a region
|
|
452
452
|
* id nor a known sibling — i.e. it looks like a settings document. A plain (or
|
|
453
453
|
* dirty) `cn` / `global` map is returned as-is.
|
|
454
|
+
*
|
|
455
|
+
* THE RESULT IS HEALED, not merely narrowed — see {@link healedRegionsMap}.
|
|
456
|
+
* Returning the map "as-is" is what let a corrupted document keep reading as
|
|
457
|
+
* half-empty: the field held `cn` at level 0 and `global` only one level down,
|
|
458
|
+
* so every `global` question answered "never configured" even though the file
|
|
459
|
+
* still had the answer. Reads and writes both come through here, so healing
|
|
460
|
+
* here fixes both halves at once (the Host's `regionStateOf`, the card's
|
|
461
|
+
* `nextRegion*` write bases, and the catalog repair's write).
|
|
454
462
|
*/
|
|
455
463
|
function regionsMapOf(value) {
|
|
464
|
+
const peeled = peelLiveReference(value);
|
|
465
|
+
if (typeof peeled === "object" && peeled !== null && !Array.isArray(peeled)) {
|
|
466
|
+
const candidate = peeled;
|
|
467
|
+
if (!isRecord$1(candidate["regions"]) && !carriesForeignKey(candidate)) return candidate;
|
|
468
|
+
}
|
|
456
469
|
const unwrapped = unwrapVolatileDeep(value);
|
|
457
470
|
if (typeof unwrapped !== "object" || unwrapped === null || Array.isArray(unwrapped)) return {};
|
|
458
471
|
const record = unwrapped;
|
|
459
472
|
const nested = record["regions"];
|
|
460
|
-
if (typeof nested !== "object" || nested === null || Array.isArray(nested)) return record;
|
|
461
|
-
|
|
462
|
-
|
|
473
|
+
if (typeof nested !== "object" || nested === null || Array.isArray(nested)) return healedRegionsMap(record);
|
|
474
|
+
return healedRegionsMap(hasRegionKey(record) ? record : nested);
|
|
475
|
+
}
|
|
476
|
+
/** `value` with ONE `{get(): T}` live reference peeled, exactly like a read. */
|
|
477
|
+
function peelLiveReference(value) {
|
|
478
|
+
if (value === null || typeof value !== "object") return value;
|
|
479
|
+
const get = value.get;
|
|
480
|
+
return typeof get === "function" ? get() : value;
|
|
481
|
+
}
|
|
482
|
+
/** Whether the object carries a sibling-field name where a region id belongs. */
|
|
483
|
+
function carriesForeignKey(record) {
|
|
484
|
+
return Object.keys(record).some((key) => REGION_MAP_FOREIGN_KEYS.has(key));
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Names that belong to the settings SECTION (`Config` in `src/index.ts`) or to
|
|
488
|
+
* one region SLOT (`regionStateConfig`) — never to the `regions` map.
|
|
489
|
+
*
|
|
490
|
+
* They are the signature of a data-loss bug and the list that undoes it. From
|
|
491
|
+
* 5.4.0 to 5.5.0 `mergeRegionCatalog` was handed the WHOLE settings section but
|
|
492
|
+
* read it as the `regions` field, so every boot's directory repair wrote
|
|
493
|
+
* `{ accounts, regions, cliBridge }` INTO the field — replacing the region map
|
|
494
|
+
* and burying the real one under a `regions` key. A live profile ended up five
|
|
495
|
+
* levels deep (one per startup) and 258 KB of duplicated slots.
|
|
496
|
+
*
|
|
497
|
+
* Neither a region id (`cn` / `global`) nor any future one can collide with
|
|
498
|
+
* these names, so removing them from the map is safe, idempotent, and does not
|
|
499
|
+
* discard a region that a newer build might add.
|
|
500
|
+
*/
|
|
501
|
+
const REGION_MAP_FOREIGN_KEYS = /* @__PURE__ */ new Set([
|
|
502
|
+
"authFile",
|
|
503
|
+
"accountId",
|
|
504
|
+
"accounts",
|
|
505
|
+
"regions",
|
|
506
|
+
"cliBridge",
|
|
507
|
+
"enabled",
|
|
508
|
+
"showCreditsInMainUi",
|
|
509
|
+
"creditPosition",
|
|
510
|
+
"lastCatalog",
|
|
511
|
+
"enabledModelIds",
|
|
512
|
+
"imageModelIds",
|
|
513
|
+
"imageToolEnabled",
|
|
514
|
+
"offModelIds",
|
|
515
|
+
"contextBudgets",
|
|
516
|
+
"pool"
|
|
517
|
+
]);
|
|
518
|
+
/** A non-null, non-array object: the only shape a slot or a level can have. */
|
|
519
|
+
function isRecord$1(value) {
|
|
520
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
521
|
+
}
|
|
522
|
+
/** Whether a map carries at least one real region id. */
|
|
523
|
+
function hasRegionKey(map) {
|
|
524
|
+
return Object.keys(map).some((key) => WORKBUDDY_REGIONS.includes(key));
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* How much USER CONFIG one candidate slot actually carries.
|
|
528
|
+
*
|
|
529
|
+
* Used only when the map lost a region and the copies left at different depths
|
|
530
|
+
* disagree — and they do: the bug's own directory repair wrote a slot that held
|
|
531
|
+
* nothing but `lastCatalog` (it started from a map where the region was already
|
|
532
|
+
* missing), while the copy further down still holds the pool the user had
|
|
533
|
+
* checked in. "Shallowest wins" would therefore resurrect the EMPTY one and
|
|
534
|
+
* throw away the real configuration. Depth is not age in this wreckage; CONTENT
|
|
535
|
+
* is the only usable signal.
|
|
536
|
+
*
|
|
537
|
+
* Deliberately counts only non-default, user-authored values: resolved slots
|
|
538
|
+
* carry every schema default, so a slot's key count says nothing.
|
|
539
|
+
*/
|
|
540
|
+
function slotConfigWeight(slot) {
|
|
541
|
+
let weight = 0;
|
|
542
|
+
const pool = slot["pool"];
|
|
543
|
+
if (isRecord$1(pool)) {
|
|
544
|
+
if (pool["enabled"] === true) weight += 4;
|
|
545
|
+
if (Array.isArray(pool["memberAccountIds"]) && pool["memberAccountIds"].length > 0) weight += 4;
|
|
546
|
+
if (Array.isArray(pool["orderAccountIds"]) && pool["orderAccountIds"].length > 0) weight += 2;
|
|
547
|
+
if (typeof pool["targetModelId"] === "string" && pool["targetModelId"] !== "") weight += 2;
|
|
548
|
+
if (typeof pool["probeInputTokens"] === "number" && pool["probeInputTokens"] > 0) weight += 1;
|
|
549
|
+
}
|
|
550
|
+
for (const key of [
|
|
551
|
+
"enabledModelIds",
|
|
552
|
+
"imageModelIds",
|
|
553
|
+
"offModelIds"
|
|
554
|
+
]) {
|
|
555
|
+
const list = slot[key];
|
|
556
|
+
if (Array.isArray(list) && list.length > 0) weight += 1;
|
|
557
|
+
}
|
|
558
|
+
const budgets = slot["contextBudgets"];
|
|
559
|
+
if (isRecord$1(budgets) && Object.keys(budgets).length > 0) weight += 1;
|
|
560
|
+
if (slot["enabled"] === false) weight += 1;
|
|
561
|
+
if (slot["showCreditsInMainUi"] === false) weight += 1;
|
|
562
|
+
if (slot["imageToolEnabled"] === true) weight += 1;
|
|
563
|
+
return weight;
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* The `regions` map with a corrupted document's wreckage removed.
|
|
567
|
+
*
|
|
568
|
+
* Two repairs, both keyed on the bug's signature (a foreign key or a nested
|
|
569
|
+
* `regions` copy) so a HEALTHY map passes through untouched:
|
|
570
|
+
*
|
|
571
|
+
* 1. DROP every key that names a section field or a slot field. Those are the
|
|
572
|
+
* copies the bug wrote; keeping them means the next save carries them
|
|
573
|
+
* forward forever.
|
|
574
|
+
* 2. RECOVER a region this level does not have from the copies buried beneath
|
|
575
|
+
* it. A region that IS here is never replaced — the outer copy is the one
|
|
576
|
+
* the surviving writers have been updating, and re-reading a stale inner
|
|
577
|
+
* copy over it is exactly the defect the shape rule above was fixed for.
|
|
578
|
+
*
|
|
579
|
+
* Recovery exists because `global` in the measured profile lived ONLY one level
|
|
580
|
+
* down: without it, the host reads that region as "never configured" and the
|
|
581
|
+
* first write after the fix would delete the user's international pool outright.
|
|
582
|
+
*
|
|
583
|
+
* Among the buried copies of a MISSING region, the one carrying the most user
|
|
584
|
+
* configuration wins rather than the shallowest — see {@link slotConfigWeight}:
|
|
585
|
+
* the bug's own directory repair left `{ lastCatalog }` slots that look newer
|
|
586
|
+
* but hold nothing, one level above the copy with the pool still in it.
|
|
587
|
+
*/
|
|
588
|
+
function healedRegionsMap(map) {
|
|
589
|
+
const healed = {};
|
|
590
|
+
for (const [key, slot] of Object.entries(map)) {
|
|
591
|
+
if (REGION_MAP_FOREIGN_KEYS.has(key)) continue;
|
|
592
|
+
healed[key] = slot;
|
|
593
|
+
}
|
|
594
|
+
const recovered = {};
|
|
595
|
+
let deeper = map;
|
|
596
|
+
for (let depth = 0; depth < 8; depth += 1) {
|
|
597
|
+
const nested = deeper["regions"];
|
|
598
|
+
if (!isRecord$1(nested)) break;
|
|
599
|
+
for (const region of WORKBUDDY_REGIONS) {
|
|
600
|
+
if (region in map) continue;
|
|
601
|
+
const candidate = nested[region];
|
|
602
|
+
if (!isRecord$1(candidate)) continue;
|
|
603
|
+
const weight = slotConfigWeight(candidate);
|
|
604
|
+
const best = recovered[region];
|
|
605
|
+
if (best === void 0 || weight > best.weight) recovered[region] = {
|
|
606
|
+
slot: candidate,
|
|
607
|
+
weight
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
deeper = nested;
|
|
611
|
+
}
|
|
612
|
+
for (const [region, entry] of Object.entries(recovered)) healed[region] = entry.slot;
|
|
613
|
+
return healed;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Whether the stored `regions` FIELD still carries the debris described at
|
|
617
|
+
* {@link REGION_MAP_FOREIGN_KEYS}.
|
|
618
|
+
*
|
|
619
|
+
* Pass the field itself (`Config['regions']`), not the whole section: the
|
|
620
|
+
* section legitimately has `accounts` / `cliBridge` beside `regions`, so asking
|
|
621
|
+
* that question of a section would always answer "corrupt".
|
|
622
|
+
*
|
|
623
|
+
* The catalog repair uses it to write even when the model directory already
|
|
624
|
+
* matches: without that, a profile whose directory is in agreement would never
|
|
625
|
+
* be cleaned — the very shape that made the pool look unsaved would outlive the
|
|
626
|
+
* fix that stops producing it.
|
|
627
|
+
*/
|
|
628
|
+
function regionsFieldIsCorrupt(value) {
|
|
629
|
+
const unwrapped = unwrapVolatileDeep(value);
|
|
630
|
+
if (!isRecord$1(unwrapped)) return false;
|
|
631
|
+
return Object.keys(unwrapped).some((key) => REGION_MAP_FOREIGN_KEYS.has(key));
|
|
463
632
|
}
|
|
464
633
|
/** One region's stored slot as a plain object; any other shape reads as empty. */
|
|
465
634
|
function regionSlotOf(value, region) {
|
|
@@ -560,8 +729,8 @@ const WORKBUDDY_SETTINGS_NS = "workbuddy";
|
|
|
560
729
|
/**
|
|
561
730
|
* Writing the model's saved directory back to the Host's settings document.
|
|
562
731
|
*
|
|
563
|
-
* THE HOST-SIDE HALF OF ISSUES #114/#115. The card's own save path lives in
|
|
564
|
-
*
|
|
732
|
+
* THE HOST-SIDE HALF OF ISSUES #114/#115. The card's own save path lives in the
|
|
733
|
+
* browser half (`src/client/`), but the repair that heals a STALE saved
|
|
565
734
|
* directory has to happen where the LIVE roster lands — inside the Host
|
|
566
735
|
* process, right after the upstream answered. So this module writes through
|
|
567
736
|
* the Host's own settings service, using the same merge discipline the
|
|
@@ -574,6 +743,19 @@ const WORKBUDDY_SETTINGS_NS = "workbuddy";
|
|
|
574
743
|
* replaced, every other key of the slot is preserved, and the sibling region
|
|
575
744
|
* is untouched.
|
|
576
745
|
*
|
|
746
|
+
* THE VALUE THIS MODULE WRITES IS THE `regions` FIELD, NOT THE SETTINGS
|
|
747
|
+
* SECTION. That distinction is the whole of this bug — reported as
|
|
748
|
+
* "账号池状态重启就失效". `describe()` hands back the resolved SECTION
|
|
749
|
+
* (`{authFile, accounts, regions, cliBridge, …}`), and
|
|
750
|
+
* `mutate(ns, [{ op: 'set', path: ['regions'], … }])` stores whatever it is
|
|
751
|
+
* given AT that path. Handing it the section therefore wrote the section INTO
|
|
752
|
+
* the field: the real region map was pushed down under a nested `regions` key,
|
|
753
|
+
* its own siblings (`accounts`, `cliBridge`) became fake region slots, and the
|
|
754
|
+
* Host's `regionStateOf(config, 'cn')` — which reads exactly one level — then
|
|
755
|
+
* answered "no slot". Every account-pool preference, model selection and
|
|
756
|
+
* directory looked unsaved after the next restart, and each startup added
|
|
757
|
+
* another level (measured on a live profile: five levels, 258 KB).
|
|
758
|
+
*
|
|
577
759
|
* WHY THE HOST SERVICE RATHER THAN THE SCOPE. The plugin's browser half
|
|
578
760
|
* writes through the Host's settings endpoint because the browser-side
|
|
579
761
|
* `scope.set()` replaces the whole field with the browser's own (possibly
|
|
@@ -584,25 +766,27 @@ const WORKBUDDY_SETTINGS_NS = "workbuddy";
|
|
|
584
766
|
* @module dsh-connect-workbuddy/catalog-write
|
|
585
767
|
*/
|
|
586
768
|
/**
|
|
587
|
-
* Merge one region's repaired directory into the `regions`
|
|
588
|
-
*
|
|
769
|
+
* Merge one region's repaired directory into the `regions` FIELD.
|
|
770
|
+
*
|
|
771
|
+
* `current` may be EITHER the resolved settings section (what `describe()`
|
|
772
|
+
* answers, and what {@link writeRegionCatalog} passes) OR the bare `regions`
|
|
773
|
+
* field; `regionsMapOf` narrows both and heals a document the bug above already
|
|
774
|
+
* corrupted. The RETURN value is always the field itself — the exact shape
|
|
775
|
+
* `path: ['regions']` expects — never the section around it.
|
|
589
776
|
*
|
|
590
777
|
* The merge is done on the AUTHORITATIVE value the settings service holds —
|
|
591
778
|
* not on a snapshot the caller brought — because a snapshot can be stale and
|
|
592
779
|
* a stale merge is how the other region disappears.
|
|
593
780
|
*/
|
|
594
781
|
function mergeRegionCatalog(current, region, catalog) {
|
|
595
|
-
const
|
|
596
|
-
const
|
|
597
|
-
const slot =
|
|
782
|
+
const regions = regionsMapOf(current);
|
|
783
|
+
const stored = regions[region];
|
|
784
|
+
const slot = typeof stored === "object" && stored !== null && !Array.isArray(stored) ? stored : {};
|
|
598
785
|
return {
|
|
599
|
-
...
|
|
600
|
-
|
|
601
|
-
...
|
|
602
|
-
|
|
603
|
-
...slot,
|
|
604
|
-
lastCatalog: catalog
|
|
605
|
-
}
|
|
786
|
+
...regions,
|
|
787
|
+
[region]: {
|
|
788
|
+
...slot,
|
|
789
|
+
lastCatalog: catalog
|
|
606
790
|
}
|
|
607
791
|
};
|
|
608
792
|
}
|
|
@@ -627,6 +811,39 @@ async function writeRegionCatalog(service, ns, region, catalog) {
|
|
|
627
811
|
}], void 0);
|
|
628
812
|
return true;
|
|
629
813
|
}
|
|
814
|
+
/**
|
|
815
|
+
* Clean a `regions` field an older build corrupted, WITHOUT touching any
|
|
816
|
+
* region's content.
|
|
817
|
+
*
|
|
818
|
+
* WHY THIS IS SEPARATE FROM {@link writeRegionCatalog}. The catalog repair is
|
|
819
|
+
* the writer that produced the wreckage, so it is also a natural place to clean
|
|
820
|
+
* it — but it only runs when the live roster arrived (an enabled region, a
|
|
821
|
+
* reachable upstream, a non-empty answer). A profile would therefore stay dirty
|
|
822
|
+
* for as long as the network is down, and a region the user has switched OFF
|
|
823
|
+
* would never be cleaned at all. This write needs neither: it is one settings
|
|
824
|
+
* round-trip at boot, and it is a no-op for every healthy profile (a single key
|
|
825
|
+
* scan, no mutation).
|
|
826
|
+
*
|
|
827
|
+
* It is a pure shape fix, not a merge: `regionsMapOf` already answers the
|
|
828
|
+
* healed map, and the field is replaced with exactly that. Reads are correct
|
|
829
|
+
* without this write (they narrow through the same helper), so failing to write
|
|
830
|
+
* costs tidiness only — the caller logs and carries on.
|
|
831
|
+
*
|
|
832
|
+
* @returns whether the write happened.
|
|
833
|
+
*/
|
|
834
|
+
async function healRegionsField(service, ns) {
|
|
835
|
+
if (service === void 0) return false;
|
|
836
|
+
const row = service.describe().find((entry) => entry.ns === ns);
|
|
837
|
+
if (row === void 0) return false;
|
|
838
|
+
const section = row.value ?? {};
|
|
839
|
+
if (!regionsFieldIsCorrupt(section["regions"])) return false;
|
|
840
|
+
await service.mutate(ns, [{
|
|
841
|
+
op: "set",
|
|
842
|
+
path: ["regions"],
|
|
843
|
+
value: regionsMapOf(section)
|
|
844
|
+
}], void 0);
|
|
845
|
+
return true;
|
|
846
|
+
}
|
|
630
847
|
//#endregion
|
|
631
848
|
//#region src/credential-recovery.ts
|
|
632
849
|
/** How long a verdict stays fresh by default. */
|
|
@@ -9033,8 +9250,8 @@ function registerWorkBuddyStatusRoute(ctx, deps) {
|
|
|
9033
9250
|
if (field !== "regions" && field !== "accounts" && field !== "cliBridge") return json(res, 400, { error: "field must be regions, accounts or cliBridge" });
|
|
9034
9251
|
const row = settings.describe().find((r) => String(r.ns).includes("workbuddy"));
|
|
9035
9252
|
if (row === void 0) return json(res, 503, { error: "workbuddy namespace missing from describe()" });
|
|
9036
|
-
const current = unwrapVolatileDeep(row.value?.[field] ?? {});
|
|
9037
|
-
const incoming = body.value ?? {};
|
|
9253
|
+
const current = field === "regions" ? regionsMapOf(row.value?.[field]) : unwrapVolatileDeep(row.value?.[field] ?? {});
|
|
9254
|
+
const incoming = field === "regions" ? regionsMapOf(body.value) : body.value ?? {};
|
|
9038
9255
|
if (field === "cliBridge") {
|
|
9039
9256
|
if (!isPlainRecord(incoming)) return json(res, 400, { error: "cliBridge value must be an object" });
|
|
9040
9257
|
const mergedBridge = nextCliBridgeSettings(current, incoming);
|
|
@@ -9408,7 +9625,7 @@ const Config = z.object({
|
|
|
9408
9625
|
* picks.
|
|
9409
9626
|
*/
|
|
9410
9627
|
function regionStateOf(config, region) {
|
|
9411
|
-
const stored =
|
|
9628
|
+
const stored = regionsMapOf(config.regions)[region];
|
|
9412
9629
|
if (stored !== void 0 && stored !== null) return stored;
|
|
9413
9630
|
if (region !== "cn") return {};
|
|
9414
9631
|
const lastCatalog = unwrapVolatile(config.lastCatalog);
|
|
@@ -9685,7 +9902,7 @@ function apply(ctx, config) {
|
|
|
9685
9902
|
return rest;
|
|
9686
9903
|
});
|
|
9687
9904
|
if (repaired.length === 0) return;
|
|
9688
|
-
if (catalogFingerprint(repaired)
|
|
9905
|
+
if (!(catalogFingerprint(repaired) !== catalogFingerprint(saved)) && !regionsFieldIsCorrupt(current().regions)) return;
|
|
9689
9906
|
try {
|
|
9690
9907
|
await writeRegionCatalog(hostSettings(), settingsNs, region, repaired);
|
|
9691
9908
|
} catch (error) {
|
|
@@ -9701,6 +9918,27 @@ function apply(ctx, config) {
|
|
|
9701
9918
|
*/
|
|
9702
9919
|
const hostSettings = () => ctx.get?.("settings");
|
|
9703
9920
|
/**
|
|
9921
|
+
* Clean a `regions` field a previous version corrupted, once, at boot.
|
|
9922
|
+
*
|
|
9923
|
+
* The corruption itself is fixed at the source (`src/catalog-write.ts`) and the
|
|
9924
|
+
* read paths heal in memory, so a dirty profile is CORRECT long before this
|
|
9925
|
+
* runs — but it stays dirty in the file, and the next save would carry the
|
|
9926
|
+
* debris forward. The catalog repair writes the same clean shape, yet it only
|
|
9927
|
+
* runs when a live roster arrives: a disabled region, an unreachable upstream
|
|
9928
|
+
* or an empty answer would leave the wreckage in place for as long as those
|
|
9929
|
+
* conditions hold. This needs no upstream at all.
|
|
9930
|
+
*
|
|
9931
|
+
* FAIL-SOFT, exactly like the repair: the read side is already right, so a
|
|
9932
|
+
* write that cannot land costs tidiness and nothing else.
|
|
9933
|
+
*/
|
|
9934
|
+
const healRegionsFieldIfCorrupt = async () => {
|
|
9935
|
+
try {
|
|
9936
|
+
await healRegionsField(hostSettings(), settingsNs);
|
|
9937
|
+
} catch (error) {
|
|
9938
|
+
ctx.logger.warn("dsh-connect-workbuddy: could not clean the shape of the regions setting", error);
|
|
9939
|
+
}
|
|
9940
|
+
};
|
|
9941
|
+
/**
|
|
9704
9942
|
* Legacy migration for the pre-split single `accountId`: its region is
|
|
9705
9943
|
* resolved once from the local account scan and the selection is then
|
|
9706
9944
|
* attributed to that region ONLY — the other region keeps its documented
|
|
@@ -10939,6 +11177,7 @@ function apply(ctx, config) {
|
|
|
10939
11177
|
return;
|
|
10940
11178
|
}
|
|
10941
11179
|
if (stopped) return;
|
|
11180
|
+
const regionsFieldHealed = healRegionsFieldIfCorrupt();
|
|
10942
11181
|
for (const region of REGION_KEYS) {
|
|
10943
11182
|
if (!regionEnabled(current(), region)) continue;
|
|
10944
11183
|
(async () => {
|
|
@@ -10950,6 +11189,7 @@ function apply(ctx, config) {
|
|
|
10950
11189
|
const state = regionStateOf(current(), region);
|
|
10951
11190
|
stacks[region].catalog.set(withEffectiveOff(withImageSelection(deriveCatalog(models, new Set(state.enabledModelIds ?? []), state.contextBudgets ?? {}), new Set(state.imageModelIds ?? [])), new Set(savedOffModelIds(state, models))));
|
|
10952
11191
|
adapters[region].invalidate();
|
|
11192
|
+
await regionsFieldHealed;
|
|
10953
11193
|
await repairRegionCatalog(region, models);
|
|
10954
11194
|
} catch (error) {
|
|
10955
11195
|
ctx.logger.warn(`dsh-connect-workbuddy: dynamic ${region} model catalog unavailable; serving the static fallback list`, error);
|
|
@@ -10961,4 +11201,4 @@ function apply(ctx, config) {
|
|
|
10961
11201
|
});
|
|
10962
11202
|
}
|
|
10963
11203
|
//#endregion
|
|
10964
|
-
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, REFUSES_OFF_MODEL_IDS, 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_TPS_PATH, WORKBUDDY_USAGE_PATH, WorkBuddyCatalog, WorkBuddyCredentialRejectedError, WorkBuddyCredentialStore, WorkBuddyEncryptedCredentialError, WorkBuddyUpstreamClient, apply, asVolatile, authFileName, classifyUpstreamError, clearAtRestKeyCache, clearHostHeartbeat, cooldownOf, createAccountUsabilityProbe, createWorkBuddyAdapter, createWorkBuddyShim, creditOfStream, defaultDesktopAuthCandidates, defaultDesktopAuthDirs, defaultDesktopAuthPath, defaultOffModelIds, deriveAtRestKey, deriveAtRestKeyId, deriveCatalog, fallbackModelsFor, fetchAtRestKeyPayload, findWorkbuddyAppExecutable, hasEncryptedCredentialFields, imageDefaultFor, inject, isCredentialRejectedError, isEncryptedCredentialError, isEncryptedFieldWrapper, isHeartbeatProcessAlive, isWorkbuddyBundle, knownRegionOf, legacyAttributionRegion, legacyWorkbuddyOwnAuthPath, macosBundleExecutable, macosNestedAppBundles, modelIdOfBody, name, nativeModalityOf, nextRegionEnabled, nextRegionSlots, offDefaultFor, 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, withEffectiveOff, withWorkBuddyRegion, workBuddyDisplayName, workBuddyModelInput, workBuddyThinkingLevelMap, workBuddyWebStatus, workbuddyAccountId, workbuddyAppExecutableCandidates, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, writeHostHeartbeat };
|
|
11204
|
+
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, REFUSES_OFF_MODEL_IDS, 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_TPS_PATH, WORKBUDDY_USAGE_PATH, WorkBuddyCatalog, WorkBuddyCredentialRejectedError, WorkBuddyCredentialStore, WorkBuddyEncryptedCredentialError, WorkBuddyUpstreamClient, apply, asVolatile, authFileName, classifyUpstreamError, clearAtRestKeyCache, clearHostHeartbeat, cooldownOf, createAccountUsabilityProbe, createWorkBuddyAdapter, createWorkBuddyShim, creditOfStream, defaultDesktopAuthCandidates, defaultDesktopAuthDirs, defaultDesktopAuthPath, defaultOffModelIds, deriveAtRestKey, deriveAtRestKeyId, deriveCatalog, fallbackModelsFor, fetchAtRestKeyPayload, findWorkbuddyAppExecutable, hasEncryptedCredentialFields, imageDefaultFor, inject, isCredentialRejectedError, isEncryptedCredentialError, isEncryptedFieldWrapper, isHeartbeatProcessAlive, isWorkbuddyBundle, knownRegionOf, legacyAttributionRegion, legacyWorkbuddyOwnAuthPath, macosBundleExecutable, macosNestedAppBundles, modelIdOfBody, name, nativeModalityOf, nextRegionEnabled, nextRegionSlots, offDefaultFor, openEncryptedField, outcomeOfFailure, parseCreditMultiplier, parseReasoning, parseRetryAfter, parseUpstreamModel, parseWorkBuddyAuth, prepareChatBody, probeModel, probeRequestBody, probeSucceeded, processStartTimeMs, readAtRestKey, readHostHeartbeat, regionCleared, regionEnabled, regionEnabledOf, regionOf, regionOfProvider, regionOfStatusUrl, regionStateOf, regionsFieldIsCorrupt, regionsMapOf, registerWorkBuddyStatusRoute, resolveCredentialRecovery, selectAccountFor, settingsNamespaceOf, toPersistedWorkBuddyModel, unwrapVolatileDeep, withEffectiveOff, 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": "5.5.
|
|
5
|
+
"version": "5.5.1",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|