dsh-connect-workbuddy 3.4.4 → 3.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.4.5 (2026-10-04)
4
+
5
+ ### Fixes
6
+
7
+ - **保存设置不再把「本该用实时目录」的模型槽写空,正在用的模型不会凭空消失(issue #32)。**
8
+ - **现象**:在 `regions` 从未成功写入过的 profile 上(如 issue #31 所述的环境),第一次保存(哪怕只改了一处偏好)之后,模型选择器里**正在使用的默认模型突然不见了**,其他只在上游目录里、不在内置静态名单里的模型也一起消失,且**没有任何报错**。
9
+ - **根因**(两半,缺一不可):
10
+ 1. **写半边**——`__save` 的 `merged` 以宿主解析值为种子。当 `regions` 还是空对象时,`{"cn":{"imageModelIds":[]}}` 这样的载荷会落下**一个没有 `lastCatalog` 的 cn 槽**。槽本身合法(schema 里 `lastCatalog` 默认就是 `[]`),但它缺了这份目录。
11
+ 2. **读半边**——`configuredModels` / `displayModels` 把 roster 解析成两步:`state.lastCatalog?.length ? lastCatalog : 静态名单`。「槽存在但没有目录」和「从未配置过」于是被当成同一件事,都退回**内置的 12 条静态名单**;而 `applySelection` 又把这份静态名单 `catalog.set()` 进**实时目录**——实时发现注册的 17 条就此被覆盖。正在用的 `space-bunny` 只在实时目录里、不在静态名单里,当场消失。
12
+ - **修法**:
13
+ - 写半边:`__save` 在合并之后、落盘之前,对本次载荷**真正触及**的区域(只碰 `incoming` 里出现过的键,不动调用方没提到的兄弟区域),若该槽缺目录(`lastCatalog` 缺失或为空数组),就用 `displayModels` 取到的**实时名册**补上。用 `displayModels` 而非上游往返:保存不该依赖网络,也不该因为上游不可用而失败。调用方显式传来的数组(含非空数组)仍然优先,实时名册为空则维持原状、不写空数组。
14
+ - 读半边:roster 解析改成三步 `lastCatalog → 该区域当前已注册的实时目录 → 静态名单`。静态名单退回它本来该待的位置——上游离线、什么都发现不了时的最后兜底。`applySelection` 在 `set()` **之前**读一次当前目录,避免观察到自己正要写的值。
15
+ - 这样「槽存在但缺目录」会**降到实时目录**,永远不会越过它掉到静态名单。
16
+
17
+ - **未显式设置预算的模型不再被强制压到 200K,1M 模型的真实窗口得以保留(issue #33)。**
18
+ - **现象**:上游目录里 17 个模型有 15 个被改报成 200000——`space-bunny`、`deepseek-v4.1-flash`、`deepseek-v4-pro`、`glm-5.3`、`glm-5.3-flash`、`glm-5.2`、`kimi-k3-1`、`kimi-k2.8-preview` 的 1000000、`hy4-preview` 的 960000、`minimax-m3` 的 512000、`auto`/`kimi-k2.6`/`kimi-k2.7` 的 256000 全部变成 200000。
19
+ - **根因**:`applyContextBudgets` 里 `budgets[model.id] ?? 200_000` 的默认值——**未设置预算被当成「设成 200K」**。这条默认值在 CHANGELOG 里给出的理由是「让 DSH 提前压缩以避开上游节流」,但同一份 CHANGELOG 又自述实测节流线是 **2 万~3 万 token 且单次请求即触顶**:200K 比那条线高一个数量级,压到 200K 并没有把压缩提前到安全区。200K 在静态目录里也无出处(`auto` 是 168000、`hy3` 是 192000、`default-model` 是 176000、`primary-model` 是 272000);插件自己的静态目录还把 `glm-5.3` 等声明为 1000000,与钳制自相矛盾。
20
+ - **修法**:`applyContextBudgets` 不再有默认值——**只做上限**:未设置预算时保留模型的原生窗口,设置了就用 `Math.min(原生, 预算)`。`contextBudgets` 的语义恢复成「用户设的上限」,而不是一个隐式的全局改写。(`toWebModel` 里那份重复的 `?? 200_000` 也一并去掉,改为调用同一个函数,避免卡片与运行时目录各说各话。)
21
+ - 想继续压缩的用户仍然可以显式设置 `regions.<region>.contextBudgets`;卡片上原本只在原生窗口 > 200K 时才出现的「DSH 上下文预算」选择器,现在因为不再默认钳制而更有意义。
22
+
3
23
  ## 3.4.4 (2026-10-03)
4
24
 
5
25
  ### Features
package/lib/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { E as legacyWorkbuddyOwnAuthPath, H as WORKBUDDY_APP_EXECUTABLE_ENV, J as findWorkbuddyAppExecutableWithSource, N as WorkBuddyUpstreamClient, S as defaultDesktopAuthDirs, _ as WORKBUDDY_AUTH_FILE_ENV, a as readHostHeartbeat, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, k as workbuddyOwnAuthPath, l as piAiRuntimeInfo, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS, v as WorkBuddyCredentialStore, x as defaultDesktopAuthCandidates } from "./host-heartbeat-DTuwptkM.js";
2
+ import { A as workbuddyOwnAuthPath, C as defaultDesktopAuthDirs, D as legacyWorkbuddyOwnAuthPath, P as WorkBuddyUpstreamClient, S as defaultDesktopAuthCandidates, U as WORKBUDDY_APP_EXECUTABLE_ENV, Y as findWorkbuddyAppExecutableWithSource, a as readHostHeartbeat, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, l as piAiRuntimeInfo, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS, v as WORKBUDDY_AUTH_FILE_ENV, y as WorkBuddyCredentialStore } from "./host-heartbeat-CTigiLJK.js";
3
3
  import { realpathSync } from "node:fs";
4
4
  import { fileURLToPath } from "node:url";
5
5
  //#region src/bin.ts
@@ -2556,11 +2556,26 @@ const FALLBACK_WORKBUDDY_MODELS_GLOBAL = [
2556
2556
  function fallbackModelsFor(region) {
2557
2557
  return region === "global" ? FALLBACK_WORKBUDDY_MODELS_GLOBAL : FALLBACK_WORKBUDDY_MODELS;
2558
2558
  }
2559
- /** Apply the saved local DSH budget; models above 200K default to 200K. */
2559
+ /**
2560
+ * Apply the saved local DSH budget. A budget is an UPPER LIMIT the user sets,
2561
+ * never a default: a model without an explicit budget keeps its own window, and
2562
+ * a budget can only ever LOWER a window (`Math.min`), so a value above the
2563
+ * native one is not an error, just a no-op.
2564
+ *
2565
+ * There used to be a `?? 200_000` default here (issue #33), on the rationale
2566
+ * that a lower advertised window makes DSH compact sooner and so keeps a long
2567
+ * session under the upstream throttle. That rationale does not hold: the
2568
+ * measured throttle fires on a SINGLE request past 20k-30k input tokens (the
2569
+ * CHANGELOG's own wording calls 200K "远超上游 ~20–30k 的节流线"), so clamping to
2570
+ * 200K bought none of the safety it claimed while costing every >200K model its
2571
+ * real window — and contradicting the static roster in this very file, which
2572
+ * declares those models as 1M. A user who wants the conservative behaviour sets
2573
+ * a budget explicitly; the card offers that per model.
2574
+ */
2560
2575
  function applyContextBudgets(catalog, budgets = {}) {
2561
2576
  return catalog.map((model) => ({
2562
2577
  ...model,
2563
- contextWindow: model.contextWindow > 2e5 ? Math.min(model.contextWindow, budgets[model.id] ?? 2e5) : model.contextWindow
2578
+ contextWindow: budgets[model.id] === void 0 ? model.contextWindow : Math.min(model.contextWindow, budgets[model.id])
2564
2579
  }));
2565
2580
  }
2566
2581
  function deriveCatalog(catalog, enabled, budgets = {}) {
@@ -2791,7 +2806,7 @@ function generationOf(version) {
2791
2806
  //#endregion
2792
2807
  //#region src/version.ts
2793
2808
  /** The npm package version this build was produced from. */
2794
- const WORKBUDDY_CONNECT_VERSION = "3.4.4";
2809
+ const WORKBUDDY_CONNECT_VERSION = "3.4.5";
2795
2810
  //#endregion
2796
2811
  //#region src/host-heartbeat.ts
2797
2812
  /**
@@ -2949,4 +2964,4 @@ async function clearHostHeartbeat() {
2949
2964
  await rm(workbuddyHostHeartbeatPath(), { force: true });
2950
2965
  }
2951
2966
  //#endregion
2952
- export { openEncryptedField as $, CREDENTIAL_REJECTED_CODE as A, prepareChatBody as B, defaultDesktopAuthPath as C, parseWorkBuddyAuth as D, legacyWorkbuddyOwnAuthPath as E, declaredTools as F, deriveAtRestKeyId as G, WORKBUDDY_APP_EXECUTABLE_ENV as H, isCredentialRejectedError as I, findWorkbuddyAppExecutableWithSource as J, fetchAtRestKeyPayload as K, parseCreditMultiplier as L, WorkBuddyCredentialRejectedError as M, WorkBuddyUpstreamClient as N, workbuddyAccountId as O, classifyUpstreamError as P, macosNestedAppBundles as Q, parseReasoning as R, defaultDesktopAuthDirs as S, isEncryptedCredentialError as T, clearAtRestKeyCache as U, regionOf as V, deriveAtRestKey as W, isWorkbuddyBundle as X, isEncryptedFieldWrapper as Y, macosBundleExecutable as Z, WORKBUDDY_AUTH_FILE_ENV as _, readHostHeartbeat as a, authFileName as b, WORKBUDDY_CONNECT_VERSION as c, FALLBACK_WORKBUDDY_MODELS_GLOBAL as d, readAtRestKey as et, WorkBuddyCatalog as f, WORKBUDDY_AUTH_FILENAME as g, ENCRYPTED_CREDENTIAL_CODE as h, processStartTimeMs as i, WORKBUDDY_FALLBACK_SYSTEM_PROMPT as j, workbuddyOwnAuthPath as k, piAiRuntimeInfo as l, fallbackModelsFor as m, clearHostHeartbeat as n, workbuddyHostHeartbeatPath as o, deriveCatalog as p, findWorkbuddyAppExecutable as q, isHeartbeatProcessAlive as r, writeHostHeartbeat as s, WORKBUDDY_HOST_HEARTBEAT_FILENAME as t, workbuddyAppExecutableCandidates as tt, FALLBACK_WORKBUDDY_MODELS as u, WorkBuddyCredentialStore as v, hasEncryptedCredentialFields as w, defaultDesktopAuthCandidates as x, WorkBuddyEncryptedCredentialError as y, parseUpstreamModel as z };
2967
+ export { macosNestedAppBundles as $, workbuddyOwnAuthPath as A, parseUpstreamModel as B, defaultDesktopAuthDirs as C, legacyWorkbuddyOwnAuthPath as D, isEncryptedCredentialError as E, classifyUpstreamError as F, deriveAtRestKey as G, regionOf as H, declaredTools as I, findWorkbuddyAppExecutable as J, deriveAtRestKeyId as K, isCredentialRejectedError as L, WORKBUDDY_FALLBACK_SYSTEM_PROMPT as M, WorkBuddyCredentialRejectedError as N, parseWorkBuddyAuth as O, WorkBuddyUpstreamClient as P, macosBundleExecutable as Q, parseCreditMultiplier as R, defaultDesktopAuthCandidates as S, hasEncryptedCredentialFields as T, WORKBUDDY_APP_EXECUTABLE_ENV as U, prepareChatBody as V, clearAtRestKeyCache as W, isEncryptedFieldWrapper as X, findWorkbuddyAppExecutableWithSource as Y, isWorkbuddyBundle as Z, WORKBUDDY_AUTH_FILENAME as _, readHostHeartbeat as a, WorkBuddyEncryptedCredentialError as b, WORKBUDDY_CONNECT_VERSION as c, FALLBACK_WORKBUDDY_MODELS_GLOBAL as d, openEncryptedField as et, WorkBuddyCatalog as f, ENCRYPTED_CREDENTIAL_CODE as g, fallbackModelsFor as h, processStartTimeMs as i, CREDENTIAL_REJECTED_CODE as j, workbuddyAccountId as k, piAiRuntimeInfo as l, deriveCatalog as m, clearHostHeartbeat as n, workbuddyAppExecutableCandidates as nt, workbuddyHostHeartbeatPath as o, applyContextBudgets as p, fetchAtRestKeyPayload as q, isHeartbeatProcessAlive as r, writeHostHeartbeat as s, WORKBUDDY_HOST_HEARTBEAT_FILENAME as t, readAtRestKey as tt, FALLBACK_WORKBUDDY_MODELS as u, WORKBUDDY_AUTH_FILE_ENV as v, defaultDesktopAuthPath as w, authFileName as x, WorkBuddyCredentialStore as y, parseReasoning as z };
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as openEncryptedField, A as CREDENTIAL_REJECTED_CODE, B as prepareChatBody, C as defaultDesktopAuthPath, D as parseWorkBuddyAuth, E as legacyWorkbuddyOwnAuthPath, F as declaredTools, G as deriveAtRestKeyId, H as WORKBUDDY_APP_EXECUTABLE_ENV, I as isCredentialRejectedError, K as fetchAtRestKeyPayload, L as parseCreditMultiplier, M as WorkBuddyCredentialRejectedError, N as WorkBuddyUpstreamClient, O as workbuddyAccountId, P as classifyUpstreamError, Q as macosNestedAppBundles, R as parseReasoning, S as defaultDesktopAuthDirs, T as isEncryptedCredentialError, U as clearAtRestKeyCache, V as regionOf, W as deriveAtRestKey, X as isWorkbuddyBundle, Y as isEncryptedFieldWrapper, Z as macosBundleExecutable, _ as WORKBUDDY_AUTH_FILE_ENV, a as readHostHeartbeat, b as authFileName, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, et as readAtRestKey, f as WorkBuddyCatalog, g as WORKBUDDY_AUTH_FILENAME, h as ENCRYPTED_CREDENTIAL_CODE, i as processStartTimeMs, j as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, k as workbuddyOwnAuthPath, l as piAiRuntimeInfo, m as fallbackModelsFor, n as clearHostHeartbeat, o as workbuddyHostHeartbeatPath, p as deriveCatalog, q as findWorkbuddyAppExecutable, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, tt as workbuddyAppExecutableCandidates, u as FALLBACK_WORKBUDDY_MODELS, v as WorkBuddyCredentialStore, w as hasEncryptedCredentialFields, x as defaultDesktopAuthCandidates, y as WorkBuddyEncryptedCredentialError, z as parseUpstreamModel } from "./host-heartbeat-DTuwptkM.js";
1
+ import { $ as macosNestedAppBundles, A as workbuddyOwnAuthPath, B as parseUpstreamModel, C as defaultDesktopAuthDirs, D as legacyWorkbuddyOwnAuthPath, E as isEncryptedCredentialError, F as classifyUpstreamError, G as deriveAtRestKey, H as regionOf, I as declaredTools, J as findWorkbuddyAppExecutable, K as deriveAtRestKeyId, L as isCredentialRejectedError, M as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, N as WorkBuddyCredentialRejectedError, O as parseWorkBuddyAuth, P as WorkBuddyUpstreamClient, Q as macosBundleExecutable, R as parseCreditMultiplier, S as defaultDesktopAuthCandidates, T as hasEncryptedCredentialFields, U as WORKBUDDY_APP_EXECUTABLE_ENV, V as prepareChatBody, W as clearAtRestKeyCache, X as isEncryptedFieldWrapper, Z as isWorkbuddyBundle, _ as WORKBUDDY_AUTH_FILENAME, a as readHostHeartbeat, b as WorkBuddyEncryptedCredentialError, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, et as openEncryptedField, f as WorkBuddyCatalog, g as ENCRYPTED_CREDENTIAL_CODE, h as fallbackModelsFor, i as processStartTimeMs, j as CREDENTIAL_REJECTED_CODE, k as workbuddyAccountId, l as piAiRuntimeInfo, m as deriveCatalog, n as clearHostHeartbeat, nt as workbuddyAppExecutableCandidates, o as workbuddyHostHeartbeatPath, p as applyContextBudgets, q as fetchAtRestKeyPayload, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, tt as readAtRestKey, u as FALLBACK_WORKBUDDY_MODELS, v as WORKBUDDY_AUTH_FILE_ENV, w as defaultDesktopAuthPath, x as authFileName, y as WorkBuddyCredentialStore, z as parseReasoning } from "./host-heartbeat-CTigiLJK.js";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import { readFile } from "node:fs/promises";
4
4
  import { randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
@@ -3093,10 +3093,11 @@ function toCredits(answer) {
3093
3093
  }
3094
3094
  /** Project a model into the card's row, dropping empty optional fields. */
3095
3095
  function toWebModel(model, budgets) {
3096
+ const budgeted = applyContextBudgets([model], budgets)[0] ?? model;
3096
3097
  return {
3097
3098
  id: model.id,
3098
3099
  name: model.name,
3099
- contextWindow: model.contextWindow > 2e5 ? Math.min(model.contextWindow, budgets[model.id] ?? 2e5) : model.contextWindow,
3100
+ contextWindow: budgeted.contextWindow,
3100
3101
  nativeContextWindow: model.contextWindow,
3101
3102
  maxTokens: model.maxTokens,
3102
3103
  ...model.creditMultiplier === void 0 ? {} : { creditMultiplier: model.creditMultiplier },
@@ -3551,6 +3552,14 @@ function registerWorkBuddyStatusRoute(ctx, deps) {
3551
3552
  ...slot
3552
3553
  } : slot;
3553
3554
  }
3555
+ if (field === "regions") for (const key of Object.keys(incoming)) {
3556
+ const slot = merged[key];
3557
+ if (!isPlainRecord(slot) || key !== "cn" && key !== "global") continue;
3558
+ if (Array.isArray(slot.lastCatalog) && slot.lastCatalog.length > 0) continue;
3559
+ const live = deps.displayModels(key);
3560
+ if (live.length === 0) continue;
3561
+ slot.lastCatalog = live;
3562
+ }
3554
3563
  await settings.mutate(row.ns, [{
3555
3564
  op: "set",
3556
3565
  path: [field],
@@ -3926,13 +3935,16 @@ function apply(ctx, config) {
3926
3935
  ...model,
3927
3936
  ...images.has(model.id) ? { multimodal: true } : { multimodal: false }
3928
3937
  }));
3929
- const configuredModels = (value, region) => {
3938
+ const configuredModels = (value, region, liveModels = []) => {
3930
3939
  const state = regionStateOf(value, region);
3931
- return withImageSelection(deriveCatalog(state.lastCatalog?.length ? state.lastCatalog : fallbackModelsFor(region), new Set(state.enabledModelIds ?? []), state.contextBudgets ?? {}), new Set(state.imageModelIds ?? []));
3940
+ const roster = state.lastCatalog?.length ? state.lastCatalog : liveModels.length ? liveModels : fallbackModelsFor(region);
3941
+ return withImageSelection(deriveCatalog(roster, new Set(state.enabledModelIds ?? []), state.contextBudgets ?? {}), new Set(state.imageModelIds ?? []));
3932
3942
  };
3933
3943
  const displayModels = (value, region) => {
3934
3944
  const state = regionStateOf(value, region);
3935
- return state.lastCatalog?.length ? state.lastCatalog : fallbackModelsFor(region);
3945
+ if (state.lastCatalog?.length) return state.lastCatalog;
3946
+ const live = stacks[region]?.catalog?.current();
3947
+ return live?.length ? live : fallbackModelsFor(region);
3936
3948
  };
3937
3949
  let current = () => config;
3938
3950
  let invalidateCatalog = () => {};
@@ -3955,7 +3967,7 @@ function apply(ctx, config) {
3955
3967
  for (const region of REGION_KEYS) {
3956
3968
  stacks[region].store.setDesktopPath(authFile);
3957
3969
  stacks[region].store.selectAccount(effectiveAccountFor(region, value));
3958
- stacks[region].catalog.set(configuredModels(value, region));
3970
+ stacks[region].catalog.set(configuredModels(value, region, stacks[region].catalog.current()));
3959
3971
  }
3960
3972
  invalidateCatalog();
3961
3973
  syncRegionRegistration(value);
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-connect-workbuddy",
3
3
  "displayName": "DSH Connect WorkBuddy",
4
4
  "description": "把本机登录的 WorkBuddy 模型接入 DeepSeek Harness,并提供只读的积分概览与模型管理。",
5
- "version": "3.4.4",
5
+ "version": "3.4.5",
6
6
  "author": "dingminhua",
7
7
  "copyright": "Copyright (c) 2026 LaoDing",
8
8
  "license": "MIT",