dsh-connect-trae 3.6.0 → 3.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.6.1 (2026-10-16)
4
+
5
+ > **「自动续期」不再是一句没有期限的承诺。** 卡片现在会告诉你**刷新令牌**何时到期 ——
6
+ > 在那之前访问令牌会自动续期,在那之后自动续期就彻底停了,必须重新登录一次 Trae。
7
+
8
+ ### 修复
9
+
10
+ - **刷新令牌的到期时间对用户完全不可见(issue #37 追问引出)**:
11
+ - **问题**:卡片一直只写「访问令牌 {时间} 过期(自动续期)」。这句话是对的,
12
+ 而且一直对 —— 直到**刷新令牌**到期的那个瞬间,自动续期就永久停止了。
13
+ 一个读了几个月「自动续期」的用户,没有任何理由预期这件事。
14
+ - **根因**:刷新令牌的到期时间**被解析了、也被存下来了,但从来没送到浏览器** ——
15
+ 状态接口里根本没有这个字段,所以卡片无从显示。
16
+ - **现在**:状态接口暴露刷新令牌到期时间,卡片**按账号**在临近到期时给出一行提示:
17
+ - 剩余 **30 天**以内 → 提醒(警告色),写明**到期时间**与**还剩几天**;
18
+ - **已到期** → 明确说明「自动续期已停止」,并给出恢复方式(重新登录一次 Trae);
19
+ - **还很久 / 没有该字段**(CLI 凭据本身不带)→ **不显示任何东西**。
20
+ 健康的令牌不该在每一行都占一行文字,伪造的期限比不显示更糟。
21
+
22
+ > 这条不是理论问题:我们在排查 `#38` 时**亲身遇到过** —— 某区域插件自存的凭据访问令牌已过期,
23
+ 刷新也返回 401,而卡片上一直写着「自动续期」。当时只能靠人肉发现。
24
+
25
+ ### 测试
26
+
27
+ - 新增 5 例覆盖三种状态与两个边界(健康不显示、临近显示截止日与剩余天数、已过期显示错误态、
28
+ 无该字段不伪造、最后一天仍算「未过期」)。
29
+ - **变异验证**:去掉 30 天提醒窗口 → 2 例变红;在缺少字段时伪造一个期限 → 1 例变红。
30
+ - 全仓 **660** 例测试通过,类型检查通过。
31
+
3
32
  ## 3.6.0 (2026-10-16)
4
33
 
5
34
  > **多个账号可以并存了。** 本版新增**账号导入 / 导出**:不再需要每次把某台机器的登录凭据
package/lib/client.js CHANGED
@@ -335,6 +335,13 @@ window.__ModuleLoader__.load({
335
335
  .dsm-trae-usage-account{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:14px;background:var(--dsw-alias-bg-layer-2,#24262c)}
336
336
  .dsm-trae-usage-account-copy{display:flex;flex-direction:column;gap:3px;min-width:0}
337
337
  .dsm-trae-usage-expiry{padding-left:19px;color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:12px;line-height:18px}
338
+ /* Refresh-token countdown (issue #37 follow-up). Sits under the access-token
339
+ line and aligns with it, so the two read as one stack: "token renews" then
340
+ "but this is when renewal itself ends". Only rendered when it is close or
341
+ already past, so a healthy account is unchanged. */
342
+ .dsm-trae-refresh-warn{padding-left:19px;font-size:12px;line-height:18px}
343
+ .dsm-trae-refresh-warn-soon{color:var(--dsw-alias-state-warn-primary,#b45309)}
344
+ .dsm-trae-refresh-warn-lapsed{color:var(--dsw-alias-state-error-primary,#dc2626)}
338
345
  .dsm-trae-account-picker{display:block}
339
346
  /* The 璁剧疆 block: label + hint on the left, control on the right, one save for
340
347
  the whole block. It replaces the bare checkbox rows that used to write on
@@ -1306,6 +1313,40 @@ window.__ModuleLoader__.load({
1306
1313
  minute: "2-digit"
1307
1314
  }).format(new Date(value));
1308
1315
  }
1316
+ /** How close a refresh token must be to lapsing before the card warns, in days. */
1317
+ const REFRESH_WARN_DAYS = 30;
1318
+ /**
1319
+ * The refresh-token warning for one account, or undefined when there is nothing
1320
+ * worth saying.
1321
+ *
1322
+ * The access token renews itself every few minutes, so its own expiry answers
1323
+ * "when does this need attention" with "never, until the refresh token lapses".
1324
+ * The refresh token is therefore the only honest countdown on this card, and it
1325
+ * has to be shown BEFORE it runs out: once it lapses the plugin cannot renew at
1326
+ * all and the only recovery is signing in to Trae again, which a user who has
1327
+ * been reading "auto-renewal" for months has no reason to expect.
1328
+ *
1329
+ * Three tones, matching the credit-expiry notice: `lapsed` (renewal already
1330
+ * dead), `soon` (inside the warning window), and nothing otherwise — a healthy
1331
+ * token must not add a line to every account row forever.
1332
+ */
1333
+ function refreshTokenWarning(expiresAtMs, nowMs, t, formatTime) {
1334
+ if (expiresAtMs === void 0) return void 0;
1335
+ const text = formatTime(expiresAtMs);
1336
+ if (expiresAtMs <= nowMs) return {
1337
+ tone: "lapsed",
1338
+ text: t("row.refreshTokenLapsed", { expiresAt: text })
1339
+ };
1340
+ const days = Math.ceil((expiresAtMs - nowMs) / 864e5);
1341
+ if (days > REFRESH_WARN_DAYS) return void 0;
1342
+ return {
1343
+ tone: "soon",
1344
+ text: t("row.refreshTokenExpiring", {
1345
+ expiresAt: text,
1346
+ days
1347
+ })
1348
+ };
1349
+ }
1309
1350
  function formatCapacity(value, unknown) {
1310
1351
  if (value === void 0) return unknown;
1311
1352
  if (value >= 1e6 && value % 1e6 === 0) return `${value / 1e6}M`;
@@ -2575,6 +2616,14 @@ window.__ModuleLoader__.load({
2575
2616
  sourceLabel(account.source)
2576
2617
  ]
2577
2618
  }),
2619
+ (() => {
2620
+ const warning = refreshTokenWarning(account.refreshExpiresAtMs, Date.now(), t, formatDateTime);
2621
+ return warning === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2622
+ className: `dsm-trae-refresh-warn dsm-trae-refresh-warn-${warning.tone}`,
2623
+ role: "status",
2624
+ children: warning.text
2625
+ });
2626
+ })(),
2578
2627
  error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2579
2628
  className: "dsm-trae-account-note",
2580
2629
  role: "alert",
@@ -3617,6 +3666,8 @@ window.__ModuleLoader__.load({
3617
3666
  "row.sourceCli": "Trae CLI",
3618
3667
  "row.signedIn": "Signed in: {accountName}",
3619
3668
  "row.tokenExpiry": "Access token expires {expiresAt} (auto-renewal)",
3669
+ "row.refreshTokenExpiring": "Sign in to Trae again by {expiresAt} ({days} day(s) left) — automatic renewal stops after that.",
3670
+ "row.refreshTokenLapsed": "Automatic renewal has stopped: the Trae sign-in expired {expiresAt}. Sign in to Trae again to restore it.",
3620
3671
  "row.requestFailed": "Request failed",
3621
3672
  "row.creditsAvailableLabel": "Available",
3622
3673
  "row.workCreditsLabel": "Work credits",
@@ -3783,6 +3834,8 @@ window.__ModuleLoader__.load({
3783
3834
  "row.sourceCli": "Trae CLI",
3784
3835
  "row.signedIn": "已登录:{accountName}",
3785
3836
  "row.tokenExpiry": "访问令牌 {expiresAt} 过期(自动续期)",
3837
+ "row.refreshTokenExpiring": "请在 {expiresAt} 前重新登录一次 Trae(还剩 {days} 天),此后将无法自动续期。",
3838
+ "row.refreshTokenLapsed": "自动续期已停止:Trae 登录凭据已于 {expiresAt} 过期。请重新登录一次 Trae 以恢复。",
3786
3839
  "row.requestFailed": "请求失败",
3787
3840
  "row.creditsAvailableLabel": "可用",
3788
3841
  "row.workCreditsLabel": "Work 积分",
package/lib/index.d.ts CHANGED
@@ -590,6 +590,13 @@ interface TraeAccountChoice {
590
590
  region: TraeRegion;
591
591
  source: 'desktop' | 'dsh' | 'cli';
592
592
  tokenExpiresAtMs: number;
593
+ /**
594
+ * When this account's refresh token lapses, when the credential carries one.
595
+ * The access token above renews itself automatically until this expires; see
596
+ * the mirror field on `TraeWebAccount` in status-paths.ts for why the card
597
+ * needs it.
598
+ */
599
+ refreshExpiresAtMs?: number;
593
600
  selected: boolean;
594
601
  }
595
602
  /**
@@ -2002,6 +2009,18 @@ interface TraeWebAccount {
2002
2009
  region: TraeRegion;
2003
2010
  source: 'desktop' | 'dsh' | 'cli';
2004
2011
  tokenExpiresAtMs: number;
2012
+ /**
2013
+ * When this account's REFRESH token expires, when the credential carries one.
2014
+ *
2015
+ * The access token above is renewed automatically — the plugin refreshes it
2016
+ * five minutes before it lapses — so by itself it answers "when does this
2017
+ * need attention", which is "never, while the refresh token lives". The
2018
+ * refresh token is the real horizon: once IT expires the automatic renewal
2019
+ * stops working and the user must sign in to Trae again. Before this field the
2020
+ * card could only say "auto-renewal", which stays true right up to the moment
2021
+ * it silently stops being true.
2022
+ */
2023
+ refreshExpiresAtMs?: number;
2005
2024
  selected: boolean;
2006
2025
  }
2007
2026
  /**
@@ -2228,6 +2247,13 @@ type TraeWebUsage = {
2228
2247
  accountId: string;
2229
2248
  accountName: string;
2230
2249
  tokenExpiresAtMs: number;
2250
+ /**
2251
+ * When the BOUND account's refresh token expires (absent when the credential
2252
+ * carries none). See {@link TraeWebAccount.refreshExpiresAtMs} — this is the
2253
+ * horizon that actually matters, because the access token above renews
2254
+ * itself until this one lapses.
2255
+ */
2256
+ refreshExpiresAtMs?: number;
2231
2257
  /** Which per-region model directory and selection this account owns. */
2232
2258
  region: TraeRegion;
2233
2259
  /** Whether this region's provider is currently offered to DSH. */
package/lib/index.js CHANGED
@@ -1867,6 +1867,7 @@ var TraeCredentialStore = class {
1867
1867
  region: regionOfCredential(live),
1868
1868
  source: live.source,
1869
1869
  tokenExpiresAtMs: live.expiresAtMs,
1870
+ ...live.refreshExpiresAtMs === void 0 ? {} : { refreshExpiresAtMs: live.refreshExpiresAtMs },
1870
1871
  selected: false
1871
1872
  };
1872
1873
  }
@@ -1881,6 +1882,7 @@ var TraeCredentialStore = class {
1881
1882
  region: regionOfCredential(credential),
1882
1883
  source: credential.source,
1883
1884
  tokenExpiresAtMs: credential.expiresAtMs,
1885
+ ...credential.refreshExpiresAtMs === void 0 ? {} : { refreshExpiresAtMs: credential.refreshExpiresAtMs },
1884
1886
  selected: selectedExists ? traeAccountId(credential) === this.accountId : credential === defaultSelected
1885
1887
  }));
1886
1888
  }
@@ -5484,6 +5486,7 @@ async function traeWebUsage(deps, region) {
5484
5486
  accountId: accounts.find((item) => item.selected)?.id ?? "",
5485
5487
  accountName: credential.accountName ?? credential.userId,
5486
5488
  tokenExpiresAtMs: credential.expiresAtMs,
5489
+ ...credential.refreshExpiresAtMs === void 0 ? {} : { refreshExpiresAtMs: credential.refreshExpiresAtMs },
5487
5490
  region,
5488
5491
  enabled,
5489
5492
  accounts,
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-connect-trae",
3
3
  "displayName": "DSH Connect Trae",
4
4
  "description": "把本机登录的 Trae 模型接入 DeepSeek Harness:国内版与国际版双供应商并行,提供用量/积分概览与每日签到领取。",
5
- "version": "3.6.0",
5
+ "version": "3.6.1",
6
6
  "author": "dingminhua",
7
7
  "copyright": "Copyright (c) 2026 LaoDing",
8
8
  "license": "MIT",