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/client.js CHANGED
@@ -8,19 +8,6 @@ window.__ModuleLoader__.load({
8
8
  let react_jsx_runtime = require("react/jsx-runtime");
9
9
  let react_dom = require("react-dom");
10
10
  //#region src/status-paths.ts
11
- /**
12
- * Node-free constants and types shared by the Host and browser halves.
13
- *
14
- * 参考:dingminhua/dsh-connect-trae(MIT,Copyright (c) 2026 LaoDing)
15
- * — 「3 条同源只读路由(usage / models:refresh / accounts:refresh)+ 一份
16
- * 与浏览器共享的 node-free 类型定义」的 host↔client 桥梁形态来自该项目
17
- * (其 `status-paths.ts` 亦如此,并注明沿用
18
- * corrinehu/dsh-workbuddy-connect 的 status-route 模式)。
19
- * 改动:路由路径改用本插件 id;类型字段按 WorkBuddy 上游实际给出的能力
20
- * (积分倍率、多模态、推理档位)调整,不保留 trae 的 1M 变体字段。
21
- *
22
- * @module dsh-connect-workbuddy/status-paths
23
- */
24
11
  /** Plugin-owned usage endpoint consumed by its browser half. */
25
12
  const WORKBUDDY_USAGE_PATH = "/plugins/dsh-connect-workbuddy/usage";
26
13
  /**
@@ -1514,6 +1501,227 @@ window.__ModuleLoader__.load({
1514
1501
  return t("row.poolProbeDaysAgo", { count: Math.round(age / DAY_MS) });
1515
1502
  }
1516
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
1569
+ //#region src/promo.ts
1570
+ /**
1571
+ * `'HH:mm'` → minutes since midnight, or undefined for anything else.
1572
+ *
1573
+ * `24:00` is accepted and means exactly 1440 — the END of a day. The upstream
1574
+ * writes "all day" as `0:00`–`23:59` today, which leaves a one-minute hole
1575
+ * between 23:59 and midnight where a standing campaign reads as not running.
1576
+ * Accepting `24:00` lets such a window be expressed (and lets a future upstream
1577
+ * fix it) without that hole; as a START it is rejected, because a day that
1578
+ * begins at 24:00 is the next day and that is a different statement.
1579
+ *
1580
+ * Any other malformed value is left to the caller to drop — a wrong window
1581
+ * would hide a live promotion, and guessing is not this parser's job.
1582
+ */
1583
+ function minutesOfClock(value) {
1584
+ const match = /^(\d{1,2}):(\d{2})$/.exec(value.trim());
1585
+ if (match === null) return void 0;
1586
+ const hours = Number(match[1]);
1587
+ const minutes = Number(match[2]);
1588
+ if (!Number.isInteger(hours) || !Number.isInteger(minutes)) return void 0;
1589
+ if (minutes > 59) return void 0;
1590
+ if (hours > 24) return void 0;
1591
+ if (hours === 24) return minutes === 0 ? 1440 : void 0;
1592
+ return hours * 60 + minutes;
1593
+ }
1594
+ /**
1595
+ * Minutes since local midnight in `timeZone` at `nowMs`.
1596
+ *
1597
+ * `Intl` does the zone maths, because the upstream's windows are written in
1598
+ * the zone IT names (Asia/Shanghai for the CN feed) and the user's own clock
1599
+ * is not necessarily in that zone. An unusable zone falls back to UTC rather
1600
+ * than throwing: a badge that cannot be placed is a missing badge, not a
1601
+ * broken card.
1602
+ */
1603
+ function zonedMinutes(nowMs, timeZone) {
1604
+ const zone = timeZone ?? "UTC";
1605
+ try {
1606
+ const parts = new Intl.DateTimeFormat("en-US", {
1607
+ timeZone: zone,
1608
+ hour: "2-digit",
1609
+ minute: "2-digit",
1610
+ hourCycle: "h23"
1611
+ }).formatToParts(new Date(nowMs));
1612
+ const hour = Number(parts.find((part) => part.type === "hour")?.value ?? "0");
1613
+ const minute = Number(parts.find((part) => part.type === "minute")?.value ?? "0");
1614
+ if (!Number.isFinite(hour) || !Number.isFinite(minute)) return 0;
1615
+ return hour * 60 + minute;
1616
+ } catch {
1617
+ return 0;
1618
+ }
1619
+ }
1620
+ /** Whether `nowMinutes` falls inside one of the daily windows. */
1621
+ function withinDailyWindows(windows, nowMinutes) {
1622
+ return windows.some((window) => {
1623
+ const start = minutesOfClock(window.start);
1624
+ const end = minutesOfClock(window.end);
1625
+ if (start === void 0 || end === void 0) return false;
1626
+ if (start === end) return true;
1627
+ if (end > start) return nowMinutes >= start && nowMinutes < end;
1628
+ return nowMinutes >= start || nowMinutes < end;
1629
+ });
1630
+ }
1631
+ /**
1632
+ * Whether the promotion's ABSOLUTE window holds now.
1633
+ *
1634
+ * The absolute window (`validFrom` / `validUntil`) is the promotion's own
1635
+ * lifetime. It gates the badge on its own: a promotion whose lifetime has
1636
+ * ended must not keep advertising itself, whatever its daily hours say.
1637
+ */
1638
+ function promoWithinAbsoluteWindow(promo, nowMs) {
1639
+ const schedule = promo.schedule;
1640
+ if (schedule === void 0) return true;
1641
+ if (schedule.validFrom !== void 0 && nowMs < schedule.validFrom) return false;
1642
+ if (schedule.validUntil !== void 0 && nowMs >= schedule.validUntil) return false;
1643
+ return true;
1644
+ }
1645
+ /**
1646
+ * Whether the promotion's DAILY window holds now.
1647
+ *
1648
+ * The daily window is the discount's own hours — it is what makes a
1649
+ * promotion a "night discount" — and a promotion without one is running
1650
+ * whenever its absolute window holds.
1651
+ *
1652
+ * A schedule whose `daily` was published but could not be evaluated
1653
+ * ({@link WorkBuddyPromoSchedule.dailyUnusable}) is NOT running: the upstream
1654
+ * did say the campaign has hours, we just cannot read them, and assuming
1655
+ * "always" is the one guess that overstates the offer.
1656
+ */
1657
+ function promoWithinDailyWindow(promo, nowMs) {
1658
+ const schedule = promo.schedule;
1659
+ if (schedule === void 0) return true;
1660
+ if (schedule.dailyUnusable === true) return false;
1661
+ if (schedule.daily === void 0 || schedule.daily.length === 0) return true;
1662
+ return withinDailyWindows(schedule.daily, zonedMinutes(nowMs, schedule.timezone));
1663
+ }
1664
+ /**
1665
+ * Whether the promotion is running right now: its absolute window AND its
1666
+ * daily hours both hold.
1667
+ */
1668
+ function promoActiveAt(promo, nowMs) {
1669
+ return promoWithinAbsoluteWindow(promo, nowMs) && promoWithinDailyWindow(promo, nowMs);
1670
+ }
1671
+ /**
1672
+ * Whether the promotion carries ANY temporal statement of its own.
1673
+ *
1674
+ * A tier badge ("Pro Only") has no `schedule` at all: it describes the model
1675
+ * unconditionally, so `promoActiveAt` is trivially true for it. That is
1676
+ * correct on its own, but it made such a badge eligible for step 1, where it
1677
+ * could shadow a lower-priority campaign whose window really is open.
1678
+ * "Running" should mean "this entry says it runs now", and an entry with no
1679
+ * schedule never does — it only ever "stands". Entries WITH a schedule (a
1680
+ * lifetime, hours, or both) are the ones that can be running.
1681
+ */
1682
+ function hasSchedule(promo) {
1683
+ return promo.schedule !== void 0;
1684
+ }
1685
+ /**
1686
+ * The badge to draw for a model right now, or undefined when there is none.
1687
+ *
1688
+ * The order of the two answers is the whole point:
1689
+ *
1690
+ * 1. A promotion RUNNING NOW wins — highest `priority` first. "Running" means
1691
+ * its absolute window AND its daily hours both hold.
1692
+ * 2. Otherwise a STANDING badge (one the upstream did not mark `activeOnly`)
1693
+ * describes the model for as long as the promotion itself lasts. It is
1694
+ * offered only inside the promotion's absolute window, so an expired
1695
+ * promotion stops advertising itself even though its badge was standing.
1696
+ *
1697
+ * TWO RULES KEEP STEP 2 FROM STATING SOMETHING THE UPSTREAM DID NOT:
1698
+ *
1699
+ * a. An entry that publishes daily hours may stand outside them (that is what
1700
+ * "the hours govern the discount, not the badge" means), but an entry whose
1701
+ * hours could not be parsed cannot stand at all — see `dailyUnusable`.
1702
+ * b. A standing entry with NO schedule at all must not outrank a promotion
1703
+ * that is genuinely running. `promoActiveAt` returns true for a
1704
+ * schedule-less tier, which is correct on its own, but it made a
1705
+ * high-priority tier shadow a lower-priority campaign whose window really
1706
+ * is open. Step 1 is therefore restricted to entries that carry a
1707
+ * `schedule`; the schedule-less badges compete only in step 2, where they
1708
+ * belong.
1709
+ *
1710
+ * The upstream's own `priority` orders entries. Ties are broken toward the
1711
+ * promotion: a price badge ("Free now") is more actionable than an access-tier
1712
+ * badge ("Pro Only"), and the upstream numbers them equally.
1713
+ *
1714
+ * Only ONE badge is drawn per model: a model row is one line wide, and two
1715
+ * badges would push the model name and the image/think checkboxes out of it.
1716
+ */
1717
+ function selectPromoBadge(promos, nowMs) {
1718
+ if (promos === void 0 || promos.length === 0) return void 0;
1719
+ const sorted = [...promos].sort((a, b) => b.priority - a.priority || (a.source === b.source ? 0 : a.source === "promotion" ? -1 : 1));
1720
+ const running = sorted.find((promo) => hasSchedule(promo) && promoActiveAt(promo, nowMs));
1721
+ if (running !== void 0) return running;
1722
+ return sorted.find((promo) => promo.badge.display !== "activeOnly" && promoWithinAbsoluteWindow(promo, nowMs) && promo.schedule?.dailyUnusable !== true);
1723
+ }
1724
+ //#endregion
1517
1725
  //#region src/probe.ts
1518
1726
  /**
1519
1727
  * The probe sizes the card offers, in tokens.
@@ -3592,53 +3800,135 @@ window.__ModuleLoader__.load({
3592
3800
  });
3593
3801
  }
3594
3802
  //#endregion
3595
- //#region src/native-modality.ts
3803
+ //#region src/client/promo-badge.ts
3596
3804
  /**
3597
- * Reviewed vendor classifications, keyed by EXACT model id (never a prefix).
3598
- * Ids absent from this record are `unknown` — see {@link nativeModalityOf}.
3805
+ * The UI's language, as the browser reports it.
3806
+ *
3807
+ * The card already formats dates and numbers with the browser's locale
3808
+ * (`formatDate`), so reading the same signal for the upstream's copy is
3809
+ * consistent: the badge says what the upstream says, in the language the
3810
+ * user reads.
3811
+ *
3812
+ * `navigator` is read defensively — this module is imported by unit tests
3813
+ * that run in a plain Node environment, where it does not exist.
3599
3814
  */
3600
- const NATIVE_MODALITY_BY_MODEL_ID = {
3601
- "deepseek-v4-pro": "text",
3602
- "glm-5.1": "text",
3603
- "glm-5.2": "text",
3604
- "glm-5.3": "text",
3605
- "hy3": "text",
3606
- "hy4-preview": "text",
3607
- "deepseek-v4.1-flash": "multimodal",
3608
- "glm-5.3-flash": "multimodal",
3609
- "glm-5v-turbo": "multimodal",
3610
- "kimi-k2.6": "multimodal",
3611
- "kimi-k2.7": "multimodal",
3612
- "kimi-k3-1": "multimodal",
3613
- "minimax-m3": "multimodal"
3614
- };
3615
- /** The `auto` entry is a router that picks a model, not a model itself. */
3616
- const ROUTER_MODEL_ID = "auto";
3815
+ function uiLanguage() {
3816
+ return typeof navigator === "undefined" ? "" : navigator.language;
3817
+ }
3818
+ /** Whether the given language is an English one. */
3819
+ function isEnglish(language) {
3820
+ return (language ?? "").toLowerCase().startsWith("en");
3821
+ }
3617
3822
  /**
3618
- * Native modality of one model id. Exact match only: a new model never
3619
- * inherits a classification from its family, its display name, or the
3620
- * platform's image flag.
3823
+ * The badge's own wording.
3824
+ *
3825
+ * The upstream's label is the region's own wording, so it is the default for
3826
+ * every UI. An English UI prefers the English label when the upstream
3827
+ * published one; the global feed publishes none, and there its label is
3828
+ * already English.
3621
3829
  */
3622
- function nativeModalityOf(modelId) {
3623
- if (modelId === ROUTER_MODEL_ID) return "router";
3624
- return NATIVE_MODALITY_BY_MODEL_ID[modelId] ?? "unknown";
3830
+ function promoBadgeLabel(promo, language) {
3831
+ const badge = promo.badge;
3832
+ if (isEnglish(language)) return badge.labelEn ?? badge.label;
3833
+ return badge.label;
3625
3834
  }
3626
3835
  /**
3627
- * Whether a model refresh should PRE-CHECK this model's image box.
3836
+ * The chip's colour, when the upstream published a usable one.
3628
3837
  *
3629
- * Only a documented `multimodal` is pre-checked. `text` is documented as
3630
- * unable to read images, and `unknown` is deliberately left unchecked rather
3631
- * than guessed — the whole reason this table exists is that the platform flag
3632
- * guessed wrong. A platform `supportsImages: false` still vetoes, so a
3633
- * documented-multimodal id can never be pre-checked against the platform's
3634
- * explicit "no".
3838
+ * Only a hex colour is accepted: the value reaches an inline style, and the
3839
+ * upstream's feed publishes hex (`#009273`, `#FF0000`). Anything else is
3840
+ * refused rather than passed through, and the card falls back to its own
3841
+ * brand colour.
3842
+ */
3843
+ function promoBadgeColor(promo) {
3844
+ const color = promo.badge.color;
3845
+ if (color === void 0) return void 0;
3846
+ return /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i.test(color) ? color : void 0;
3847
+ }
3848
+ /**
3849
+ * A calendar day, in the user's own locale — read in the ZONE the upstream
3850
+ * wrote the instant in.
3635
3851
  *
3636
- * The user can always tick a box by hand; this decides a DEFAULT only, and the
3637
- * effective runtime flag remains the saved `imageModelIds`.
3852
+ * The zone is part of the statement, exactly as it is for the daily hours. The
3853
+ * upstream publishes `validFrom: "2026-07-06T00:00:00+08:00"` and describes the
3854
+ * same campaign as "7月6日–10月31日". Formatting that instant in the reader's
3855
+ * own zone shows 7月5日 to anyone west of UTC, contradicting the upstream's own
3856
+ * copy in the same tooltip. Keeping the upstream's zone makes the tooltip
3857
+ * self-consistent wherever it is read.
3858
+ *
3859
+ * `medium` rather than the card's numeric `MM/DD` form: this is a promotion's
3860
+ * lifetime, and "Jul 6, 2026" reads as a date while "07/06" reads as a number
3861
+ * the user has to decode.
3638
3862
  */
3639
- function imageDefaultFor(info) {
3640
- if (info.supportsImages === false) return false;
3641
- return NATIVE_MODALITY_BY_MODEL_ID[info.id] === "multimodal";
3863
+ function formatDay(ms, language, timeZone) {
3864
+ try {
3865
+ return new Intl.DateTimeFormat(language === void 0 || language === "" ? void 0 : language, {
3866
+ year: "numeric",
3867
+ month: "short",
3868
+ day: "numeric",
3869
+ ...timeZone === void 0 ? {} : { timeZone }
3870
+ }).format(new Date(ms));
3871
+ } catch {
3872
+ return new Date(ms).toISOString().slice(0, 10);
3873
+ }
3874
+ }
3875
+ /**
3876
+ * The promotion's lifetime, as the upstream's own two ends.
3877
+ *
3878
+ * `validUntil` is EXCLUSIVE — see `promoWithinAbsoluteWindow`: the promotion is
3879
+ * over AT that instant. The upstream writes it as the midnight that opens the
3880
+ * day after the campaign, and says "– Oct 31" in its own copy for a
3881
+ * `validUntil` of `2026-11-01T00:00:00+08:00`. Printing the raw instant would
3882
+ * put "Nov 1, 2026" in the same tooltip and read as if the campaign ran a day
3883
+ * longer than it does. One millisecond before the boundary is the last instant
3884
+ * that still belongs to it, so that is what gets rendered.
3885
+ */
3886
+ function promoRangeText(promo, language) {
3887
+ const schedule = promo.schedule;
3888
+ if (schedule === void 0) return void 0;
3889
+ const zone = schedule.timezone;
3890
+ const from = schedule.validFrom === void 0 ? void 0 : formatDay(schedule.validFrom, language, zone);
3891
+ const to = schedule.validUntil === void 0 ? void 0 : formatDay(schedule.validUntil - 1, language, zone);
3892
+ if (from === void 0 && to === void 0) return void 0;
3893
+ if (from === void 0) return `– ${to ?? ""}`;
3894
+ if (to === void 0) return `${from} –`;
3895
+ return `${from} – ${to}`;
3896
+ }
3897
+ /**
3898
+ * The hours the discount runs, in the zone the upstream named.
3899
+ *
3900
+ * The zone is part of the statement: "23:00–7:50" means nothing to a reader
3901
+ * in another zone, and the upstream's windows are written in ITS zone
3902
+ * (Asia/Shanghai for the CN feed), not the user's.
3903
+ */
3904
+ function promoHoursText(promo) {
3905
+ const schedule = promo.schedule;
3906
+ const daily = schedule?.daily;
3907
+ if (daily === void 0 || daily.length === 0) return void 0;
3908
+ const windows = daily.map((window) => `${window.start}–${window.end}`).join(", ");
3909
+ const zone = schedule?.timezone;
3910
+ return zone === void 0 ? windows : `${windows} (${zone})`;
3911
+ }
3912
+ /**
3913
+ * The chip's tooltip: the upstream's own copy, its lifetime, and the hours
3914
+ * the discount runs.
3915
+ *
3916
+ * Empty pieces are left out rather than rendered as blanks, so a promotion
3917
+ * that publishes nothing but a label gets a chip with a label and no hover
3918
+ * text at all.
3919
+ */
3920
+ function promoBadgeTooltip(promo, language) {
3921
+ const parts = [];
3922
+ const hover = promo.hover;
3923
+ if (hover !== void 0) {
3924
+ const copy = isEnglish(language) ? hover.textEn ?? hover.textZh : hover.textZh ?? hover.textEn;
3925
+ if (copy !== void 0) parts.push(copy);
3926
+ }
3927
+ const range = promoRangeText(promo, language);
3928
+ if (range !== void 0) parts.push(range);
3929
+ const hours = promoHoursText(promo);
3930
+ if (hours !== void 0) parts.push(hours);
3931
+ return parts.join(" · ");
3642
3932
  }
3643
3933
  //#endregion
3644
3934
  //#region src/client/icon.ts
@@ -3894,6 +4184,12 @@ window.__ModuleLoader__.load({
3894
4184
  .dsm-workbuddy-model-copy{display:flex;align-items:baseline;gap:8px;min-width:0}
3895
4185
  .dsm-workbuddy-model-name{display:inline-flex;align-items:baseline;gap:7px;color:var(--dsw-alias-label-primary,#e6e6e6);font-size:13px;font-weight:500;line-height:19px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
3896
4186
  .dsm-workbuddy-model-name-rate{color:var(--dsw-alias-label-tertiary,#999);font-size:11px;font-weight:400;line-height:16px;flex:none}
4187
+ /* The upstream's own promotion badge ("限时免费" / "Free now" / "Pro Only").
4188
+ The colour is the upstream's own, handed in as a custom property; without
4189
+ one the brand colour stands in. "flex:none" keeps the badge whole while the
4190
+ model name beside it truncates with an ellipsis. The plain background comes
4191
+ first so an engine without color-mix still gets a readable chip. */
4192
+ .dsm-workbuddy-model-promo{flex:none;display:inline-flex;align-items:center;padding:0 7px;border-radius:999px;font-size:10px;line-height:16px;font-weight:600;white-space:nowrap;color:var(--dsm-workbuddy-promo-color,var(--dsw-alias-brand-primary,#5686fe));background:rgba(255,255,255,.08);background:color-mix(in srgb,var(--dsm-workbuddy-promo-color,var(--dsw-alias-brand-primary,#5686fe)) 16%,transparent);border:1px solid transparent;border-color:color-mix(in srgb,var(--dsm-workbuddy-promo-color,var(--dsw-alias-brand-primary,#5686fe)) 40%,transparent)}
3897
4193
  .dsm-workbuddy-model-id{color:var(--dsw-alias-label-tertiary,#999);font-size:11px;line-height:16px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
3898
4194
  .dsm-workbuddy-model-desc{margin:0;color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:12px;line-height:18px}
3899
4195
  .dsm-workbuddy-model-details{display:flex;align-items:center;justify-content:space-between;gap:12px;min-width:0}
@@ -4666,6 +4962,37 @@ window.__ModuleLoader__.load({
4666
4962
  default: return t("row.modelImageUnverified");
4667
4963
  }
4668
4964
  }
4965
+ /**
4966
+ * The upstream's own promotion badge for one model, or nothing.
4967
+ *
4968
+ * Drawn from the model's UNRESOLVED promo list at RENDER time, because a badge
4969
+ * is a function of the clock: the upstream's promotions run on wall-clock
4970
+ * hours in a named zone, so a badge chosen when the document was read would
4971
+ * keep claiming the night after the night is over. The card re-renders at
4972
+ * least once a minute while it is open (it polls the usage route), so a
4973
+ * window edge is picked up within a minute without a second timer.
4974
+ *
4975
+ * The chip says what the UPSTREAM says — its label, its colour, its hover
4976
+ * copy — and adds nothing of its own: a badge the user cannot trace back to
4977
+ * the upstream's feed is worse than no badge.
4978
+ *
4979
+ * One badge per model: the upstream's `priority` orders the entries that name
4980
+ * a model, and a model row is one line wide — two badges would push the
4981
+ * model name out of the row.
4982
+ */
4983
+ function promoChip(model) {
4984
+ const promo = selectPromoBadge(model.promos, Date.now());
4985
+ if (promo === void 0) return null;
4986
+ const language = uiLanguage();
4987
+ const color = promoBadgeColor(promo);
4988
+ const tooltip = promoBadgeTooltip(promo, language);
4989
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4990
+ className: "dsm-workbuddy-model-promo",
4991
+ title: tooltip === "" ? void 0 : tooltip,
4992
+ style: color === void 0 ? void 0 : { "--dsm-workbuddy-promo-color": color },
4993
+ children: promoBadgeLabel(promo, language)
4994
+ });
4995
+ }
4669
4996
  /** Inject or refresh the shared card CSS for the current client bundle. */
4670
4997
  if (typeof document !== "undefined") {
4671
4998
  const cssId = "dsh-connect-workbuddy/client.css";
@@ -5938,9 +6265,9 @@ window.__ModuleLoader__.load({
5938
6265
  onChange: () => {
5939
6266
  toggleModel(model.id);
5940
6267
  }
5941
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6268
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5942
6269
  className: "dsm-workbuddy-model-copy",
5943
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6270
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5944
6271
  className: "dsm-workbuddy-model-name",
5945
6272
  children: [model.name, model.creditMultiplier === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5946
6273
  className: "dsm-workbuddy-model-name-rate",
@@ -5950,7 +6277,7 @@ window.__ModuleLoader__.load({
5950
6277
  "x)"
5951
6278
  ]
5952
6279
  })]
5953
- })
6280
+ }), promoChip(model)]
5954
6281
  })]
5955
6282
  }),
5956
6283
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {