dsh-connect-workbuddy 4.14.0 → 4.14.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 CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.14.1 (2026-10-21)
4
+
5
+ > 版本号是 **patch**:没有接口、配置项或请求结构的变更,发往上游的请求体**一个字节都没变**。本版只改「哪些上游条目算作模型」这一处判断,以及它带出的一个目录退化路径。
6
+
7
+ ### Fixes
8
+
9
+ - **不再把上游的 `auto` 路由器当成模型列出来**
10
+ - **它不是模型**:国内区 `cli` 名单里的 `auto` 是一个**路由决策**,网关在请求时把它解析成它认为合适的那一个(实测 → `hy4-preview`)。所以它的价格、上下文窗口与行为都由别人在请求时决定,插件无法如实描述;把它列成一行,用户看到的将是一行「倍率未知、行为不定」的条目。
11
+ - **它还制造重复**:同一份名单里本来就有它会被解析到的那个真实模型(`hy4-preview`),于是选择器出现两行指向同一个上游模型、其中一行没有倍率。
12
+ - **改在名单构建的单一位置**(`selectCliModels`),因此卡片、模型选择器、探针与发现路由看到的是同一份结果,不会出现「卡片上没有、选择器里还有」。
13
+ - **用显式表而不是名字启发式**:新增 `ROUTER_MODEL_IDS` / `isRouterModelId`(`src/native-modality.ts`),与既有的 `nativeModalityOf(...) === 'router'` 共用同一个来源。路由是上游的概念、没有声明字段,按名字猜迟早会误伤真实模型。
14
+ - **静态兜底同步移除**:`FALLBACK_WORKBUDDY_MODELS` 里也有 `auto`。只改实时名单的话,它会在离线/首帧路径上复活——那是这类不一致里最难察觉的一种。
15
+ - **空名单守卫挪到并入保留模型之后**:否则名单只剩 router 时,守卫会在合并前抛错,把「上游改了 router」放大成「这个区域一个模型都没有」。现在「上游名单为空、但用户仍有在跑的真实模型」会正常降级为后者。
16
+
17
+ - **勾选与目录零交集时退回整个目录,而不是返回空**
18
+ - 真实触发路径正是一位「只勾过 `auto`」的用户:4.14.0 里他还能勾选它,本版不再提供之后,他的 `enabledModelIds` 与目录的交集为空,运行时目录会变成空的——那是 issue #32 的故障形态(选择器突然没有模型,且没有任何报错)。
19
+ - `deriveCatalog` 现在只在「非空勾选 ∩ 目录 = ∅」时退回整个目录。用户勾选表达的是「要一些模型开着」,不是「一个都不要」;空目录仍然返回空,这条只处理「勾选不再匹配任何东西」。
20
+
21
+ ### Tests
22
+
23
+ - `tests/upstream.spec.ts` 新增 2 条:**router 绝不作为模型出现**(名单里 `auto` 与 `hy4-preview` 同时存在时,结果只有后者);**名单只剩 router 时保留模型仍然生效**——这条专门钉住「守卫在合并前还是合并后」这个坑,守卫放错位置它会红。
24
+ - `tests/catalog.spec.ts` 新增/改写:两区静态兜底都不含 `auto`;勾选只命中 router 时退回整个目录而不是空。
25
+ - 既有两条把 `auto` 当素材的用例改用真实模型(`hy3`),避免测试本身依赖刚被删掉的行为。
26
+ - **真实上游核验**(只读 GET,不消耗积分):国内区 `cli` 名单 20 → **19** 条,`auto` 已不在其中;其余 9 枚活动徽标一个不少,与 WorkBuddy 客户端逐条一致。
27
+
3
28
  ## 4.14.0 (2026-10-21)
4
29
 
5
30
  > 版本号是 **minor**:有用户可见的功能与行为变更(模型行新增上游活动徽标;国内区改以 App 身份读产品配置,因此目录与客户端一致)。发往上游的**聊天**请求体、路由 id、凭据、计费与换号规则一律未变——变的只有**读取模型目录**那一次 GET 的 User-Agent。
package/lib/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { C as defaultDesktopAuthDirs, F as WorkBuddyUpstreamClient, O as legacyWorkbuddyOwnAuthPath, S as defaultDesktopAuthCandidates, a as readHostHeartbeat, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, et as WORKBUDDY_APP_EXECUTABLE_ENV, j as workbuddyOwnAuthPath, l as piAiRuntimeInfo, mt as hostPeersInfo, o as workbuddyHostHeartbeatPath, ot as findWorkbuddyAppExecutableWithSource, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS, v as WORKBUDDY_AUTH_FILE_ENV, y as WorkBuddyCredentialStore } from "./host-heartbeat-B1I95fKS.js";
2
+ import { C as defaultDesktopAuthDirs, F as WorkBuddyUpstreamClient, O as legacyWorkbuddyOwnAuthPath, S as defaultDesktopAuthCandidates, _t as hostPeersInfo, a as readHostHeartbeat, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, j as workbuddyOwnAuthPath, l as piAiRuntimeInfo, lt as findWorkbuddyAppExecutableWithSource, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, rt as WORKBUDDY_APP_EXECUTABLE_ENV, u as FALLBACK_WORKBUDDY_MODELS, v as WORKBUDDY_AUTH_FILE_ENV, y as WorkBuddyCredentialStore } from "./host-heartbeat-D1WnqQY2.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
@@ -1501,6 +1501,71 @@ window.__ModuleLoader__.load({
1501
1501
  return t("row.poolProbeDaysAgo", { count: Math.round(age / DAY_MS) });
1502
1502
  }
1503
1503
  //#endregion
1504
+ //#region src/native-modality.ts
1505
+ /**
1506
+ * Reviewed vendor classifications, keyed by EXACT model id (never a prefix).
1507
+ * Ids absent from this record are `unknown` — see {@link nativeModalityOf}.
1508
+ */
1509
+ const NATIVE_MODALITY_BY_MODEL_ID = {
1510
+ "deepseek-v4-pro": "text",
1511
+ "glm-5.1": "text",
1512
+ "glm-5.2": "text",
1513
+ "glm-5.3": "text",
1514
+ "hy3": "text",
1515
+ "hy4-preview": "text",
1516
+ "deepseek-v4.1-flash": "multimodal",
1517
+ "glm-5.3-flash": "multimodal",
1518
+ "glm-5v-turbo": "multimodal",
1519
+ "kimi-k2.6": "multimodal",
1520
+ "kimi-k2.7": "multimodal",
1521
+ "kimi-k3-1": "multimodal",
1522
+ "minimax-m3": "multimodal"
1523
+ };
1524
+ /**
1525
+ * Ids the upstream lists in the model roster that are ROUTERS, not models.
1526
+ *
1527
+ * `auto` (CN) is the one observed today: the gateway resolves it to whichever
1528
+ * model it considers the best fit (measured: CN `auto` → `hy4-preview`), so its
1529
+ * price, context window and behaviour are decided at request time by something
1530
+ * else. Offering it as a row advertises a model whose terms the plugin cannot
1531
+ * state, and it duplicates a real row already in the same roster.
1532
+ *
1533
+ * Kept as an explicit table rather than a heuristic: a router is an upstream
1534
+ * concept with no declared flag, and guessing from the name would eventually
1535
+ * hide a real model. Add an id here only after the same kind of evidence.
1536
+ */
1537
+ const ROUTER_MODEL_IDS = /* @__PURE__ */ new Set(["auto"]);
1538
+ /** Whether this id is an upstream router rather than a model. */
1539
+ function isRouterModelId(modelId) {
1540
+ return ROUTER_MODEL_IDS.has(modelId);
1541
+ }
1542
+ /**
1543
+ * Native modality of one model id. Exact match only: a new model never
1544
+ * inherits a classification from its family, its display name, or the
1545
+ * platform's image flag.
1546
+ */
1547
+ function nativeModalityOf(modelId) {
1548
+ if (isRouterModelId(modelId)) return "router";
1549
+ return NATIVE_MODALITY_BY_MODEL_ID[modelId] ?? "unknown";
1550
+ }
1551
+ /**
1552
+ * Whether a model refresh should PRE-CHECK this model's image box.
1553
+ *
1554
+ * Only a documented `multimodal` is pre-checked. `text` is documented as
1555
+ * unable to read images, and `unknown` is deliberately left unchecked rather
1556
+ * than guessed — the whole reason this table exists is that the platform flag
1557
+ * guessed wrong. A platform `supportsImages: false` still vetoes, so a
1558
+ * documented-multimodal id can never be pre-checked against the platform's
1559
+ * explicit "no".
1560
+ *
1561
+ * The user can always tick a box by hand; this decides a DEFAULT only, and the
1562
+ * effective runtime flag remains the saved `imageModelIds`.
1563
+ */
1564
+ function imageDefaultFor(info) {
1565
+ if (info.supportsImages === false) return false;
1566
+ return NATIVE_MODALITY_BY_MODEL_ID[info.id] === "multimodal";
1567
+ }
1568
+ //#endregion
1504
1569
  //#region src/promo.ts
1505
1570
  /**
1506
1571
  * `'HH:mm'` → minutes since midnight, or undefined for anything else.
@@ -3735,55 +3800,6 @@ window.__ModuleLoader__.load({
3735
3800
  });
3736
3801
  }
3737
3802
  //#endregion
3738
- //#region src/native-modality.ts
3739
- /**
3740
- * Reviewed vendor classifications, keyed by EXACT model id (never a prefix).
3741
- * Ids absent from this record are `unknown` — see {@link nativeModalityOf}.
3742
- */
3743
- const NATIVE_MODALITY_BY_MODEL_ID = {
3744
- "deepseek-v4-pro": "text",
3745
- "glm-5.1": "text",
3746
- "glm-5.2": "text",
3747
- "glm-5.3": "text",
3748
- "hy3": "text",
3749
- "hy4-preview": "text",
3750
- "deepseek-v4.1-flash": "multimodal",
3751
- "glm-5.3-flash": "multimodal",
3752
- "glm-5v-turbo": "multimodal",
3753
- "kimi-k2.6": "multimodal",
3754
- "kimi-k2.7": "multimodal",
3755
- "kimi-k3-1": "multimodal",
3756
- "minimax-m3": "multimodal"
3757
- };
3758
- /** The `auto` entry is a router that picks a model, not a model itself. */
3759
- const ROUTER_MODEL_ID = "auto";
3760
- /**
3761
- * Native modality of one model id. Exact match only: a new model never
3762
- * inherits a classification from its family, its display name, or the
3763
- * platform's image flag.
3764
- */
3765
- function nativeModalityOf(modelId) {
3766
- if (modelId === ROUTER_MODEL_ID) return "router";
3767
- return NATIVE_MODALITY_BY_MODEL_ID[modelId] ?? "unknown";
3768
- }
3769
- /**
3770
- * Whether a model refresh should PRE-CHECK this model's image box.
3771
- *
3772
- * Only a documented `multimodal` is pre-checked. `text` is documented as
3773
- * unable to read images, and `unknown` is deliberately left unchecked rather
3774
- * than guessed — the whole reason this table exists is that the platform flag
3775
- * guessed wrong. A platform `supportsImages: false` still vetoes, so a
3776
- * documented-multimodal id can never be pre-checked against the platform's
3777
- * explicit "no".
3778
- *
3779
- * The user can always tick a box by hand; this decides a DEFAULT only, and the
3780
- * effective runtime flag remains the saved `imageModelIds`.
3781
- */
3782
- function imageDefaultFor(info) {
3783
- if (info.supportsImages === false) return false;
3784
- return NATIVE_MODALITY_BY_MODEL_ID[info.id] === "multimodal";
3785
- }
3786
- //#endregion
3787
3803
  //#region src/client/promo-badge.ts
3788
3804
  /**
3789
3805
  * The UI's language, as the browser reports it.
@@ -962,6 +962,71 @@ function clearAtRestKeyCache() {
962
962
  cachedAppExecutable = void 0;
963
963
  }
964
964
  //#endregion
965
+ //#region src/native-modality.ts
966
+ /**
967
+ * Reviewed vendor classifications, keyed by EXACT model id (never a prefix).
968
+ * Ids absent from this record are `unknown` — see {@link nativeModalityOf}.
969
+ */
970
+ const NATIVE_MODALITY_BY_MODEL_ID = {
971
+ "deepseek-v4-pro": "text",
972
+ "glm-5.1": "text",
973
+ "glm-5.2": "text",
974
+ "glm-5.3": "text",
975
+ "hy3": "text",
976
+ "hy4-preview": "text",
977
+ "deepseek-v4.1-flash": "multimodal",
978
+ "glm-5.3-flash": "multimodal",
979
+ "glm-5v-turbo": "multimodal",
980
+ "kimi-k2.6": "multimodal",
981
+ "kimi-k2.7": "multimodal",
982
+ "kimi-k3-1": "multimodal",
983
+ "minimax-m3": "multimodal"
984
+ };
985
+ /**
986
+ * Ids the upstream lists in the model roster that are ROUTERS, not models.
987
+ *
988
+ * `auto` (CN) is the one observed today: the gateway resolves it to whichever
989
+ * model it considers the best fit (measured: CN `auto` → `hy4-preview`), so its
990
+ * price, context window and behaviour are decided at request time by something
991
+ * else. Offering it as a row advertises a model whose terms the plugin cannot
992
+ * state, and it duplicates a real row already in the same roster.
993
+ *
994
+ * Kept as an explicit table rather than a heuristic: a router is an upstream
995
+ * concept with no declared flag, and guessing from the name would eventually
996
+ * hide a real model. Add an id here only after the same kind of evidence.
997
+ */
998
+ const ROUTER_MODEL_IDS = /* @__PURE__ */ new Set(["auto"]);
999
+ /** Whether this id is an upstream router rather than a model. */
1000
+ function isRouterModelId(modelId) {
1001
+ return ROUTER_MODEL_IDS.has(modelId);
1002
+ }
1003
+ /**
1004
+ * Native modality of one model id. Exact match only: a new model never
1005
+ * inherits a classification from its family, its display name, or the
1006
+ * platform's image flag.
1007
+ */
1008
+ function nativeModalityOf(modelId) {
1009
+ if (isRouterModelId(modelId)) return "router";
1010
+ return NATIVE_MODALITY_BY_MODEL_ID[modelId] ?? "unknown";
1011
+ }
1012
+ /**
1013
+ * Whether a model refresh should PRE-CHECK this model's image box.
1014
+ *
1015
+ * Only a documented `multimodal` is pre-checked. `text` is documented as
1016
+ * unable to read images, and `unknown` is deliberately left unchecked rather
1017
+ * than guessed — the whole reason this table exists is that the platform flag
1018
+ * guessed wrong. A platform `supportsImages: false` still vetoes, so a
1019
+ * documented-multimodal id can never be pre-checked against the platform's
1020
+ * explicit "no".
1021
+ *
1022
+ * The user can always tick a box by hand; this decides a DEFAULT only, and the
1023
+ * effective runtime flag remains the saved `imageModelIds`.
1024
+ */
1025
+ function imageDefaultFor(info) {
1026
+ if (info.supportsImages === false) return false;
1027
+ return NATIVE_MODALITY_BY_MODEL_ID[info.id] === "multimodal";
1028
+ }
1029
+ //#endregion
965
1030
  //#region src/off-thinking.ts
966
1031
  /**
967
1032
  * Ids observed to REFUSE `reasoning_effort: "off"` with HTTP 400 despite
@@ -2328,6 +2393,18 @@ function parseUpstreamModel(value) {
2328
2393
  * is exposed rather than nothing: the roster is an upstream detail that may
2329
2394
  * change, and an empty answer would silently disarm the provider.
2330
2395
  *
2396
+ * ROUTERS ARE NOT MODELS. The upstream lists `auto` (CN) and its global
2397
+ * equivalents alongside real models, but those entries are ROUTING decisions,
2398
+ * not models: the gateway resolves them to whichever model it thinks fits, so
2399
+ * a row for one would offer a name whose price, context window and behaviour
2400
+ * are all decided at request time by something else. Selected this way it also
2401
+ * duplicates a real row — upstream resolves CN `auto` to `hy4-preview`, which
2402
+ * is already in the same roster. They are dropped here, at the single place
2403
+ * the roster is built, so the card, the picker, the probe and the discovery
2404
+ * route all agree. A user who already had one enabled keeps it through
2405
+ * `keepIds` below: this removes the OFFER, never a running model (issue #32's
2406
+ * failure mode).
2407
+ *
2331
2408
  * `keepIds` is the user's own selection riding along with the refresh. The
2332
2409
  * upstream roster may drop a model the user is actually running — the app
2333
2410
  * channel's `cli` roster dropped `minimax-m2.7` on 2026-10-21 while the SAME
@@ -2351,10 +2428,11 @@ function selectCliModels(rawModels, agents, keepIds = []) {
2351
2428
  }
2352
2429
  }
2353
2430
  const ids = cliIds !== void 0 && cliIds.length > 0 ? cliIds : [...byId.keys()];
2354
- const models = ids.map((id) => byId.get(id)).filter((model) => model !== void 0);
2355
- if (models.length === 0) throw new Error("workbuddy model catalog resolved to an empty list");
2431
+ const models = ids.filter((id) => !isRouterModelId(id)).map((id) => byId.get(id)).filter((model) => model !== void 0);
2356
2432
  const kept = keepIds.filter((id) => !ids.includes(id)).map((id) => byId.get(id)).filter((model) => model !== void 0);
2357
- return [...models, ...kept];
2433
+ const resolved = [...models, ...kept];
2434
+ if (resolved.length === 0) throw new Error("workbuddy model catalog resolved to an empty list");
2435
+ return resolved;
2358
2436
  }
2359
2437
  /**
2360
2438
  * The roster plus the promotion badges the SAME document carries.
@@ -4255,14 +4333,14 @@ var WorkBuddyCredentialStore = class {
4255
4333
  * Static CLI models captured from the CN endpoint (2026-08-30). The upstream
4256
4334
  * refresh replaces this list at startup; it exists so the provider registers
4257
4335
  * with a usable catalog even while the first fetch is in flight or offline.
4336
+ *
4337
+ * The upstream's `auto` ROUTER is deliberately absent, matching what
4338
+ * `selectCliModels` now does to the live roster: it resolves to a different
4339
+ * model per request, so it is not a row this plugin can describe. Keeping it
4340
+ * here would have reintroduced it on the offline/第一帧 path only, which is the
4341
+ * hardest kind of inconsistency to notice.
4258
4342
  */
4259
4343
  const FALLBACK_WORKBUDDY_MODELS = [
4260
- {
4261
- id: "auto",
4262
- name: "Auto",
4263
- contextWindow: 168e3,
4264
- maxTokens: 32e3
4265
- },
4266
4344
  {
4267
4345
  id: "hy3",
4268
4346
  name: "Hy3",
@@ -4510,8 +4588,23 @@ function applyContextBudgets(catalog, budgets = {}) {
4510
4588
  contextWindow: budgets[model.id] === void 0 ? model.contextWindow : Math.min(model.contextWindow, budgets[model.id])
4511
4589
  }));
4512
4590
  }
4591
+ /**
4592
+ * Derive the runtime catalog from the last-refreshed directory plus the user's
4593
+ * selection.
4594
+ *
4595
+ * An empty selection means "never configured" and serves the whole directory.
4596
+ * But a NON-empty selection that intersects the directory in nothing must NOT
4597
+ * serve nothing: that is the issue #32 failure mode (the picker empties out),
4598
+ * and it now has a realistic trigger — a user whose only ticked model was the
4599
+ * upstream's `auto` router, which this version stops offering. Such a selection
4600
+ * degrades to the whole directory rather than to silence: the user asked for
4601
+ * "some models to be on", and the alternative is a region with no models at
4602
+ * all. An empty directory still yields an empty result; the guard is only about
4603
+ * a selection that no longer matches anything.
4604
+ */
4513
4605
  function deriveCatalog(catalog, enabled, budgets = {}) {
4514
- return applyContextBudgets(enabled.size === 0 ? catalog : catalog.filter((model) => enabled.has(model.id)), budgets);
4606
+ const selected = enabled.size === 0 ? catalog : catalog.filter((model) => enabled.has(model.id));
4607
+ return applyContextBudgets(selected.length === 0 && enabled.size > 0 ? catalog : selected, budgets);
4515
4608
  }
4516
4609
  /**
4517
4610
  * Mutable catalog shared by the shim's `/v1/models` and the adapter.
@@ -4770,7 +4863,7 @@ function generationOf(version) {
4770
4863
  //#endregion
4771
4864
  //#region src/version.ts
4772
4865
  /** The npm package version this build was produced from. */
4773
- const WORKBUDDY_CONNECT_VERSION = "4.14.0";
4866
+ const WORKBUDDY_CONNECT_VERSION = "4.14.1";
4774
4867
  //#endregion
4775
4868
  //#region src/host-heartbeat.ts
4776
4869
  /**
@@ -4928,4 +5021,4 @@ async function clearHostHeartbeat() {
4928
5021
  await rm(workbuddyHostHeartbeatPath(), { force: true });
4929
5022
  }
4930
5023
  //#endregion
4931
- export { withEffectiveOff 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, defaultOffModelIds as J, systemOpeningsOfBody as K, declaredTools as L, CREDENTIAL_REJECTED_CODE as M, WORKBUDDY_FALLBACK_SYSTEM_PROMPT as N, legacyWorkbuddyOwnAuthPath as O, WorkBuddyCredentialRejectedError as P, recordRefusal as Q, isCredentialRejectedError as R, defaultDesktopAuthCandidates as S, hasEncryptedCredentialFields as T, parseUpstreamModel as U, parseCreditMultiplier as V, prepareChatBody as W, offDefaultFor as X, lastRefusalFor as Y, offRefused as Z, WORKBUDDY_AUTH_FILENAME as _, writeFileAtomic as _t, readHostHeartbeat as a, findWorkbuddyAppExecutable as at, WorkBuddyEncryptedCredentialError as b, WORKBUDDY_CONNECT_VERSION as c, isWorkbuddyBundle as ct, FALLBACK_WORKBUDDY_MODELS_GLOBAL as d, openEncryptedField as dt, WORKBUDDY_APP_EXECUTABLE_ENV as et, WorkBuddyCatalog as f, readAtRestKey as ft, ENCRYPTED_CREDENTIAL_CODE as g, withFileLock as gt, fallbackModelsFor as h, resolveDshHome as ht, processStartTimeMs as i, fetchAtRestKeyPayload as it, workbuddyOwnAuthPath as j, parseWorkBuddyAuth as k, piAiRuntimeInfo as l, macosBundleExecutable as lt, deriveCatalog as m, hostPeersInfo as mt, clearHostHeartbeat as n, deriveAtRestKey as nt, workbuddyHostHeartbeatPath as o, findWorkbuddyAppExecutableWithSource as ot, applyContextBudgets as p, workbuddyAppExecutableCandidates as pt, REFUSES_OFF_MODEL_IDS as q, isHeartbeatProcessAlive as r, deriveAtRestKeyId as rt, writeHostHeartbeat as s, isEncryptedFieldWrapper as st, WORKBUDDY_HOST_HEARTBEAT_FILENAME as t, clearAtRestKeyCache as tt, FALLBACK_WORKBUDDY_MODELS as u, macosNestedAppBundles as ut, WORKBUDDY_AUTH_FILE_ENV as v, defaultDesktopAuthPath as w, authFileName as x, WorkBuddyCredentialStore as y, knownRegionOf as z };
5024
+ export { withEffectiveOff 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, defaultOffModelIds as J, systemOpeningsOfBody as K, declaredTools as L, CREDENTIAL_REJECTED_CODE as M, WORKBUDDY_FALLBACK_SYSTEM_PROMPT as N, legacyWorkbuddyOwnAuthPath as O, WorkBuddyCredentialRejectedError as P, recordRefusal as Q, isCredentialRejectedError as R, defaultDesktopAuthCandidates as S, hasEncryptedCredentialFields as T, parseUpstreamModel as U, parseCreditMultiplier as V, prepareChatBody as W, offDefaultFor as X, lastRefusalFor as Y, offRefused as Z, WORKBUDDY_AUTH_FILENAME as _, hostPeersInfo as _t, readHostHeartbeat as a, deriveAtRestKey as at, WorkBuddyEncryptedCredentialError as b, writeFileAtomic as bt, WORKBUDDY_CONNECT_VERSION as c, findWorkbuddyAppExecutable as ct, FALLBACK_WORKBUDDY_MODELS_GLOBAL as d, isWorkbuddyBundle as dt, NATIVE_MODALITY_BY_MODEL_ID as et, WorkBuddyCatalog as f, macosBundleExecutable as ft, ENCRYPTED_CREDENTIAL_CODE as g, workbuddyAppExecutableCandidates as gt, fallbackModelsFor as h, readAtRestKey as ht, processStartTimeMs as i, clearAtRestKeyCache as it, workbuddyOwnAuthPath as j, parseWorkBuddyAuth as k, piAiRuntimeInfo as l, findWorkbuddyAppExecutableWithSource as lt, deriveCatalog as m, openEncryptedField as mt, clearHostHeartbeat as n, nativeModalityOf as nt, workbuddyHostHeartbeatPath as o, deriveAtRestKeyId as ot, applyContextBudgets as p, macosNestedAppBundles as pt, REFUSES_OFF_MODEL_IDS as q, isHeartbeatProcessAlive as r, WORKBUDDY_APP_EXECUTABLE_ENV as rt, writeHostHeartbeat as s, fetchAtRestKeyPayload as st, WORKBUDDY_HOST_HEARTBEAT_FILENAME as t, imageDefaultFor as tt, FALLBACK_WORKBUDDY_MODELS as u, isEncryptedFieldWrapper as ut, WORKBUDDY_AUTH_FILE_ENV as v, resolveDshHome as vt, defaultDesktopAuthPath as w, authFileName as x, WorkBuddyCredentialStore as y, withFileLock as yt, knownRegionOf as z };
package/lib/index.d.ts CHANGED
@@ -1286,6 +1286,12 @@ type WorkBuddyModelInfo = WorkBuddyUpstreamModel;
1286
1286
  * Static CLI models captured from the CN endpoint (2026-08-30). The upstream
1287
1287
  * refresh replaces this list at startup; it exists so the provider registers
1288
1288
  * with a usable catalog even while the first fetch is in flight or offline.
1289
+ *
1290
+ * The upstream's `auto` ROUTER is deliberately absent, matching what
1291
+ * `selectCliModels` now does to the live roster: it resolves to a different
1292
+ * model per request, so it is not a row this plugin can describe. Keeping it
1293
+ * here would have reintroduced it on the offline/第一帧 path only, which is the
1294
+ * hardest kind of inconsistency to notice.
1289
1295
  */
1290
1296
  declare const FALLBACK_WORKBUDDY_MODELS: readonly WorkBuddyModelInfo[];
1291
1297
  /**
@@ -1312,6 +1318,20 @@ declare function fallbackModelsFor(region: 'cn' | 'global'): readonly WorkBuddyM
1312
1318
  * never been configured must still serve models rather than nothing.
1313
1319
  */
1314
1320
  type WorkBuddyContextBudget = number;
1321
+ /**
1322
+ * Derive the runtime catalog from the last-refreshed directory plus the user's
1323
+ * selection.
1324
+ *
1325
+ * An empty selection means "never configured" and serves the whole directory.
1326
+ * But a NON-empty selection that intersects the directory in nothing must NOT
1327
+ * serve nothing: that is the issue #32 failure mode (the picker empties out),
1328
+ * and it now has a realistic trigger — a user whose only ticked model was the
1329
+ * upstream's `auto` router, which this version stops offering. Such a selection
1330
+ * degrades to the whole directory rather than to silence: the user asked for
1331
+ * "some models to be on", and the alternative is a region with no models at
1332
+ * all. An empty directory still yields an empty result; the guard is only about
1333
+ * a selection that no longer matches anything.
1334
+ */
1315
1335
  declare function deriveCatalog(catalog: readonly WorkBuddyModelInfo[], enabled: ReadonlySet<string>, budgets?: Readonly<Record<string, WorkBuddyContextBudget | undefined>>): WorkBuddyModelInfo[];
1316
1336
  /**
1317
1337
  * Mutable catalog shared by the shim's `/v1/models` and the adapter.
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as withEffectiveOff, A as workbuddyAccountId, B as modelIdOfBody, C as defaultDesktopAuthDirs, D as isVaultAccountId, E as isEncryptedCredentialError, F as WorkBuddyUpstreamClient, G as regionOf, H as parseReasoning, I as classifyUpstreamError, J as defaultOffModelIds, K as systemOpeningsOfBody, L as declaredTools, M as CREDENTIAL_REJECTED_CODE, N as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, O as legacyWorkbuddyOwnAuthPath, P as WorkBuddyCredentialRejectedError, Q as recordRefusal, R as isCredentialRejectedError, S as defaultDesktopAuthCandidates, T as hasEncryptedCredentialFields, U as parseUpstreamModel, V as parseCreditMultiplier, W as prepareChatBody, X as offDefaultFor, Y as lastRefusalFor, Z as offRefused, _ as WORKBUDDY_AUTH_FILENAME, _t as writeFileAtomic, a as readHostHeartbeat, at as findWorkbuddyAppExecutable, b as WorkBuddyEncryptedCredentialError, c as WORKBUDDY_CONNECT_VERSION, ct as isWorkbuddyBundle, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, dt as openEncryptedField, et as WORKBUDDY_APP_EXECUTABLE_ENV, f as WorkBuddyCatalog, ft as readAtRestKey, g as ENCRYPTED_CREDENTIAL_CODE, gt as withFileLock, h as fallbackModelsFor, ht as resolveDshHome, i as processStartTimeMs, it as fetchAtRestKeyPayload, j as workbuddyOwnAuthPath, k as parseWorkBuddyAuth, l as piAiRuntimeInfo, lt as macosBundleExecutable, m as deriveCatalog, n as clearHostHeartbeat, nt as deriveAtRestKey, o as workbuddyHostHeartbeatPath, p as applyContextBudgets, pt as workbuddyAppExecutableCandidates, q as REFUSES_OFF_MODEL_IDS, r as isHeartbeatProcessAlive, rt as deriveAtRestKeyId, s as writeHostHeartbeat, st as isEncryptedFieldWrapper, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, tt as clearAtRestKeyCache, u as FALLBACK_WORKBUDDY_MODELS, ut as macosNestedAppBundles, v as WORKBUDDY_AUTH_FILE_ENV, w as defaultDesktopAuthPath, x as authFileName, y as WorkBuddyCredentialStore, z as knownRegionOf } from "./host-heartbeat-B1I95fKS.js";
1
+ import { $ as withEffectiveOff, A as workbuddyAccountId, B as modelIdOfBody, C as defaultDesktopAuthDirs, D as isVaultAccountId, E as isEncryptedCredentialError, F as WorkBuddyUpstreamClient, G as regionOf, H as parseReasoning, I as classifyUpstreamError, J as defaultOffModelIds, K as systemOpeningsOfBody, L as declaredTools, M as CREDENTIAL_REJECTED_CODE, N as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, O as legacyWorkbuddyOwnAuthPath, P as WorkBuddyCredentialRejectedError, Q as recordRefusal, R as isCredentialRejectedError, S as defaultDesktopAuthCandidates, T as hasEncryptedCredentialFields, U as parseUpstreamModel, V as parseCreditMultiplier, W as prepareChatBody, X as offDefaultFor, Y as lastRefusalFor, Z as offRefused, _ as WORKBUDDY_AUTH_FILENAME, a as readHostHeartbeat, at as deriveAtRestKey, b as WorkBuddyEncryptedCredentialError, bt as writeFileAtomic, c as WORKBUDDY_CONNECT_VERSION, ct as findWorkbuddyAppExecutable, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, dt as isWorkbuddyBundle, et as NATIVE_MODALITY_BY_MODEL_ID, f as WorkBuddyCatalog, ft as macosBundleExecutable, g as ENCRYPTED_CREDENTIAL_CODE, gt as workbuddyAppExecutableCandidates, h as fallbackModelsFor, ht as readAtRestKey, i as processStartTimeMs, it as clearAtRestKeyCache, j as workbuddyOwnAuthPath, k as parseWorkBuddyAuth, l as piAiRuntimeInfo, m as deriveCatalog, mt as openEncryptedField, n as clearHostHeartbeat, nt as nativeModalityOf, o as workbuddyHostHeartbeatPath, ot as deriveAtRestKeyId, p as applyContextBudgets, pt as macosNestedAppBundles, q as REFUSES_OFF_MODEL_IDS, r as isHeartbeatProcessAlive, rt as WORKBUDDY_APP_EXECUTABLE_ENV, s as writeHostHeartbeat, st as fetchAtRestKeyPayload, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, tt as imageDefaultFor, u as FALLBACK_WORKBUDDY_MODELS, ut as isEncryptedFieldWrapper, v as WORKBUDDY_AUTH_FILE_ENV, vt as resolveDshHome, w as defaultDesktopAuthPath, x as authFileName, y as WorkBuddyCredentialStore, yt as withFileLock, z as knownRegionOf } from "./host-heartbeat-D1WnqQY2.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";
@@ -6173,55 +6173,6 @@ function registerWorkBuddyStatusRoute(ctx, deps) {
6173
6173
  }, "dsh-connect-workbuddy: Web status route");
6174
6174
  }
6175
6175
  //#endregion
6176
- //#region src/native-modality.ts
6177
- /**
6178
- * Reviewed vendor classifications, keyed by EXACT model id (never a prefix).
6179
- * Ids absent from this record are `unknown` — see {@link nativeModalityOf}.
6180
- */
6181
- const NATIVE_MODALITY_BY_MODEL_ID = {
6182
- "deepseek-v4-pro": "text",
6183
- "glm-5.1": "text",
6184
- "glm-5.2": "text",
6185
- "glm-5.3": "text",
6186
- "hy3": "text",
6187
- "hy4-preview": "text",
6188
- "deepseek-v4.1-flash": "multimodal",
6189
- "glm-5.3-flash": "multimodal",
6190
- "glm-5v-turbo": "multimodal",
6191
- "kimi-k2.6": "multimodal",
6192
- "kimi-k2.7": "multimodal",
6193
- "kimi-k3-1": "multimodal",
6194
- "minimax-m3": "multimodal"
6195
- };
6196
- /** The `auto` entry is a router that picks a model, not a model itself. */
6197
- const ROUTER_MODEL_ID = "auto";
6198
- /**
6199
- * Native modality of one model id. Exact match only: a new model never
6200
- * inherits a classification from its family, its display name, or the
6201
- * platform's image flag.
6202
- */
6203
- function nativeModalityOf(modelId) {
6204
- if (modelId === ROUTER_MODEL_ID) return "router";
6205
- return NATIVE_MODALITY_BY_MODEL_ID[modelId] ?? "unknown";
6206
- }
6207
- /**
6208
- * Whether a model refresh should PRE-CHECK this model's image box.
6209
- *
6210
- * Only a documented `multimodal` is pre-checked. `text` is documented as
6211
- * unable to read images, and `unknown` is deliberately left unchecked rather
6212
- * than guessed — the whole reason this table exists is that the platform flag
6213
- * guessed wrong. A platform `supportsImages: false` still vetoes, so a
6214
- * documented-multimodal id can never be pre-checked against the platform's
6215
- * explicit "no".
6216
- *
6217
- * The user can always tick a box by hand; this decides a DEFAULT only, and the
6218
- * effective runtime flag remains the saved `imageModelIds`.
6219
- */
6220
- function imageDefaultFor(info) {
6221
- if (info.supportsImages === false) return false;
6222
- return NATIVE_MODALITY_BY_MODEL_ID[info.id] === "multimodal";
6223
- }
6224
- //#endregion
6225
6176
  //#region src/index.ts
6226
6177
  /** Stable Cordis plugin name. */
6227
6178
  const name = "dsh-connect-workbuddy";
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": "4.14.0",
5
+ "version": "4.14.1",
6
6
  "author": "dingminhua",
7
7
  "copyright": "Copyright (c) 2026 LaoDing",
8
8
  "license": "MIT",