dsh-connect-trae 2.3.0 → 2.4.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/client.js CHANGED
@@ -265,6 +265,10 @@ window.__ModuleLoader__.load({
265
265
  .dsm-trae-checkin-label{color:var(--dsw-alias-label-primary,#e6e6e6);font-size:13px;line-height:18px;font-weight:600}
266
266
  .dsm-trae-checkin-hint{color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:12px;line-height:17px}
267
267
  .dsm-trae-checkin-button{flex:none}
268
+ /* The "this machine already used today's check-in" note is INFORMATIONAL, not
269
+ an error: it is Trae's one-per-device-per-day rule, and the day is simply
270
+ spent. Rendering it in the error colour would read as a plugin fault. */
271
+ .dsm-trae-checkin-note{margin:8px 0 0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary,#c6c9d0)}
268
272
  `;
269
273
  //#endregion
270
274
  //#region src/client/TraeUsageCard.tsx
@@ -389,6 +393,14 @@ window.__ModuleLoader__.load({
389
393
  /** Last claim refusal, shown until the next successful refresh. */
390
394
  const [claimError, setClaimError] = (0, react.useState)(void 0);
391
395
  /**
396
+ * Whether this MACHINE has spent today's check-in while the selected account
397
+ * has not been paid — the "switched account" state. Kept separate from
398
+ * `claimError` because it is not a failure: it is the upstream's one-per-
399
+ * device-per-day rule, and rendering it through the "签到失败:…" line would
400
+ * present a normal rule as a plugin fault.
401
+ */
402
+ const [deviceSpentNotice, setDeviceSpentNotice] = (0, react.useState)(false);
403
+ /**
392
404
  * A refused or unpersisted settings write, surfaced instead of silently
393
405
  * reverting. On 0.1.7 a rejected write resolves normally, so without this
394
406
  * the control would flip and then quietly snap back on the next snapshot.
@@ -469,6 +481,7 @@ window.__ModuleLoader__.load({
469
481
  */
470
482
  (0, react.useEffect)(() => {
471
483
  setClaimError(void 0);
484
+ setDeviceSpentNotice(false);
472
485
  }, [activeRegion]);
473
486
  (0, react.useEffect)(() => {
474
487
  if (!open || !activeRegionOn || status.status !== "signed-in") return;
@@ -536,13 +549,21 @@ window.__ModuleLoader__.load({
536
549
  * (it stops an obviously pointless click), not the protection.
537
550
  *
538
551
  * A refusal is not thrown away: the route answers `claimed: false` with the
539
- * upstream's business code and message, and the card shows it. The upstream
540
- * also reports success for an already-claimed day, so `alreadyCheckedIn`
541
- * refreshes the state without pretending a new reward landed.
552
+ * upstream's business code and message, and the card shows it. Three answers
553
+ * are distinguished rather than collapsed into "failed":
554
+ *
555
+ * - `alreadyCheckedIn` — this ACCOUNT was already paid today; show it as
556
+ * claimed and refresh, never as an error.
557
+ * - `deviceCheckedIn` (code 9095) — this MACHINE already spent today's
558
+ * check-in, so this account cannot claim here today. That is the normal
559
+ * outcome after switching accounts, and saying "check-in failed" would
560
+ * blame the plugin for an upstream rule.
561
+ * - anything else — a real refusal, reported verbatim.
542
562
  */
543
563
  const claimCheckin = async () => {
544
564
  setClaiming(true);
545
565
  setClaimError(void 0);
566
+ setDeviceSpentNotice(false);
546
567
  try {
547
568
  const response = await fetch(withTraeRegion(TRAE_CHECKIN_PATH, activeRegion), {
548
569
  method: "POST",
@@ -551,6 +572,11 @@ window.__ModuleLoader__.load({
551
572
  });
552
573
  const body = await response.json().catch(() => void 0);
553
574
  if (!response.ok) throw new Error(body?.error ?? `HTTP ${response.status}`);
575
+ if (body?.claimed === false && body.deviceCheckedIn === true) {
576
+ await refreshUsage(activeRegion);
577
+ if (mounted.current) setDeviceSpentNotice(true);
578
+ return;
579
+ }
554
580
  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}`);
555
581
  await refreshUsage(activeRegion);
556
582
  } catch (error) {
@@ -637,6 +663,23 @@ window.__ModuleLoader__.load({
637
663
  const savedEnabledIds = status.status === "signed-in" ? new Set(status.enabledModelIds) : /* @__PURE__ */ new Set();
638
664
  const activeEnabledIds = draft?.enabledIds ?? savedEnabledIds;
639
665
  const activeImageIds = draft?.imageIds ?? savedImageIds;
666
+ /**
667
+ * Effective image capability of one row, mirroring what the Host reports to
668
+ * DSH: the user's opt-in unioned with Trae's own `multimodal` flag (issue
669
+ * #16). Rendering the union is what keeps this checkbox truthful — showing the
670
+ * raw `imageModelIds` would draw an unticked box next to a model the plugin
671
+ * is in fact advertising as image-capable.
672
+ */
673
+ const rowAcceptsImage = (model) => activeImageIds.has(model.id) || model.multimodal === true;
674
+ /**
675
+ * A row Trae itself declares multimodal needs no manual opt-in, so its box is
676
+ * shown ticked and read-only rather than offering a toggle that the union
677
+ * would immediately undo. Turning image input off for such a model is
678
+ * deliberately not supported: the flag is the vendor's own capability answer,
679
+ * and enabling it only lets DSH attach an image the user still has to pick per
680
+ * message — it never sends one by itself.
681
+ */
682
+ const rowImageIsAutomatic = (model) => model.multimodal === true;
640
683
  const activeContextBudgets = draft?.contextBudgets ?? savedContextBudgets;
641
684
  const dirty = draft !== void 0;
642
685
  /** Apply an edit to the ACTIVE tab's draft, seeding it from the current view. */
@@ -699,7 +742,8 @@ window.__ModuleLoader__.load({
699
742
  enabled: regionOn(activeRegion),
700
743
  lastCatalog: visibleModels.map((model) => ({
701
744
  ...model,
702
- input: ["text"]
745
+ input: ["text"],
746
+ ...model.multimodal === void 0 ? {} : { multimodal: model.multimodal }
703
747
  })),
704
748
  enabledModelIds: [...activeEnabledIds],
705
749
  imageModelIds: [...activeImageIds].filter((id) => activeEnabledIds.has(id)),
@@ -955,10 +999,11 @@ window.__ModuleLoader__.load({
955
999
  children: t("row.creditsError", { message: status.creditsError })
956
1000
  }),
957
1001
  status.checkin === void 0 ? null : (() => {
958
- const done = status.checkin.checkedIn || status.checkin.didCheckedIn;
1002
+ const accountClaimed = status.checkin.checkedIn;
1003
+ const deviceSpent = !accountClaimed && (status.checkin.didCheckedIn || deviceSpentNotice);
959
1004
  const reward = formatNumber(status.checkin.credits);
960
1005
  const bonus = status.checkin.extraCredits === void 0 ? "" : ` + ${formatNumber(status.checkin.extraCredits)}`;
961
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1006
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
962
1007
  className: "dsm-trae-checkin",
963
1008
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
964
1009
  className: "dsm-trae-checkin-copy",
@@ -967,18 +1012,22 @@ window.__ModuleLoader__.load({
967
1012
  children: t("row.checkinLabel")
968
1013
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
969
1014
  className: "dsm-trae-checkin-hint",
970
- children: [t("row.checkinReward", { reward: `${reward}${bonus}` }), done ? ` · ${t("row.checkinDoneHint")}` : ""]
1015
+ children: [t("row.checkinReward", { reward: `${reward}${bonus}` }), accountClaimed ? ` · ${t("row.checkinDoneHint")}` : ""]
971
1016
  })]
972
1017
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
973
1018
  type: "button",
974
1019
  className: "dsm-btn dsm-btn-primary dsm-trae-checkin-button",
975
- disabled: !status.checkin.enabled || done || claiming,
1020
+ disabled: !status.checkin.enabled || accountClaimed || deviceSpent || claiming,
976
1021
  onClick: () => {
977
1022
  claimCheckin();
978
1023
  },
979
- children: claiming ? t("row.checkinClaiming") : done ? t("row.checkinClaimed") : t("row.checkinClaim")
1024
+ children: claiming ? t("row.checkinClaiming") : accountClaimed ? t("row.checkinClaimed") : t("row.checkinClaim")
980
1025
  })]
981
- });
1026
+ }), deviceSpent ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1027
+ className: "dsm-trae-checkin-note",
1028
+ role: "status",
1029
+ children: t("row.checkinDeviceSpent")
1030
+ }) : null] });
982
1031
  })(),
983
1032
  status.checkin !== void 0 && !status.checkin.enabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
984
1033
  className: "dsm-trae-usage-error",
@@ -1046,8 +1095,9 @@ window.__ModuleLoader__.load({
1046
1095
  className: "dsm-trae-model-image",
1047
1096
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1048
1097
  type: "checkbox",
1049
- checked: activeImageIds.has(model.id),
1050
- disabled: settingsScope?.getSnapshot().writable !== true || saving,
1098
+ checked: rowAcceptsImage(model),
1099
+ disabled: rowImageIsAutomatic(model) || settingsScope?.getSnapshot().writable !== true || saving,
1100
+ title: rowImageIsAutomatic(model) ? t("row.modelImageAuto") : void 0,
1051
1101
  onChange: () => {
1052
1102
  toggleImage(model.id);
1053
1103
  }
@@ -1210,6 +1260,7 @@ window.__ModuleLoader__.load({
1210
1260
  "row.checkinDoneHint": "come back tomorrow",
1211
1261
  "row.checkinRefused": "Trae refused the check-in (code {code})",
1212
1262
  "row.checkinError": "Check-in failed: {message}",
1263
+ "row.checkinDeviceSpent": "This machine already used today's check-in — Trae allows one per device per day, so this account cannot claim here today. Claim it on another device or from the account that checked in, or come back tomorrow.",
1213
1264
  "row.checkinDisabled": "This account has no check-in activity available.",
1214
1265
  "row.regionCn": "CN",
1215
1266
  "row.regionAi": "International",
@@ -1238,11 +1289,12 @@ window.__ModuleLoader__.load({
1238
1289
  "row.contextBudget": "DSH context budget",
1239
1290
  "row.modelOutput": "Output {output}",
1240
1291
  "row.modelImage": "Image",
1292
+ "row.modelImageAuto": "Enabled automatically: Trae marks this model multimodal.",
1241
1293
  "row.modelRate": "{rate}x credits",
1242
1294
  "row.modelReasoning": "Reasoning: {efforts}",
1243
1295
  "row.modelReasoningSupported": "Reasoning: supported",
1244
1296
  "row.modelUnknown": "Unknown",
1245
- "row.modelCapabilityPending": "Image input is enabled only for models you select.",
1297
+ "row.modelCapabilityPending": "Image input follows Trae's own multimodal flag, plus anything you tick yourself.",
1246
1298
  "row.cheer": "Star on GitHub"
1247
1299
  };
1248
1300
  const zh = {
@@ -1288,6 +1340,7 @@ window.__ModuleLoader__.load({
1288
1340
  "row.checkinDoneHint": "明天再来",
1289
1341
  "row.checkinRefused": "Trae 拒绝了本次签到(业务码 {code})",
1290
1342
  "row.checkinError": "签到失败:{message}",
1343
+ "row.checkinDeviceSpent": "本机今日的签到已用掉——Trae 按「每台设备每天一次」限制,所以当前账号在本机今天无法再签。可换一台设备领取,或用今天已签到的那台设备/账号,或明天再来。",
1291
1344
  "row.checkinDisabled": "该账号当前没有可用的签到活动。",
1292
1345
  "row.regionCn": "国内版",
1293
1346
  "row.regionAi": "国际版",
@@ -1316,11 +1369,12 @@ window.__ModuleLoader__.load({
1316
1369
  "row.contextBudget": "DSH 上下文预算",
1317
1370
  "row.modelOutput": "最大输出 {output}",
1318
1371
  "row.modelImage": "图片",
1372
+ "row.modelImageAuto": "已自动开启:Trae 把该模型标为多模态。",
1319
1373
  "row.modelRate": "积分 {rate}x",
1320
1374
  "row.modelReasoning": "推理强度:{efforts}",
1321
1375
  "row.modelReasoningSupported": "推理:支持",
1322
1376
  "row.modelUnknown": "未知",
1323
- "row.modelCapabilityPending": "仅对你手动勾选的模型启用图片输入。",
1377
+ "row.modelCapabilityPending": "图片输入跟随 Trae 自身的多模态标注,并叠加你手动勾选的模型。",
1324
1378
  "row.cheer": "鼓励一下"
1325
1379
  };
1326
1380
  //#endregion
package/lib/index.d.ts CHANGED
@@ -46,6 +46,15 @@ interface TraeStorageCandidate {
46
46
  path: string;
47
47
  source: TraeCredentialSource;
48
48
  }
49
+ /**
50
+ * One edition's plausible Windows directory spellings, in probe order.
51
+ *
52
+ * Exported so the identity reader and the credential scanner probe the SAME
53
+ * names: the two resolve different files (the install's `product.json` vs the
54
+ * per-user `storage.json`) but they describe one installation, and a spelling
55
+ * that works for one but not the other is a bug rather than a preference.
56
+ */
57
+ declare function traeWindowsAppNames(edition: TraeEdition): readonly string[];
49
58
  declare function traeStorageCandidates(platform?: NodeJS.Platform, home?: string, env?: NodeJS.ProcessEnv): TraeStorageCandidate[];
50
59
  //#endregion
51
60
  //#region src/region.d.ts
@@ -111,6 +120,23 @@ interface TraeModelInfo {
111
120
  wireConfigName?: string;
112
121
  /** The directory function this model must be called through (see {@link TraeWireModel.function}). */
113
122
  wireFunction?: string;
123
+ /**
124
+ * Trae's own `multimodal` flag for this model, carried through from the
125
+ * directory that advertised it. It is the upstream's answer to "does this
126
+ * model read images", and {@link applyImageSelection} unions it with the
127
+ * user's explicit opt-ins.
128
+ *
129
+ * Deliberately NOT the same thing as `input`: `input` is what DSH is told
130
+ * this route accepts, while this field records what Trae said. Keeping both
131
+ * lets a future refresh re-derive `input` without losing the upstream fact,
132
+ * and lets the card explain WHY a model is image-capable.
133
+ *
134
+ * Absent means the directory did not say (an older `lastCatalog` saved before
135
+ * this field existed, a fallback row, or a directory entry that omitted the
136
+ * key). Absent is treated as false, never as "assume capable": sending an
137
+ * image to a model that cannot read one burns credits for a silent failure.
138
+ */
139
+ multimodal?: boolean;
114
140
  }
115
141
  /**
116
142
  * Bootstrap catalog: identity only where current Trae metadata has not been
@@ -166,7 +192,7 @@ declare function traeInputModalities(model: Pick<TraeModelInfo, 'input'>): TraeI
166
192
  * model discovery) use this decorated name.
167
193
  */
168
194
  declare function traeModelDisplayName(model: Pick<TraeModelInfo, 'name' | 'creditMultiplier'>): string;
169
- /** Apply the user's explicit image opt-ins; upstream and saved row hints are ignored. */
195
+ /** Apply the user's explicit image opt-ins plus Trae's own multimodal flag. */
170
196
  declare function applyImageSelection(models: readonly TraeModelInfo[], selected: ReadonlySet<string>): TraeModelInfo[];
171
197
  /** One row from `get_detail_param`: the authoritative llm_utils_chat wire id + display name. */
172
198
  interface TraeWireModel {
@@ -230,7 +256,7 @@ type TraeContextBudget = number;
230
256
  * never to a fabricated number. Everything else keeps the dev window.
231
257
  */
232
258
  declare function applyContextBudgets(catalog: readonly TraeModelInfo[], budgets?: Readonly<Record<string, TraeContextBudget | undefined>>): TraeModelInfo[];
233
- /** Convert Trae metadata into text-only model rows; image support is user-owned configuration. */
259
+ /** Convert Trae metadata into catalog rows; image input is decided by {@link applyImageSelection}. */
234
260
  declare function discoveredCatalog(models: readonly TraeDiscoveredModel[]): TraeModelInfo[];
235
261
  /**
236
262
  * Drop rows saved by older releases that generated `@1m` variant models, so a
@@ -576,6 +602,8 @@ interface TraeObservedModelConfig {
576
602
  declare function parseObservedModelConfig(line: string): TraeObservedModelConfig | undefined;
577
603
  //#endregion
578
604
  //#region src/model-cache.d.ts
605
+ /** Basename of the SQLite cache sitting beside the credential document. */
606
+ declare const TRAE_STATE_DB_FILENAME = "state.vscdb";
579
607
  interface TraeCachedModelConfig {
580
608
  name: string;
581
609
  customConfig?: Record<string, unknown>;
@@ -594,11 +622,54 @@ interface TraeCachedModelReadOptions {
594
622
  home?: string;
595
623
  /** Environment override for testing; defaults to process.env. */
596
624
  env?: NodeJS.ProcessEnv;
625
+ /**
626
+ * The credential candidate whose account is being read. Supplying it pins the
627
+ * lookup to the installation the credential actually came from, which is the
628
+ * only way to stay correct when several editions are installed: `storage.json`
629
+ * and `state.vscdb` live in the SAME `globalStorage` directory, so the cache
630
+ * belongs to whichever candidate yielded the sign-in.
631
+ */
632
+ candidate?: TraeStorageCandidate;
633
+ /** Injectable for tests; defaults to node:fs existsSync. */
634
+ exists?: (path: string) => boolean;
635
+ /**
636
+ * Injectable `sqlite3` runner for tests; defaults to promisified `execFile`.
637
+ * Mirrors the `fetchImpl` injection used elsewhere in this plugin, and exists
638
+ * so a test can assert the exact argv — in particular WHICH database path was
639
+ * chosen, which no assertion on the candidate list alone can prove.
640
+ */
641
+ runSqlite?: (database: string, sql: string) => Promise<{
642
+ stdout: string;
643
+ }>;
597
644
  }
645
+ /**
646
+ * The cache paths to try, most-specific first.
647
+ *
648
+ * `state.vscdb` is a SIBLING of the credential document (both sit in
649
+ * `globalStorage`), so a candidate's own path is the authority for its cache —
650
+ * this is why the database is derived from {@link traeStorageCandidates} rather
651
+ * than re-spelled here.
652
+ *
653
+ * It previously hardcoded the single macOS spelling `Trae CN`, which was wrong
654
+ * in two ways on Windows: `paths.ts` probes `Trae CN` AND `trae-cn`, and the
655
+ * edition actually installed is often SOLO (`TRAE SOLO CN`). This module would
656
+ * then point at a directory that does not exist. That went unnoticed only
657
+ * because the Windows `sqlite3` dependency is absent, so the lookup failed and
658
+ * callers fell back — masking the wrong path behind a different error. Sharing
659
+ * one path table means a spelling that works for sign-in also works for the
660
+ * cache.
661
+ */
662
+ declare function traeStateDatabaseCandidates(options?: TraeCachedModelReadOptions): string[];
598
663
  /**
599
664
  * Read Trae's own current user's model map via sqlite3 without exposing
600
665
  * secrets. The sqlite3 command line is a macOS prerequisite; on Windows it is
601
666
  * typically absent, so the call fails and callers fall back gracefully.
667
+ *
668
+ * Every candidate database is tried in order and the first that EXISTS is
669
+ * queried. Existence is checked before spawning because a missing file and a
670
+ * missing `sqlite3` binary are different problems: choosing the wrong directory
671
+ * would otherwise surface as a confusing `sqlite3` failure, hiding a path bug
672
+ * behind a dependency error.
602
673
  */
603
674
  declare function readTraeCachedModel(functionName: string, modelName: string, userId: string, options?: TraeCachedModelReadOptions): Promise<TraeCachedModelConfig | undefined>;
604
675
  //#endregion
@@ -634,6 +705,55 @@ interface TraeModelDetailRequest {
634
705
  /** Build the exact request shape observed in successful current Trae CN logs. */
635
706
  declare function buildTraeModelDetailRequest(configName?: string, custom?: boolean): TraeModelDetailRequest;
636
707
  //#endregion
708
+ //#region src/redact.d.ts
709
+ /**
710
+ * Redaction helpers for diagnostics that a user may paste into a public issue.
711
+ *
712
+ * A verification report is only useful if it can be shared, and it is only safe
713
+ * to share if it carries no personal identifiers. Paths are the hard case:
714
+ * `C:\Users\<real name>\AppData\Roaming\Trae CN\...` contains both a real
715
+ * account name AND the single most diagnostic token in the whole report (the
716
+ * Trae data directory spelling). Redacting the whole path would make the report
717
+ * worthless; printing it whole would leak the user's name. So the user segment
718
+ * is replaced and the meaningful tail is preserved.
719
+ *
720
+ * @module dsh-connect-trae/redact
721
+ */
722
+ /**
723
+ * Replace the user segment of a path with `<user>`, keeping the rest intact.
724
+ *
725
+ * Handles the shapes a Trae install can produce:
726
+ * - `C:\Users\<name>\...` and `C:\Documents and Settings\<name>\...` (Windows)
727
+ * - `/Users/<name>/...` and `/home/<name>/...` (macOS / Linux)
728
+ *
729
+ * Anything after that segment — edition directory, `User\globalStorage`,
730
+ * `storage.json` — is preserved verbatim, because that is what a report is
731
+ * actually read for. Cross-platform on purpose: the function is called by
732
+ * Windows-focused diagnostics, but the same helpers run in tests and on other
733
+ * hosts, and a report from the wrong platform must not leak either.
734
+ */
735
+ declare function maskUserPath(path: string): string;
736
+ /**
737
+ * Describe a name by shape instead of by value: a character count, plus whether
738
+ * it is purely numeric.
739
+ *
740
+ * This is the deliberate middle ground for account names. A Trae account name
741
+ * is often a real name or a phone number, so it must never be printed; but the
742
+ * report still needs to show that an account WAS resolved, and the shape
743
+ * distinguishes a real nickname from a generated `用户<n>` id — which is the
744
+ * only thing a reader needs to judge whether account discovery worked.
745
+ */
746
+ declare function describeNameShape(name: unknown): string;
747
+ /**
748
+ * Describe an identifier by shape instead of by value.
749
+ *
750
+ * A device id (`x-device-id`) is a stable installation identifier and is
751
+ * directly tied to the upstream's per-device rules, so it is never printed.
752
+ * Its shape still proves which resolution path ran: a real `icube-dc` id is
753
+ * 15 digits, while the fallback is a 32-character hex hash.
754
+ */
755
+ declare function describeIdShape(value: unknown): string;
756
+ //#endregion
637
757
  //#region src/protocol.d.ts
638
758
  declare const TRAE_CN_AGENT_TASK_PATH = "/api/agent/v3/create_agent_task";
639
759
  declare const TRAE_CN_TITLE_PATH = "/api/agent/v3/llm_utils_chat";
@@ -1239,15 +1359,32 @@ interface TraeUsageSnapshot {
1239
1359
  packs: TraeUsagePack[];
1240
1360
  }
1241
1361
  interface TraeCheckinStatus {
1362
+ /**
1363
+ * Whether the account's REWARD PACK for today already exists. This is the
1364
+ * account-wide answer and it is the one that decides whether there is
1365
+ * anything left to claim: a day with `checkedIn: true` has already been paid
1366
+ * out, whichever device produced it.
1367
+ */
1242
1368
  checkedIn: boolean;
1243
1369
  /** Reward for one check-in, as reported by the upstream (`credits`). */
1244
1370
  credits: number;
1245
1371
  enabled: boolean;
1246
1372
  /**
1247
- * Whether today's reward was claimed in a previous session. The upstream
1248
- * reports `did_checked_in` separately from `checked_in`: the app uses the
1249
- * former to keep the claim button disabled for the rest of the Beijing day
1250
- * even when the status read happens to report `checked_in: false`.
1373
+ * Whether THIS device's claim for today was already recorded. The upstream
1374
+ * keys this on the `x-device-id` the request carries — not on the account —
1375
+ * so it answers `true` for the device that claimed and `false` for the same
1376
+ * account and day read from any other device.
1377
+ *
1378
+ * Measured 2026-09-26 on a live CN account whose claim had succeeded minutes
1379
+ * earlier: `did_checked_in` was `true` with the claiming device id and
1380
+ * `false` with the header omitted, with a synthetic id, and with another
1381
+ * install's id. Repeating the read never flips it, so it is not "have I seen
1382
+ * you before" state either — only an actual claim sets it.
1383
+ *
1384
+ * It is therefore NOT a "today is done" signal on its own (claiming again
1385
+ * answers the upstream's own 9095 "该设备今日已参与签到" refusal); it is
1386
+ * evidence that this exact device is already accounted for, which is what
1387
+ * makes a claim safe to skip.
1251
1388
  */
1252
1389
  didCheckedIn: boolean;
1253
1390
  /** Bonus credit granted on top of the base reward, when the upstream reports one. */
@@ -1323,6 +1460,12 @@ declare class TraeUsageClient {
1323
1460
  /**
1324
1461
  * Daily check-in status. Read-only, and answered with or without the device
1325
1462
  * header — the claim is what needs it.
1463
+ *
1464
+ * The read DOES carry the header whenever this installation has one, and
1465
+ * that is deliberate rather than incidental: `did_checked_in` is answered
1466
+ * per device, so omitting the header would make a device that already claimed
1467
+ * today look identical to one that never has (measured 2026-09-26, see
1468
+ * {@link TraeCheckinStatus.didCheckedIn}).
1326
1469
  */
1327
1470
  checkinStatus(signal?: AbortSignal): Promise<TraeCheckinStatus>;
1328
1471
  /**
@@ -1398,15 +1541,33 @@ interface TraeWebModel {
1398
1541
  defaultEffort?: string;
1399
1542
  };
1400
1543
  maxContextWindow?: number;
1544
+ /**
1545
+ * Trae's own image-capability flag, as the runtime catalog carries it. The
1546
+ * card renders these rows with their image checkbox already ticked so the
1547
+ * control matches what DSH is actually told (issue #16); absent means the
1548
+ * directory did not declare it, which is not a capability claim either way.
1549
+ */
1550
+ multimodal?: boolean;
1401
1551
  }
1402
1552
  /**
1403
1553
  * Daily check-in state rendered below the credit stats.
1404
1554
  *
1405
- * `didCheckedIn` is not redundant with `checkedIn`: the upstream reports them
1406
- * separately, and the official client keeps the claim button disabled for the
1407
- * rest of the Beijing day off `did_checked_in` even when a status read comes
1408
- * back `checked_in: false`. The card mirrors that rule so a stale-looking
1409
- * status cannot invite a pointless second claim.
1555
+ * The two booleans answer DIFFERENT questions and must never be collapsed into
1556
+ * one "done" flag — doing that was a shipped bug (see CHANGELOG 2.3.1):
1557
+ *
1558
+ * - `checkedIn` is ACCOUNT-level: today's reward pack exists for this account,
1559
+ * so there is nothing left to claim and the balance already includes it.
1560
+ * - `didCheckedIn` is DEVICE-level: this MACHINE already spent its one
1561
+ * check-in for the day, whichever account spent it. The upstream keys it on
1562
+ * the `x-device-id` in the request and answers it per device, not per
1563
+ * account (measured 2026-09-26: the same account and Beijing day answered
1564
+ * `false` with the header omitted, with a synthetic id, and with another
1565
+ * install's id, and `true` for the device that actually claimed).
1566
+ *
1567
+ * So `didCheckedIn && !checkedIn` is the "switched account" case: the device's
1568
+ * daily check-in is used up, but THIS account was never rewarded. Claiming
1569
+ * again is refused upstream (business code 9095, "该设备今日已参与签到"), so
1570
+ * the card disables the button — but it must say that, not "claimed today".
1410
1571
  */
1411
1572
  interface TraeWebCheckin {
1412
1573
  checkedIn: boolean;
@@ -1419,8 +1580,19 @@ interface TraeWebCheckin {
1419
1580
  interface TraeWebCheckinClaim {
1420
1581
  /** False when the upstream answered a non-zero business code (e.g. 9004). */
1421
1582
  claimed: boolean;
1422
- /** True when today was already claimed, so nothing was sent upstream. */
1583
+ /**
1584
+ * True only when THIS ACCOUNT's reward for today already exists, so nothing
1585
+ * was sent upstream. Distinct from {@link TraeWebCheckinClaim.deviceCheckedIn}:
1586
+ * a device that already claimed for another account leaves this false.
1587
+ */
1423
1588
  alreadyCheckedIn: boolean;
1589
+ /**
1590
+ * True when today's check-in was already spent on this DEVICE (by this or
1591
+ * another account), so no claim was sent or the upstream refused it with
1592
+ * 9095. Not an error: it is the truthful answer to "can this account claim
1593
+ * here today?" — no, and the reason is the machine, not the account.
1594
+ */
1595
+ deviceCheckedIn?: boolean;
1424
1596
  code?: number;
1425
1597
  message?: string;
1426
1598
  /** The refreshed check-in state, present whenever the claim path ran. */
@@ -1731,4 +1903,4 @@ declare function asVolatile<S>(schema: S): S;
1731
1903
  declare const Config: z<Config>;
1732
1904
  declare function apply(ctx: Context, config: Config): void;
1733
1905
  //#endregion
1734
- 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, asVolatile, 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, settingsNamespaceOf, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStorageCandidates, traeWebUsage, unwrapVolatile, unwrapVolatileDeep, withTraeRegion };
1906
+ 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_STATE_DB_FILENAME, 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, asVolatile, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyTraeRawChatFailure, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, describeIdShape, describeNameShape, discoveredCatalog, fallbackModelsFor, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, maskUserPath, 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, settingsNamespaceOf, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStateDatabaseCandidates, traeStorageCandidates, traeWebUsage, traeWindowsAppNames, unwrapVolatile, unwrapVolatileDeep, withTraeRegion };