dsh-connect-workbuddy 4.13.2 → 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/lib/index.d.ts CHANGED
@@ -574,6 +574,179 @@ declare class WorkBuddyCredentialStore {
574
574
  }>;
575
575
  }
576
576
  //#endregion
577
+ //#region src/promo.d.ts
578
+ /**
579
+ * WorkBuddy promotion & tier badges — the upstream's own "this model is on
580
+ * offer" annotation, parsed and resolved against the wall clock.
581
+ *
582
+ * WHY A SEPARATE MODULE, AND WHY IT COSTS NO REQUEST. The upstream ships the
583
+ * badges inside the SAME `/v3/config` document that already carries the model
584
+ * roster: `data.modelPromotions` and `data.modelTiers` sit beside
585
+ * `data.models` (observed on the global feed, 2026-10-21). The plugin already
586
+ * reads that document for the roster, so drawing a promotion badge adds ZERO
587
+ * upstream traffic — it only adds a parser, because the document is not a
588
+ * clean list of promotions: the same `modelPromotions` array also carries
589
+ * NON-promotion entries (`hy-image-3.5`, `kling-video-v3` — entries with
590
+ * `tags` / `videoGeneration` and no `modelIds` and no `badge`). Those must be
591
+ * dropped, not rendered.
592
+ *
593
+ * WHY THE RESOLUTION LIVES HERE AND NOT IN THE FETCHER. A badge is a function
594
+ * of the clock, not of the fetch: the upstream's windows are wall-clock
595
+ * windows in a named zone (`schedule.daily`, e.g. `23:00`–`7:50` in
596
+ * `Asia/Shanghai`) and absolute windows (`schedule.validFrom` /
597
+ * `schedule.validUntil`). Picking the badge once, at parse time, FREEZES it —
598
+ * a badge read at 22:59 would still claim "night discount" at 08:00 the next
599
+ * morning. So the parser keeps EVERY candidate and the RENDERER asks for the
600
+ * one that holds at the moment it draws. The card re-renders at least once a
601
+ * minute while it is open (it polls the usage route), so a window edge is
602
+ * picked up within a minute without a second timer.
603
+ *
604
+ * THE SHAPES THIS PARSER ACCEPTS (all observed on the global feed's
605
+ * `/v3/config`, 2026-10-21 — the CN feed publishes the same two arrays, with
606
+ * Chinese labels and the extra fields listed at the end of this header):
607
+ *
608
+ * {
609
+ * id: 'hy3-free-trial-202608',
610
+ * kind: 'discount',
611
+ * enabled: true,
612
+ * priority: 200,
613
+ * modelIds: ['hy3'],
614
+ * badge: { label: 'Free now', color: '#FF0000', display: 'activeOnly' },
615
+ * discount: { discountedCredits: '0x', displayMode: 'replace', factor: 0 },
616
+ * hover: { textZh: 'Aug 6 – Oct 31: Free daily use with unlimited credits.' },
617
+ * schedule: {
618
+ * timezone: 'Asia/Shanghai',
619
+ * validFrom: '2026-07-06T00:00:00+08:00',
620
+ * validUntil: '2026-11-01T00:00:00+08:00',
621
+ * },
622
+ * }
623
+ *
624
+ * and the tier shape (`data.modelTiers`), which has the same `badge` /
625
+ * `modelIds` / `priority` core, no `schedule`, and a `requiredUserType`:
626
+ *
627
+ * {
628
+ * id: 'gpt-pro-only',
629
+ * badge: { label: 'Pro Only', labelEn: 'Pro Only', color: '#009273' },
630
+ * hover: { textEn: 'Exclusive to Pro subscribers.', textZh: '模型仅面向 Pro 用户开放。' },
631
+ * modelIds: ['gpt-6-astra', …],
632
+ * priority: 200,
633
+ * requiredUserType: 'standard',
634
+ * tier: 'standard',
635
+ * }
636
+ *
637
+ * THE CN FEED IS THE SAME FEED, READ AS THE APP READS IT. The CN gateway
638
+ * publishes `modelPromotions` (8 entries, measured 2026-10-21) and
639
+ * `modelTiers` (1 entry) in the SAME `/v3/config` document — with Chinese
640
+ * labels (`夜间折扣` / `限时免费` / `夜间免费` / `独家限免` / `限时折扣` /
641
+ * `订阅优先`), a `schedule.daily` night window, a base64 `badge.icon` and an
642
+ * optional `badge.display: 'activeOnly'`. Those richer shapes are exactly what
643
+ * this parser accepts below.
644
+ *
645
+ * They are visible only to the APP's product identity: the CLI channel's
646
+ * answer to the same URL publishes neither array, because it is a strict
647
+ * subset of the app channel's document. The plugin therefore reads the
648
+ * product configuration as the app does (`PRODUCT_UA` in `src/upstream.ts`) —
649
+ * which is why a CN account sees the same badges here as in the WorkBuddy
650
+ * app, without a second upstream request.
651
+ *
652
+ * @module dsh-connect-workbuddy/promo
653
+ */
654
+ /** How the upstream wants the badge to appear. */
655
+ interface WorkBuddyPromoBadge {
656
+ /**
657
+ * The badge's own wording, region-localized ("限时免费" on the CN feed,
658
+ * "Free now" on the global one).
659
+ */
660
+ label: string;
661
+ /** The English wording, when the upstream publishes one. */
662
+ labelEn?: string;
663
+ /** A compact wording, when the upstream publishes one. */
664
+ shortLabel?: string;
665
+ /** The English compact wording, when the upstream publishes one. */
666
+ shortLabelEn?: string;
667
+ /**
668
+ * The badge's colour, as a hex string ("#009273").
669
+ *
670
+ * The upstream publishes it for most promotions and omits it for others
671
+ * (the CN tier entry has none), so the renderer treats it as optional.
672
+ */
673
+ color?: string;
674
+ /**
675
+ * `'activeOnly'` — the badge belongs to the promotion's RUNNING period and
676
+ * must disappear when the promotion is over.
677
+ *
678
+ * Absent — the badge is STANDING: it describes the model for as long as the
679
+ * promotion itself lasts, while the promotion's daily window governs only
680
+ * the discount. The upstream uses this for its night-discount pairs, where
681
+ * one entry carries the badge plus the night hours and a second, separate
682
+ * entry carries the same badge plus the daytime hours.
683
+ */
684
+ display?: 'activeOnly';
685
+ }
686
+ /** One daily window, as wall-clock times in `schedule.timezone`. */
687
+ interface WorkBuddyPromoWindow {
688
+ /** `'HH:mm'`, inclusive. */
689
+ start: string;
690
+ /**
691
+ * `'HH:mm'`, exclusive.
692
+ *
693
+ * `end` at or before `start` means the window CROSSES MIDNIGHT: `23:00` to
694
+ * `7:50` is the observed CN night window.
695
+ */
696
+ end: string;
697
+ }
698
+ /** When a promotion runs. */
699
+ interface WorkBuddyPromoSchedule {
700
+ /** The IANA zone the daily windows are written in ("Asia/Shanghai"). */
701
+ timezone?: string;
702
+ /** Epoch milliseconds; the promotion has not started before it. */
703
+ validFrom?: number;
704
+ /** Epoch milliseconds; the promotion is over AT it. */
705
+ validUntil?: number;
706
+ /** The daily windows, when the upstream publishes them. */
707
+ daily?: readonly WorkBuddyPromoWindow[];
708
+ /**
709
+ * The upstream PUBLISHED `daily`, but not one entry of it could be evaluated.
710
+ *
711
+ * This is deliberately NOT the same as "no `daily`": an entry with no
712
+ * `daily` is a 24-hour campaign (the discount runs whenever the promotion
713
+ * does), while an entry whose every window failed to parse says nothing at
714
+ * all about when it runs. Reading the second as "always" would turn a live
715
+ * night-only campaign into a badge shown around the clock — the one
716
+ * direction that overstates what the upstream said. A promotion carrying
717
+ * this flag therefore never counts as running; the parse failure hides the
718
+ * badge instead of doubling it.
719
+ */
720
+ dailyUnusable?: true;
721
+ }
722
+ /** The upstream's explanatory copy for the badge. */
723
+ interface WorkBuddyPromoHover {
724
+ /** The Chinese wording. */
725
+ textZh?: string;
726
+ /** The English wording. */
727
+ textEn?: string;
728
+ }
729
+ /** One upstream promotion or tier, resolved into the shape this plugin uses. */
730
+ interface WorkBuddyPromo {
731
+ /** The upstream's id ("hy3-free-trial-202608"). */
732
+ id: string;
733
+ /** Which upstream list the entry came from. */
734
+ source: 'promotion' | 'tier';
735
+ /** The models the badge applies to. */
736
+ modelIds: readonly string[];
737
+ /** What the badge says and how it looks. */
738
+ badge: WorkBuddyPromoBadge;
739
+ /** The upstream's explanatory copy, when it publishes one. */
740
+ hover?: WorkBuddyPromoHover;
741
+ /** When the promotion runs, when it publishes that. */
742
+ schedule?: WorkBuddyPromoSchedule;
743
+ /**
744
+ * The upstream's ordering weight: the higher the number, the more the
745
+ * badge matters. Used to pick ONE badge per model when several apply.
746
+ */
747
+ priority: number;
748
+ }
749
+ //#endregion
577
750
  //#region src/upstream.d.ts
578
751
  /** WorkBuddy region selected by the credential's login domain. */
579
752
  type WorkBuddyRegion = 'cn' | 'global';
@@ -677,6 +850,20 @@ interface WorkBuddyUpstreamModel {
677
850
  descriptionZh?: string;
678
851
  descriptionEn?: string;
679
852
  supportsToolCall?: boolean;
853
+ /**
854
+ * The upstream's own promotion / tier badges that name THIS model.
855
+ *
856
+ * Not a field of the model entry: the upstream publishes the badges in
857
+ * sibling arrays keyed by `modelIds` (`data.modelPromotions` /
858
+ * `data.modelTiers`), inside the SAME `/v3/config` document that carries
859
+ * the roster — so reading them costs no extra request.
860
+ *
861
+ * They ride along UNRESOLVED, and that is deliberate: a badge is a function
862
+ * of the clock (the upstream's windows are wall-clock hours in a named
863
+ * zone), so the entry that holds NOW is decided where the badge is drawn,
864
+ * not where the document was read. See `src/promo.ts`.
865
+ */
866
+ promos?: readonly WorkBuddyPromo[];
680
867
  }
681
868
  /** One billing package as the upstream returns it, dates already parsed. */
682
869
  interface WorkBuddyCreditPackage {
@@ -1018,13 +1205,14 @@ declare class WorkBuddyUpstreamClient {
1018
1205
  /**
1019
1206
  * Read the model directory for the credential's region.
1020
1207
  *
1021
- * Both regions prefer `/v3/config`, the document the WorkBuddy app itself
1022
- * consumes, so the plugin's roster and the app's agree. They differ in the
1023
- * user agent that requests it, and that difference is load-bearing: `/v3`
1024
- * serves a different roster per client channel. Global asks as the desktop
1025
- * channel (see {@link DESKTOP_UA}); CN asks as the CLI channel
1026
- * ({@link CLIENT_UA}), which is the only CN channel whose `/v3` answer
1027
- * contains the free `hy4-preview-f` (see {@link GLOBAL_CONFIG_PATH}).
1208
+ * Both regions read `/v3/config`, the document the WorkBuddy app itself
1209
+ * consumes, and both read it as the APP's product identity
1210
+ * ({@link PRODUCT_UA}) — the header the app sends, not the CLI channel's.
1211
+ * That is load-bearing, not cosmetic: the CLI channel's answer is a strict
1212
+ * subset of the app channel's and publishes neither `modelPromotions` nor
1213
+ * `modelTiers`, so reading `/v3/config` as the CLI channel is what left the
1214
+ * CN card without a single promotion badge while the app showed one on the
1215
+ * same account (see {@link PRODUCT_UA}).
1028
1216
  *
1029
1217
  * CN falls back to the legacy {@link MODELS_CATALOG_PATH} when `/v3` fails,
1030
1218
  * returns a non-zero envelope, or resolves to no usable model, so a gateway
@@ -1035,8 +1223,14 @@ declare class WorkBuddyUpstreamClient {
1035
1223
  *
1036
1224
  * No user-side toggle is involved: the region comes from the credential's
1037
1225
  * `domain`.
1226
+ *
1227
+ * `keepModelIds` is the user's own selection, passed through to
1228
+ * {@link selectCliModels}: a model the upstream roster dropped but the SAME
1229
+ * document still publishes stays in the roster, so reading the directory
1230
+ * never withdraws a model the user is running (issue #32's failure mode,
1231
+ * one step upstream of it).
1038
1232
  */
1039
- fetchModels(credential: WorkBuddyCredential, signal?: AbortSignal): Promise<readonly WorkBuddyUpstreamModel[]>;
1233
+ fetchModels(credential: WorkBuddyCredential, signal?: AbortSignal, keepModelIds?: readonly string[]): Promise<readonly WorkBuddyUpstreamModel[]>;
1040
1234
  /** Query today's check-in status without changing account state. */
1041
1235
  fetchCheckinStatus(credential: WorkBuddyCredential): Promise<WorkBuddyCheckinStatus>;
1042
1236
  /**
@@ -1092,6 +1286,12 @@ type WorkBuddyModelInfo = WorkBuddyUpstreamModel;
1092
1286
  * Static CLI models captured from the CN endpoint (2026-08-30). The upstream
1093
1287
  * refresh replaces this list at startup; it exists so the provider registers
1094
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.
1095
1295
  */
1096
1296
  declare const FALLBACK_WORKBUDDY_MODELS: readonly WorkBuddyModelInfo[];
1097
1297
  /**
@@ -1118,6 +1318,20 @@ declare function fallbackModelsFor(region: 'cn' | 'global'): readonly WorkBuddyM
1118
1318
  * never been configured must still serve models rather than nothing.
1119
1319
  */
1120
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
+ */
1121
1335
  declare function deriveCatalog(catalog: readonly WorkBuddyModelInfo[], enabled: ReadonlySet<string>, budgets?: Readonly<Record<string, WorkBuddyContextBudget | undefined>>): WorkBuddyModelInfo[];
1122
1336
  /**
1123
1337
  * Mutable catalog shared by the shim's `/v1/models` and the adapter.
@@ -2393,19 +2607,6 @@ interface WorkBuddyPoolTestRow {
2393
2607
  }
2394
2608
  //#endregion
2395
2609
  //#region src/status-paths.d.ts
2396
- /**
2397
- * Node-free constants and types shared by the Host and browser halves.
2398
- *
2399
- * 参考:dingminhua/dsh-connect-trae(MIT,Copyright (c) 2026 LaoDing)
2400
- * — 「3 条同源只读路由(usage / models:refresh / accounts:refresh)+ 一份
2401
- * 与浏览器共享的 node-free 类型定义」的 host↔client 桥梁形态来自该项目
2402
- * (其 `status-paths.ts` 亦如此,并注明沿用
2403
- * corrinehu/dsh-workbuddy-connect 的 status-route 模式)。
2404
- * 改动:路由路径改用本插件 id;类型字段按 WorkBuddy 上游实际给出的能力
2405
- * (积分倍率、多模态、推理档位)调整,不保留 trae 的 1M 变体字段。
2406
- *
2407
- * @module dsh-connect-workbuddy/status-paths
2408
- */
2409
2610
  /** Plugin-owned usage endpoint consumed by its browser half. */
2410
2611
  declare const WORKBUDDY_USAGE_PATH = "/plugins/dsh-connect-workbuddy/usage";
2411
2612
  /** Plugin-owned live model refresh endpoint. */
@@ -2641,6 +2842,18 @@ interface WorkBuddyWebModel {
2641
2842
  refusesOff?: boolean;
2642
2843
  };
2643
2844
  description?: string;
2845
+ /**
2846
+ * The upstream's promotion / tier badges that name this model, UNRESOLVED.
2847
+ *
2848
+ * Every candidate that names the model rides along, in priority order, so
2849
+ * the card can pick the one that holds at the moment it draws — a badge is a
2850
+ * function of the clock, and a badge frozen at fetch time would keep
2851
+ * claiming a night discount after the night is over. See `src/promo.ts`.
2852
+ *
2853
+ * Persisted inside `lastCatalog` (it rides along in the saved entries) and
2854
+ * read back from there, which is why it must stay JSON-shaped.
2855
+ */
2856
+ promos?: readonly WorkBuddyPromo[];
2644
2857
  }
2645
2858
  /**
2646
2859
  * Project one card row into its persisted `lastCatalog` shape: the native
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-BJAL9pEe.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";
@@ -5000,19 +5000,6 @@ async function writePoolProbes(region, updates) {
5000
5000
  }
5001
5001
  //#endregion
5002
5002
  //#region src/status-paths.ts
5003
- /**
5004
- * Node-free constants and types shared by the Host and browser halves.
5005
- *
5006
- * 参考:dingminhua/dsh-connect-trae(MIT,Copyright (c) 2026 LaoDing)
5007
- * — 「3 条同源只读路由(usage / models:refresh / accounts:refresh)+ 一份
5008
- * 与浏览器共享的 node-free 类型定义」的 host↔client 桥梁形态来自该项目
5009
- * (其 `status-paths.ts` 亦如此,并注明沿用
5010
- * corrinehu/dsh-workbuddy-connect 的 status-route 模式)。
5011
- * 改动:路由路径改用本插件 id;类型字段按 WorkBuddy 上游实际给出的能力
5012
- * (积分倍率、多模态、推理档位)调整,不保留 trae 的 1M 变体字段。
5013
- *
5014
- * @module dsh-connect-workbuddy/status-paths
5015
- */
5016
5003
  /** Plugin-owned usage endpoint consumed by its browser half. */
5017
5004
  const WORKBUDDY_USAGE_PATH = "/plugins/dsh-connect-workbuddy/usage";
5018
5005
  /**
@@ -5403,7 +5390,8 @@ function toWebModel(model, budgets) {
5403
5390
  ...model.reasoning.canDisableThinking === void 0 ? {} : { canDisableThinking: model.reasoning.canDisableThinking },
5404
5391
  ...offRefused(model.id) ? { offRefused: true } : {},
5405
5392
  ...model.reasoning.refusesOff === true ? { refusesOff: true } : {}
5406
- } }
5393
+ } },
5394
+ ...model.promos === void 0 ? {} : { promos: model.promos }
5407
5395
  };
5408
5396
  }
5409
5397
  /**
@@ -6185,55 +6173,6 @@ function registerWorkBuddyStatusRoute(ctx, deps) {
6185
6173
  }, "dsh-connect-workbuddy: Web status route");
6186
6174
  }
6187
6175
  //#endregion
6188
- //#region src/native-modality.ts
6189
- /**
6190
- * Reviewed vendor classifications, keyed by EXACT model id (never a prefix).
6191
- * Ids absent from this record are `unknown` — see {@link nativeModalityOf}.
6192
- */
6193
- const NATIVE_MODALITY_BY_MODEL_ID = {
6194
- "deepseek-v4-pro": "text",
6195
- "glm-5.1": "text",
6196
- "glm-5.2": "text",
6197
- "glm-5.3": "text",
6198
- "hy3": "text",
6199
- "hy4-preview": "text",
6200
- "deepseek-v4.1-flash": "multimodal",
6201
- "glm-5.3-flash": "multimodal",
6202
- "glm-5v-turbo": "multimodal",
6203
- "kimi-k2.6": "multimodal",
6204
- "kimi-k2.7": "multimodal",
6205
- "kimi-k3-1": "multimodal",
6206
- "minimax-m3": "multimodal"
6207
- };
6208
- /** The `auto` entry is a router that picks a model, not a model itself. */
6209
- const ROUTER_MODEL_ID = "auto";
6210
- /**
6211
- * Native modality of one model id. Exact match only: a new model never
6212
- * inherits a classification from its family, its display name, or the
6213
- * platform's image flag.
6214
- */
6215
- function nativeModalityOf(modelId) {
6216
- if (modelId === ROUTER_MODEL_ID) return "router";
6217
- return NATIVE_MODALITY_BY_MODEL_ID[modelId] ?? "unknown";
6218
- }
6219
- /**
6220
- * Whether a model refresh should PRE-CHECK this model's image box.
6221
- *
6222
- * Only a documented `multimodal` is pre-checked. `text` is documented as
6223
- * unable to read images, and `unknown` is deliberately left unchecked rather
6224
- * than guessed — the whole reason this table exists is that the platform flag
6225
- * guessed wrong. A platform `supportsImages: false` still vetoes, so a
6226
- * documented-multimodal id can never be pre-checked against the platform's
6227
- * explicit "no".
6228
- *
6229
- * The user can always tick a box by hand; this decides a DEFAULT only, and the
6230
- * effective runtime flag remains the saved `imageModelIds`.
6231
- */
6232
- function imageDefaultFor(info) {
6233
- if (info.supportsImages === false) return false;
6234
- return NATIVE_MODALITY_BY_MODEL_ID[info.id] === "multimodal";
6235
- }
6236
- //#endregion
6237
6176
  //#region src/index.ts
6238
6177
  /** Stable Cordis plugin name. */
6239
6178
  const name = "dsh-connect-workbuddy";
@@ -6633,7 +6572,8 @@ function apply(ctx, config) {
6633
6572
  const effectiveAccountFor = (region, value) => selectAccountFor(region, value, legacyAccountRegion);
6634
6573
  const discoverModels = async (region, signal) => {
6635
6574
  const credential = await stacks[region].store.resolve();
6636
- return client.fetchModels(credential, signal);
6575
+ const keep = regionStateOf(current(), region).enabledModelIds ?? [];
6576
+ return client.fetchModels(credential, signal, keep);
6637
6577
  };
6638
6578
  /** Push the current config into every region's store selection and catalog. */
6639
6579
  const applySelection = (value) => {
@@ -7635,7 +7575,7 @@ function apply(ctx, config) {
7635
7575
  try {
7636
7576
  const credential = await stacks[region].store.resolve();
7637
7577
  if (stopped) return;
7638
- const models = await client.fetchModels(credential);
7578
+ const models = await client.fetchModels(credential, void 0, regionStateOf(current(), region).enabledModelIds ?? []);
7639
7579
  if (stopped) return;
7640
7580
  const state = regionStateOf(current(), region);
7641
7581
  stacks[region].catalog.set(withEffectiveOff(withImageSelection(deriveCatalog(models, new Set(state.enabledModelIds ?? []), state.contextBudgets ?? {}), new Set(state.imageModelIds ?? [])), new Set(savedOffModelIds(state, models))));
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.13.2",
5
+ "version": "4.14.1",
6
6
  "author": "dingminhua",
7
7
  "copyright": "Copyright (c) 2026 LaoDing",
8
8
  "license": "MIT",