dsh-connect-trae 2.10.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.d.ts CHANGED
@@ -1689,15 +1689,6 @@ interface TraeWebAccount {
1689
1689
  tokenExpiresAtMs: number;
1690
1690
  selected: boolean;
1691
1691
  }
1692
- /**
1693
- * Another account on this machine that still has credit, surfaced only while
1694
- * the BOUND account is exhausted (issue #25).
1695
- *
1696
- * The account switch is persisted, so a user whose bound account ran out sees
1697
- * a permanently zeroed card with no explanation. This carries just enough to
1698
- * name the alternative in the UI — identity plus the two CN credit buckets —
1699
- * and is only ever produced by a read-only upstream call.
1700
- */
1701
1692
  interface TraeWebCreditAlternative {
1702
1693
  id: string;
1703
1694
  accountName: string;
@@ -2023,19 +2014,20 @@ interface Config {
2023
2014
  */
2024
2015
  accounts?: Partial<Record<TraeRegion, string>>;
2025
2016
  /**
2026
- * Show the CN account's general credit balance as a permanent line at the
2027
- * bottom of the DSH sidebar (issue #26, contributed by JiewiW).
2017
+ * Show the credit readout in the composer tool row: `Trae CN · <n>`, rendered
2018
+ * ONLY while the session's selected model belongs to this plugin, clickable
2019
+ * for the account/balances panel.
2028
2020
  *
2029
- * ON by default and opt-out since 2.9.1. It was opt-in first, on the theory
2030
- * that a user who never asked for a periodic usage read should not get one —
2031
- * but the switch lived only in the plugin card, and the card is not reachable
2032
- * in every host: a market plugin can own the Plugins tab and never dispatch
2033
- * `plugins.bundle.config`, so the line was permanently off with no discoverable
2034
- * way to turn it on. The line now carries its own on/off control in the
2035
- * sidebar, which is reachable exactly where the feature renders.
2021
+ * This field first gated the sidebar credit line (issue #26, contributed by
2022
+ * JiewiW); when that line was removed in 2.13.0 the composer readout replaced
2023
+ * it and rendered unconditionally; 2.13.3 restores the switch, because the
2024
+ * user went looking for it and a readout inside the SHARED composer row
2025
+ * deserves the same opt-in the sidebar line had. The field is REUSED so an
2026
+ * existing `true` keeps the readout on across the upgrade.
2036
2027
  *
2037
- * Declared volatile alongside `accounts`/`regions` so the settings form can
2038
- * both read and write it.
2028
+ * OFF by default. The card's checkbox is the only writer, so exactly one path
2029
+ * can change it. Declared volatile alongside `accounts`/`regions` so the
2030
+ * settings form can both read and write it.
2039
2031
  */
2040
2032
  showPointsInMainUi?: boolean;
2041
2033
  /**
package/lib/index.js CHANGED
@@ -1266,13 +1266,16 @@ var TraeCredentialStore = class {
1266
1266
  */
1267
1267
  async readAll() {
1268
1268
  const { credentials: desktop } = await this.readDesktopAll();
1269
- const credentials = [...desktop.filter((credential) => this.matchesRegion(credential))];
1270
- for (const own of await this.readOwns()) {
1271
- if (!this.matchesRegion(own)) continue;
1272
- if (credentials.some((credential) => traeAccountId(credential) === traeAccountId(own))) continue;
1273
- credentials.push(own);
1274
- }
1275
- return credentials;
1269
+ const byAccount = /* @__PURE__ */ new Map();
1270
+ const consider = (credential) => {
1271
+ if (!this.matchesRegion(credential)) return;
1272
+ const id = traeAccountId(credential);
1273
+ const incumbent = byAccount.get(id);
1274
+ if (incumbent === void 0 || credential.expiresAtMs > incumbent.expiresAtMs) byAccount.set(id, credential);
1275
+ };
1276
+ for (const credential of desktop) consider(credential);
1277
+ for (const own of await this.readOwns()) consider(own);
1278
+ return [...byAccount.values()];
1276
1279
  }
1277
1280
  /**
1278
1281
  * Which paths were tried and why each one failed. Read-only and token-free:
@@ -3939,6 +3942,17 @@ const TRAE_MODELS_TEST_PATH = "/plugins/dsh-connect-trae/models/test";
3939
3942
  /** Plugin-owned local account rescan endpoint. */
3940
3943
  const TRAE_ACCOUNTS_REFRESH_PATH = "/plugins/dsh-connect-trae/accounts/refresh";
3941
3944
  /**
3945
+ * General balance of EVERY account in a region, for the composer panel's table.
3946
+ *
3947
+ * Separate from the usage route on purpose: the usage document backs the
3948
+ * 5-minute readout, and multiplying its upstream reads by the account count
3949
+ * would charge a healthy account for something it never looks at — the same
3950
+ * principle that keeps `creditAlternatives` firing only when a balance hits
3951
+ * zero. This one is paid when the panel is actually opened, which is the only
3952
+ * place the table is shown.
3953
+ */
3954
+ const TRAE_ACCOUNT_CREDITS_PATH = "/plugins/dsh-connect-trae/account-credits";
3955
+ /**
3942
3956
  * Plugin-owned daily check-in claim endpoint. POST, loopback-only, and the only
3943
3957
  * route in this plugin that changes upstream account state.
3944
3958
  */
@@ -4297,6 +4311,37 @@ async function creditAlternatives(deps, region, accounts) {
4297
4311
  * The region a request addresses, or a 400 answer. Absent parameter means the
4298
4312
  * domestic tab; an unknown value is refused rather than guessed.
4299
4313
  */
4314
+ /**
4315
+ * The general balance of every account in one region.
4316
+ *
4317
+ * One upstream read per account — the same call `creditsOfAccount` already
4318
+ * makes for the exhausted-balance alternatives, widened to every account and to
4319
+ * zero balances. Costs nothing until the composer panel is opened, so the
4320
+ * 5-minute readout never pays for it (see {@link TRAE_ACCOUNT_CREDITS_PATH}).
4321
+ *
4322
+ * An account whose read fails or is unavailable still appears, without a
4323
+ * figure: the table lists how many accounts exist even when one cannot be
4324
+ * quoted, rather than dropping it and making the switch unavailable.
4325
+ */
4326
+ async function traeAccountCredits(deps, region) {
4327
+ const accounts = (await deps.store(region).accounts()).filter((account) => account.region === region);
4328
+ if (accounts.length === 0) return [];
4329
+ return Promise.all(accounts.map(async (account) => {
4330
+ let generalAvailable;
4331
+ if (account.selected) try {
4332
+ generalAvailable = toCredits(await deps.client(region).snapshot()).generalAvailable;
4333
+ } catch {}
4334
+ else if (deps.creditsOfAccount !== void 0) try {
4335
+ generalAvailable = (await deps.creditsOfAccount(region, account.id))?.generalAvailable;
4336
+ } catch {}
4337
+ return {
4338
+ id: account.id,
4339
+ accountName: account.accountName,
4340
+ selected: account.selected,
4341
+ ...generalAvailable === void 0 ? {} : { generalAvailable }
4342
+ };
4343
+ }));
4344
+ }
4300
4345
  function requestRegion(req, res) {
4301
4346
  const region = regionOfTraeStatusUrl(req.url ?? "/");
4302
4347
  if (region === void 0) {
@@ -4329,6 +4374,27 @@ function registerTraeUsageRoute(ctx, deps) {
4329
4374
  }
4330
4375
  }
4331
4376
  });
4377
+ /**
4378
+ * General balance of every account in the region — read only when the
4379
+ * composer panel is opened. One upstream read per account, paid once per
4380
+ * open rather than on the 5-minute cycle (see
4381
+ * {@link TRAE_ACCOUNT_CREDITS_PATH}).
4382
+ */
4383
+ const disposeAccountCredits = ctx.webServer.register({
4384
+ kind: "exact",
4385
+ path: TRAE_ACCOUNT_CREDITS_PATH,
4386
+ handler: async (req, res) => {
4387
+ if (req.method !== "GET") return json(res, 405, { error: "method not allowed" });
4388
+ if (!loopbackOrigin(req)) return json(res, 403, { error: "origin-not-trusted" });
4389
+ const region = requestRegion(req, res);
4390
+ if (region === void 0) return;
4391
+ try {
4392
+ json(res, 200, { accounts: await traeAccountCredits(deps, region) });
4393
+ } catch (error) {
4394
+ json(res, 500, { error: safeMessage(error) });
4395
+ }
4396
+ }
4397
+ });
4332
4398
  const disposeAccounts = ctx.webServer.register({
4333
4399
  kind: "exact",
4334
4400
  path: TRAE_ACCOUNTS_REFRESH_PATH,
@@ -4462,6 +4528,7 @@ function registerTraeUsageRoute(ctx, deps) {
4462
4528
  disposeRefresh();
4463
4529
  disposeModelsTest();
4464
4530
  disposeAccounts();
4531
+ disposeAccountCredits();
4465
4532
  disposeUsage();
4466
4533
  };
4467
4534
  }, "dsh-connect-trae: Web usage route");
@@ -4769,7 +4836,7 @@ const Config = z.object({
4769
4836
  "solo-sg"
4770
4837
  ]).default("auto").description("Trae edition hint")),
4771
4838
  accountId: z.string().description("Deprecated: pre-split account selector, attributed to its own region"),
4772
- showPointsInMainUi: asVolatile(z.boolean().default(true).description("在DSH主界面显示积分状态(可在侧边栏那一行上随时关闭)")),
4839
+ showPointsInMainUi: asVolatile(z.boolean().default(false).description("在输入框工具栏显示积分读数「Trae CN · <数值>」(仅当选中的模型属于本插件时出现,可点击查看账号与积分明细)")),
4773
4840
  accounts: asVolatile(accountSelectionConfig.description("Per-region account selections, keyed cn | ai")),
4774
4841
  regions: asVolatile(z.dict(regionStateConfig).default({}).description("Per-region model directory and selection, keyed cn | ai")),
4775
4842
  lastCatalog: z.array(modelConfig).description("Deprecated: pre-region-split CN model directory"),
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": "2.10.0",
5
+ "version": "3.0.0",
6
6
  "author": "dingminhua",
7
7
  "copyright": "Copyright (c) 2026 LaoDing",
8
8
  "license": "MIT",
@@ -109,6 +109,7 @@
109
109
  "@testing-library/react": "^16.3.3",
110
110
  "@types/node": "^22.20.0",
111
111
  "@types/react": "~18.3.31",
112
+ "@types/react-dom": "^18.3.7",
112
113
  "@types/semver": "^7.7.0",
113
114
  "jsdom": "^25.0.1",
114
115
  "react": "^18.3.1",