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/index.js CHANGED
@@ -13,6 +13,7 @@ import { createServer } from "node:http";
13
13
  import { Readable } from "node:stream";
14
14
  import { execFile } from "node:child_process";
15
15
  import { promisify } from "node:util";
16
+ import { existsSync } from "node:fs";
16
17
  //#region src/catalog.ts
17
18
  /**
18
19
  * Bootstrap catalog: identity only where current Trae metadata has not been
@@ -131,11 +132,45 @@ function traeInputModalities(model) {
131
132
  function traeModelDisplayName(model) {
132
133
  return model.creditMultiplier === void 0 ? model.name : `${model.name} · x${model.creditMultiplier.toFixed(2)}`;
133
134
  }
134
- /** Apply the user's explicit image opt-ins; upstream and saved row hints are ignored. */
135
+ /**
136
+ * Whether one model should be offered image input.
137
+ *
138
+ * Two independent sources, unioned (issue #16):
139
+ *
140
+ * - the user's explicit `imageModelIds` opt-in, which stays authoritative and
141
+ * can still switch an individual model on;
142
+ * - Trae's own `multimodal` flag, which is what makes "auto" work at all.
143
+ *
144
+ * Rationale: Trae's IDE enables its attach-image button purely from this flag
145
+ * (`metadata.capabilities.vision`), with no user switch, so a model Trae itself
146
+ * calls multimodal is one the vendor expects to receive images. Requiring a
147
+ * manual tick for those made this plugin the only place where a vendor-declared
148
+ * capability had to be re-enabled by hand.
149
+ *
150
+ * Why the flag alone is not enough, and the union is:
151
+ *
152
+ * - the flag is advisory, not a contract. `llm_utils_chat` is a lightweight
153
+ * SOLO channel and this plugin has been burned by `4001 param is invalid`
154
+ * for ids that LOOKED valid, so the flag is not treated as proof of
155
+ * callability — a model still has to have survived the wire join to appear
156
+ * at all. The flag only decides image input, never model availability.
157
+ * - Users who already ticked a model keep that tick even after a refresh stops
158
+ * reporting the flag, so the union never silently revokes a working setup.
159
+ * - `input` remains the ONLY thing told to DSH: a model that ends up without
160
+ * `image` here is one DSH will degrade images to text for, which is the
161
+ * safe direction for a model whose capability we cannot confirm.
162
+ *
163
+ * `multimodal === true` is a strict test: absent, `false`, or any non-boolean
164
+ * leaves the model text-only unless the user opted in.
165
+ */
166
+ function traeModelAcceptsImage(model, selected) {
167
+ return selected.has(model.id) || model.multimodal === true;
168
+ }
169
+ /** Apply the user's explicit image opt-ins plus Trae's own multimodal flag. */
135
170
  function applyImageSelection(models, selected) {
136
171
  return models.map((model) => ({
137
172
  ...model,
138
- input: selected.has(model.id) ? ["text", "image"] : ["text"]
173
+ input: traeModelAcceptsImage(model, selected) ? ["text", "image"] : ["text"]
139
174
  }));
140
175
  }
141
176
  /** Normalise a display name for cross-source joining. */
@@ -191,6 +226,7 @@ function mergeTraeModelSources(remote, wire) {
191
226
  ...model.maxContextWindow === void 0 ? {} : { maxContextWindow: model.maxContextWindow },
192
227
  ...creditMultiplier === void 0 ? {} : { creditMultiplier },
193
228
  input: ["text"],
229
+ ...model.multimodal === void 0 ? {} : { multimodal: model.multimodal },
194
230
  reasoningSupported: model.reasoningSupported,
195
231
  ...model.reasoning === void 0 ? {} : {
196
232
  reasoning: model.reasoning,
@@ -213,7 +249,7 @@ function applyContextBudgets(catalog, budgets = {}) {
213
249
  ...model.maxContextWindow !== void 0 && budgets[model.id] === model.maxContextWindow ? { contextWindow: model.maxContextWindow } : {}
214
250
  }));
215
251
  }
216
- /** Convert Trae metadata into text-only model rows; image support is user-owned configuration. */
252
+ /** Convert Trae metadata into catalog rows; image input is decided by {@link applyImageSelection}. */
217
253
  function discoveredCatalog(models) {
218
254
  const result = [];
219
255
  for (const model of models) result.push({
@@ -223,6 +259,7 @@ function discoveredCatalog(models) {
223
259
  ...model.maxContextWindow === void 0 ? {} : { maxContextWindow: model.maxContextWindow },
224
260
  input: ["text"],
225
261
  ...model.creditMultiplier === void 0 ? {} : { creditMultiplier: model.creditMultiplier },
262
+ ...model.multimodal === void 0 ? {} : { multimodal: model.multimodal },
226
263
  reasoningSupported: model.reasoningSupported,
227
264
  ...model.reasoning === void 0 ? {} : {
228
265
  reasoning: model.reasoning,
@@ -800,6 +837,43 @@ const LINUX_APP_NAMES = {
800
837
  solo: ["trae-solo-cn", "TRAE SOLO CN"],
801
838
  "solo-sg": ["trae-solo", "TRAE SOLO"]
802
839
  };
840
+ /**
841
+ * Windows desktop config directory names.
842
+ *
843
+ * Trae is a VS Code-family Electron app, and that family names its per-user
844
+ * data directory from the installer-registered product name — which on Windows
845
+ * is `product.json`'s `win32DirName`, NOT the macOS bundle spelling. Measured
846
+ * from the shipped bundles (2026-09-26):
847
+ *
848
+ * Trae CN -> win32DirName = "Trae CN" (applicationName trae-cn)
849
+ * TRAE SOLO CN -> win32DirName = "TRAE SOLO CN" (applicationName trae-solo-cn)
850
+ *
851
+ * So the macOS spellings happen to be right here, and `win32DirName` is the
852
+ * authority that says so. The lowercase `applicationName` spellings
853
+ * (`trae-cn`, `trae-solo-cn`) are probed as well because the same family uses
854
+ * them for the Linux config directory, and which of the two a given installer
855
+ * writes has never been confirmed on a real Windows host
856
+ * (docs/WINDOWS_TOKEN_PROBE.md). Windows file systems are case-insensitive, so
857
+ * listing `trae cn` beside `Trae CN` would be the same directory twice — only
858
+ * genuinely different spellings are listed.
859
+ */
860
+ const WINDOWS_APP_NAMES = {
861
+ cn: ["Trae CN", "trae-cn"],
862
+ sg: ["Trae"],
863
+ solo: ["TRAE SOLO CN", "trae-solo-cn"],
864
+ "solo-sg": ["TRAE SOLO"]
865
+ };
866
+ /**
867
+ * One edition's plausible Windows directory spellings, in probe order.
868
+ *
869
+ * Exported so the identity reader and the credential scanner probe the SAME
870
+ * names: the two resolve different files (the install's `product.json` vs the
871
+ * per-user `storage.json`) but they describe one installation, and a spelling
872
+ * that works for one but not the other is a bug rather than a preference.
873
+ */
874
+ function traeWindowsAppNames(edition) {
875
+ return WINDOWS_APP_NAMES[edition];
876
+ }
803
877
  function traeStorageCandidates(platform = process.platform, home = homedir(), env = process.env) {
804
878
  const result = [];
805
879
  for (const edition of [
@@ -816,7 +890,7 @@ function traeStorageCandidates(platform = process.platform, home = homedir(), en
816
890
  appNames = [app];
817
891
  } else if (platform === "win32") {
818
892
  roots = [env.APPDATA, join(home, "AppData", "Roaming")].filter((value, index, all) => typeof value === "string" && value !== "" && all.indexOf(value) === index);
819
- appNames = [app];
893
+ appNames = WINDOWS_APP_NAMES[edition];
820
894
  } else if (platform === "linux") {
821
895
  roots = [env.XDG_CONFIG_HOME || join(home, ".config")];
822
896
  appNames = LINUX_APP_NAMES[edition];
@@ -1402,7 +1476,7 @@ async function readTraeIdentity(candidate, options = {}) {
1402
1476
  if (platform === "darwin") productPaths.push(join("/Applications", `${appName}.app`, "Contents", "Resources", "app", "product.json"));
1403
1477
  else {
1404
1478
  const localRoots = [env.LOCALAPPDATA, join(home, "AppData", "Local")].filter((value) => typeof value === "string" && value !== "").filter((value, index, all) => all.indexOf(value) === index);
1405
- for (const root of localRoots) productPaths.push(join(root, "Programs", appName, "resources", "app", "product.json"));
1479
+ for (const root of localRoots) for (const spelling of traeWindowsAppNames(candidate.edition)) productPaths.push(join(root, "Programs", spelling, "resources", "app", "product.json"));
1406
1480
  }
1407
1481
  }
1408
1482
  let product = {};
@@ -2952,6 +3026,8 @@ function createTraeRawGateway(options) {
2952
3026
  //#endregion
2953
3027
  //#region src/model-cache.ts
2954
3028
  const execFileAsync = promisify(execFile);
3029
+ /** Basename of the SQLite cache sitting beside the credential document. */
3030
+ const TRAE_STATE_DB_FILENAME = "state.vscdb";
2955
3031
  function positive$1(value) {
2956
3032
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
2957
3033
  }
@@ -2979,18 +3055,59 @@ function parseTraeCachedModel(value) {
2979
3055
  };
2980
3056
  }
2981
3057
  /**
3058
+ * The cache paths to try, most-specific first.
3059
+ *
3060
+ * `state.vscdb` is a SIBLING of the credential document (both sit in
3061
+ * `globalStorage`), so a candidate's own path is the authority for its cache —
3062
+ * this is why the database is derived from {@link traeStorageCandidates} rather
3063
+ * than re-spelled here.
3064
+ *
3065
+ * It previously hardcoded the single macOS spelling `Trae CN`, which was wrong
3066
+ * in two ways on Windows: `paths.ts` probes `Trae CN` AND `trae-cn`, and the
3067
+ * edition actually installed is often SOLO (`TRAE SOLO CN`). This module would
3068
+ * then point at a directory that does not exist. That went unnoticed only
3069
+ * because the Windows `sqlite3` dependency is absent, so the lookup failed and
3070
+ * callers fell back — masking the wrong path behind a different error. Sharing
3071
+ * one path table means a spelling that works for sign-in also works for the
3072
+ * cache.
3073
+ */
3074
+ function traeStateDatabaseCandidates(options = {}) {
3075
+ const platform = options.platform ?? process.platform;
3076
+ const home = options.home ?? homedir();
3077
+ const env = options.env ?? process.env;
3078
+ const paths = [];
3079
+ const push = (storagePath) => {
3080
+ const database = join(dirname(storagePath), TRAE_STATE_DB_FILENAME);
3081
+ if (!paths.some((existing) => existing.toLowerCase() === database.toLowerCase())) paths.push(database);
3082
+ };
3083
+ if (options.candidate !== void 0 && options.candidate.source === "desktop") push(options.candidate.path);
3084
+ for (const candidate of traeStorageCandidates(platform, home, env)) {
3085
+ if (candidate.source !== "desktop") continue;
3086
+ if (options.candidate !== void 0 && candidate.edition !== options.candidate.edition) continue;
3087
+ push(candidate.path);
3088
+ }
3089
+ return paths;
3090
+ }
3091
+ /**
2982
3092
  * Read Trae's own current user's model map via sqlite3 without exposing
2983
3093
  * secrets. The sqlite3 command line is a macOS prerequisite; on Windows it is
2984
3094
  * typically absent, so the call fails and callers fall back gracefully.
3095
+ *
3096
+ * Every candidate database is tried in order and the first that EXISTS is
3097
+ * queried. Existence is checked before spawning because a missing file and a
3098
+ * missing `sqlite3` binary are different problems: choosing the wrong directory
3099
+ * would otherwise surface as a confusing `sqlite3` failure, hiding a path bug
3100
+ * behind a dependency error.
2985
3101
  */
2986
3102
  async function readTraeCachedModel(functionName, modelName, userId, options = {}) {
2987
- const platform = options.platform ?? process.platform;
2988
- const home = options.home ?? homedir();
2989
- const env = options.env ?? process.env;
2990
- const database = platform === "win32" ? join(env.APPDATA ?? join(home, "AppData", "Roaming"), "Trae CN", "User", "globalStorage", "state.vscdb") : join(home, "Library", "Application Support", "Trae CN", "User", "globalStorage", "state.vscdb");
3103
+ const exists = options.exists ?? existsSync;
3104
+ const databases = traeStateDatabaseCandidates(options);
3105
+ const fallback = databases[0];
3106
+ if (fallback === void 0) throw new Error("trae: no desktop storage candidate to locate the model cache from");
3107
+ const database = databases.find((candidate) => exists(candidate)) ?? fallback;
2991
3108
  const key = `${userId}_AI.agent.model.model_list_map`;
2992
3109
  const sql = `select value from ItemTable where key=${JSON.stringify(key)} limit 1;`;
2993
- const { stdout } = await execFileAsync("sqlite3", [database, sql], { maxBuffer: 8388608 });
3110
+ const { stdout } = await (options.runSqlite ?? ((db, statement) => execFileAsync("sqlite3", [db, statement], { maxBuffer: 8388608 })))(database, sql);
2994
3111
  const document = JSON.parse(stdout);
2995
3112
  return parseTraeCachedModel((Array.isArray(document[functionName]) ? document[functionName] : []).find((item) => typeof item === "object" && item !== null && item.name === modelName));
2996
3113
  }
@@ -3002,7 +3119,7 @@ async function resolveTraeRawRuntime(store, modelName) {
3002
3119
  const candidate = store.candidates().find((item) => item.edition === credential.edition);
3003
3120
  if (candidate === void 0) throw new Error(`no Trae ${credential.edition} storage candidate for Raw Chat identity`);
3004
3121
  const identity = await readTraeIdentity(candidate);
3005
- const cached = credential.edition === "cn" ? await readTraeCachedModel("solo_agent", modelName, credential.userId).catch(() => void 0) : void 0;
3122
+ const cached = credential.edition === "cn" ? await readTraeCachedModel("solo_agent", modelName, credential.userId, { candidate }).catch(() => void 0) : void 0;
3006
3123
  return {
3007
3124
  identity,
3008
3125
  runtime: buildTraeRawChatRuntimeConfig(modelName, cached, {
@@ -3202,6 +3319,12 @@ var TraeUsageClient = class {
3202
3319
  /**
3203
3320
  * Daily check-in status. Read-only, and answered with or without the device
3204
3321
  * header — the claim is what needs it.
3322
+ *
3323
+ * The read DOES carry the header whenever this installation has one, and
3324
+ * that is deliberate rather than incidental: `did_checked_in` is answered
3325
+ * per device, so omitting the header would make a device that already claimed
3326
+ * today look identical to one that never has (measured 2026-09-26, see
3327
+ * {@link TraeCheckinStatus.didCheckedIn}).
3205
3328
  */
3206
3329
  async checkinStatus(signal) {
3207
3330
  await this.requireCnRegion("check-in status");
@@ -3423,6 +3546,16 @@ function nextRegionEnabled(value, region, enabled) {
3423
3546
  }
3424
3547
  //#endregion
3425
3548
  //#region src/web-status.ts
3549
+ /**
3550
+ * The upstream's business code for "该设备今日已参与签到" — this DEVICE already
3551
+ * used today's check-in (measured 2026-09-26 on a CN account whose claim was
3552
+ * refused with exactly this code after an account switch on the same machine).
3553
+ *
3554
+ * It arrives as HTTP 200 with a non-zero `code`, which is why it has to be
3555
+ * matched on the code rather than on the HTTP status: read as a transport
3556
+ * failure it looks like the plugin is broken, when the day is simply spent.
3557
+ */
3558
+ const CHECKIN_DEVICE_ALREADY_CLAIMED = 9095;
3426
3559
  /** Redact token-like content before it crosses to the browser. */
3427
3560
  function safeMessage(error) {
3428
3561
  return (error instanceof Error ? error.message : String(error)).replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/gu, "[redacted token]").replace(/(\b(?:code|token|refresh_token|access_token)=)[^&\s]+/giu, "$1[redacted]").slice(0, 500);
@@ -3619,11 +3752,22 @@ function registerTraeUsageRoute(ctx, deps) {
3619
3752
  /**
3620
3753
  * Daily check-in claim — the ONLY route in this plugin that mutates
3621
3754
  * upstream account state, so it is guarded more tightly than its siblings:
3622
- * POST only, loopback origin only, and the status read runs FIRST so an
3623
- * already-claimed day never reaches the upstream claim at all. The upstream
3624
- * is idempotent per Beijing day anyway (verified 2026-09-24: a repeat claim
3625
- * answers `code: 0` with the entitlement total unchanged), but relying on
3626
- * that for correctness would put the guard in someone else's hands.
3755
+ * POST only, loopback origin only, and the status read runs FIRST so a day
3756
+ * the ACCOUNT has already been paid for never reaches the upstream claim.
3757
+ *
3758
+ * `alreadyCheckedIn` and `deviceCheckedIn` are deliberately separate
3759
+ * answers, because the two flags mean different things (see
3760
+ * {@link TraeWebCheckin}): `checkedIn` is the account's reward for today
3761
+ * existing, `didCheckedIn` is this MACHINE having spent its check-in —
3762
+ * possibly for a different account. Treating the latter as "claimed today"
3763
+ * was a shipped bug: after switching accounts the card said "claimed
3764
+ * today" and disabled the button, which is the right call for the wrong
3765
+ * reason and hides that the new account was never rewarded.
3766
+ *
3767
+ * The upstream is idempotent per Beijing day (verified 2026-09-24: a
3768
+ * repeat claim answers `code: 0` with the entitlement total unchanged), but
3769
+ * relying on that for correctness would put the guard in someone else's
3770
+ * hands.
3627
3771
  */
3628
3772
  const disposeCheckin = ctx.webServer.register({
3629
3773
  kind: "exact",
@@ -3638,16 +3782,27 @@ function registerTraeUsageRoute(ctx, deps) {
3638
3782
  const client = deps.client(region);
3639
3783
  const current = await client.checkinStatus();
3640
3784
  if (!current.enabled) return json(res, 409, { error: "check-in is not enabled for this account" });
3641
- if (current.checkedIn || current.didCheckedIn) return json(res, 200, {
3785
+ if (current.checkedIn) return json(res, 200, {
3642
3786
  claimed: false,
3643
3787
  alreadyCheckedIn: true,
3788
+ deviceCheckedIn: current.didCheckedIn,
3789
+ checkin: toCheckin(current)
3790
+ });
3791
+ if (current.didCheckedIn) return json(res, 200, {
3792
+ claimed: false,
3793
+ alreadyCheckedIn: false,
3794
+ deviceCheckedIn: true,
3795
+ code: CHECKIN_DEVICE_ALREADY_CLAIMED,
3796
+ message: "this device already used today's check-in",
3644
3797
  checkin: toCheckin(current)
3645
3798
  });
3646
3799
  const claim = await client.claimCheckin();
3647
3800
  const checkin = await client.checkinStatus();
3801
+ const deviceClaimed = claim.code === CHECKIN_DEVICE_ALREADY_CLAIMED;
3648
3802
  json(res, 200, {
3649
3803
  claimed: claim.claimed,
3650
3804
  alreadyCheckedIn: false,
3805
+ ...deviceClaimed ? { deviceCheckedIn: true } : {},
3651
3806
  code: claim.code,
3652
3807
  message: claim.message,
3653
3808
  checkin: toCheckin(checkin)
@@ -3764,6 +3919,64 @@ function buildTraeModelDetailRequest(configName = "", custom = false) {
3764
3919
  };
3765
3920
  }
3766
3921
  //#endregion
3922
+ //#region src/redact.ts
3923
+ /**
3924
+ * Redaction helpers for diagnostics that a user may paste into a public issue.
3925
+ *
3926
+ * A verification report is only useful if it can be shared, and it is only safe
3927
+ * to share if it carries no personal identifiers. Paths are the hard case:
3928
+ * `C:\Users\<real name>\AppData\Roaming\Trae CN\...` contains both a real
3929
+ * account name AND the single most diagnostic token in the whole report (the
3930
+ * Trae data directory spelling). Redacting the whole path would make the report
3931
+ * worthless; printing it whole would leak the user's name. So the user segment
3932
+ * is replaced and the meaningful tail is preserved.
3933
+ *
3934
+ * @module dsh-connect-trae/redact
3935
+ */
3936
+ /**
3937
+ * Replace the user segment of a path with `<user>`, keeping the rest intact.
3938
+ *
3939
+ * Handles the shapes a Trae install can produce:
3940
+ * - `C:\Users\<name>\...` and `C:\Documents and Settings\<name>\...` (Windows)
3941
+ * - `/Users/<name>/...` and `/home/<name>/...` (macOS / Linux)
3942
+ *
3943
+ * Anything after that segment — edition directory, `User\globalStorage`,
3944
+ * `storage.json` — is preserved verbatim, because that is what a report is
3945
+ * actually read for. Cross-platform on purpose: the function is called by
3946
+ * Windows-focused diagnostics, but the same helpers run in tests and on other
3947
+ * hosts, and a report from the wrong platform must not leak either.
3948
+ */
3949
+ function maskUserPath(path) {
3950
+ return String(path).replace(/([A-Za-z]:\\Users\\)[^\\]+/i, "$1<user>").replace(/([A-Za-z]:\\Documents and Settings\\)[^\\]+/i, "$1<user>").replace(/^(\/(?:Users|home)\/)[^/]+/, "$1<user>");
3951
+ }
3952
+ /**
3953
+ * Describe a name by shape instead of by value: a character count, plus whether
3954
+ * it is purely numeric.
3955
+ *
3956
+ * This is the deliberate middle ground for account names. A Trae account name
3957
+ * is often a real name or a phone number, so it must never be printed; but the
3958
+ * report still needs to show that an account WAS resolved, and the shape
3959
+ * distinguishes a real nickname from a generated `用户<n>` id — which is the
3960
+ * only thing a reader needs to judge whether account discovery worked.
3961
+ */
3962
+ function describeNameShape(name) {
3963
+ const text = String(name ?? "");
3964
+ return `${text.length} 字符${/^\d+$/.test(text) ? ",纯数字" : ""}`;
3965
+ }
3966
+ /**
3967
+ * Describe an identifier by shape instead of by value.
3968
+ *
3969
+ * A device id (`x-device-id`) is a stable installation identifier and is
3970
+ * directly tied to the upstream's per-device rules, so it is never printed.
3971
+ * Its shape still proves which resolution path ran: a real `icube-dc` id is
3972
+ * 15 digits, while the fallback is a 32-character hex hash.
3973
+ */
3974
+ function describeIdShape(value) {
3975
+ const text = String(value ?? "");
3976
+ if (text === "") return "(空)";
3977
+ return `${text.length} 位${/^\d+$/.test(text) ? ",纯数字" : ",含非数字"}`;
3978
+ }
3979
+ //#endregion
3767
3980
  //#region src/raw-envelope.ts
3768
3981
  /** Stable JSON hash candidate used by the native DTO's arg/args_hash fields. */
3769
3982
  function hashTraeRawChatArg(arg) {
@@ -3886,7 +4099,8 @@ const modelConfig = z.object({
3886
4099
  maxTokens: z.number().step(1).min(1),
3887
4100
  input: z.array(z.union(["text", "image"])),
3888
4101
  creditMultiplier: z.number(),
3889
- wireFunction: z.string()
4102
+ wireFunction: z.string(),
4103
+ multimodal: z.boolean()
3890
4104
  });
3891
4105
  const regionStateConfig = z.object({
3892
4106
  enabled: z.boolean().default(true).description("Whether this region's provider is offered to DSH (opt-out; false withdraws it entirely)"),
@@ -4273,4 +4487,4 @@ function apply(ctx, config) {
4273
4487
  });
4274
4488
  }
4275
4489
  //#endregion
4276
- 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, 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 };
4490
+ 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_STATE_DB_FILENAME, 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, 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 };
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, with a usage/credits summary and daily check-in claiming.",
5
- "version": "2.3.0",
4
+ "description": "把本机登录的 Trae 模型接入 DeepSeek Harness:国内版与国际版双供应商并行,提供用量/积分概览与每日签到领取。",
5
+ "version": "2.4.0",
6
6
  "author": "dingminhua",
7
7
  "copyright": "Copyright (c) 2026 LaoDing",
8
8
  "license": "MIT",
@@ -34,6 +34,7 @@
34
34
  "lib",
35
35
  "icons",
36
36
  "docs/assets/dsh-connect-trae-usage-card.png",
37
+ "scripts/verify-windows.mjs",
37
38
  "screenshots.json",
38
39
  "cordis.patch.yml",
39
40
  "README.md",
@@ -75,42 +76,44 @@
75
76
  },
76
77
  "peerDependencies": {
77
78
  "@deepseek-ai/cordis": ">=4.0.4 <5.0.0",
78
- "@deepseek-ai/dsh-atomic-write": ">=0.1.7-rc.1 <0.2.0-0",
79
- "@deepseek-ai/dsh-attachment": ">=0.1.7-rc.1 <0.2.0-0",
80
- "@deepseek-ai/dsh-client-ui-primitives": ">=0.1.7-rc.1 <0.2.0-0",
81
- "@deepseek-ai/dsh-home-paths": ">=0.1.7-rc.1 <0.2.0-0",
82
- "@deepseek-ai/dsh-host-webserver": ">=0.1.7-rc.1 <0.2.0-0",
83
- "@deepseek-ai/dsh-llm": ">=0.1.7-rc.1 <0.2.0-0",
84
- "@deepseek-ai/dsh-llm-pi-ai": ">=0.1.7-rc.1 <0.2.0-0",
85
- "@deepseek-ai/dsh-settings": ">=0.1.7-rc.1 <0.2.0-0",
79
+ "@deepseek-ai/dsh-atomic-write": ">=0.1.7-rc.1 <0.3.0-0",
80
+ "@deepseek-ai/dsh-attachment": ">=0.1.7-rc.1 <0.3.0-0",
81
+ "@deepseek-ai/dsh-client-ui-primitives": ">=0.1.7-rc.1 <0.3.0-0",
82
+ "@deepseek-ai/dsh-home-paths": ">=0.1.7-rc.1 <0.3.0-0",
83
+ "@deepseek-ai/dsh-host-webserver": ">=0.1.7-rc.1 <0.3.0-0",
84
+ "@deepseek-ai/dsh-llm": ">=0.1.7-rc.1 <0.3.0-0",
85
+ "@deepseek-ai/dsh-llm-pi-ai": ">=0.1.7-rc.1 <0.3.0-0",
86
+ "@deepseek-ai/dsh-settings": ">=0.1.7-rc.1 <0.3.0-0",
86
87
  "@deepseek-ai/schemastery": ">=3.18.4 <4.0.0",
87
88
  "@earendil-works/pi-ai": ">=0.85.1",
88
89
  "react": "^18.2.0"
89
90
  },
90
91
  "devDependencies": {
91
92
  "@deepseek-ai/cordis": "^4.0.4",
92
- "@deepseek-ai/dsh-atomic-write": "0.1.7-rc.1",
93
- "@deepseek-ai/dsh-attachment": "0.1.7-rc.1",
94
- "@deepseek-ai/dsh-client-locale": "0.1.7-rc.1",
95
- "@deepseek-ai/dsh-client-ui-primitives": "0.1.7-rc.1",
96
- "@deepseek-ai/dsh-client-ui-renderer": "0.1.7-rc.1",
97
- "@deepseek-ai/dsh-client-ui-settings": "0.1.7-rc.1",
98
- "@deepseek-ai/dsh-client-ui-settings-plugins": "0.1.7-rc.1",
99
- "@deepseek-ai/dsh-client-ui-slots": "0.1.7-rc.1",
100
- "@deepseek-ai/dsh-home-paths": "0.1.7-rc.1",
101
- "@deepseek-ai/dsh-host-webserver": "0.1.7-rc.1",
102
- "@deepseek-ai/dsh-llm": "0.1.7-rc.1",
103
- "@deepseek-ai/dsh-llm-pi-ai": "0.1.7-rc.1",
104
- "@deepseek-ai/dsh-settings": "0.1.7-rc.1",
93
+ "@deepseek-ai/dsh-atomic-write": "0.2.0-rc.1",
94
+ "@deepseek-ai/dsh-attachment": "0.2.0-rc.1",
95
+ "@deepseek-ai/dsh-client-locale": "0.2.0-rc.1",
96
+ "@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.1",
97
+ "@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.1",
98
+ "@deepseek-ai/dsh-client-ui-settings": "0.2.0-rc.1",
99
+ "@deepseek-ai/dsh-client-ui-settings-plugins": "0.2.0-rc.1",
100
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.1",
101
+ "@deepseek-ai/dsh-home-paths": "0.2.0-rc.1",
102
+ "@deepseek-ai/dsh-host-webserver": "0.2.0-rc.1",
103
+ "@deepseek-ai/dsh-llm": "0.2.0-rc.1",
104
+ "@deepseek-ai/dsh-llm-pi-ai": "0.2.0-rc.1",
105
+ "@deepseek-ai/dsh-settings": "0.2.0-rc.1",
105
106
  "@deepseek-ai/schemastery": "3.18.4",
106
107
  "@earendil-works/pi-ai": "^0.85.1",
107
108
  "@testing-library/dom": "^10.4.2",
108
109
  "@testing-library/react": "^16.3.3",
109
110
  "@types/node": "^22.20.0",
110
111
  "@types/react": "~18.3.31",
112
+ "@types/semver": "^7.7.0",
111
113
  "jsdom": "^25.0.1",
112
114
  "react": "^18.3.1",
113
115
  "react-dom": "18.3.1",
116
+ "semver": "^7.7.2",
114
117
  "tsdown": "^0.22.2",
115
118
  "typescript": "^5.9.3",
116
119
  "vitest": "^4.1.8"