dsh-connect-trae 4.4.1 → 4.4.3

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
@@ -23,6 +23,10 @@ window.__ModuleLoader__.load({
23
23
  if (panel === "enterprise") return "trae-enterprise";
24
24
  return panel === "ai" ? "trae-global" : "trae";
25
25
  }
26
+ /** The panel that owns a provider id, or undefined for an unknown id. */
27
+ function panelOfProvider(provider) {
28
+ return TRAE_PANELS.find((panel) => providerOfPanel(panel) === provider);
29
+ }
26
30
  //#endregion
27
31
  //#region src/status-paths.ts
28
32
  /** Plugin-owned usage endpoint consumed by its browser half. */
@@ -243,8 +247,9 @@ window.__ModuleLoader__.load({
243
247
  if (typeof unwrapped !== "object" || unwrapped === null || Array.isArray(unwrapped)) return {};
244
248
  const record = unwrapped;
245
249
  const nested = record["regions"];
246
- if (typeof nested === "object" && nested !== null && !Array.isArray(nested)) return nested;
247
- return record;
250
+ if (typeof nested !== "object" || nested === null || Array.isArray(nested)) return record;
251
+ const panelIds = TRAE_PANELS;
252
+ return Object.keys(record).some((key) => panelIds.includes(key)) ? record : nested;
248
253
  }
249
254
  /** One region's stored slot as a plain object; any other shape reads as empty. */
250
255
  function regionSlotOf(value, panel) {
@@ -990,13 +995,38 @@ window.__ModuleLoader__.load({
990
995
  * leave a hole on the right. *
991
996
  * ------------------------------------------------------------------ */
992
997
  .dsm-trae-pool{--dsm-trae-pool-pad-x:14px;--dsm-trae-pool-pad-y:10px;--dsm-trae-pool-radius:14px;display:flex;flex-direction:column;gap:12px;border-top:1px solid var(--dsw-alias-border-l2,#36373b);padding:14px 0 0}
993
- .dsm-trae-pool-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
998
+ .dsm-trae-pool-head{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px}
994
999
  .dsm-trae-pool-title{margin:0;font-size:13px;font-weight:600;line-height:18px;color:var(--dsw-alias-label-primary,#e6e6e6)}
995
1000
  .dsm-trae-pool-region-note{margin:3px 0 0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary,#9aa0a8)}
996
1001
  .dsm-trae-pool-current{flex:none;padding:2px 8px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2,#3a3d45);font-size:11px;line-height:16px;color:var(--dsw-alias-label-secondary,#c6c9d0);white-space:nowrap}
1002
+ /* Two classes on the element (settings + modebar): a single-class rule here loses
1003
+ to .dsm-trae-pool-settings further down, which resets padding to 0 and repaints
1004
+ the background. Match both classes so the strip keeps its own frame. */
1005
+ .dsm-trae-pool-settings.dsm-trae-pool-modebar{padding:12px 14px;gap:8px;border-color:rgba(86,134,254,.36);background:linear-gradient(180deg,rgba(86,134,254,.10),rgba(86,134,254,.02)),var(--dsw-alias-bg-layer-2,#24262c)}
1006
+ .dsm-trae-pool-modebar-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
1007
+ .dsm-trae-pool-modebar-label{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e6e6)}
1008
+ .dsm-trae-pool-modebar-state{flex:none;padding:1px 9px;border-radius:999px;font-size:12px;line-height:18px;font-weight:500;color:var(--dsw-alias-brand-primary,#5686fe);background:rgba(86,134,254,.14);border:1px solid rgba(86,134,254,.3)}
1009
+ .dsm-trae-pool-modebar .dsm-trae-pool-dirty{margin-left:auto}
1010
+ .dsm-trae-pool-modebar .dsm-trae-pool-note{line-height:18px}
1011
+ /* Modifier rules must out-specify their base class wherever the base is defined
1012
+ further down (.dsm-trae-pool-note, .dsm-trae-select): matching both classes keeps
1013
+ the warn tone and the wider select from being reset by the later base rule. */
1014
+ .dsm-trae-pool-note.dsm-trae-pool-serving-note{color:var(--dsw-alias-state-warn-primary,#b45309)}
1015
+ .dsm-trae-pool-card .dsm-trae-pool-actions{padding:0 var(--dsm-trae-pool-pad-x) 12px}
1016
+ .dsm-trae-pool-card .dsm-trae-pool-action-note{padding:0 var(--dsm-trae-pool-pad-x) 12px}
997
1017
  .dsm-trae-pool-actions{display:flex;align-items:center;flex-wrap:wrap;gap:8px}
998
1018
  .dsm-trae-pool-action-checkin{flex:none;white-space:nowrap}
999
1019
  .dsm-trae-pool-action-test{flex:none;white-space:nowrap}
1020
+ /* Reference text, folded away. Same <details>/<summary> shape as the WorkBuddy
1021
+ sibling's "how the pool picks an account": keyboard-operable and
1022
+ screen-reader-expanded natively, and nothing to keep in sync in state. The
1023
+ paragraphs inside are plain .dsm-trae-pool-note - the container only supplies
1024
+ the frame, so their own type scale stays authoritative. */
1025
+ .dsm-trae-pool-more{border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.18));border-radius:10px;background:var(--dsw-alias-bg-layer-2,#24262c);overflow:hidden}
1026
+ .dsm-trae-pool-more-summary{padding:8px 12px;cursor:pointer;user-select:none;color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:12px;font-weight:600;line-height:18px}
1027
+ .dsm-trae-pool-more-summary:hover{color:var(--dsw-alias-label-primary,#e6e6e6)}
1028
+ .dsm-trae-pool-more-summary:focus-visible{outline:2px solid rgba(86,134,254,.55);outline-offset:-2px}
1029
+ .dsm-trae-pool-more-body{display:flex;flex-direction:column;gap:8px;padding:10px 12px 12px;border-top:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.18))}
1000
1030
  /* Removing an account from the pool is destructive (it drops THIS panel's saved
1001
1031
  membership), so the hover state takes the error tone rather than the neutral
1002
1032
  one every other outline button gets. */
@@ -1031,8 +1061,7 @@ window.__ModuleLoader__.load({
1031
1061
  .dsm-trae-pool-set-copy span{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary,#9aa0a8)}
1032
1062
  .dsm-trae-pool-set-ctl{display:flex;align-items:center;gap:8px;flex:none}
1033
1063
  .dsm-trae-pool-set-ctl select{min-width:180px}
1034
- .dsm-trae-pool-bind{border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:var(--dsm-trae-pool-radius);background:var(--dsw-alias-bg-layer-2,#24262c)}
1035
- .dsm-trae-pool-account-select{min-width:180px}
1064
+ .dsm-trae-select.dsm-trae-pool-account-select{min-width:180px;max-width:min(320px,50%)}
1036
1065
  .dsm-trae-pool-tools{display:flex;flex-direction:column;gap:8px}
1037
1066
  .dsm-trae-pool-tool-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
1038
1067
  .dsm-trae-pool-meta{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary,#9aa0a8);white-space:nowrap}
@@ -1076,7 +1105,6 @@ window.__ModuleLoader__.load({
1076
1105
  .dsm-trae-pool-tag{padding:0 6px;border-radius:999px;font-size:10px;line-height:16px;white-space:nowrap}
1077
1106
  .dsm-trae-pool-member{font-size:11px;line-height:16px;color:var(--dsw-alias-brand-primary,#5686fe)}
1078
1107
  .dsm-trae-pool-not-member{font-size:11px;line-height:16px;color:var(--dsw-alias-label-dimmed,#9aa0a6)}
1079
- .dsm-trae-pool-tag-current{padding:1px 6px;border-radius:999px;background:rgba(86,134,254,.16);color:var(--dsw-alias-brand-primary,#5686fe);font-size:11px;font-weight:600;line-height:16px}
1080
1108
  .dsm-trae-pool-tag-member{border:1px solid var(--dsw-alias-border-l2,#3a3d45);color:var(--dsw-alias-label-tertiary,#9aa0a8)}
1081
1109
  .dsm-trae-pool-tag-excluded{background:var(--dsw-alias-state-warn-tertiary,#fef3c7);color:var(--dsw-alias-state-warn-primary,#b45309)}
1082
1110
  .dsm-trae-pool-tag-ghost{border:1px dashed var(--dsw-alias-border-l2,#3a3d45);color:var(--dsw-alias-label-tertiary,#9aa0a8)}
@@ -1115,7 +1143,7 @@ window.__ModuleLoader__.load({
1115
1143
  /* "Every member is unusable" is the one case that IS the pool's fault, so it
1116
1144
  keeps the warn tone the base class sets. */
1117
1145
  .dsm-trae-pool-warning-unusable{border-color:var(--dsw-alias-state-warn-secondary,#fbbf24);background:var(--dsw-alias-state-warn-tertiary,#fef3c7);color:var(--dsw-alias-state-warn-primary,#b45309)}
1118
- .dsm-trae-pool-save-row{display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:1px solid var(--dsw-alias-border-l2,#36373b);padding-top:12px}
1146
+ .dsm-trae-pool-save-row{display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:1px solid var(--dsw-alias-border-l2,#36373b);padding-top:12px;position:sticky;bottom:0;z-index:2;padding-bottom:8px;background:var(--dsw-alias-bg-layer-3,#2a2c33)}
1119
1147
  .dsm-trae-pool-save-buttons{display:flex;align-items:center;justify-content:flex-end;gap:8px}
1120
1148
  .dsm-trae-pool-save-row .dsm-btn{padding:5px 14px;font-size:13px;line-height:1.5}
1121
1149
  .dsm-trae-pool-dirty{padding:0 6px;border-radius:999px;background:var(--dsw-alias-state-warn-tertiary,#fef3c7);color:var(--dsw-alias-state-warn-primary,#b45309);font-size:10px;line-height:16px}
@@ -1591,6 +1619,7 @@ window.__ModuleLoader__.load({
1591
1619
  const [selected, setSelected] = (0, react.useState)(() => new Set(saved.memberAccountIds ?? []));
1592
1620
  const [busy, setBusy] = (0, react.useState)(void 0);
1593
1621
  const [saveError, setSaveError] = (0, react.useState)(void 0);
1622
+ const [moreRulesOpen, setMoreRulesOpen] = (0, react.useState)(false);
1594
1623
  const [testing, setTesting] = (0, react.useState)(void 0);
1595
1624
  const [rows, setRows] = (0, react.useState)({});
1596
1625
  const [deletingIds, setDeletingIds] = (0, react.useState)(() => /* @__PURE__ */ new Set());
@@ -1943,60 +1972,79 @@ window.__ModuleLoader__.load({
1943
1972
  className: "dsm-trae-pool",
1944
1973
  "aria-label": t("row.poolTitle"),
1945
1974
  children: [
1946
- pool.enabled !== true && onBind !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1947
- className: "dsm-trae-pool-set dsm-trae-pool-bind",
1948
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1949
- className: "dsm-trae-pool-set-copy",
1950
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: t("row.poolManualAccountLabel") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.poolManualAccountHint") })]
1951
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1952
- className: "dsm-trae-pool-set-ctl",
1953
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1954
- className: "dsm-trae-select dsm-trae-pool-account-select",
1955
- value: serving?.accountId ?? "",
1956
- disabled: bindingBusy === true || !writable,
1957
- onChange: (event) => {
1958
- const next = event.target.value;
1959
- if (next !== "") onBind(next);
1960
- },
1961
- children: [serving === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1962
- value: "",
1963
- disabled: true,
1964
- children: t("row.poolManualAccountNone")
1965
- }) : null, pool.accounts.filter((account) => account.reachable !== false).map((account) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1966
- value: account.accountId,
1967
- children: account.accountName
1968
- }, account.accountId))]
1969
- })
1970
- })]
1971
- }) : null,
1972
1975
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1973
- className: "dsm-trae-pool-actions",
1974
- children: [
1975
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1976
- type: "button",
1977
- className: "dsm-btn dsm-btn-outline dsm-trae-pool-action-test",
1978
- disabled: busy !== void 0 || selected.size === 0 || pool.targetModelSource === "none",
1979
- title: pool.targetModelSource === "none" ? t("row.poolTargetNoneHint") : void 0,
1980
- onClick: () => {
1981
- runTest();
1982
- },
1983
- children: busy === "test" ? t("row.poolTesting") : t("row.poolTestAll")
1984
- }),
1985
- busy === "test" || testing !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1986
- className: "dsm-trae-pool-busy",
1987
- role: "status",
1988
- children: t("row.poolBusyHint")
1989
- }) : null,
1990
- topActions === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1991
- className: "dsm-trae-pool-actions-end",
1992
- children: topActions
1993
- })
1994
- ]
1976
+ className: "dsm-trae-pool-head",
1977
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1978
+ className: "dsm-trae-pool-title",
1979
+ children: t("row.poolTitle")
1980
+ }), pool.enabled !== true && onBind !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1981
+ className: "dsm-trae-select dsm-trae-pool-account-select",
1982
+ "aria-label": t("row.poolManualAccountLabel"),
1983
+ value: serving?.accountId ?? "",
1984
+ disabled: bindingBusy === true || !writable,
1985
+ onChange: (event) => {
1986
+ const next = event.target.value;
1987
+ if (next !== "") onBind(next);
1988
+ },
1989
+ children: [serving === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1990
+ value: "",
1991
+ disabled: true,
1992
+ children: t("row.poolManualAccountNone")
1993
+ }) : null, pool.accounts.filter((account) => account.reachable !== false).map((account) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1994
+ value: account.accountId,
1995
+ children: account.accountName
1996
+ }, account.accountId))]
1997
+ }) : serving === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1998
+ className: "dsm-trae-pool-current",
1999
+ children: t("row.poolCurrentHeader", { account: serving.accountName })
2000
+ })]
1995
2001
  }),
1996
- actionNotes.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1997
- className: "dsm-trae-pool-action-note",
1998
- children: actionNotes.join(" · ")
2002
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2003
+ className: "dsm-trae-pool-settings dsm-trae-pool-modebar",
2004
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2005
+ className: "dsm-trae-pool-modebar-row",
2006
+ children: [
2007
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2008
+ className: "dsm-trae-switch dsm-trae-switch-pill",
2009
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2010
+ type: "checkbox",
2011
+ checked: enabled,
2012
+ disabled: busy !== void 0 || !writable,
2013
+ "aria-label": t("row.poolEnabled"),
2014
+ onChange: (e) => {
2015
+ setEnabled(e.target.checked);
2016
+ }
2017
+ })
2018
+ }),
2019
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", {
2020
+ className: "dsm-trae-pool-modebar-label",
2021
+ children: t("row.poolEnabled")
2022
+ }),
2023
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2024
+ className: "dsm-trae-pool-modebar-state",
2025
+ children: pool.enabled === true ? t("row.poolModeAuto") : t("row.poolModeManual")
2026
+ }),
2027
+ dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2028
+ className: "dsm-trae-pool-dirty",
2029
+ role: "status",
2030
+ children: t("row.poolDirty")
2031
+ }) : null
2032
+ ]
2033
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2034
+ className: "dsm-trae-pool-note",
2035
+ children: t("row.poolEnabledHint")
2036
+ })]
1999
2037
  }),
2038
+ pool.enabled === true && serving !== void 0 && serving.member !== true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2039
+ className: "dsm-trae-pool-note dsm-trae-pool-serving-note",
2040
+ role: "status",
2041
+ children: t("row.poolServingOutside", { account: serving.accountName })
2042
+ }) : null,
2043
+ saved.enabled === true && enabled === false ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2044
+ className: "dsm-trae-pool-off-notice",
2045
+ role: "status",
2046
+ children: t("row.poolOffNotice")
2047
+ }) : null,
2000
2048
  showNoCandidate ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NoCandidateNotice, {
2001
2049
  noCandidate,
2002
2050
  t
@@ -2036,6 +2084,34 @@ window.__ModuleLoader__.load({
2036
2084
  ]
2037
2085
  })]
2038
2086
  }),
2087
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2088
+ className: "dsm-trae-pool-actions",
2089
+ children: [
2090
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2091
+ type: "button",
2092
+ className: "dsm-btn dsm-btn-outline dsm-trae-pool-action-test",
2093
+ disabled: busy !== void 0 || selected.size === 0 || pool.targetModelSource === "none",
2094
+ title: pool.targetModelSource === "none" ? t("row.poolTargetNoneHint") : void 0,
2095
+ onClick: () => {
2096
+ runTest();
2097
+ },
2098
+ children: busy === "test" ? t("row.poolTesting") : t("row.poolTestAll")
2099
+ }),
2100
+ busy === "test" || testing !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2101
+ className: "dsm-trae-pool-busy",
2102
+ role: "status",
2103
+ children: t("row.poolBusyHint")
2104
+ }) : null,
2105
+ topActions === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2106
+ className: "dsm-trae-pool-actions-end",
2107
+ children: topActions
2108
+ })
2109
+ ]
2110
+ }),
2111
+ actionNotes.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2112
+ className: "dsm-trae-pool-action-note",
2113
+ children: actionNotes.join(" · ")
2114
+ }),
2039
2115
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2040
2116
  className: pool.checkinSupported ? "dsm-trae-pool-table" : "dsm-trae-pool-table dsm-trae-pool-table-no-checkin",
2041
2117
  role: "table",
@@ -2053,6 +2129,7 @@ window.__ModuleLoader__.load({
2053
2129
  rows,
2054
2130
  checked: selected.has(account.accountId),
2055
2131
  poolEnabled: pool.enabled === true,
2132
+ poolModelId: pool.targetModelId ?? "",
2056
2133
  detail: details?.[account.accountId],
2057
2134
  claim: claims?.[account.accountId],
2058
2135
  onClaim,
@@ -2120,66 +2197,49 @@ window.__ModuleLoader__.load({
2120
2197
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2121
2198
  className: "dsm-trae-pool-settings-wrap",
2122
2199
  children: [
2123
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2124
- className: "dsm-trae-pool-head",
2125
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2126
- className: "dsm-trae-pool-title",
2127
- children: t("row.poolSettingsTitle")
2128
- }), dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2129
- className: "dsm-trae-pool-dirty",
2130
- role: "status",
2131
- children: t("row.poolDirty")
2132
- }) : null]
2133
- }),
2134
2200
  tools === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2135
2201
  className: "dsm-trae-pool-tools dsm-trae-pool-tools-dashed",
2136
2202
  children: tools
2137
2203
  }),
2138
- pool.copies === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2139
- className: "dsm-trae-pool-note dsm-trae-pool-copyfile",
2140
- role: "note",
2141
- children: t("row.poolCopyFile", {
2142
- path: pool.copies.path,
2143
- count: pool.copies.count
2204
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2205
+ className: "dsm-trae-pool-head",
2206
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2207
+ className: "dsm-trae-pool-title",
2208
+ children: t("row.poolSettingsTitle")
2144
2209
  })
2145
2210
  }),
2146
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2147
- className: "dsm-trae-pool-note",
2148
- children: t("row.poolSettingsNote")
2149
- }),
2150
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2151
- className: "dsm-trae-pool-note",
2152
- children: t("row.poolDeleteHint")
2211
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
2212
+ className: "dsm-trae-pool-more",
2213
+ open: moreRulesOpen,
2214
+ onToggle: (event) => setMoreRulesOpen(event.currentTarget.open),
2215
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", {
2216
+ className: "dsm-trae-pool-more-summary",
2217
+ children: t("row.poolMoreRulesTitle")
2218
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2219
+ className: "dsm-trae-pool-more-body",
2220
+ children: [
2221
+ pool.copies === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2222
+ className: "dsm-trae-pool-note",
2223
+ role: "note",
2224
+ children: t("row.poolCopyFile", {
2225
+ path: pool.copies.path,
2226
+ count: pool.copies.count
2227
+ })
2228
+ }),
2229
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2230
+ className: "dsm-trae-pool-note",
2231
+ children: t("row.poolSettingsNote")
2232
+ }),
2233
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2234
+ className: "dsm-trae-pool-note",
2235
+ children: t("row.poolDeleteHint")
2236
+ })
2237
+ ]
2238
+ })]
2153
2239
  }),
2154
2240
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2155
2241
  className: "dsm-trae-pool-settings",
2156
2242
  children: [
2157
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2158
- className: "dsm-trae-pool-set",
2159
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2160
- className: "dsm-trae-pool-set-copy",
2161
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: t("row.poolEnabled") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.poolEnabledHint") })]
2162
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2163
- className: "dsm-trae-pool-set-ctl",
2164
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2165
- className: "dsm-trae-switch dsm-trae-switch-pill",
2166
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2167
- type: "checkbox",
2168
- checked: enabled,
2169
- disabled: busy !== void 0 || !writable,
2170
- "aria-label": t("row.poolEnabled"),
2171
- onChange: (e) => {
2172
- setEnabled(e.target.checked);
2173
- }
2174
- })
2175
- })
2176
- })]
2177
- }),
2178
- saved.enabled === true && enabled === false ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2179
- className: "dsm-trae-pool-off-notice",
2180
- role: "status",
2181
- children: t("row.poolOffNotice")
2182
- }) : null,
2183
2243
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2184
2244
  className: "dsm-trae-pool-set",
2185
2245
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
@@ -2351,7 +2411,7 @@ window.__ModuleLoader__.load({
2351
2411
  ]
2352
2412
  });
2353
2413
  }
2354
- function MemberRow({ account, models, rows, checked, onToggle, onTest, t, disabled, deleting, onDelete, canDelete, formatNumber, relativeAt, detail, claim, onClaim, showCheckin, deviceClaimed, poolEnabled }) {
2414
+ function MemberRow({ account, models, rows, checked, onToggle, onTest, t, disabled, deleting, onDelete, canDelete, formatNumber, relativeAt, detail, claim, onClaim, showCheckin, deviceClaimed, poolEnabled, poolModelId }) {
2355
2415
  /**
2356
2416
  * Whether this row is the one billing the next request — and, with the pool on,
2357
2417
  * whether that claim is the pool's own.
@@ -2417,10 +2477,6 @@ window.__ModuleLoader__.load({
2417
2477
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2418
2478
  className: "dsm-trae-pool-account-tags",
2419
2479
  children: [
2420
- isCurrent ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2421
- className: "dsm-trae-pool-tag dsm-trae-pool-tag-current",
2422
- children: t("row.poolCurrentBadge")
2423
- }) : null,
2424
2480
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2425
2481
  className: checked ? "dsm-trae-pool-member" : "dsm-trae-pool-not-member",
2426
2482
  children: checked ? t("row.poolMember") : t("row.poolNotMember")
@@ -2467,7 +2523,8 @@ window.__ModuleLoader__.load({
2467
2523
  t,
2468
2524
  relativeAt,
2469
2525
  now: Date.now(),
2470
- detail: testRow.detail
2526
+ detail: testRow.detail,
2527
+ poolModelId
2471
2528
  }) : account.probe !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProbeResult, {
2472
2529
  outcome: account.probe.outcome,
2473
2530
  modelId: account.probe.modelId,
@@ -2476,7 +2533,8 @@ window.__ModuleLoader__.load({
2476
2533
  t,
2477
2534
  relativeAt,
2478
2535
  now: Date.now(),
2479
- detail: account.probe.message
2536
+ detail: account.probe.message,
2537
+ poolModelId
2480
2538
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2481
2539
  className: "dsm-trae-pool-probe-none",
2482
2540
  children: t("row.poolNeverTested")
@@ -2592,12 +2650,15 @@ window.__ModuleLoader__.load({
2592
2650
  ]
2593
2651
  });
2594
2652
  }
2595
- function ProbeResult({ outcome, modelId, atMs, source, detail, t, relativeAt, now }) {
2653
+ function ProbeResult({ outcome, modelId, atMs, source, detail, poolModelId, t, relativeAt, now }) {
2654
+ const tone = outcome === "ok" ? "ok" : "warn";
2655
+ const modelDiffers = modelId !== void 0 && modelId !== poolModelId;
2656
+ const sourceLabel = source === void 0 ? "" : probeSourceLabel(source, t);
2596
2657
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2597
2658
  className: "dsm-trae-pool-probe-cell",
2598
2659
  children: [
2599
2660
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2600
- className: `dsm-trae-pool-probe-outcome dsm-trae-pool-probe-${outcome === "ok" ? "ok" : "warn"}`,
2661
+ className: `dsm-trae-pool-probe-outcome dsm-trae-pool-probe-${tone}`,
2601
2662
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2602
2663
  className: "dsm-trae-pool-dot dsm-trae-pool-dot-ok",
2603
2664
  "aria-hidden": "true"
@@ -2607,9 +2668,12 @@ window.__ModuleLoader__.load({
2607
2668
  className: "dsm-trae-pool-probe-meta",
2608
2669
  children: t("row.poolProbeMeasured", { when: relativeAt(atMs, now, t) })
2609
2670
  }),
2610
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2671
+ modelDiffers ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2672
+ className: "dsm-trae-pool-probe-meta",
2673
+ children: [t("row.poolProbeModel", { model: modelId }), sourceLabel === "" ? "" : ` · ${sourceLabel}`]
2674
+ }) : sourceLabel === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2611
2675
  className: "dsm-trae-pool-probe-meta",
2612
- children: [modelId !== void 0 ? t("row.poolProbeModel", { model: modelId }) : t("row.poolProbeModelUnknown"), source === void 0 ? "" : ` · ${probeSourceLabel(source, t)}`]
2676
+ children: sourceLabel
2613
2677
  }),
2614
2678
  detail !== void 0 && detail !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2615
2679
  className: "dsm-trae-pool-probe-detail",
@@ -3559,13 +3623,13 @@ window.__ModuleLoader__.load({
3559
3623
  * - the number shown is upstream's own pay ratio (`discount: 50` ⇒ 五折 /
3560
3624
  * "50% off"), never a re-derived price.
3561
3625
  */
3562
- const PROMO_LABELS = {
3626
+ const PROMO_LABELS$1 = {
3563
3627
  limited: "row.promoLimited",
3564
3628
  subsidy: "row.promoSubsidy",
3565
3629
  offPeak: "row.promoOffPeak"
3566
3630
  };
3567
3631
  /** Pay ratio as the "折" figure a Chinese price tag uses: 50 → "5", 25 → "2.5". */
3568
- function promoZhe(discountPercent) {
3632
+ function promoZhe$1(discountPercent) {
3569
3633
  const zhe = Math.round(discountPercent * 10) / 100;
3570
3634
  return Number.isInteger(zhe) ? String(zhe) : zhe.toFixed(1);
3571
3635
  }
@@ -3585,7 +3649,7 @@ window.__ModuleLoader__.load({
3585
3649
  function promoBadge(model, t, now) {
3586
3650
  const badge = selectTraePromoBadge(model.activityDiscount, now);
3587
3651
  if (badge === void 0) return null;
3588
- const zhe = promoZhe(badge.discountPercent);
3652
+ const zhe = promoZhe$1(badge.discountPercent);
3589
3653
  const hints = [];
3590
3654
  if (badge.beforeRate !== void 0 && badge.rate !== void 0) hints.push(t("row.promoHintRate", {
3591
3655
  before: String(badge.beforeRate),
@@ -3597,16 +3661,16 @@ window.__ModuleLoader__.load({
3597
3661
  zone: badge.timeZone
3598
3662
  }));
3599
3663
  if (badge.memberDiscountPercent !== void 0) hints.push(t("row.promoHintMember", {
3600
- zhe: promoZhe(badge.memberDiscountPercent),
3664
+ zhe: promoZhe$1(badge.memberDiscountPercent),
3601
3665
  percent: String(100 - badge.memberDiscountPercent)
3602
3666
  }));
3603
3667
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3604
3668
  className: `dsm-trae-model-promo dsm-trae-model-promo-${badge.kind}`,
3605
- title: hints.length === 0 ? t(PROMO_LABELS[badge.kind], {
3669
+ title: hints.length === 0 ? t(PROMO_LABELS$1[badge.kind], {
3606
3670
  zhe,
3607
3671
  percent: String(badge.percentOff)
3608
3672
  }) : hints.join(" · "),
3609
- children: t(PROMO_LABELS[badge.kind], {
3673
+ children: t(PROMO_LABELS$1[badge.kind], {
3610
3674
  zhe,
3611
3675
  percent: String(badge.percentOff)
3612
3676
  })
@@ -6229,7 +6293,8 @@ window.__ModuleLoader__.load({
6229
6293
  "row.poolEnabled": "Enable the account pool",
6230
6294
  "row.poolEnabledHint": "The pool picks the account to bill for this panel's requests, ranking by credits and availability. Off means the manually bound account serves.",
6231
6295
  "row.poolCurrentHeader": "Billing now: {account}",
6232
- "row.poolCurrentBadge": "serving",
6296
+ "row.poolModeAuto": "pool picks",
6297
+ "row.poolModeManual": "you pick",
6233
6298
  "row.poolMember": "in pool",
6234
6299
  "row.poolNotMember": "not in pool",
6235
6300
  "row.poolBusyHint": "Working through accounts one at a time…",
@@ -6241,6 +6306,7 @@ window.__ModuleLoader__.load({
6241
6306
  "row.poolNoCandidateEmptyHint": "Tick accounts in the membership list to bring them into the pool.",
6242
6307
  "row.poolNoCandidateUnusable": "Every selected member is unavailable right now.",
6243
6308
  "row.poolNoCandidateHint": "Traffic keeps landing on the saved selection until one recovers.",
6309
+ "row.poolServingOutside": "{account} is billing right now but is not in the pool — until a member becomes available, requests fall back to the saved selection.",
6244
6310
  "row.poolMembersTitle": "Accounts in this pool",
6245
6311
  "row.poolRescan": "Re-detect accounts",
6246
6312
  "row.poolToolsLabel": "Export / import accounts",
@@ -6256,7 +6322,7 @@ window.__ModuleLoader__.load({
6256
6322
  "row.poolSelectNone": "None",
6257
6323
  "row.poolColumnAccount": "Account",
6258
6324
  "row.poolColumnCredits": "Credits",
6259
- "row.poolColumnProbe": "Target model",
6325
+ "row.poolColumnProbe": "Status",
6260
6326
  "row.poolColumnCheckin": "Check-in",
6261
6327
  "row.poolGeneralLabel": "General",
6262
6328
  "row.poolWorkHint": "Work credits are not spendable by DSH — only General powers chat.",
@@ -6281,9 +6347,9 @@ window.__ModuleLoader__.load({
6281
6347
  "row.poolProbeSizeHint": "How much text a test sends. A one-word probe only proves the account can answer a one-word request — if the upstream refuses long turns, this is where you find out. Bigger is closer to a real conversation, and costs more credit on every account in the batch.",
6282
6348
  "row.poolProbeSizeDefault": "Minimal (default)",
6283
6349
  "row.poolProbeSizeOption": "{wan}0k tokens",
6284
- "row.poolSettingsTitle": "Pool settings",
6350
+ "row.poolSettingsTitle": "More settings",
6351
+ "row.poolMoreRulesTitle": "Account copies & saving rules",
6285
6352
  "row.poolManualAccountLabel": "Manually billed account",
6286
- "row.poolManualAccountHint": "Used while the pool is off. With it on, the Host picks by credits.",
6287
6353
  "row.poolManualAccountNone": "No account in effect",
6288
6354
  "row.poolDirty": "unsaved",
6289
6355
  "row.poolSaved": "Save",
@@ -6578,7 +6644,8 @@ window.__ModuleLoader__.load({
6578
6644
  "row.poolEnabled": "启用账号池",
6579
6645
  "row.poolEnabledHint": "账号池按积分与可用性挑选本面板请求的计费账号;关闭时由手动绑定的账号计费。",
6580
6646
  "row.poolCurrentHeader": "当前计费:{account}",
6581
- "row.poolCurrentBadge": "派单中",
6647
+ "row.poolModeAuto": "自动挑号",
6648
+ "row.poolModeManual": "手动选号",
6582
6649
  "row.poolMember": "已在池中",
6583
6650
  "row.poolNotMember": "未入池",
6584
6651
  "row.poolBusyHint": "逐个处理账号中…",
@@ -6590,6 +6657,7 @@ window.__ModuleLoader__.load({
6590
6657
  "row.poolNoCandidateEmptyHint": "在成员列表中勾选账号即可加入账号池。",
6591
6658
  "row.poolNoCandidateUnusable": "所有已选成员当前均不可用。",
6592
6659
  "row.poolNoCandidateHint": "请求会继续落在已保存的账号上,直到有成员恢复可用。",
6660
+ "row.poolServingOutside": "{account} 当前在计费,但它不在池中——在池里有可用成员之前,请求会落回你保存的选择。",
6593
6661
  "row.poolMembersTitle": "本池包含的账号",
6594
6662
  "row.poolRescan": "重新检测账号",
6595
6663
  "row.poolToolsLabel": "导出 / 导入账号",
@@ -6605,7 +6673,7 @@ window.__ModuleLoader__.load({
6605
6673
  "row.poolSelectNone": "清空",
6606
6674
  "row.poolColumnAccount": "账号",
6607
6675
  "row.poolColumnCredits": "积分",
6608
- "row.poolColumnProbe": "目标模型",
6676
+ "row.poolColumnProbe": "状态",
6609
6677
  "row.poolColumnCheckin": "签到",
6610
6678
  "row.poolGeneralLabel": "通用",
6611
6679
  "row.poolWorkHint": "工作积分不能由 DSH 消费——只有通用积分用于对话。",
@@ -6630,9 +6698,9 @@ window.__ModuleLoader__.load({
6630
6698
  "row.poolProbeSizeHint": "测试时发送多少文本。只发一个词的探针,只能证明「这个账号能回答一个词」——如果上游拒绝长请求,问题只会在你真实对话时才出现。填得越多越接近真实对话,每个账号每次测试消耗的积分也越多。",
6631
6699
  "row.poolProbeSizeDefault": "最小请求(默认)",
6632
6700
  "row.poolProbeSizeOption": "{wan} 万 token",
6633
- "row.poolSettingsTitle": "账号池设置",
6701
+ "row.poolSettingsTitle": "更多设置",
6702
+ "row.poolMoreRulesTitle": "账号副本与保存规则",
6634
6703
  "row.poolManualAccountLabel": "手动计费账号",
6635
- "row.poolManualAccountHint": "账号池关闭时由它计费;开启后由 Host 按积分挑选。",
6636
6704
  "row.poolManualAccountNone": "当前无生效账号",
6637
6705
  "row.poolDirty": "未保存",
6638
6706
  "row.poolSaved": "保存",
@@ -6724,6 +6792,340 @@ window.__ModuleLoader__.load({
6724
6792
  "row.cheer": "鼓励一下"
6725
6793
  };
6726
6794
  //#endregion
6795
+ //#region src/client/TraeModelLabel.tsx
6796
+ /**
6797
+ * The HOST's model picker label: the catalog name, the upstream's activity
6798
+ * badge, and EXACTLY ONE multiplier.
6799
+ *
6800
+ * WHY A SLOT. The picker renders the model name as a plain string, so the only
6801
+ * thing a provider can put there is a character — and a character's colour is
6802
+ * the emoji font's business, not ours. The host therefore offers exactly one
6803
+ * seam for this: the `conversation.input.model.label` slot, whose contract is
6804
+ * "render non-interactive inline content such as a coloured name or badge"
6805
+ * while the picker keeps owning clicks, focus, check marks and the
6806
+ * accessibility text. The shipped example is the official DeepSeek "1017"
6807
+ * release badge — a coloured marker next to a model name — which is the same
6808
+ * job. (Ported from the sibling `dsh-connect-workbuddy` plugin, which solved
6809
+ * the same problem for the same host; the two must stay one presentation.)
6810
+ *
6811
+ * WHY THE BADGE BELONGS HERE AT ALL. The picker is where the choice is made,
6812
+ * and a time-boxed promotion is a choice criterion: "half price at night"
6813
+ * changes which row you click. The card already drew these badges, but the card
6814
+ * is the settings surface — by the time you read them there you have already
6815
+ * picked. This is the same upstream fact, moved to where it can still change a
6816
+ * decision.
6817
+ *
6818
+ * WHY ONE MULTIPLIER AND NOT TWO. `traeModelDisplayName` ALREADY ends the name
6819
+ * in the catalog multiplier (`Kimi-K2 · x0.55` — see `src/catalog.ts`), and the
6820
+ * host renders that string verbatim. Appending another rate here would print
6821
+ * `· x0.55 · x0.55`, which is precisely the defect the sibling plugin shipped
6822
+ * and had to fix (its #120 regression). The suffix is therefore pulled back OUT
6823
+ * of the name and re-drawn in the span that can carry the DISCOUNTED value, so
6824
+ * the row always shows exactly one number and it is the one that holds now.
6825
+ *
6826
+ * WHY A SEPARATE FETCH. The slot is rendered by the host, outside the plugin's
6827
+ * card, so it cannot read the card's React state (nor its injected `t`). It
6828
+ * asks the Host itself, over the SAME route the card polls
6829
+ * (`TRAE_USAGE_PATH`) — but with `detail=fast` (issue #77), the first-paint
6830
+ * document built from LOCAL state: the model roster, its `creditMultiplier` and
6831
+ * its unresolved `activityDiscount` are all known locally, so opening the
6832
+ * picker must not trigger an upstream read. The answer is cached briefly: a
6833
+ * picker that re-fetched on every re-render would be wasteful, and a level a few
6834
+ * seconds old is still true.
6835
+ *
6836
+ * WHY THE CLOCK IS RE-READ ON EVERY RENDER. A promotion's badge is a function
6837
+ * of the clock (an off-peak window opens and closes; a campaign expires).
6838
+ * Resolving it once at fetch time and holding it would leave "闲时 5 折" on the
6839
+ * picker the next morning — the exact overstatement `src/promo.ts` refuses to
6840
+ * make. The snapshot is fetched once and the BADGE is resolved at render time;
6841
+ * a shared one-minute ticker re-renders the mounted labels, so a window edge is
6842
+ * picked up without one timer per model row.
6843
+ *
6844
+ * WHY THE HOST STILL OWNS THE NAME. The contract says to keep `model.name` as
6845
+ * the text and render the addition beside it, so this component returns the name
6846
+ * first — it never replaces it — and it renders nothing extra for a provider
6847
+ * that is not ours.
6848
+ *
6849
+ * @module dsh-connect-trae/client/TraeModelLabel
6850
+ */
6851
+ /** How long a fetched snapshot is trusted, in milliseconds. */
6852
+ const CACHE_MS = 6e4;
6853
+ /** How often a mounted label re-reads the clock, in milliseconds. */
6854
+ const TICK_MS = 6e4;
6855
+ /** The snapshot per panel, plus when it was read. */
6856
+ const caches = /* @__PURE__ */ new Map();
6857
+ /**
6858
+ * The snapshot, read from the Host once and reused briefly.
6859
+ *
6860
+ * `undefined` means "not known" — the caller renders the plain name rather than
6861
+ * guessing, because a badge that claims a promotion the upstream never stated
6862
+ * is worse than no badge.
6863
+ */
6864
+ async function statusNow(panel) {
6865
+ const cached = caches.get(panel);
6866
+ if (cached !== void 0 && Date.now() - cached.at < CACHE_MS) return cached.snapshot;
6867
+ try {
6868
+ const response = await fetch(withTraeRegionFastDetail(TRAE_USAGE_PATH, panel), {
6869
+ headers: { accept: "application/json" },
6870
+ credentials: "same-origin"
6871
+ });
6872
+ if (!response.ok) return void 0;
6873
+ const body = await response.json();
6874
+ const models = /* @__PURE__ */ new Map();
6875
+ for (const model of body.models ?? []) models.set(model.id, model);
6876
+ const snapshot = { models };
6877
+ caches.set(panel, {
6878
+ snapshot,
6879
+ at: Date.now()
6880
+ });
6881
+ return snapshot;
6882
+ } catch {
6883
+ return;
6884
+ }
6885
+ }
6886
+ /** One shared minute ticker, so N mounted labels cost one timer, not N. */
6887
+ const minuteTicker = {
6888
+ timer: void 0,
6889
+ listeners: /* @__PURE__ */ new Set()
6890
+ };
6891
+ /**
6892
+ * Re-render the caller once a minute while it is mounted.
6893
+ *
6894
+ * @param listener - The callback to run at each tick.
6895
+ * @returns The unsubscribe function, which stops the shared timer when the last
6896
+ * listener leaves.
6897
+ */
6898
+ function subscribeMinute(listener) {
6899
+ minuteTicker.listeners.add(listener);
6900
+ if (minuteTicker.timer === void 0) {
6901
+ minuteTicker.timer = setInterval(() => {
6902
+ for (const each of [...minuteTicker.listeners]) each();
6903
+ }, TICK_MS);
6904
+ minuteTicker.timer.unref?.();
6905
+ }
6906
+ return () => {
6907
+ minuteTicker.listeners.delete(listener);
6908
+ if (minuteTicker.listeners.size === 0 && minuteTicker.timer !== void 0) {
6909
+ clearInterval(minuteTicker.timer);
6910
+ minuteTicker.timer = void 0;
6911
+ }
6912
+ };
6913
+ }
6914
+ /**
6915
+ * The UI's language, as the browser reports it.
6916
+ *
6917
+ * The card formats dates and numbers with the browser's locale, so reading the
6918
+ * same signal here keeps the badge in the language the user reads. Read
6919
+ * defensively: this module is imported by tests that run in a plain Node
6920
+ * environment, where neither global exists.
6921
+ */
6922
+ function uiLanguage() {
6923
+ if (typeof navigator !== "undefined" && typeof navigator.language === "string" && navigator.language !== "") return navigator.language;
6924
+ if (typeof document !== "undefined" && typeof document.documentElement?.lang === "string") return document.documentElement.lang;
6925
+ return "";
6926
+ }
6927
+ /** Whether the given language is an English one. */
6928
+ function isEnglish(language) {
6929
+ return language.toLowerCase().startsWith("en");
6930
+ }
6931
+ /** The copy table for one language, so the slot needs no injected `t`. */
6932
+ function copyFor(language) {
6933
+ return isEnglish(language) ? en : zh;
6934
+ }
6935
+ /**
6936
+ * Fill `{name}` placeholders in one locale string.
6937
+ *
6938
+ * The card's own `t` does this; the slot cannot use `t` (it is rendered by the
6939
+ * host, outside the card's injection), so the same substitution is done here on
6940
+ * the same table. Both phrasings are always passed: the zh locale spells a pay
6941
+ * ratio as "5 折" and needs `{zhe}`, the en one says "50% off" and needs
6942
+ * `{percent}` — each uses the placeholder it names, the other is ignored.
6943
+ */
6944
+ function fillPlaceholders(template, vars) {
6945
+ return template.replace(/\{(\w+)\}/g, (whole, key) => vars[key] ?? whole);
6946
+ }
6947
+ /** Pay ratio as the "折" figure a Chinese price tag uses: 50 → "5", 25 → "2.5". */
6948
+ function promoZhe(discountPercent) {
6949
+ const zhe = Math.round(discountPercent * 10) / 100;
6950
+ return Number.isInteger(zhe) ? String(zhe) : zhe.toFixed(1);
6951
+ }
6952
+ /** The locale key that names each promotion kind. */
6953
+ const PROMO_LABELS = {
6954
+ limited: "row.promoLimited",
6955
+ subsidy: "row.promoSubsidy",
6956
+ offPeak: "row.promoOffPeak"
6957
+ };
6958
+ /**
6959
+ * The chip's colour per kind, mirroring `.dsm-trae-model-promo-*` in
6960
+ * `styles.ts`.
6961
+ *
6962
+ * Written inline because a slot component is rendered OUTSIDE the card: the
6963
+ * card's stylesheet is only mounted while the card is, so a picker opened on a
6964
+ * page where the card was never opened would otherwise get an uncoloured chip.
6965
+ * The tokens are the same ones the stylesheet uses, with the same fallbacks.
6966
+ */
6967
+ const PROMO_TONES = {
6968
+ subsidy: {
6969
+ color: "var(--dsw-alias-state-error-primary,#d92d20)",
6970
+ border: "color-mix(in srgb,var(--dsw-alias-state-error-primary,#d92d20) 40%,transparent)",
6971
+ background: "color-mix(in srgb,var(--dsw-alias-state-error-primary,#d92d20) 10%,transparent)"
6972
+ },
6973
+ limited: {
6974
+ color: "var(--dsw-alias-state-warn-primary,#f59e0b)",
6975
+ border: "color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 45%,transparent)",
6976
+ background: "color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 12%,transparent)"
6977
+ },
6978
+ offPeak: {
6979
+ color: "var(--dsw-alias-brand-primary,#5686fe)",
6980
+ border: "color-mix(in srgb,var(--dsw-alias-brand-primary,#5686fe) 40%,transparent)",
6981
+ background: "color-mix(in srgb,var(--dsw-alias-brand-primary,#5686fe) 10%,transparent)"
6982
+ }
6983
+ };
6984
+ /**
6985
+ * Split the host's display name into "name" and "the multiplier to show".
6986
+ *
6987
+ * `traeModelDisplayName` builds `"<name> · x<multiplier>"` and the host renders
6988
+ * that string verbatim, so appending another rate next to it would print the
6989
+ * figure twice in the picker (`Kimi-K2 · x0.55 · x0.55`). The suffix is
6990
+ * therefore pulled back OUT of the string and re-drawn in the span that can
6991
+ * carry the discounted value — exactly one number, and it is the one that holds
6992
+ * right now.
6993
+ *
6994
+ * The suffix is recognised by SHAPE, not by comparing against the snapshot's
6995
+ * `creditMultiplier`: the two can disagree (a promotion can change the catalog
6996
+ * figure between the snapshot and this render), and leaving the suffix in place
6997
+ * then prints two numbers again — the very defect this function removes.
6998
+ *
6999
+ * @param name - The host's rendered name, possibly ending in ` · x<rate>`.
7000
+ * @param catalogRate - The catalog multiplier, as the snapshot reports it.
7001
+ * @param discounted - The rate to show instead, when a discount is running.
7002
+ * @returns The name without its rate suffix, and the single rate to draw.
7003
+ * `undefined` means "draw no number" (the name had none and no discount
7004
+ * is running).
7005
+ */
7006
+ function splitCatalogRate(name, catalogRate, discounted) {
7007
+ const match = / · x(\d+(?:\.\d+)?)$/.exec(name);
7008
+ const fromName = match === null ? void 0 : Number(match[1]);
7009
+ return {
7010
+ base: match === null ? name : name.slice(0, match.index),
7011
+ rate: discounted ?? fromName ?? catalogRate
7012
+ };
7013
+ }
7014
+ /** The rate a live badge says the user pays, when it names one. */
7015
+ function discountedRateOf(badge) {
7016
+ return badge?.rate;
7017
+ }
7018
+ /**
7019
+ * The upstream's promotion chip, as the picker draws it.
7020
+ *
7021
+ * Same classes as the card's capsule so the two surfaces cannot drift apart,
7022
+ * plus the inline tone described in {@link PROMO_TONES}.
7023
+ *
7024
+ * @param props - The resolved badge and the copy table for the UI's language.
7025
+ * @returns The chip.
7026
+ */
7027
+ function PromoChip(props) {
7028
+ const { badge, copy } = props;
7029
+ const zhe = promoZhe(badge.discountPercent);
7030
+ const label = fillPlaceholders(copy[PROMO_LABELS[badge.kind]], {
7031
+ zhe,
7032
+ percent: String(badge.percentOff)
7033
+ });
7034
+ const hints = [];
7035
+ if (badge.beforeRate !== void 0 && badge.rate !== void 0) hints.push(fillPlaceholders(copy["row.promoHintRate"], {
7036
+ before: String(badge.beforeRate),
7037
+ rate: String(badge.rate)
7038
+ }));
7039
+ if (badge.fromClock !== void 0 && badge.untilClock !== void 0 && badge.timeZone !== void 0) hints.push(fillPlaceholders(copy["row.promoHintWindow"], {
7040
+ from: badge.fromClock,
7041
+ until: badge.untilClock,
7042
+ zone: badge.timeZone
7043
+ }));
7044
+ if (badge.memberDiscountPercent !== void 0) hints.push(fillPlaceholders(copy["row.promoHintMember"], {
7045
+ zhe: promoZhe(badge.memberDiscountPercent),
7046
+ percent: String(100 - badge.memberDiscountPercent)
7047
+ }));
7048
+ const tone = PROMO_TONES[badge.kind];
7049
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7050
+ className: `dsm-trae-model-promo dsm-trae-model-promo-${badge.kind} dsm-trae-model-label-promo`,
7051
+ title: hints.length === 0 ? label : hints.join(" · "),
7052
+ style: {
7053
+ flex: "none",
7054
+ borderRadius: "999px",
7055
+ padding: "1px 7px",
7056
+ fontSize: "10px",
7057
+ fontWeight: 500,
7058
+ lineHeight: "15px",
7059
+ whiteSpace: "nowrap",
7060
+ border: `1px solid ${tone.border}`,
7061
+ color: tone.color,
7062
+ background: tone.background,
7063
+ maxWidth: "6em",
7064
+ overflow: "hidden",
7065
+ textOverflow: "ellipsis"
7066
+ },
7067
+ children: label
7068
+ });
7069
+ }
7070
+ /**
7071
+ * The label for one model row in the host's picker.
7072
+ *
7073
+ * @param props - The host's model identity and the placement of this label.
7074
+ * @returns The catalog name, followed by the promotion chip when one holds.
7075
+ */
7076
+ function TraeModelLabel(props) {
7077
+ const { provider, model } = props;
7078
+ const panel = panelOfProvider(provider.id);
7079
+ if (panel === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: model.name });
7080
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TraeModelLabelInner, {
7081
+ modelId: model.id,
7082
+ name: model.name,
7083
+ panel
7084
+ });
7085
+ }
7086
+ /** The stateful half: fetches the snapshot, then renders what holds right now. */
7087
+ function TraeModelLabelInner(props) {
7088
+ const { modelId, name, panel } = props;
7089
+ const [snapshot, setSnapshot] = (0, react.useState)(void 0);
7090
+ const [, setTick] = (0, react.useState)(0);
7091
+ (0, react.useEffect)(() => {
7092
+ let live = true;
7093
+ statusNow(panel).then((next) => {
7094
+ if (!live || next === void 0) return;
7095
+ setSnapshot(next);
7096
+ });
7097
+ return () => {
7098
+ live = false;
7099
+ };
7100
+ }, [modelId, panel]);
7101
+ (0, react.useEffect)(() => subscribeMinute(() => setTick((count) => count + 1)), []);
7102
+ const model = snapshot?.models.get(modelId);
7103
+ const badge = model === void 0 ? void 0 : selectTraePromoBadge(model.activityDiscount, /* @__PURE__ */ new Date());
7104
+ const { base, rate } = splitCatalogRate(name, model?.creditMultiplier, discountedRateOf(badge));
7105
+ const copy = copyFor(uiLanguage());
7106
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7107
+ className: "dsm-trae-model-label",
7108
+ style: {
7109
+ display: "inline-flex",
7110
+ alignItems: "center",
7111
+ gap: "5px",
7112
+ whiteSpace: "nowrap",
7113
+ verticalAlign: "baseline"
7114
+ },
7115
+ children: [
7116
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: base }),
7117
+ rate === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7118
+ className: "dsm-trae-model-label-rate",
7119
+ children: [" · x", rate.toFixed(2)]
7120
+ }),
7121
+ badge === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PromoChip, {
7122
+ badge,
7123
+ copy
7124
+ })
7125
+ ]
7126
+ });
7127
+ }
7128
+ //#endregion
6727
7129
  //#region src/client/index.tsx
6728
7130
  /** Stable browser-plugin name. */
6729
7131
  const name = "dsh-connect-trae-client";
@@ -6892,6 +7294,30 @@ window.__ModuleLoader__.load({
6892
7294
  };
6893
7295
  registerPointsSlot("conversation.input.left", "dsh-connect-trae-composer-points", 4, "composer");
6894
7296
  registerPointsSlot("conversation.composer.dock", "dsh-connect-trae-stats-points", 10, "stats");
7297
+ /**
7298
+ * The upstream's activity badge in the HOST's model picker (ported from the
7299
+ * sibling `dsh-connect-workbuddy` plugin, whose #120 shipped the same fix).
7300
+ *
7301
+ * The picker renders the model name as a plain string, so a provider can
7302
+ * only put a CHARACTER there — and a character's colour is the emoji font's
7303
+ * business, not ours. The host therefore offers exactly one seam for this:
7304
+ * the `conversation.input.model.label` slot, whose contract is "render
7305
+ * non-interactive inline content such as a coloured name or badge" while the
7306
+ * picker keeps owning clicks, focus and check marks.
7307
+ *
7308
+ * Registered unconditionally: the component renders the plain name for a
7309
+ * provider that is not ours, so there is nothing to gate on. Its own
7310
+ * try/catch keeps a host that does not serve this slot from taking the card
7311
+ * down with it.
7312
+ */
7313
+ const registerModelLabel = () => {
7314
+ try {
7315
+ return ctx.slots.inject("conversation.input.model.label", () => ctx.slots.register({ name: "conversation.input.model.label" }, TraeModelLabel));
7316
+ } catch (error) {
7317
+ console.error("[dsh-connect-trae] model label slot failed to register (host provider unaffected):", error);
7318
+ }
7319
+ };
7320
+ registerModelLabel();
6895
7321
  } catch (error) {
6896
7322
  console.error("[dsh-connect-trae] client card failed to load (host provider unaffected):", error);
6897
7323
  }