dsh-connect-trae 2.0.6 → 2.1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.1.0 (2026-09-24)
4
+
5
+ ### Features
6
+
7
+ - **新增「每日签到领取」**(对齐 `dsh-connect-workbuddy` 的签到能力):
8
+ - **交互**:国内版卡片在积分面板下方多一行签到:显示每日奖励(基础 + 额外,本机实测 `150 + 50`)与一个「立即领取」按钮;领取中显示「领取中…」,当日已领取显示「今日已领取」并禁用。国际版没有签到活动,因此**不显示该行**。
9
+ - **这是插件里唯一的写操作**,也是唯一会改变账号状态的调用(`POST /trae/api/v2/ug/checkin_credits/claim`);其余查询全部只读。README 顶部与特性列表都按此措辞改写,不再把整个用量面板称为「只读」。
10
+ - **根因级发现:缺 `x-device-id` 会被静默拒绝**。同一份 Authorization,只加这一个头,结果从「HTTP 200 + 业务码 `9004`(The submitted order parameters are incorrect)」变成「`code: 0`,奖励到账」——**HTTP 状态始终是 200**,只看状态码会把它误判为成功。设备号取自本机 `iCubeAuthInfo://icube-dc:<id>`,即 `identity.ts` 里聊天通道同源的那个 `deviceId`(官方客户端在 `main.js` 的 `fb(headers)` 里给每个 ug 请求都补上它)。`deviceId` 由 `index.ts` 惰性注入;读不到时降级为不发该头——**只影响领取,不影响状态查询**(状态接口两种情况下都正常返回)。
11
+ - **领取前的守卫比上游更强**:路由 `POST /plugins/dsh-connect-trae/checkin` 仅接受 POST、仅接受回环来源、仅服务 `cn` 区域,并且**先读状态**——已领取(`checked_in` 或 `did_checked_in` 任一为真)就直接返回 `alreadyCheckedIn` 且**根本不发领取请求**;活动关闭答 409。国际版直接答 404 并说明原因,而不是把上游的 HTML 404 当成网络故障抛给用户。
12
+ - **已实测上游按北京自然日幂等**:重复领取仍回 `code: 0`,而权益总额(`total=1600, consumed=390.8`)逐字节不变,**不会重复发放**。插件仍然自行守卫——「不会重复发放」是上游的行为,不是插件可以依赖的保证。
13
+ - **业务拒绝不当成故障**:`claimed: false` + 原始业务码与 message 原样回给卡片并显示出来,而不是包成 500。否则用户只会看到「领取失败」,而丢掉唯一能解释原因的 `9004`。
14
+ - **对齐官方客户端的状态判定**:`checked_in` 与 `did_checked_in` 是**两个独立字段**,官方在「今日已领取」上同时看后者(即使状态读回 `checked_in: false` 也保持按钮禁用)。插件在**卡片与路由两侧都**按同一规则判定,`extra_credits` 也一并展示,避免把当天实际到账的 200 说成 150。
15
+ - 卡片新增 8 条真实点击测试(`tests/card-checkin.spec.tsx`,渲染**真组件**、`fireEvent.click` 真按钮、断言真发出去的请求),`usage.spec.ts` 补 6 条、`web-status.spec.ts` 补 7 条,共 21 条新测试(全仓 261 → 282)。做过**变异验证**:去掉路由的 `did_checked_in` 守卫 → 2 条失败;去掉卡片的同款判定 → 1 条失败;把卡片的 `POST` 改成 `GET` → 1 条失败;删掉「已领取则不发请求」的守卫 → 2 条失败。
16
+ - 取证与接口契约:`docs/USAGE_API_RESEARCH.md` 新增「每日签到领取」一节(含决定性请求头对照表、幂等性实测表、区域限制与官方字段语义)。
17
+
3
18
  ## 2.0.6 (2026-09-22)
4
19
 
5
20
  ### Features
package/README.en.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <h1 align="center">dsh-connect-trae</h1>
6
6
 
7
- <p align="center"><b>Connect locally signed-in Trae models to DeepSeek Harness with local DSH tools and a read-only credits overview.</b></p>
7
+ <p align="center"><b>Connect locally signed-in Trae models to DeepSeek Harness with local DSH tools, a credits overview, and daily check-in claiming.</b></p>
8
8
 
9
9
  <p align="center">
10
10
  <a href="README.md">中文</a> ·
@@ -24,7 +24,9 @@
24
24
  <a href="https://dshfind.com/plugins/dingminhua/dsh-connect-trae"><img src="https://dshfind.com/api/badge/dingminhua/dsh-connect-trae" alt="dshfind plugin"></a>
25
25
  </p>
26
26
 
27
- A [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) bundle plugin that connects locally signed-in Trae models (**both CN and international installs**) to the DSH model picker. Trae generates structured tool calls while DSH executes its own local tools, with a read-only usage overview (Work/general credits on CN, subscription status on international) and model-management panel. **The CN and international sides are two parallel providers (`trae` / `trae-global`) that can be used at the same time**; the settings card separates them with tabs for convenient management.
27
+ A [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) bundle plugin that connects locally signed-in Trae models (**both CN and international installs**) to the DSH model picker. Trae generates structured tool calls while DSH executes its own local tools, with a usage overview (Work/general credits on CN, subscription status on international), daily check-in claiming, and a model-management panel. **The CN and international sides are two parallel providers (`trae` / `trae-global`) that can be used at the same time**; the settings card separates them with tabs for convenient management.
28
+
29
+ > Apart from the daily check-in claim, every query this plugin makes is read-only and consumes nothing; the claim only happens when you click the button.
28
30
 
29
31
  ## Features
30
32
 
@@ -37,6 +39,7 @@ A [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) bund
37
39
  - **Region-isolated directories and selections** — CN and international each keep their own model directory, enabled picks, image opt-ins, and context budgets, and each provider reads only its own slot.
38
40
  - **Account switching** — refreshes the token list and lets users select an account without storing tokens in DSH settings.
39
41
  - **Read-only usage and model management** — Work/general credits on CN accounts, subscription/trial status on international ones; enable Trae models freely. Read-only queries consume nothing.
42
+ - **Daily check-in claiming** — a one-click claim on the CN card (the button shows the daily reward, and reads "Claimed today" and disables itself once done). This is the plugin's **only** operation that changes account state, and it runs only when you click. The international side has no check-in activity, so no button is shown there.
40
43
  - **Secure loopback shim** — one random port + in-process random secret per region; the real Trae token is never handed to pi-ai.
41
44
 
42
45
  ## How it works
@@ -54,7 +57,7 @@ DSH PiAiAdapter (one stack per provider)
54
57
 
55
58
  The CN and international sides each own a complete runtime stack — credential store, model catalog, wire map, upstream clients, loopback shim, adapter — with visibility filtered by the credential's own region claim, so **both regions' accounts can be signed in and used by different sessions at the same time**.
56
59
 
57
- Usage overview hits the read-only `https://api.trae.cn/trae/api/v2/pay/*` and `/trae/api/v2/ug/*` endpoints (international accounts read their own gateway's subscription status). Refreshed tokens are kept per region in `$DSH_HOME/.trae-auth.cn.json` and `$DSH_HOME/.trae-auth.ai.json` (two simultaneously signed-in accounts never overwrite each other; the legacy single file `.trae-auth.json` is still read as a migration source).
60
+ Usage overview hits the read-only `https://api.trae.cn/trae/api/v2/pay/*` and `/trae/api/v2/ug/*` endpoints (international accounts read their own gateway's subscription status); the daily check-in claim is the one write among them, going to `POST /trae/api/v2/ug/checkin_credits/claim` with this installation's device id (`x-device-id`, the same identity the chat path sends). Refreshed tokens are kept per region in `$DSH_HOME/.trae-auth.cn.json` and `$DSH_HOME/.trae-auth.ai.json` (two simultaneously signed-in accounts never overwrite each other; the legacy single file `.trae-auth.json` is still read as a migration source).
58
61
 
59
62
  > See `docs/IMPLEMENTATION_PLAN.md`, `docs/SOLO_ROUTE_DECISION.md`, `docs/USAGE_API_RESEARCH.md`.
60
63
 
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <h1 align="center">dsh-connect-trae</h1>
6
6
 
7
- <p align="center"><b>把本机登录的 Trae 模型接入 DeepSeek Harness,并提供只读的用量/积分概览。</b></p>
7
+ <p align="center"><b>把本机登录的 Trae 模型接入 DeepSeek Harness,并提供用量/积分概览与每日签到领取。</b></p>
8
8
 
9
9
  <p align="center">
10
10
  <a href="README.en.md">English</a> ·
@@ -24,7 +24,9 @@
24
24
  <a href="https://dshfind.com/plugins/dingminhua/dsh-connect-trae"><img src="https://dshfind.com/api/badge/dingminhua/dsh-connect-trae" alt="dshfind plugin"></a>
25
25
  </p>
26
26
 
27
- 一个独立的 [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) bundle 插件。它把本机已登录的 Trae 账号(**国内版与国际版均支持**)接到 DSH 的模型选择器:模型负责生成结构化工具调用,`bash` / `read` / `write` / `edit` 等工具由 DSH 本地执行;同时提供**只读**的用量概览(国内版 Work/通用积分、国际版订阅状态)与模型管理界面。**国内版与国际版是两个并行的供应商(`trae` / `trae-global`),可同时使用**;插件设置卡片以 tab 区分两者,方便统一管理。
27
+ 一个独立的 [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) bundle 插件。它把本机已登录的 Trae 账号(**国内版与国际版均支持**)接到 DSH 的模型选择器:模型负责生成结构化工具调用,`bash` / `read` / `write` / `edit` 等工具由 DSH 本地执行;同时提供用量概览(国内版 Work/通用积分、国际版订阅状态)、每日签到领取与模型管理界面。**国内版与国际版是两个并行的供应商(`trae` / `trae-global`),可同时使用**;插件设置卡片以 tab 区分两者,方便统一管理。
28
+
29
+ > 除每日签到领取外,插件的所有查询都是只读的、不消耗额度;签到领取也只在你点击按钮时才发生。
28
30
 
29
31
  ## 功能特性
30
32
 
@@ -37,6 +39,7 @@
37
39
  - **目录与勾选按区域隔离** —— 国内版与国际版各一套模型目录、勾选、图片开关与上下文预算,两个供应商各自读各自的槽位。
38
40
  - **多账号切换** —— 支持重新读取 Token 列表并选择账号;Token 不写入 DSH 设置。
39
41
  - **只读用量与模型管理** —— 国内版查看 Work 积分与通用积分,国际版查看订阅/试用状态;刷新/启用 Trae 模型;只读查询不消耗额度。
42
+ - **每日签到领取** —— 国内版卡片上一键领取当日签到积分(按钮显示每日奖励,已领取则显示「今日已领取」并禁用);这是插件里**唯一会改变账号状态**的操作,由你主动点击触发。国际版没有签到活动,因此不显示该按钮。
40
43
  - **安全 loopback shim** —— 每区域一个随机端口 + 进程内随机 secret,真实 Trae token 不交给 pi-ai。
41
44
 
42
45
  ## 工作原理
@@ -54,7 +57,7 @@ DSH PiAiAdapter(每个 provider 一套)
54
57
 
55
58
  国内版与国际版各持一套完整的运行时栈——凭据 store、模型 catalog、wire 映射、上游客户端、回环 shim、adapter——按凭证自带的区域声明隔离可见账号,所以**两个区域的账号可以同时在线、同时被不同会话使用**。
56
59
 
57
- 用量概览走 `https://api.trae.cn/trae/api/v2/pay/*` 与 `/trae/api/v2/ug/*` 只读接口(国际账号走其自有网关的订阅状态端点)。刷新得到的 token 按区域存放在 `$DSH_HOME/.trae-auth.cn.json` 与 `$DSH_HOME/.trae-auth.ai.json`(两个账号同时在线互不覆盖;旧的单文件 `.trae-auth.json` 作为迁移来源保留读取)。
60
+ 用量概览走 `https://api.trae.cn/trae/api/v2/pay/*` 与 `/trae/api/v2/ug/*` 只读接口(国际账号走其自有网关的订阅状态端点);每日签到领取是其中唯一的写操作,走 `POST /trae/api/v2/ug/checkin_credits/claim`,需要携带本机安装的设备号(`x-device-id`,与聊天通道同源)。刷新得到的 token 按区域存放在 `$DSH_HOME/.trae-auth.cn.json` 与 `$DSH_HOME/.trae-auth.ai.json`(两个账号同时在线互不覆盖;旧的单文件 `.trae-auth.json` 作为迁移来源保留读取)。
58
61
 
59
62
  > 详见 `docs/IMPLEMENTATION_PLAN.md`、`docs/SOLO_ROUTE_DECISION.md`、`docs/USAGE_API_RESEARCH.md`。
60
63
 
package/lib/client.js CHANGED
@@ -14,6 +14,11 @@ window.__ModuleLoader__.load({
14
14
  const TRAE_MODELS_REFRESH_PATH = "/plugins/dsh-connect-trae/models/refresh";
15
15
  /** Plugin-owned local account rescan endpoint. */
16
16
  const TRAE_ACCOUNTS_REFRESH_PATH = "/plugins/dsh-connect-trae/accounts/refresh";
17
+ /**
18
+ * Plugin-owned daily check-in claim endpoint. POST, loopback-only, and the only
19
+ * route in this plugin that changes upstream account state.
20
+ */
21
+ const TRAE_CHECKIN_PATH = "/plugins/dsh-connect-trae/checkin";
17
22
  /** Query parameter naming the region a card request addresses. */
18
23
  const TRAE_REGION_PARAM = "region";
19
24
  /** Every region, in card tab order. */
@@ -209,6 +214,11 @@ window.__ModuleLoader__.load({
209
214
  .dsm-trae-searched-list code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;line-height:16px;color:var(--dsw-alias-label-secondary,#b8b8b8);word-break:break-all}
210
215
  .dsm-trae-searched-reason{color:var(--dsw-alias-label-tertiary,#999);font-size:11px}
211
216
  .dsm-trae-usage-cheer:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:2px}
217
+ .dsm-trae-checkin{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:14px;background:var(--dsw-alias-bg-layer-2,#24262c)}
218
+ .dsm-trae-checkin-copy{display:flex;flex-direction:column;gap:2px;min-width:0}
219
+ .dsm-trae-checkin-label{color:var(--dsw-alias-label-primary,#e6e6e6);font-size:13px;line-height:18px;font-weight:600}
220
+ .dsm-trae-checkin-hint{color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:12px;line-height:17px}
221
+ .dsm-trae-checkin-button{flex:none}
212
222
  `;
213
223
  //#endregion
214
224
  //#region src/client/TraeUsageCard.tsx
@@ -286,6 +296,14 @@ window.__ModuleLoader__.load({
286
296
  const [switchingAccount, setSwitchingAccount] = (0, react.useState)(false);
287
297
  /** Region whose on/off checkbox write is in flight, so its box can't race. */
288
298
  const [togglingRegion, setTogglingRegion] = (0, react.useState)(void 0);
299
+ /**
300
+ * Whether a check-in claim is in flight. A single flag rather than a
301
+ * per-region map: the claim exists only on the CN tab, and one shared flag
302
+ * keeps a tab switch from stranding a second concurrent claim.
303
+ */
304
+ const [claiming, setClaiming] = (0, react.useState)(false);
305
+ /** Last claim refusal, shown until the next successful refresh. */
306
+ const [claimError, setClaimError] = (0, react.useState)(void 0);
289
307
  const mounted = (0, react.useRef)(true);
290
308
  (0, react.useEffect)(() => {
291
309
  mounted.current = true;
@@ -354,6 +372,14 @@ window.__ModuleLoader__.load({
354
372
  status: "signed-out",
355
373
  accounts: []
356
374
  };
375
+ /**
376
+ * Drop a claim refusal when the tab changes. The error belongs to the region
377
+ * it came from — carrying it onto the other tab would attribute an
378
+ * international account's problem to the CN one (or the reverse).
379
+ */
380
+ (0, react.useEffect)(() => {
381
+ setClaimError(void 0);
382
+ }, [activeRegion]);
357
383
  (0, react.useEffect)(() => {
358
384
  if (!open || !activeRegionOn || status.status !== "signed-in") return;
359
385
  const controller = new AbortController();
@@ -407,6 +433,38 @@ window.__ModuleLoader__.load({
407
433
  }
408
434
  };
409
435
  /**
436
+ * Claim today's check-in reward for the active tab's region.
437
+ *
438
+ * The Host route owns every guard that matters — POST only, loopback origin
439
+ * only, CN only, and a status read before the claim — because a browser-side
440
+ * check is not a guard. The button's own `disabled` is therefore a courtesy
441
+ * (it stops an obviously pointless click), not the protection.
442
+ *
443
+ * A refusal is not thrown away: the route answers `claimed: false` with the
444
+ * upstream's business code and message, and the card shows it. The upstream
445
+ * also reports success for an already-claimed day, so `alreadyCheckedIn`
446
+ * refreshes the state without pretending a new reward landed.
447
+ */
448
+ const claimCheckin = async () => {
449
+ setClaiming(true);
450
+ setClaimError(void 0);
451
+ try {
452
+ const response = await fetch(withTraeRegion(TRAE_CHECKIN_PATH, activeRegion), {
453
+ method: "POST",
454
+ headers: { accept: "application/json" },
455
+ credentials: "same-origin"
456
+ });
457
+ const body = await response.json().catch(() => void 0);
458
+ if (!response.ok) throw new Error(body?.error ?? `HTTP ${response.status}`);
459
+ if (body?.claimed === false && body.alreadyCheckedIn !== true) throw new Error(body.message === void 0 || body.message === "" ? t("row.checkinRefused", { code: String(body.code ?? "") }) : `${t("row.checkinRefused", { code: String(body.code ?? "") })}: ${body.message}`);
460
+ await refreshUsage(activeRegion);
461
+ } catch (error) {
462
+ if (mounted.current) setClaimError(error instanceof Error ? error.message : t("row.requestFailed"));
463
+ } finally {
464
+ if (mounted.current) setClaiming(false);
465
+ }
466
+ };
467
+ /**
410
468
  * Switch one region's provider off or on (issue #11). The write carries the
411
469
  * region's whole slot through untouched — only `enabled` changes — so the
412
470
  * user's directory, model picks, image opt-ins and budgets survive a
@@ -787,6 +845,45 @@ window.__ModuleLoader__.load({
787
845
  className: "dsm-trae-usage-error",
788
846
  children: t("row.creditsError", { message: status.creditsError })
789
847
  }),
848
+ status.checkin === void 0 ? null : (() => {
849
+ const done = status.checkin.checkedIn || status.checkin.didCheckedIn;
850
+ const reward = formatNumber(status.checkin.credits);
851
+ const bonus = status.checkin.extraCredits === void 0 ? "" : ` + ${formatNumber(status.checkin.extraCredits)}`;
852
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
853
+ className: "dsm-trae-checkin",
854
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
855
+ className: "dsm-trae-checkin-copy",
856
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
857
+ className: "dsm-trae-checkin-label",
858
+ children: t("row.checkinLabel")
859
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
860
+ className: "dsm-trae-checkin-hint",
861
+ children: [t("row.checkinReward", { reward: `${reward}${bonus}` }), done ? ` · ${t("row.checkinDoneHint")}` : ""]
862
+ })]
863
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
864
+ type: "button",
865
+ className: "dsm-btn dsm-btn-primary dsm-trae-checkin-button",
866
+ disabled: !status.checkin.enabled || done || claiming,
867
+ onClick: () => {
868
+ claimCheckin();
869
+ },
870
+ children: claiming ? t("row.checkinClaiming") : done ? t("row.checkinClaimed") : t("row.checkinClaim")
871
+ })]
872
+ });
873
+ })(),
874
+ status.checkin !== void 0 && !status.checkin.enabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
875
+ className: "dsm-trae-usage-error",
876
+ children: t("row.checkinDisabled")
877
+ }) : null,
878
+ status.checkinError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
879
+ className: "dsm-trae-usage-error",
880
+ children: t("row.checkinError", { message: status.checkinError })
881
+ }),
882
+ claimError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
883
+ className: "dsm-trae-usage-error",
884
+ role: "alert",
885
+ children: t("row.checkinError", { message: claimError })
886
+ }),
790
887
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
791
888
  className: "dsm-trae-models",
792
889
  "aria-label": t("row.modelsTitle"),
@@ -995,6 +1092,15 @@ window.__ModuleLoader__.load({
995
1092
  "row.creditsConsumedLabel": "Consumed",
996
1093
  "row.creditsTotalLabel": "Total",
997
1094
  "row.creditsError": "Usage unavailable: {message}",
1095
+ "row.checkinLabel": "Daily check-in",
1096
+ "row.checkinReward": "{reward} credits per day",
1097
+ "row.checkinClaim": "Claim today",
1098
+ "row.checkinClaiming": "Claiming…",
1099
+ "row.checkinClaimed": "Claimed today",
1100
+ "row.checkinDoneHint": "come back tomorrow",
1101
+ "row.checkinRefused": "Trae refused the check-in (code {code})",
1102
+ "row.checkinError": "Check-in failed: {message}",
1103
+ "row.checkinDisabled": "This account has no check-in activity available.",
998
1104
  "row.regionCn": "CN",
999
1105
  "row.regionAi": "International",
1000
1106
  "row.subscriptionLabel": "Subscription",
@@ -1063,6 +1169,15 @@ window.__ModuleLoader__.load({
1063
1169
  "row.creditsConsumedLabel": "消耗",
1064
1170
  "row.creditsTotalLabel": "合计",
1065
1171
  "row.creditsError": "用量查询失败:{message}",
1172
+ "row.checkinLabel": "每日签到",
1173
+ "row.checkinReward": "每日 {reward} 积分",
1174
+ "row.checkinClaim": "立即领取",
1175
+ "row.checkinClaiming": "领取中…",
1176
+ "row.checkinClaimed": "今日已领取",
1177
+ "row.checkinDoneHint": "明天再来",
1178
+ "row.checkinRefused": "Trae 拒绝了本次签到(业务码 {code})",
1179
+ "row.checkinError": "签到失败:{message}",
1180
+ "row.checkinDisabled": "该账号当前没有可用的签到活动。",
1066
1181
  "row.regionCn": "国内版",
1067
1182
  "row.regionAi": "国际版",
1068
1183
  "row.subscriptionLabel": "订阅状态",
package/lib/index.d.ts CHANGED
@@ -1156,6 +1156,21 @@ declare class TraeDelegatingUpstreamClient implements TraeUpstreamClient {
1156
1156
  declare const TRAE_PAY_BASE = "https://api.trae.cn";
1157
1157
  interface TraeUsageOptions {
1158
1158
  credential(): Promise<TraeCredential | undefined>;
1159
+ /**
1160
+ * Device identity of the credential's own installation, sent as
1161
+ * `x-device-id` on the check-in routes.
1162
+ *
1163
+ * The claim endpoint is the one WRITE in this client, and it is guarded
1164
+ * server-side by that header: with the same Authorization but no
1165
+ * `x-device-id`, the upstream answers HTTP 200 with business code `9004`
1166
+ * ("The submitted order parameters are incorrect") and grants nothing —
1167
+ * verified 2026-09-24 against a live CN account, where adding the header
1168
+ * flipped the same request to `code: 0` and the daily reward landed. The
1169
+ * official client sends it on every check-in call
1170
+ * (`main.js`'s `fb(headers)` sets `x-device-id` from `guaranteedDeviceId`).
1171
+ * Absent, the client still reads status; only the claim refuses.
1172
+ */
1173
+ deviceId?(): Promise<string | undefined>;
1159
1174
  fetchImpl?: typeof fetch;
1160
1175
  baseUrl?: string;
1161
1176
  timeoutMs?: number;
@@ -1209,8 +1224,27 @@ interface TraeUsageSnapshot {
1209
1224
  }
1210
1225
  interface TraeCheckinStatus {
1211
1226
  checkedIn: boolean;
1227
+ /** Reward for one check-in, as reported by the upstream (`credits`). */
1212
1228
  credits: number;
1213
1229
  enabled: boolean;
1230
+ /**
1231
+ * Whether today's reward was claimed in a previous session. The upstream
1232
+ * reports `did_checked_in` separately from `checked_in`: the app uses the
1233
+ * former to keep the claim button disabled for the rest of the Beijing day
1234
+ * even when the status read happens to report `checked_in: false`.
1235
+ */
1236
+ didCheckedIn: boolean;
1237
+ /** Bonus credit granted on top of the base reward, when the upstream reports one. */
1238
+ extraCredits?: number;
1239
+ }
1240
+ /** Result of claiming today's check-in reward. */
1241
+ interface TraeCheckinClaim {
1242
+ /** Whether the upstream accepted the claim (`code === 0`). */
1243
+ claimed: boolean;
1244
+ /** Business code from the answer; `0` on success. */
1245
+ code: number;
1246
+ /** Upstream message, verbatim but truncated — never token material. */
1247
+ message: string;
1214
1248
  }
1215
1249
  interface TraeActivityRule {
1216
1250
  activityId: string;
@@ -1226,8 +1260,11 @@ interface TraeUsageView {
1226
1260
  activities: TraeActivityRule[];
1227
1261
  }
1228
1262
  /**
1229
- * A read-only client for the verified Trae usage/credits endpoints.
1230
- * All methods are safe to call from a plugin and never mutate account state.
1263
+ * A client for the verified Trae usage/credits endpoints.
1264
+ *
1265
+ * Every method is read-only except {@link TraeUsageClient.claimCheckin}, which
1266
+ * claims the daily check-in reward — the one deliberate mutation, requested by
1267
+ * the user from the card and guarded before it is sent.
1231
1268
  */
1232
1269
  declare class TraeUsageClient {
1233
1270
  private readonly options;
@@ -1243,6 +1280,13 @@ declare class TraeUsageClient {
1243
1280
  * international region its own verified pay gateway.
1244
1281
  */
1245
1282
  private payBase;
1283
+ /**
1284
+ * Device id for the check-in routes, or undefined when the machine's
1285
+ * installation identity cannot be read. Best-effort on purpose: a missing
1286
+ * identity must not break the read-only status query, which the upstream
1287
+ * answers with or without the header.
1288
+ */
1289
+ private deviceIdHeader;
1246
1290
  private authedHeaders;
1247
1291
  private post;
1248
1292
  /**
@@ -1260,8 +1304,26 @@ declare class TraeUsageClient {
1260
1304
  private requireCnRegion;
1261
1305
  /** Total entitlements / credits and per-pack breakdown. */
1262
1306
  snapshot(signal?: AbortSignal): Promise<TraeUsageSnapshot>;
1263
- /** Daily check-in status. */
1307
+ /**
1308
+ * Daily check-in status. Read-only, and answered with or without the device
1309
+ * header — the claim is what needs it.
1310
+ */
1264
1311
  checkinStatus(signal?: AbortSignal): Promise<TraeCheckinStatus>;
1312
+ /**
1313
+ * Claim today's check-in reward. The ONLY state-changing call in this client.
1314
+ *
1315
+ * The upstream is idempotent per Beijing day (verified 2026-09-24: repeating
1316
+ * the call on an already-claimed day answers `code: 0` while the entitlement
1317
+ * total stays byte-identical), so a double click cannot double-grant. The
1318
+ * card and its route still guard, because "cannot double-grant" is a property
1319
+ * of the upstream we verify rather than one we rely on.
1320
+ *
1321
+ * A business refusal arrives as HTTP 200 with a non-zero `code` — most often
1322
+ * `9004` when the request carries no `x-device-id`. That is reported as
1323
+ * `claimed: false` rather than thrown, so the caller can tell "the upstream
1324
+ * refused this" apart from "the request never arrived".
1325
+ */
1326
+ claimCheckin(signal?: AbortSignal): Promise<TraeCheckinClaim>;
1265
1327
  /** Rewards / activity rules. */
1266
1328
  activities(signal?: AbortSignal): Promise<TraeActivityRule[]>;
1267
1329
  /** Convenience: snapshot + check-in + activities in one call (best-effort, non-fatal on missing). */
@@ -1271,6 +1333,11 @@ declare class TraeUsageClient {
1271
1333
  //#region src/status-paths.d.ts
1272
1334
  /** Plugin-owned usage endpoint consumed by its browser half. */
1273
1335
  declare const TRAE_USAGE_PATH = "/plugins/dsh-connect-trae/usage";
1336
+ /**
1337
+ * Plugin-owned daily check-in claim endpoint. POST, loopback-only, and the only
1338
+ * route in this plugin that changes upstream account state.
1339
+ */
1340
+ declare const TRAE_CHECKIN_PATH = "/plugins/dsh-connect-trae/checkin";
1274
1341
  /** Query parameter naming the region a card request addresses. */
1275
1342
  declare const TRAE_REGION_PARAM = "region";
1276
1343
  /** Every region, in card tab order. */
@@ -1316,10 +1383,32 @@ interface TraeWebModel {
1316
1383
  };
1317
1384
  maxContextWindow?: number;
1318
1385
  }
1319
- /** Daily check-in status rendered by the card. */
1386
+ /**
1387
+ * Daily check-in state rendered below the credit stats.
1388
+ *
1389
+ * `didCheckedIn` is not redundant with `checkedIn`: the upstream reports them
1390
+ * separately, and the official client keeps the claim button disabled for the
1391
+ * rest of the Beijing day off `did_checked_in` even when a status read comes
1392
+ * back `checked_in: false`. The card mirrors that rule so a stale-looking
1393
+ * status cannot invite a pointless second claim.
1394
+ */
1320
1395
  interface TraeWebCheckin {
1321
1396
  checkedIn: boolean;
1397
+ didCheckedIn: boolean;
1322
1398
  credits: number;
1399
+ enabled: boolean;
1400
+ extraCredits?: number;
1401
+ }
1402
+ /** The claim route's answer, so the card can report a business refusal verbatim. */
1403
+ interface TraeWebCheckinClaim {
1404
+ /** False when the upstream answered a non-zero business code (e.g. 9004). */
1405
+ claimed: boolean;
1406
+ /** True when today was already claimed, so nothing was sent upstream. */
1407
+ alreadyCheckedIn: boolean;
1408
+ code?: number;
1409
+ message?: string;
1410
+ /** The refreshed check-in state, present whenever the claim path ran. */
1411
+ checkin?: TraeWebCheckin;
1323
1412
  }
1324
1413
  /** One reward activity rule (name + work/general credits when present). */
1325
1414
  interface TraeWebActivity {
@@ -1425,6 +1514,14 @@ type TraeWebUsage = {
1425
1514
  };
1426
1515
  credits?: TraeWebCredits;
1427
1516
  creditsError?: string;
1517
+ /**
1518
+ * Daily check-in state (CN only). Absent on the international region,
1519
+ * whose check-in surface does not exist — probed 2026-09-24: the
1520
+ * `/trae/api/v2/ug/*` family answers 404 on every ai gateway, so the card
1521
+ * shows no claim button there rather than a button that cannot work.
1522
+ */
1523
+ checkin?: TraeWebCheckin;
1524
+ checkinError?: string;
1428
1525
  /** Subscription status of an international account (region ai only). */
1429
1526
  payStatus?: TraeWebPayStatus;
1430
1527
  payStatusError?: string;
@@ -1561,4 +1658,4 @@ declare function regionEnabled(config: Config, region: TraeRegion): boolean;
1561
1658
  declare const Config: z<Config>;
1562
1659
  declare function apply(ctx: Context, config: Config): void;
1563
1660
  //#endregion
1564
- export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, REGION_GATEWAYS, type RawChatDelta, type RawChatMessage, type RawChatTool, SseDecoder, type SseEvent, TRAE_AI_PROVIDER, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROVIDER, TRAE_PROVIDERS, TRAE_PROVIDER_DISPLAY_NAMES, TRAE_RAW_CHAT_V1_PATH, TRAE_RAW_CHAT_V2_PATH, TRAE_REASONING_EFFORTS, TRAE_REGIONS, TRAE_REGION_PARAM, TRAE_SETTINGS_NS, TRAE_SOLO_CHAT_PATH, TRAE_SOLO_FUNCTION, TRAE_SOLO_MODELS_PATH, TRAE_SOLO_REMOTE_BASE, TRAE_STREAM_IDLE_TIMEOUT_MS, TRAE_USAGE_PATH, TRAE_VERSION_CODE_FALLBACK, type TraeActivityRule, type TraeAdapter, type TraeCachedModelConfig, TraeCatalog, type TraeChatResult, type TraeCheckinStatus, type TraeContextBudget, type TraeCredential, TraeCredentialStore, TraeDelegatingUpstreamClient, type TraeDiscoveredModel, type TraeDiscoveredReasoning, type TraeEdition, TraeFallbackUpstreamClient, type TraeFallbackUpstreamOptions, type TraeFusionRawChatEnvelope, TraeGatedUpstreamClient, type TraeGatedUpstreamOptions, type TraeIdentity, type TraeInputModality, type TraeModelDetailRequest, type TraeModelInfo, type TraeObservedModelConfig, TraeRawCapabilityController, type TraeRawCapabilityControllerOptions, type TraeRawCapabilitySnapshot, TraeRawCapabilityState, type TraeRawChatBehaviorConfig, type TraeRawChatCapability, type TraeRawChatClientOptions, type TraeRawChatConfig, type TraeRawChatFailureReason, type TraeRawChatRuntimeConfig, TraeRawChatUpstreamClient, type TraeRawDiagnostic, type TraeRawDiagnosticState, type TraeRawGateway, type TraeRawGatewayOptions, type TraeRawResolverResult, type TraeReasoningCapability, type TraeReasoningEffort, type TraeRefreshDevice, type TraeRegion, type TraeRegionGateways, TraeRegionState, type TraeShim, TraeSoloBridge, type TraeSoloClientOptions, TraeSoloRemoteCatalogClient, type TraeSoloRemoteCatalogOptions, TraeSoloUpstreamClient, type TraeStorageCandidate, type TraeStreamEvent, type TraeUpstreamClient, TraeUsageClient, type TraeUsageOptions, type TraeUsagePack, type TraeUsageRouteOptions, type TraeUsageSnapshot, type TraeUsageSummary, type TraeUsageView, type TraeWebActivity, type TraeWebCheckin, type TraeWebCredits, type TraeWebUsage, type TraeWireModel, UnconfiguredTraeUpstreamClient, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyTraeRawChatFailure, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, discoveredCatalog, fallbackModelsFor, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, mergeTraeModelSources, name, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeTraeRawChatCapability, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeCliIdentity, readTraeIdentity, refreshTraeCredential, regionEnabled, regionEnabledOf, regionOfCredential, regionOfEdition, regionOfHost, regionOfTraeProvider, regionOfTraeStatusUrl, regionOfUserRegion, regionStateOf, registerTraeUsageRoute, resolveTraeIdentity, resolveTraeRawRuntime, sanitizeCatalog, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStorageCandidates, traeWebUsage, withTraeRegion };
1661
+ export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, REGION_GATEWAYS, type RawChatDelta, type RawChatMessage, type RawChatTool, SseDecoder, type SseEvent, TRAE_AI_PROVIDER, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROVIDER, TRAE_PROVIDERS, TRAE_PROVIDER_DISPLAY_NAMES, TRAE_RAW_CHAT_V1_PATH, TRAE_RAW_CHAT_V2_PATH, TRAE_REASONING_EFFORTS, TRAE_REGIONS, TRAE_REGION_PARAM, TRAE_SETTINGS_NS, TRAE_SOLO_CHAT_PATH, TRAE_SOLO_FUNCTION, TRAE_SOLO_MODELS_PATH, TRAE_SOLO_REMOTE_BASE, TRAE_STREAM_IDLE_TIMEOUT_MS, TRAE_USAGE_PATH, TRAE_VERSION_CODE_FALLBACK, type TraeActivityRule, type TraeAdapter, type TraeCachedModelConfig, TraeCatalog, type TraeChatResult, type TraeCheckinClaim, type TraeCheckinStatus, type TraeContextBudget, type TraeCredential, TraeCredentialStore, TraeDelegatingUpstreamClient, type TraeDiscoveredModel, type TraeDiscoveredReasoning, type TraeEdition, TraeFallbackUpstreamClient, type TraeFallbackUpstreamOptions, type TraeFusionRawChatEnvelope, TraeGatedUpstreamClient, type TraeGatedUpstreamOptions, type TraeIdentity, type TraeInputModality, type TraeModelDetailRequest, type TraeModelInfo, type TraeObservedModelConfig, TraeRawCapabilityController, type TraeRawCapabilityControllerOptions, type TraeRawCapabilitySnapshot, TraeRawCapabilityState, type TraeRawChatBehaviorConfig, type TraeRawChatCapability, type TraeRawChatClientOptions, type TraeRawChatConfig, type TraeRawChatFailureReason, type TraeRawChatRuntimeConfig, TraeRawChatUpstreamClient, type TraeRawDiagnostic, type TraeRawDiagnosticState, type TraeRawGateway, type TraeRawGatewayOptions, type TraeRawResolverResult, type TraeReasoningCapability, type TraeReasoningEffort, type TraeRefreshDevice, type TraeRegion, type TraeRegionGateways, TraeRegionState, type TraeShim, TraeSoloBridge, type TraeSoloClientOptions, TraeSoloRemoteCatalogClient, type TraeSoloRemoteCatalogOptions, TraeSoloUpstreamClient, type TraeStorageCandidate, type TraeStreamEvent, type TraeUpstreamClient, TraeUsageClient, type TraeUsageOptions, type TraeUsagePack, type TraeUsageRouteOptions, type TraeUsageSnapshot, type TraeUsageSummary, type TraeUsageView, type TraeWebActivity, type TraeWebCheckin, type TraeWebCheckinClaim, type TraeWebCredits, type TraeWebUsage, type TraeWireModel, UnconfiguredTraeUpstreamClient, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyTraeRawChatFailure, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, discoveredCatalog, fallbackModelsFor, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, mergeTraeModelSources, name, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeTraeRawChatCapability, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeCliIdentity, readTraeIdentity, refreshTraeCredential, regionEnabled, regionEnabledOf, regionOfCredential, regionOfEdition, regionOfHost, regionOfTraeProvider, regionOfTraeStatusUrl, regionOfUserRegion, regionStateOf, registerTraeUsageRoute, resolveTraeIdentity, resolveTraeRawRuntime, sanitizeCatalog, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStorageCandidates, traeWebUsage, withTraeRegion };
package/lib/index.js CHANGED
@@ -3043,8 +3043,11 @@ function parseUsageSnapshot(payload) {
3043
3043
  };
3044
3044
  }
3045
3045
  /**
3046
- * A read-only client for the verified Trae usage/credits endpoints.
3047
- * All methods are safe to call from a plugin and never mutate account state.
3046
+ * A client for the verified Trae usage/credits endpoints.
3047
+ *
3048
+ * Every method is read-only except {@link TraeUsageClient.claimCheckin}, which
3049
+ * claims the daily check-in reward — the one deliberate mutation, requested by
3050
+ * the user from the card and guarded before it is sent.
3048
3051
  */
3049
3052
  var TraeUsageClient = class {
3050
3053
  options;
@@ -3070,6 +3073,20 @@ var TraeUsageClient = class {
3070
3073
  async payBase() {
3071
3074
  return this.baseUrl ?? REGION_GATEWAYS[await this.currentRegion()].pay;
3072
3075
  }
3076
+ /**
3077
+ * Device id for the check-in routes, or undefined when the machine's
3078
+ * installation identity cannot be read. Best-effort on purpose: a missing
3079
+ * identity must not break the read-only status query, which the upstream
3080
+ * answers with or without the header.
3081
+ */
3082
+ async deviceIdHeader() {
3083
+ try {
3084
+ const deviceId = await this.options.deviceId?.();
3085
+ return deviceId === void 0 || deviceId === "" ? {} : { "x-device-id": deviceId };
3086
+ } catch {
3087
+ return {};
3088
+ }
3089
+ }
3073
3090
  async authedHeaders() {
3074
3091
  const credential = await this.options.credential();
3075
3092
  if (credential === void 0 || credential.accessToken === "") throw new Error("Trae credential is not available; cannot query usage");
@@ -3082,8 +3099,11 @@ var TraeUsageClient = class {
3082
3099
  "Referer": `${origin}/`
3083
3100
  };
3084
3101
  }
3085
- async post(path, data, signal) {
3086
- const headers = await this.authedHeaders();
3102
+ async post(path, data, signal, extraHeaders = {}) {
3103
+ const headers = {
3104
+ ...await this.authedHeaders(),
3105
+ ...extraHeaders
3106
+ };
3087
3107
  const response = await this.fetchImpl(`${await this.payBase()}${path}`, {
3088
3108
  method: "POST",
3089
3109
  headers,
@@ -3137,14 +3157,46 @@ var TraeUsageClient = class {
3137
3157
  await this.requireCnRegion("usage snapshot");
3138
3158
  return parseUsageSnapshot(await this.post("/trae/api/v2/pay/web_user_ent_usage", { require_usage: true }, signal));
3139
3159
  }
3140
- /** Daily check-in status. */
3160
+ /**
3161
+ * Daily check-in status. Read-only, and answered with or without the device
3162
+ * header — the claim is what needs it.
3163
+ */
3141
3164
  async checkinStatus(signal) {
3142
3165
  await this.requireCnRegion("check-in status");
3143
- const payload = await this.post("/trae/api/v2/ug/checkin_credits/status", {}, signal);
3166
+ const headers = await this.deviceIdHeader();
3167
+ const payload = await this.post("/trae/api/v2/ug/checkin_credits/status", {}, signal, headers);
3168
+ const extraCredits = asNumber(payload["extra_credits"]);
3144
3169
  return {
3145
3170
  checkedIn: payload["checked_in"] === true,
3146
3171
  credits: asNumber(payload["credits"]) ?? 0,
3147
- enabled: payload["enable"] !== false
3172
+ enabled: payload["enable"] !== false,
3173
+ didCheckedIn: payload["did_checked_in"] === true,
3174
+ ...extraCredits === void 0 || extraCredits <= 0 ? {} : { extraCredits }
3175
+ };
3176
+ }
3177
+ /**
3178
+ * Claim today's check-in reward. The ONLY state-changing call in this client.
3179
+ *
3180
+ * The upstream is idempotent per Beijing day (verified 2026-09-24: repeating
3181
+ * the call on an already-claimed day answers `code: 0` while the entitlement
3182
+ * total stays byte-identical), so a double click cannot double-grant. The
3183
+ * card and its route still guard, because "cannot double-grant" is a property
3184
+ * of the upstream we verify rather than one we rely on.
3185
+ *
3186
+ * A business refusal arrives as HTTP 200 with a non-zero `code` — most often
3187
+ * `9004` when the request carries no `x-device-id`. That is reported as
3188
+ * `claimed: false` rather than thrown, so the caller can tell "the upstream
3189
+ * refused this" apart from "the request never arrived".
3190
+ */
3191
+ async claimCheckin(signal) {
3192
+ await this.requireCnRegion("check-in claim");
3193
+ const headers = await this.deviceIdHeader();
3194
+ const payload = await this.post("/trae/api/v2/ug/checkin_credits/claim", {}, signal, headers);
3195
+ const code = asNumber(payload["code"]) ?? 0;
3196
+ return {
3197
+ claimed: code === 0,
3198
+ code,
3199
+ message: typeof payload["message"] === "string" ? payload["message"].slice(0, 200) : ""
3148
3200
  };
3149
3201
  }
3150
3202
  /** Rewards / activity rules. */
@@ -3189,6 +3241,11 @@ const TRAE_USAGE_PATH = "/plugins/dsh-connect-trae/usage";
3189
3241
  const TRAE_MODELS_REFRESH_PATH = "/plugins/dsh-connect-trae/models/refresh";
3190
3242
  /** Plugin-owned local account rescan endpoint. */
3191
3243
  const TRAE_ACCOUNTS_REFRESH_PATH = "/plugins/dsh-connect-trae/accounts/refresh";
3244
+ /**
3245
+ * Plugin-owned daily check-in claim endpoint. POST, loopback-only, and the only
3246
+ * route in this plugin that changes upstream account state.
3247
+ */
3248
+ const TRAE_CHECKIN_PATH = "/plugins/dsh-connect-trae/checkin";
3192
3249
  /** Query parameter naming the region a card request addresses. */
3193
3250
  const TRAE_REGION_PARAM = "region";
3194
3251
  /** Every region, in card tab order. */
@@ -3323,6 +3380,16 @@ function toCredits(snapshot) {
3323
3380
  accounts
3324
3381
  };
3325
3382
  }
3383
+ /** Map the check-in answer to the card's compact document. */
3384
+ function toCheckin(status) {
3385
+ return {
3386
+ checkedIn: status.checkedIn,
3387
+ didCheckedIn: status.didCheckedIn,
3388
+ credits: status.credits,
3389
+ enabled: status.enabled,
3390
+ ...status.extraCredits === void 0 ? {} : { extraCredits: status.extraCredits }
3391
+ };
3392
+ }
3326
3393
  /**
3327
3394
  * Assemble one region's card document. `region` is the tab the card is on; the
3328
3395
  * region-scoped store already answers with only that region's accounts, so the
@@ -3389,20 +3456,13 @@ async function traeWebUsage(deps, region) {
3389
3456
  payStatusError: safeMessage(error)
3390
3457
  };
3391
3458
  }
3392
- try {
3393
- const snapshot = await client.snapshot();
3394
- return {
3395
- status: "signed-in",
3396
- ...account,
3397
- credits: toCredits(snapshot)
3398
- };
3399
- } catch (error) {
3400
- return {
3401
- status: "signed-in",
3402
- ...account,
3403
- creditsError: safeMessage(error)
3404
- };
3405
- }
3459
+ const [snapshotResult, checkinResult] = await Promise.allSettled([client.snapshot(), client.checkinStatus()]);
3460
+ return {
3461
+ status: "signed-in",
3462
+ ...account,
3463
+ ...snapshotResult.status === "fulfilled" ? { credits: toCredits(snapshotResult.value) } : { creditsError: safeMessage(snapshotResult.reason) },
3464
+ ...checkinResult.status === "fulfilled" ? { checkin: toCheckin(checkinResult.value) } : { checkinError: safeMessage(checkinResult.reason) }
3465
+ };
3406
3466
  }
3407
3467
  /**
3408
3468
  * The region a request addresses, or a 400 answer. Absent parameter means the
@@ -3471,7 +3531,49 @@ function registerTraeUsageRoute(ctx, deps) {
3471
3531
  }
3472
3532
  }
3473
3533
  });
3534
+ /**
3535
+ * Daily check-in claim — the ONLY route in this plugin that mutates
3536
+ * upstream account state, so it is guarded more tightly than its siblings:
3537
+ * POST only, loopback origin only, and the status read runs FIRST so an
3538
+ * already-claimed day never reaches the upstream claim at all. The upstream
3539
+ * is idempotent per Beijing day anyway (verified 2026-09-24: a repeat claim
3540
+ * answers `code: 0` with the entitlement total unchanged), but relying on
3541
+ * that for correctness would put the guard in someone else's hands.
3542
+ */
3543
+ const disposeCheckin = ctx.webServer.register({
3544
+ kind: "exact",
3545
+ path: TRAE_CHECKIN_PATH,
3546
+ handler: async (req, res) => {
3547
+ if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
3548
+ if (!loopbackOrigin(req)) return json(res, 403, { error: "origin-not-trusted" });
3549
+ const region = requestRegion(req, res);
3550
+ if (region === void 0) return;
3551
+ if (region !== "cn") return json(res, 404, { error: "check-in is not available for the international region" });
3552
+ try {
3553
+ const client = deps.client(region);
3554
+ const current = await client.checkinStatus();
3555
+ if (!current.enabled) return json(res, 409, { error: "check-in is not enabled for this account" });
3556
+ if (current.checkedIn || current.didCheckedIn) return json(res, 200, {
3557
+ claimed: false,
3558
+ alreadyCheckedIn: true,
3559
+ checkin: toCheckin(current)
3560
+ });
3561
+ const claim = await client.claimCheckin();
3562
+ const checkin = await client.checkinStatus();
3563
+ json(res, 200, {
3564
+ claimed: claim.claimed,
3565
+ alreadyCheckedIn: false,
3566
+ code: claim.code,
3567
+ message: claim.message,
3568
+ checkin: toCheckin(checkin)
3569
+ });
3570
+ } catch (error) {
3571
+ json(res, 500, { error: safeMessage(error) });
3572
+ }
3573
+ }
3574
+ });
3474
3575
  return () => {
3576
+ disposeCheckin();
3475
3577
  disposeRefresh();
3476
3578
  disposeAccounts();
3477
3579
  disposeUsage();
@@ -3866,7 +3968,10 @@ function apply(ctx, config) {
3866
3968
  catalog,
3867
3969
  shim,
3868
3970
  delegating,
3869
- usageClient: new TraeUsageClient({ credential: () => store.resolve() }),
3971
+ usageClient: new TraeUsageClient({
3972
+ credential: () => store.resolve(),
3973
+ deviceId: async () => (await identity()).deviceId
3974
+ }),
3870
3975
  discoverModels: async (signal) => {
3871
3976
  const merged = mergeTraeModelSources(await withDirectoryRetry(() => remoteCatalog.fetchModels(signal)), await solo.fetchModels(signal));
3872
3977
  wire.callableKeys.clear();
@@ -4048,4 +4153,4 @@ function apply(ctx, config) {
4048
4153
  });
4049
4154
  }
4050
4155
  //#endregion
4051
- export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, REGION_GATEWAYS, SseDecoder, TRAE_AI_PROVIDER, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROVIDER, TRAE_PROVIDERS, TRAE_PROVIDER_DISPLAY_NAMES, TRAE_RAW_CHAT_V1_PATH, TRAE_RAW_CHAT_V2_PATH, TRAE_REASONING_EFFORTS, TRAE_REGIONS, TRAE_REGION_PARAM, TRAE_SETTINGS_NS, TRAE_SOLO_CHAT_PATH, TRAE_SOLO_FUNCTION, TRAE_SOLO_MODELS_PATH, TRAE_SOLO_REMOTE_BASE, TRAE_STREAM_IDLE_TIMEOUT_MS, TRAE_USAGE_PATH, TRAE_VERSION_CODE_FALLBACK, TraeCatalog, TraeCredentialStore, TraeDelegatingUpstreamClient, TraeFallbackUpstreamClient, TraeGatedUpstreamClient, TraeRawCapabilityController, TraeRawCapabilityState, TraeRawChatUpstreamClient, TraeSoloBridge, TraeSoloRemoteCatalogClient, TraeSoloUpstreamClient, TraeUsageClient, UnconfiguredTraeUpstreamClient, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyTraeRawChatFailure, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, discoveredCatalog, fallbackModelsFor, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, mergeTraeModelSources, name, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeTraeRawChatCapability, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeCliIdentity, readTraeIdentity, refreshTraeCredential, regionEnabled, regionEnabledOf, regionOfCredential, regionOfEdition, regionOfHost, regionOfTraeProvider, regionOfTraeStatusUrl, regionOfUserRegion, regionStateOf, registerTraeUsageRoute, resolveTraeIdentity, resolveTraeRawRuntime, sanitizeCatalog, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStorageCandidates, traeWebUsage, withTraeRegion };
4156
+ export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, REGION_GATEWAYS, SseDecoder, TRAE_AI_PROVIDER, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROVIDER, TRAE_PROVIDERS, TRAE_PROVIDER_DISPLAY_NAMES, TRAE_RAW_CHAT_V1_PATH, TRAE_RAW_CHAT_V2_PATH, TRAE_REASONING_EFFORTS, TRAE_REGIONS, TRAE_REGION_PARAM, TRAE_SETTINGS_NS, TRAE_SOLO_CHAT_PATH, TRAE_SOLO_FUNCTION, TRAE_SOLO_MODELS_PATH, TRAE_SOLO_REMOTE_BASE, TRAE_STREAM_IDLE_TIMEOUT_MS, TRAE_USAGE_PATH, TRAE_VERSION_CODE_FALLBACK, TraeCatalog, TraeCredentialStore, TraeDelegatingUpstreamClient, TraeFallbackUpstreamClient, TraeGatedUpstreamClient, TraeRawCapabilityController, TraeRawCapabilityState, TraeRawChatUpstreamClient, TraeSoloBridge, TraeSoloRemoteCatalogClient, TraeSoloUpstreamClient, TraeUsageClient, UnconfiguredTraeUpstreamClient, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyTraeRawChatFailure, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, discoveredCatalog, fallbackModelsFor, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, mergeTraeModelSources, name, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeTraeRawChatCapability, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeCliIdentity, readTraeIdentity, refreshTraeCredential, regionEnabled, regionEnabledOf, regionOfCredential, regionOfEdition, regionOfHost, regionOfTraeProvider, regionOfTraeStatusUrl, regionOfUserRegion, regionStateOf, registerTraeUsageRoute, resolveTraeIdentity, resolveTraeRawRuntime, sanitizeCatalog, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStorageCandidates, traeWebUsage, withTraeRegion };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "dsh-connect-trae",
3
3
  "displayName": "DSH Connect Trae",
4
- "description": "Connect locally signed-in Trae models to DeepSeek Harness, plus a read-only usage/credits summary.",
5
- "version": "2.0.6",
4
+ "description": "Connect locally signed-in Trae models to DeepSeek Harness, with a usage/credits summary and daily check-in claiming.",
5
+ "version": "2.1.0",
6
6
  "author": "dingminhua",
7
7
  "copyright": "Copyright (c) 2026 LaoDing",
8
8
  "license": "MIT",