dsh-qoder-connect 0.2.3-alpha.2 → 0.2.4

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/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { D as QoderCatalogStore, O as qoderCatalogPath, U as qoderCredentialIdentity, V as QoderCredentialStore, b as createQoderTransport, f as QODER_CONNECT_VERSION, i as variantFor, k as FALLBACK_QODER_MODELS, l as qoderHostHeartbeatPath, m as QoderUpstreamClient, r as QODER_VARIANTS, s as isHeartbeatProcessAlive, t as CHINA_VARIANT, u as readHostHeartbeat, y as validateApiKey } from "./variants-UX2MxZaz.js";
2
+ import { D as QoderCatalogStore, O as qoderCatalogPath, U as qoderCredentialIdentity, V as QoderCredentialStore, b as createQoderTransport, f as QODER_CONNECT_VERSION, i as variantFor, k as FALLBACK_QODER_MODELS, l as qoderHostHeartbeatPath, m as QoderUpstreamClient, r as QODER_VARIANTS, s as isHeartbeatProcessAlive, t as CHINA_VARIANT, u as readHostHeartbeat, y as validateApiKey } from "./variants-OJKPI_c7.js";
3
3
  import { readFile } from "node:fs/promises";
4
4
  import { realpathSync } from "node:fs";
5
5
  import { fileURLToPath } from "node:url";
package/lib/client.js CHANGED
@@ -40,6 +40,169 @@ window.__ModuleLoader__.load({
40
40
  const QODER_GLOBAL_PROBE_PATH = "/plugins/dsh-qoder-connect/global/probe";
41
41
  const QODER_GLOBAL_AUTH_PATH = "/plugins/dsh-qoder-connect/global/auth";
42
42
  //#endregion
43
+ //#region src/client/quota-merge.ts
44
+ /**
45
+ * Group credit accounts by (packageName, packageEndTime) and sum each group.
46
+ *
47
+ * A missing figure counts as 0 (the user's ruling): a package whose total the
48
+ * upstream did not report contributes nothing to the group's `size` rather
49
+ * than poisoning the bar into "unknown". A group whose summed `size` is still
50
+ * 0 keeps an honest "unknown total" rendering in the card — the merge never
51
+ * invents a denominator. `unlimited` is sticky: one unlimited member makes the
52
+ * whole group unlimited, and its sums are not displayed as a quota.
53
+ *
54
+ * MERGE KEY IS THE NAME ALONE (the user's correction): same-named packages
55
+ * whose expiries differ by seconds (stacked purchase batches) must still be
56
+ * one overview row — keying on the expiry exploded 28 same-named packages
57
+ * back into 28 separate bars the moment the host started reporting real
58
+ * dates. The expiry travels on the group (earliest of the members) for the
59
+ * sort/visibility rules; the itemised per-expiry breakdown lives in the
60
+ * dashboard's table.
61
+ *
62
+ * @param accounts - the status document's per-package credit entries.
63
+ * @returns one group per distinct package NAME, in first-seen order.
64
+ */
65
+ function mergeCreditAccounts(accounts) {
66
+ const groups = /* @__PURE__ */ new Map();
67
+ for (const account of accounts) {
68
+ const key = account.packageName;
69
+ const existing = groups.get(key);
70
+ if (existing === void 0) {
71
+ groups.set(key, {
72
+ packageName: account.packageName,
73
+ packageEndTime: account.packageEndTime,
74
+ remain: account.remain,
75
+ size: account.size,
76
+ unlimited: account.unlimited === true
77
+ });
78
+ continue;
79
+ }
80
+ existing.remain += account.remain;
81
+ existing.size += account.size;
82
+ existing.unlimited = existing.unlimited || account.unlimited === true;
83
+ if (existing.packageEndTime !== void 0 && account.packageEndTime !== void 0) {
84
+ const a = Date.parse(existing.packageEndTime);
85
+ const b = Date.parse(account.packageEndTime);
86
+ if (!Number.isNaN(a) && !Number.isNaN(b) && b < a) existing.packageEndTime = account.packageEndTime;
87
+ } else existing.packageEndTime = existing.packageEndTime ?? account.packageEndTime;
88
+ }
89
+ return [...groups.values()];
90
+ }
91
+ /** Clamp helper shared by the card's percent math. */
92
+ function clampPercent(remain, size) {
93
+ if (!(size > 0)) return void 0;
94
+ const percent = remain / size * 100;
95
+ if (!Number.isFinite(percent)) return void 0;
96
+ return Math.min(100, Math.max(0, percent));
97
+ }
98
+ /**
99
+ * The percent format every surface prints a RATIO with: at most two decimals,
100
+ * with a whole value left whole — "100", never "100.00".
101
+ *
102
+ * Both cards show the SAME package, so they must print the SAME number: while
103
+ * the sidebar rounded to whole percent and the settings card kept one decimal,
104
+ * 2,291 / 2,300 read "100%" in one place and "99.6%" in the other, and the two
105
+ * surfaces contradicted each other about one figure.
106
+ *
107
+ * Two decimals, not a fixed two: the padding would buy no accuracy, and the
108
+ * sidebar's percent column is 38px wide, where "99.61%" measures 36px while
109
+ * "100.00%" measures 42px.
110
+ *
111
+ * @param percent - an already-clamped percentage value.
112
+ * @returns the percent as text, without the trailing sign.
113
+ */
114
+ function formatPercentText(percent) {
115
+ return new Intl.NumberFormat(void 0, { maximumFractionDigits: 2 }).format(percent);
116
+ }
117
+ /**
118
+ * The same ratio read the other way round: the cycle's USED percentage as a
119
+ * bare number string ("3"), ready for the `quotaPackageUsed` copy.
120
+ *
121
+ * Both surfaces that print it call this ONE function — the sidebar card's label
122
+ * suffix and the settings card's credit bar — because they must never disagree
123
+ * about the same package.
124
+ *
125
+ * Unlike {@link formatPercentText}, this one stays whole percent on purpose:
126
+ * the suffix rides inside the package label, and at two decimals the sidebar's
127
+ * label measures 105px against the 93px its row can spare, which would ellipsize
128
+ * the very suffix it exists to show.
129
+ *
130
+ * Derived from remain/size rather than read from the upstream's per-package
131
+ * `used`: the transport normalizes `remaining` from `used`, so the value is the
132
+ * same, while a package the upstream reports without `used` normalizes to a
133
+ * literal 0 — adopting that field would print "已使用0%" under a half-empty bar.
134
+ *
135
+ * @param remain - the package's remaining credit.
136
+ * @param size - the package's granted total; a non-positive size has no ratio.
137
+ * @returns the used percentage as a bare number string, or undefined when there is no ratio to state.
138
+ */
139
+ function usedPercentText(remain, size) {
140
+ const percent = clampPercent(remain, size);
141
+ return percent === void 0 ? void 0 : String(Math.round(100 - percent));
142
+ }
143
+ /** Parse an upstream expiry string ("YYYY-MM-DD HH:mm:ss") into a timestamp; undefined when unparseable. */
144
+ function parseExpiry(value) {
145
+ if (value === void 0) return void 0;
146
+ const parsed = Date.parse(value);
147
+ return Number.isNaN(parsed) ? void 0 : parsed;
148
+ }
149
+ /** Whether a group is spent (remain 0) and NOT unlimited. */
150
+ function isSpent(group) {
151
+ return !group.unlimited && group.remain <= 0;
152
+ }
153
+ /** Whether a spent group's expiry date has already passed (undated → false: no proof of expiry). */
154
+ function isExpired(group, now) {
155
+ if (!isSpent(group)) return false;
156
+ const expiry = parseExpiry(group.packageEndTime);
157
+ return expiry !== void 0 && expiry < now;
158
+ }
159
+ /**
160
+ * SIDEBAR overview rule (the user's spec, restored after it was wrongly
161
+ * applied to the panel):
162
+ *
163
+ * - A group with credit LEFT (remain > 0, or unlimited) renders.
164
+ * - A SPENT group (remain 0) renders ONLY when EVERY group is spent AND it
165
+ * is not expired — the account's standing quota whose emptiness is itself
166
+ * the news. When anything still has credit, spent rows are noise.
167
+ * - An EXPIRED group (spent and its expiry date has passed) renders
168
+ * NOWHERE, whatever the rest of the account looks like.
169
+ *
170
+ * Applied AFTER the merge so the sums are settled before the test.
171
+ * "Now" is injectable for tests.
172
+ *
173
+ * @param groups - merged groups, in first-seen order.
174
+ * @param now - current timestamp (defaults to Date.now()).
175
+ * @returns the groups to display, in first-seen order.
176
+ */
177
+ function visibleQuotaGroups(groups, now = Date.now()) {
178
+ const hasCredit = groups.some((group) => group.unlimited || group.remain > 0);
179
+ return groups.filter((group) => {
180
+ if (group.unlimited || group.remain > 0) return true;
181
+ return !hasCredit && !isExpired(group, now);
182
+ });
183
+ }
184
+ /**
185
+ * PANEL detail-table ordering (the user's spec): every row renders — the
186
+ * panel is the itemised ledger — but spent-yet-still-active rows sink to the
187
+ * BOTTOM (lowest priority), and expired rows are dropped entirely. Rows keep
188
+ * their first-seen order within each band.
189
+ *
190
+ * @param rows - per-package rows, unmerged, in first-seen order.
191
+ * @param now - current timestamp (defaults to Date.now()).
192
+ */
193
+ function sortPackageRows(rows, now = Date.now()) {
194
+ const live = [];
195
+ const spent = [];
196
+ for (const row of rows) {
197
+ const unlimited = row.unlimited === true;
198
+ const expiry = parseExpiry(row.packageEndTime);
199
+ if (!unlimited && row.remain <= 0 && expiry !== void 0 && expiry < now) continue;
200
+ if (unlimited || row.remain > 0) live.push(row);
201
+ else spent.push(row);
202
+ }
203
+ return [...live, ...spent];
204
+ }
205
+ //#endregion
43
206
  //#region src/client/status-document.ts
44
207
  /**
45
208
  * Whether a parsed status response really is a status document.
@@ -1235,9 +1398,6 @@ window.__ModuleLoader__.load({
1235
1398
  function formatNumber(value) {
1236
1399
  return new Intl.NumberFormat(void 0).format(value);
1237
1400
  }
1238
- function formatPercent(value) {
1239
- return new Intl.NumberFormat(void 0, { maximumFractionDigits: 1 }).format(value);
1240
- }
1241
1401
  function formatTime(ms) {
1242
1402
  return new Intl.DateTimeFormat(void 0, {
1243
1403
  dateStyle: "medium",
@@ -1301,13 +1461,14 @@ window.__ModuleLoader__.load({
1301
1461
  size: formatNumber(size)
1302
1462
  }) : t("creditPackageUnknownSize", { remain: formatNumber(remain) });
1303
1463
  const percent = sizeKnown ? remain / size * 100 : void 0;
1304
- const display = percent === void 0 ? t("percentUnknown") : t("percentRemaining", { percent: formatPercent(percent) });
1464
+ const display = percent === void 0 ? t("percentUnknown") : t("percentRemaining", { percent: formatPercentText(percent) });
1465
+ const usedPercent = usedPercentText(remain, size);
1305
1466
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1306
1467
  style: quotaGroupStyle,
1307
1468
  children: [
1308
1469
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1309
1470
  style: quotaLabelStyle,
1310
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: display })]
1471
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [label, usedPercent === void 0 ? null : t("quotaPackageUsed", { percent: usedPercent })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: display })]
1311
1472
  }),
1312
1473
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1313
1474
  style: progressTrackStyle,
@@ -1760,17 +1921,17 @@ window.__ModuleLoader__.load({
1760
1921
  height: 6,
1761
1922
  borderRadius: "50%",
1762
1923
  flexShrink: 0,
1763
- background: log.status === "claimed" ? "var(--dsw-alias-status-success, #52c41a)" : log.status === "already-claimed" ? "var(--dsw-alias-status-info, #1890ff)" : log.status === "no-campaign" ? "var(--dsw-alias-label-tertiary, #999)" : "var(--dsw-alias-status-error, #f5222d)"
1764
- } }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: log.status === "claimed" ? t("autoCheckInStatusClaimed", { amount: log.amount ?? 100 }) : log.status === "already-claimed" ? t("autoCheckInStatusAlready") : log.status === "no-campaign" ? t("autoCheckInStatusNoCampaign") : t("autoCheckInStatusError", { message: log.message ?? "" }) })]
1924
+ background: log.status === "claimed" ? "var(--dsw-alias-status-success, #52c41a)" : log.status === "already-claimed" ? "var(--dsw-alias-status-info, #1890ff)" : log.status === "no-campaign" || log.status === "before-window" ? "var(--dsw-alias-label-tertiary, #999)" : "var(--dsw-alias-status-error, #f5222d)"
1925
+ } }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: log.status === "claimed" ? t("autoCheckInStatusClaimed", { amount: log.amount ?? 100 }) : log.status === "already-claimed" ? t("autoCheckInStatusAlready") : log.status === "no-campaign" ? t("autoCheckInStatusNoCampaign") : log.status === "before-window" ? t("autoCheckInStatusBeforeWindow") : t("autoCheckInStatusError", { message: log.message ?? "" }) })]
1765
1926
  }),
1766
1927
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1767
1928
  style: {
1768
1929
  flex: 1,
1769
1930
  textAlign: "right",
1770
1931
  fontWeight: 600,
1771
- color: log.amount ? "var(--dsw-alias-brand-primary)" : "inherit"
1932
+ color: log.status === "claimed" ? "var(--dsw-alias-brand-primary)" : "inherit"
1772
1933
  },
1773
- children: log.amount ? `+${log.amount}` : "-"
1934
+ children: log.status === "claimed" ? `+${log.amount ?? 100}` : "-"
1774
1935
  })
1775
1936
  ]
1776
1937
  }, log.id))]
@@ -1950,6 +2111,7 @@ window.__ModuleLoader__.load({
1950
2111
  if (result.state === "claimed") setCheckInNotice(t("autoCheckInStatusClaimed", { amount: result.amount ?? 100 }));
1951
2112
  else if (result.state === "already-claimed") setCheckInNotice(t("autoCheckInStatusAlready"));
1952
2113
  else if (result.state === "no-campaign") setCheckInNotice(t("autoCheckInStatusNoCampaign"));
2114
+ else if (result.state === "before-window") setCheckInNotice(t("autoCheckInStatusBeforeWindow"));
1953
2115
  else if (result.reason) setCheckInNotice(t("autoCheckInStatusError", { message: result.reason }));
1954
2116
  } catch (error) {
1955
2117
  if (mounted.current && controller.signal.aborted !== true) setCheckInNotice(error instanceof Error ? error.message : t("requestFailed"));
@@ -2449,7 +2611,7 @@ window.__ModuleLoader__.load({
2449
2611
  children: t("creditsHeading")
2450
2612
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2451
2613
  style: bodyStyle,
2452
- children: status.credits.unlimited === true ? t("creditsTotalUnlimited") : t("creditsUsed", { percent: formatPercent(status.credits.total) })
2614
+ children: status.credits.unlimited === true ? t("creditsTotalUnlimited") : t("creditsUsed", { percent: formatPercentText(status.credits.total) })
2453
2615
  })]
2454
2616
  }), status.credits.cycleResetTime === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2455
2617
  style: descriptionStyle,
@@ -3079,124 +3241,6 @@ window.__ModuleLoader__.load({
3079
3241
  }
3080
3242
  };
3081
3243
  //#endregion
3082
- //#region src/client/quota-merge.ts
3083
- /**
3084
- * Group credit accounts by (packageName, packageEndTime) and sum each group.
3085
- *
3086
- * A missing figure counts as 0 (the user's ruling): a package whose total the
3087
- * upstream did not report contributes nothing to the group's `size` rather
3088
- * than poisoning the bar into "unknown". A group whose summed `size` is still
3089
- * 0 keeps an honest "unknown total" rendering in the card — the merge never
3090
- * invents a denominator. `unlimited` is sticky: one unlimited member makes the
3091
- * whole group unlimited, and its sums are not displayed as a quota.
3092
- *
3093
- * MERGE KEY IS THE NAME ALONE (the user's correction): same-named packages
3094
- * whose expiries differ by seconds (stacked purchase batches) must still be
3095
- * one overview row — keying on the expiry exploded 28 same-named packages
3096
- * back into 28 separate bars the moment the host started reporting real
3097
- * dates. The expiry travels on the group (earliest of the members) for the
3098
- * sort/visibility rules; the itemised per-expiry breakdown lives in the
3099
- * dashboard's table.
3100
- *
3101
- * @param accounts - the status document's per-package credit entries.
3102
- * @returns one group per distinct package NAME, in first-seen order.
3103
- */
3104
- function mergeCreditAccounts(accounts) {
3105
- const groups = /* @__PURE__ */ new Map();
3106
- for (const account of accounts) {
3107
- const key = account.packageName;
3108
- const existing = groups.get(key);
3109
- if (existing === void 0) {
3110
- groups.set(key, {
3111
- packageName: account.packageName,
3112
- packageEndTime: account.packageEndTime,
3113
- remain: account.remain,
3114
- size: account.size,
3115
- unlimited: account.unlimited === true
3116
- });
3117
- continue;
3118
- }
3119
- existing.remain += account.remain;
3120
- existing.size += account.size;
3121
- existing.unlimited = existing.unlimited || account.unlimited === true;
3122
- if (existing.packageEndTime !== void 0 && account.packageEndTime !== void 0) {
3123
- const a = Date.parse(existing.packageEndTime);
3124
- const b = Date.parse(account.packageEndTime);
3125
- if (!Number.isNaN(a) && !Number.isNaN(b) && b < a) existing.packageEndTime = account.packageEndTime;
3126
- } else existing.packageEndTime = existing.packageEndTime ?? account.packageEndTime;
3127
- }
3128
- return [...groups.values()];
3129
- }
3130
- /** Clamp helper shared by the card's percent math. */
3131
- function clampPercent(remain, size) {
3132
- if (!(size > 0)) return void 0;
3133
- const percent = remain / size * 100;
3134
- if (!Number.isFinite(percent)) return void 0;
3135
- return Math.min(100, Math.max(0, percent));
3136
- }
3137
- /** Parse an upstream expiry string ("YYYY-MM-DD HH:mm:ss") into a timestamp; undefined when unparseable. */
3138
- function parseExpiry(value) {
3139
- if (value === void 0) return void 0;
3140
- const parsed = Date.parse(value);
3141
- return Number.isNaN(parsed) ? void 0 : parsed;
3142
- }
3143
- /** Whether a group is spent (remain 0) and NOT unlimited. */
3144
- function isSpent(group) {
3145
- return !group.unlimited && group.remain <= 0;
3146
- }
3147
- /** Whether a spent group's expiry date has already passed (undated → false: no proof of expiry). */
3148
- function isExpired(group, now) {
3149
- if (!isSpent(group)) return false;
3150
- const expiry = parseExpiry(group.packageEndTime);
3151
- return expiry !== void 0 && expiry < now;
3152
- }
3153
- /**
3154
- * SIDEBAR overview rule (the user's spec, restored after it was wrongly
3155
- * applied to the panel):
3156
- *
3157
- * - A group with credit LEFT (remain > 0, or unlimited) renders.
3158
- * - A SPENT group (remain 0) renders ONLY when EVERY group is spent AND it
3159
- * is not expired — the account's standing quota whose emptiness is itself
3160
- * the news. When anything still has credit, spent rows are noise.
3161
- * - An EXPIRED group (spent and its expiry date has passed) renders
3162
- * NOWHERE, whatever the rest of the account looks like.
3163
- *
3164
- * Applied AFTER the merge so the sums are settled before the test.
3165
- * "Now" is injectable for tests.
3166
- *
3167
- * @param groups - merged groups, in first-seen order.
3168
- * @param now - current timestamp (defaults to Date.now()).
3169
- * @returns the groups to display, in first-seen order.
3170
- */
3171
- function visibleQuotaGroups(groups, now = Date.now()) {
3172
- const hasCredit = groups.some((group) => group.unlimited || group.remain > 0);
3173
- return groups.filter((group) => {
3174
- if (group.unlimited || group.remain > 0) return true;
3175
- return !hasCredit && !isExpired(group, now);
3176
- });
3177
- }
3178
- /**
3179
- * PANEL detail-table ordering (the user's spec): every row renders — the
3180
- * panel is the itemised ledger — but spent-yet-still-active rows sink to the
3181
- * BOTTOM (lowest priority), and expired rows are dropped entirely. Rows keep
3182
- * their first-seen order within each band.
3183
- *
3184
- * @param rows - per-package rows, unmerged, in first-seen order.
3185
- * @param now - current timestamp (defaults to Date.now()).
3186
- */
3187
- function sortPackageRows(rows, now = Date.now()) {
3188
- const live = [];
3189
- const spent = [];
3190
- for (const row of rows) {
3191
- const unlimited = row.unlimited === true;
3192
- const expiry = parseExpiry(row.packageEndTime);
3193
- if (!unlimited && row.remain <= 0 && expiry !== void 0 && expiry < now) continue;
3194
- if (unlimited || row.remain > 0) live.push(row);
3195
- else spent.push(row);
3196
- }
3197
- return [...live, ...spent];
3198
- }
3199
- //#endregion
3200
3244
  //#region src/client/SidebarQuotaCard.tsx
3201
3245
  /**
3202
3246
  * The sidebar footer quota card + the center-column dashboard it opens.
@@ -3237,7 +3281,8 @@ window.__ModuleLoader__.load({
3237
3281
  bars.push({
3238
3282
  label: group.packageName,
3239
3283
  detail,
3240
- percent: percent === void 0 ? void 0 : `${Math.round(percent)}%`,
3284
+ percent: percent === void 0 ? void 0 : `${formatPercentText(percent)}%`,
3285
+ usedPercent: usedPercentText(group.remain, group.size),
3241
3286
  barPercent: percent === void 0 ? 0 : Math.max(2, percent),
3242
3287
  warn: !group.unlimited && percent !== void 0 && percent < 20,
3243
3288
  packageEndTime: group.packageEndTime
@@ -3344,7 +3389,7 @@ window.__ModuleLoader__.load({
3344
3389
  const fetchedAt = quotaStatusFetchedAt(variantId);
3345
3390
  const title = [
3346
3391
  t(nameKey),
3347
- ...bars.map((bar) => `${bar.label} ${bar.detail}${bar.percent === void 0 ? "" : ` (${bar.percent})`}`),
3392
+ ...bars.map((bar) => `${bar.label} ${bar.detail}${bar.percent === void 0 ? "" : ` (${bar.percent})`}${bar.usedPercent === void 0 ? "" : ` ${t("quotaPackageUsed", { percent: bar.usedPercent })}`}`),
3348
3393
  fetchedAt !== void 0 ? `${t("quotaUpdated")} ${timeText(fetchedAt)}` : ""
3349
3394
  ].filter((part) => part !== "").join(" · ");
3350
3395
  if (!wide) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -3412,10 +3457,10 @@ window.__ModuleLoader__.load({
3412
3457
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3413
3458
  className: "qdp-footHead",
3414
3459
  children: [
3415
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3460
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3416
3461
  className: "qdp-footLabel",
3417
3462
  title: bar.packageEndTime !== void 0 ? `${t("quotaExpires")} ${bar.packageEndTime}` : t("quotaNoExpiry"),
3418
- children: bar.label
3463
+ children: [bar.label, bar.usedPercent === void 0 ? null : t("quotaPackageUsed", { percent: bar.usedPercent })]
3419
3464
  }),
3420
3465
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3421
3466
  className: "qdp-footAmount",
@@ -3739,7 +3784,9 @@ window.__ModuleLoader__.load({
3739
3784
  .qdp-footBar{display:block;background:var(--dsw-alias-bg-layer-2);border-radius:999px;height:5px;overflow:hidden}
3740
3785
  .qdp-footFill{display:block;background:var(--dsw-alias-brand-primary);border-radius:999px;height:100%;transition:width .3s ease}
3741
3786
  .qdp-footFillWarn{background:var(--dsw-alias-state-error-primary)}
3742
- .qdp-footPct{flex:none;width:34px;color:var(--dsw-alias-label-secondary);text-align:right;font-size:11px;line-height:16px;font-variant-numeric:tabular-nums}
3787
+ /* 38px, not 34px: two-decimal copy is wider, and "99.61%" measures 36px in this
3788
+ font. nowrap keeps a long value from wrapping the row into a second line. */
3789
+ .qdp-footPct{flex:none;width:38px;color:var(--dsw-alias-label-secondary);text-align:right;font-size:11px;line-height:16px;font-variant-numeric:tabular-nums;white-space:nowrap}
3743
3790
 
3744
3791
  /* The 56px rail: one icon button on the shell's own rail geometry (36px cell),
3745
3792
  so the collapsed column keeps a single glyph like its siblings. */
@@ -3968,6 +4015,7 @@ window.__ModuleLoader__.load({
3968
4015
  autoCheckInStatusClaimed: "Checked in today (+{amount} Credits)",
3969
4016
  autoCheckInStatusAlready: "Already checked in today",
3970
4017
  autoCheckInStatusNoCampaign: "No active benefit campaign today",
4018
+ autoCheckInStatusBeforeWindow: "Benefit window not open yet",
3971
4019
  autoCheckInStatusError: "Auto check-in error: {message}",
3972
4020
  quotaSignInRequired: "Save a PAT for this variant first to enable this card.",
3973
4021
  quotaPollLabel: "Refresh interval",
@@ -3999,6 +4047,7 @@ window.__ModuleLoader__.load({
3999
4047
  quotaTotalRemain: "Remaining",
4000
4048
  quotaTotalShare: "{percent} of this cycle’s granted total ({remain} / {size})",
4001
4049
  quotaCycleUsed: "Used this cycle: {percent}%",
4050
+ quotaPackageUsed: "(used {percent}%)",
4002
4051
  quotaColPackage: "Package",
4003
4052
  quotaColRemain: "Remaining / Total",
4004
4053
  quotaColExpiry: "Expires"
@@ -4139,6 +4188,7 @@ window.__ModuleLoader__.load({
4139
4188
  autoCheckInStatusClaimed: "今日已自动签到(+{amount} Credits)",
4140
4189
  autoCheckInStatusAlready: "今日已完成签到",
4141
4190
  autoCheckInStatusNoCampaign: "今日无可用签到福利活动",
4191
+ autoCheckInStatusBeforeWindow: "签到活动尚未开启",
4142
4192
  autoCheckInStatusError: "自动签到出错:{message}",
4143
4193
  quotaSignInRequired: "请先为该变体保存 PAT,再开启此开关。",
4144
4194
  quotaPollLabel: "刷新间隔",
@@ -4170,6 +4220,7 @@ window.__ModuleLoader__.load({
4170
4220
  quotaTotalRemain: "剩余额度",
4171
4221
  quotaTotalShare: "占本轮总额度 {percent}({remain} / {size})",
4172
4222
  quotaCycleUsed: "本轮已用 {percent}%",
4223
+ quotaPackageUsed: "(已使用{percent}%)",
4173
4224
  quotaColPackage: "资源包",
4174
4225
  quotaColRemain: "剩余 / 总量",
4175
4226
  quotaColExpiry: "到期时间"
package/lib/index.d.ts CHANGED
@@ -269,7 +269,7 @@ type QoderWebStatus = {
269
269
  checkIn?: {
270
270
  lastDate: string;
271
271
  lastAt: number;
272
- status: 'claimed' | 'already-claimed' | 'no-campaign' | 'error';
272
+ status: 'claimed' | 'already-claimed' | 'before-window' | 'no-campaign' | 'error';
273
273
  amount?: number | undefined;
274
274
  message?: string | undefined;
275
275
  /**
@@ -284,7 +284,7 @@ type QoderWebStatus = {
284
284
  id: string;
285
285
  date: string;
286
286
  timestamp: number;
287
- status: 'claimed' | 'already-claimed' | 'no-campaign' | 'error';
287
+ status: 'claimed' | 'already-claimed' | 'before-window' | 'no-campaign' | 'error';
288
288
  amount?: number | undefined;
289
289
  campaignKey?: string | undefined;
290
290
  message?: string | undefined;
@@ -747,7 +747,7 @@ interface QoderCheckInResult {
747
747
  variantId: string;
748
748
  date: string;
749
749
  timestamp: number;
750
- status: 'claimed' | 'already-claimed' | 'no-campaign' | 'error';
750
+ status: 'claimed' | 'already-claimed' | 'before-window' | 'no-campaign' | 'error';
751
751
  amount?: number | undefined;
752
752
  campaignKey?: string | undefined;
753
753
  message?: string | undefined;
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as QoderCatalog, B as QODER_PAT_ENV_GLOBAL, C as getMachineId, D as QoderCatalogStore, E as randomSentinel, F as QODER_PROBE_PATH, G as QODER_DATA_DIR_ENV, H as patTail, I as QODER_SETTINGS_FACE_PATH, J as qoderPluginDataDir, K as QODER_DATA_DIR_NAME, L as QODER_STATUS_PATH, M as QODER_GLOBAL_AUTH_PATH, N as QODER_GLOBAL_PROBE_PATH, O as qoderCatalogPath, P as QODER_GLOBAL_STATUS_PATH, R as QODER_AUTH_FILENAME, S as utc8WallClock, T as probeModel, U as qoderCredentialIdentity, V as QoderCredentialStore, W as qoderOwnAuthPath, Y as qoderStateDir, _ as modelInfoOf, a as QODER_HOST_HEARTBEAT_FILENAME, b as createQoderTransport, c as processStartTimeMs, d as writeHostHeartbeat, f as QODER_CONNECT_VERSION, g as kindFromQoderFailure, h as classifyUpstreamError, i as variantFor, j as QODER_AUTH_PATH, k as FALLBACK_QODER_MODELS, l as qoderHostHeartbeatPath, m as QoderUpstreamClient, n as GLOBAL_VARIANT, o as clearHostHeartbeat, p as KIND_STATUS, q as qoderMachineIdPath, r as QODER_VARIANTS, s as isHeartbeatProcessAlive, t as CHINA_VARIANT, u as readHostHeartbeat, v as normalizeCredits, w as PROBE_EFFORT_CANDIDATES, x as formatUtc8Date, y as validateApiKey, z as QODER_PAT_ENV_CN } from "./variants-UX2MxZaz.js";
1
+ import { A as QoderCatalog, B as QODER_PAT_ENV_GLOBAL, C as getMachineId, D as QoderCatalogStore, E as randomSentinel, F as QODER_PROBE_PATH, G as QODER_DATA_DIR_ENV, H as patTail, I as QODER_SETTINGS_FACE_PATH, J as qoderPluginDataDir, K as QODER_DATA_DIR_NAME, L as QODER_STATUS_PATH, M as QODER_GLOBAL_AUTH_PATH, N as QODER_GLOBAL_PROBE_PATH, O as qoderCatalogPath, P as QODER_GLOBAL_STATUS_PATH, R as QODER_AUTH_FILENAME, S as utc8WallClock, T as probeModel, U as qoderCredentialIdentity, V as QoderCredentialStore, W as qoderOwnAuthPath, Y as qoderStateDir, _ as modelInfoOf, a as QODER_HOST_HEARTBEAT_FILENAME, b as createQoderTransport, c as processStartTimeMs, d as writeHostHeartbeat, f as QODER_CONNECT_VERSION, g as kindFromQoderFailure, h as classifyUpstreamError, i as variantFor, j as QODER_AUTH_PATH, k as FALLBACK_QODER_MODELS, l as qoderHostHeartbeatPath, m as QoderUpstreamClient, n as GLOBAL_VARIANT, o as clearHostHeartbeat, p as KIND_STATUS, q as qoderMachineIdPath, r as QODER_VARIANTS, s as isHeartbeatProcessAlive, t as CHINA_VARIANT, u as readHostHeartbeat, v as normalizeCredits, w as PROBE_EFFORT_CANDIDATES, x as formatUtc8Date, y as validateApiKey, z as QODER_PAT_ENV_CN } from "./variants-OJKPI_c7.js";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
4
4
  import { dirname, join, resolve } from "node:path";
@@ -2849,7 +2849,7 @@ var QoderCheckInService = class {
2849
2849
  variantId: this.variantId,
2850
2850
  date: today,
2851
2851
  timestamp: nowMs,
2852
- status: "no-campaign",
2852
+ status: "before-window",
2853
2853
  campaignKey: benefitCampaign.campaignKey,
2854
2854
  message: campaignWindow.kind === "not-open" ? "Today's benefit window has not opened yet" : `Upstream is still serving the benefit window of ${campaignWindow.date}`
2855
2855
  };
@@ -4662,7 +4662,7 @@ const DEFAULT_MAX_TOKENS = 32768;
4662
4662
  const DEFAULT_CONTEXT_WINDOW = 18e4;
4663
4663
  //#endregion
4664
4664
  //#region src/version.ts
4665
- const QODER_CONNECT_VERSION = "0.2.3-alpha.2";
4665
+ const QODER_CONNECT_VERSION = "0.2.4";
4666
4666
  //#endregion
4667
4667
  //#region src/host-heartbeat.ts
4668
4668
  /**
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-qoder-connect",
3
3
  "displayName": "DSH Qoder Connect",
4
4
  "description": "将 Qoder(国内版 / 国际版)的模型接入 DeepSeek Harness — bring Qoder (CN & international) models into DeepSeek Harness.",
5
- "version": "0.2.3-alpha.2",
5
+ "version": "0.2.4",
6
6
  "author": "corrinehu",
7
7
  "keywords": [
8
8
  "dsh-plugin",