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/CHANGELOG.md +138 -2
- package/README.en.md +102 -10
- package/README.md +109 -10
- package/lib/client.js +68 -14
- package/lib/index.d.ts +185 -13
- package/lib/index.js +233 -19
- package/package.json +26 -23
- package/scripts/verify-windows.mjs +311 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,141 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.4.0 (2026-09-29)
|
|
4
|
+
|
|
5
|
+
> **包含仍未发布的 2.3.2。** 2.3.2 的 peer 范围修复(`>=0.1.7-rc.1 <0.2.0-0` → `<0.3.0-0`,见下一节)
|
|
6
|
+
> 只提交到 git、未推送也未发 npm,因此**没有用户拿到过 2.3.2**;2.4.0 在同一棵树上,同时带上该修复。
|
|
7
|
+
> 若你希望「先单独发一个只含关键修复的补丁版」,就先推 2.3.2 的 tag 并 `npm publish`,再把本节改号为 2.4.0。
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- **识图能力改为跟随 Trae 自身的 `multimodal` 标注,不再要求逐一手勾**(issue #16):
|
|
12
|
+
- **原来的行为**:插件唯一决定是否给某个模型开图片输入的入口是 `applyImageSelection()`,判据只有用户手动勾选的 `imageModelIds`;上游 `multimodal` 字段虽然在 `parseTraeRemoteModel` 里被解析出来,但**从不进入 `TraeModelInfo`**,等于被丢弃。于是 Trae 自己标为多模态的模型,在这里也必须手动勾一次才认图。
|
|
13
|
+
- **改后的判据**:`你手动勾选的 ∪ Trae 标注 multimodal=true 的`(新增 `traeModelAcceptsImage()` 作为唯一判据函数,`applyImageSelection()` 走它)。
|
|
14
|
+
- **为什么是并集而不是"只信上游"**:上游标注是**建议**而非契约——`llm_utils_chat` 是轻量 SOLO 通道,本插件历史上多次被 `4001 param is invalid` 坑过。所以上游标注**只决定图片输入,从不决定模型可用性**:模型能否出现在目录里仍只由 wire(`get_detail_param` 的 `config_name`)join 决定,标注不会把一个不可调用的模型放进来。
|
|
15
|
+
- **为什么也不"只信手动"**:Trae IDE 自己的附图按钮就是纯粹由 `capabilities.vision` 驱动的,没有任何用户开关。厂商已声明的能力还要用户再勾一次,只有本插件这一处。
|
|
16
|
+
- **缺失或为 false 一律按纯文本**:`multimodal` 缺失(旧版本保存的目录、静态兜底目录)或明确 `false` 都不自动开。宁可不发,也不对能力不确定的模型谎报"支持"——一旦声明 `image`,DSH 会把图片原样发出而不是降级成文字。
|
|
17
|
+
- **自动开启 ≠ 自动发图**:它只是让 DSH 允许给该模型附图;图片始终由你在对话里自己贴,插件不会替你发送任何东西。
|
|
18
|
+
- **卡片同步**:被自动开启的行显示为**已勾选且只读**并带说明(新增 `row.modelImageAuto`)。并集语义下取消勾选会被立刻重新加上,给它一个可点的框是在撒谎。目前**不提供**单个模型"强制关闭"的出口(那需要额外的 opt-out 状态);如需请在该 issue 下提出。
|
|
19
|
+
- **实测结果**(本机 CN 账号,实时目录 15 个模型,用户零勾选):8 个自动获得图片输入(`Doubao-Seed-Evolving`、`Doubao-Seed-2.1-Pro`、`Doubao-Seed-2.1-Turbo`、`deepseek-v4.1-flash`、`kimi-k3`、`minimax-m3`、`qwen3.8-max`、`qwen-3.7-plus`);Trae 明确标 `false` 的 4 个(`glm-5.3`、`glm-5.2`、`DeepSeek-V4-Flash-Official`、`DeepSeek-V4-Pro-Official`)**正确地保持纯文本**;目录未标注的 3 个同样保持纯文本。
|
|
20
|
+
- **文档**:`docs/MODEL_MANAGEMENT_DESIGN.md` 里「仅展示,不作为图片授权依据」的描述随此改动失效(该字段现在确实参与授权,但**不**决定可用性);README 新增「图片输入(识图)」一节说明完整语义(中英同步)。
|
|
21
|
+
|
|
22
|
+
### Bug Fixes
|
|
23
|
+
|
|
24
|
+
- **`multimodal` 必须随 `lastCatalog` 一起持久化,否则"自动识图"会在保存/重启后失效**:运行时目录每次保存与启动都从 `lastCatalog` 重新推导(`derive()` → `applyImageSelection()`)。若 schema 不声明该字段,它被剥掉后自动开图会立刻退回手动。已在 `modelConfig`(`src/index.ts`)显式声明 `multimodal: z.boolean()`,并在卡片保存处显式带上——卡片原本会强制把 `input` 写回 `['text']`(这是对的,`input` 由宿主推导),但 `multimodal` 是上游事实,不能跟着一起丢。
|
|
25
|
+
- **README「暂不支持」表格过期**:`deepseek-v4.1-flash` 已从该表移除。`docs/DS41_CALLABILITY.md`(2026-09-15)取证时 8 个 SOLO function 全部返回 `4001`,但**上游后来把它开放到了 SOLO 通道**——当前实时目录里它带 `wireFunction: solo_work_remote`,已实测刷新确认可调用。表内其余 3 个(`glm-5.3-flash`、`kimi-k2.8-preview`、`qwen3.8-flash`)实测仍不在目录里,保留在表中。
|
|
26
|
+
|
|
27
|
+
### Tests
|
|
28
|
+
|
|
29
|
+
- **新增 6 例**(全仓 367 → 373),覆盖并集的四个方向:标注 true 自动开、标注 false 不开、缺失按不开、手动勾选永不撤销(含上游后来不再标注的情况),以及"别的模型的勾选不得串到本行"。
|
|
30
|
+
- **新增 2 例端到端**(走真实插件 + `ctx.llm.listModels()`):零勾选时标注为 true 的模型 `inputModalities === ['text','image']`;标注 false 的模型仍为 `['text']`。后者同时证明**手动勾选路径没被破坏**,且旧形状(不带 `multimodal` 的 `lastCatalog` 行)仍能通过 schema 校验。
|
|
31
|
+
- **变异验证**(确认测试有约束力,3 次全部被抓):① 把并集改回"仅手动勾选" → 3 例失败(含端到端那例);② 让 schema 不声明 `multimodal` → 无法编译(弱信号,已改写为更真实的形式);③ 在 `derive` 里于 `applyImageSelection` 之前剥掉该字段 → 端到端那例精确失败。恢复后 44 文件 373 例全绿。
|
|
32
|
+
|
|
33
|
+
## 2.3.2 (2026-09-29)
|
|
34
|
+
|
|
35
|
+
### Bug Fixes
|
|
36
|
+
|
|
37
|
+
- **插件在 DSH 0.2.0-rc.1 上整个不加载——peer 上界 `<0.2.0-0` 把 0.2.0 的预发布线一起挡掉了**(用户升级宿主后发现,2.3.1 的实际故障):
|
|
38
|
+
- **现象**:升级到 0.2.0-rc.1 后插件「凭空消失」——模型选择器里没有 `trae` / `trae-global`,插件设置卡片不见,用量路由 `/plugins/dsh-connect-trae/usage` 返回 **404**。不是某个功能坏掉,是 bundle 从未挂载。
|
|
39
|
+
- **根因**:DSH 自 0.1.7-rc.1 起有一道 **bundle 级 peer 门禁**(`@deepseek-ai/dsh-app-boot` 的 `evaluatePluginCompatibility`):`loadProfileDirectory()` 逐条比对 bundle 自己声明的 DSH peer,任一条不满足就**抛错并整体跳过该 bundle**,只往 stderr 写一行 `skipping profile bundle ...`。本插件 8 条范围写的是 `>=0.1.7-rc.1 <0.2.0-0`,而 **SemVer 里 `0.2.0-rc.1 < 0.2.0-0`**——`-0` 这个上界本意是「排除 0.2.0 正式版」,实际却把**整个 0.2.0 预发布线**也排除了。宿主的判定是 `semver.satisfies(runtime, range, {includePrerelease: true})`,在这条范围下对 `0.2.0-rc.1` 返回 `false`。
|
|
40
|
+
- **不是 0.2.0 破坏了契约**:逐符号核对确认本插件消费的 API 在 0.2.0-rc.1 上**全部未变**(`resolveRetryPolicy`、`PiAiAdapter`、`ResolvedPiAiProviderProfile`、`withFileLock`、`writeFileAtomic`、`resolveDshHome`、`AttachmentStore` 的声明逐字一致;插件触及的 `dsh-atomic-write` / `dsh-home-paths` / `dsh-attachment` / `dsh-settings` / `dsh-host-webserver` / `dsh-client-ui-slots` 源码**逐字节相同**)。同级 `dsh-connect-workbuddy` 2.1.1 写的是 `<0.2.0`——**放行 0.2.0 预发布、只挡正式版**,因此照常加载;本插件用的 `-0` 是同一意图下多写了一个 `-0`,恰好把预发布线也挡在门外。差别只在这一个字符。
|
|
41
|
+
- **也不是本插件独有的现象**:本机 profile 的 19 个 bundle 里有 **5 个**同时被这道门禁拒绝(本插件、`@changfenhuang/dsh-genui`、`dsh-better-reasoning-effort`、`dsh-free-search`、`dsh-rewind-plugin`)。用宿主自带的门禁逐条判定,这 5 个全部不在挂载行里、其余全部 `active`——判定与观测逐一吻合,排除了「判定模型与宿主不一致」。
|
|
42
|
+
- **修法**:8 条 peer 上界改为 `>=0.1.7-rc.1 <0.3.0-0`——放行 0.1.7 与 0.2 两条线(含各自的 alpha/rc),仍挡住未审查的 0.3 线。用宿主自己发行的门禁代码复验:0.1.7-rc.1 / 0.1.7-rc.2 / 0.2.0-alpha.1 / 0.2.0-rc.1 / 0.2.0 / 0.2.9 全部放行,0.3.0-rc.1 / 0.3.0 / 1.0.0 仍被拒绝。
|
|
43
|
+
- **`devDependencies` 一并升到 0.2.0-rc.1**:否则本地 typecheck / 测试验的是 0.1.7 的契约,而用户跑的是 0.2.0——「开发树与用户解析不一致」正是上一轮 `pnpm-workspace.yaml` 里为 schemastery 写过的那类隐患。
|
|
44
|
+
- **为什么 CI 全绿却漏掉了**:范围写错不会让任何断言变红——`tsc` 通过(范围只是字符串)、345 条单测通过(插件自身代码没动)、`pnpm install` 通过(devDependency 钉的版本满足那段写错的范围)、CI 通过。**只有真实宿主会拒绝加载**。这个缺口已由下面的回归测试补上。
|
|
45
|
+
|
|
46
|
+
### Tests
|
|
47
|
+
|
|
48
|
+
- **新增 `tests/dsh-peer-range.spec.ts`(+22 例,全仓 345 → 367)**:把「peer 范围必须覆盖哪些运行时」变成可执行的断言,而不只是一句文档。
|
|
49
|
+
- **逐条范围断言四个边界**:**必须放行** 0.1.7-rc.1 / 0.1.7-rc.2 / 0.1.9 / **0.2.0-alpha.1** / 0.2.0-rc.1 / 0.2.0 / 0.2.9;**必须拒绝** 0.3.0-rc.1 / 0.3.0 / 1.0.0。只断言「0.2.0-rc.1 能过」是不够的——那样把范围放宽成完全没有上界的 `>=0.1.7-rc.1` 也会通过。
|
|
50
|
+
- **镜像宿主而不是自创判定**:宿主用 `semver.satisfies(runtime, range, {includePrerelease: true})`,本测试用同一选项,并**单独断言该选项确实是必需的**(同一条范围在默认选项下会拒绝 `0.2.0-rc.1`)——否则将来 semver 语义一变,断言会静默地不再测宿主真正做的事。
|
|
51
|
+
- **点名禁止 `<0.2.0-0` 回归**:把这次故障的写法单独钉死,因为它在「整理版本范围」时很容易被误改回去。
|
|
52
|
+
- **守护开发树一致性**:断言每个声明了 peer 范围的包都在 `devDependencies` 里有钉住的版本、且该版本落在自己声明的范围内;另断言 `dsh.client.inject` 里的每个客户端包都有 devDependency 钉版本。(客户端 roster 那 5 个包没有 peer 范围——宿主从自己的图解析它们,因此只断言「有钉版本」,不虚构范围。)
|
|
53
|
+
- **变异验证**(确认测试真的有约束力,不是空转):把范围改回 `<0.2.0-0` → **10 例失败**;改成无上界的 `>=0.1.7-rc.1` → **8 例失败**;把 peer 下界抬到 `>=0.2.0-rc.1` 但留下 0.1.7 的 devDependency 钉 → **8 例失败**。三次变异全部被抓住,恢复后 22 例全绿。
|
|
54
|
+
|
|
55
|
+
### Docs
|
|
56
|
+
|
|
57
|
+
- **新增 `docs/DSH_0.2.0_RC1_IMPACT_CHECK.md`**:0.1.7-rc.2 → 0.2.0-rc.1 的完整影响面核对(261 提交 / 1109 文件 / 0 处破坏性提交;插件触及的包逐字节比对;类型级与运行时值级符号核对各 0 处移除;端到端类型验证)。含方法论备注:宿主 `app.asar` 内的 payload 基准在本机实测为 `8 + headerSize`(不是常见的 `16 + headerSize`),需用条目 `integrity.hash` 做 SHA256 校验后定位,否则会读出错位数据。
|
|
58
|
+
- **README(中英)新增「peer 上界为什么不写 `<0.2.0`」**:给出三种写法的对照表与故障成因,说明该类错误为什么没有 CI 能拦、只能靠专门的测试。
|
|
59
|
+
|
|
60
|
+
## 2.3.1 (2026-09-26)
|
|
61
|
+
|
|
62
|
+
### Features
|
|
63
|
+
|
|
64
|
+
- **Windows 真机验证脚本 `scripts/verify-windows.mjs`**:把「Windows 上到底能不能读到 Trae 登录」从一串手工步骤变成**一条命令给出结论**。
|
|
65
|
+
- **跑的是插件自己的构建产物**(`lib/`),不是重写一份逻辑——单元测试只能证明「代码在 Windows 上不崩」,因为 CI 的 windows-latest 上并没有装 Trae;真机验证要回答的是「这台机器上的 Trae 登录文件,插件读不读得到」,只有把真机文件交给真代码才成立。
|
|
66
|
+
- 报告四项:① 它在这台机器上探测的**每条路径 + 文件是否存在**;② 真实账号解析解出了几个账号;③ 即将发给 Trae 的设备指纹(`x-device-type` / `x-os-version` / `x-device-id` / `x-app-version`);④ 全部通过 / 几项未通过的结论。
|
|
67
|
+
- **输出默认脱敏,可直接贴公开 issue**:Windows 用户名替换为 `<user>`(`C:\Users\<真名>\...`)、账号名与设备号只报**形态**(长度 / 是否纯数字)。脱敏而非全删是关键——`Trae CN` vs `trae-cn` 这个目录拼写正是报告唯一要查的东西,抹掉它报告就没价值。
|
|
68
|
+
- 脱敏规则抽到 `src/redact.ts`(`maskUserPath` / `describeNameShape` / `describeIdShape`)并由脚本从插件导出中取用,避免「两处各写一版、改一处漏一处」。**+11 条单元测试**,既断言敏感值必须消失,也断言目录拼写/形态信息必须保留(只测「名字没了」的话,一个恒返回 `<redacted>` 的实现在测试下也会通过)。
|
|
69
|
+
- 随 npm 包分发(`files` 白名单新增该单文件 8.8KB):README 指引用户运行它,不随包则 npm 用户根本执行不了这一步。
|
|
70
|
+
- **非 Windows 平台会显式说明**:脚本在 macOS / Linux 上也能跑,但会标注「当前平台非 Windows,平台相关的失败属预期」,避免被误读成兼容性缺陷。
|
|
71
|
+
|
|
72
|
+
### Bug Fixes
|
|
73
|
+
|
|
74
|
+
- **验证脚本在 Windows 上启动即崩,一条检查都跑不到**(在 Windows 真机上实测发现):`scripts/verify-windows.mjs` 的 `loadPlugin()` 写的是 `import(join(here, '..', 'lib', 'index.js'))`,把**路径**当成 **URL** 传给了 `import()`。Node 把 `C:\...` 解析成 scheme 为 `c:` 的 URL,抛 `ERR_UNSUPPORTED_ESM_URL_SCHEME`("On Windows, absolute paths must be valid file:// URLs")——**恰好在这个脚本唯一存在的目的平台上崩**。修法:改用 `pathToFileURL(local).href`。
|
|
75
|
+
- **为什么 CI 没拦住**:`.github/workflows/ci.yml` 只跑 typecheck / test / build,**从不执行这个脚本**;而 `tests/verify-windows.spec.ts` 测的是 `src/`,脚本是个独立的 `.mjs` 入口——「代码正确」与「入口能跑起来」是两件事,这条缝隙正好够一个致命启动错误通过。这不是笔误而是覆盖盲区:对**以单文件入口形式交付**的产物,测它所调用的库不等于测它本身。
|
|
76
|
+
- **回归测试**(+1 例,全仓 337 → 338):把脚本**复制到临时目录**并配一个桩 `lib/index.js`(令 `existsSync(local)` 为真,从而精确命中崩掉的那个分支),再以**子进程**执行它,断言 ① 不出现 `ERR_UNSUPPORTED_ESM_URL_SCHEME`、② 输出走到了「结论:」而非死在加载器里。用桩而非本仓 `lib/` 是刻意的:`lib/` 不进版本库,且 CI 在**测试之后**才构建,测试不能依赖产物已存在。
|
|
77
|
+
- **变异验证**:把脚本退回 `import(local)` → 该用例失败(`stdout` 为空);确认两条 stderr 断言也真的有约束力(同一变异下 `stderr` 确实含该错误码),不是空转。
|
|
78
|
+
- **`[2]` 段把「已成功解出账号的那个文件」报成 `invalid`**(同一轮真机演练发现):`TraeCredentialStore` 绑定单个 `storagePath` 时,会把该路径**同时当作桌面 `storage.json` 和 CLI token 文件各探一次**(覆盖路径的形态事先不可知,见 `candidates()`),因此直接打印 `diagnose()` 的 `failures` 会导致:① 每条路径重复出现两遍;② **刚刚成功解出账号的那个文件**被列为 `invalid`——一次按「不是存储文档」、一次按「不是 CLI token」。一台**唯一凭证已被成功找到**的机器,报告读起来却像有故障。修法:两段合并为**一次遍历**(`accounts()` 与 `diagnose()` 读同一批文件,探两遍纯属重复劳动),按路径**合并成一个结论**,命中账号的路径记为「已解出」;失败原因优先取更具体的一项(`missing` 是两种形态对不存在的文件的共同答案,非 `missing` 才说明读到了内容但被拒)。
|
|
79
|
+
- 实测修正前后:`invalid [solo] ...TRAE SOLO CN\...` + 每行重复两遍 → `已解出 [solo] ...TRAE SOLO CN\... (桌面)`,与「解出至少一个账号」的结论一致。
|
|
80
|
+
|
|
81
|
+
- **Windows 上只探测单一目录名,且这条路径从未在真机验证过**("项目要支持 Windows")。Trae 是 VS Code 系 Electron 应用,其每用户数据目录由安装器注册的产品名决定;插件此前在 Windows 上只按 macOS 的名字(`Trae CN` / `TRAE SOLO CN`)各探一条路径,猜错就直接「未登录」,而 `docs/WINDOWS_TOKEN_PROBE.md` 自己就写着「目录名是从 macOS 抄过来的,Windows 上实际名称未经确认」——即这条主路径从未验证,且失败时用户只看到「未登录」。
|
|
82
|
+
- **依据(2026-09-26 实测 macOS 包)**:`product.json` 的 `win32DirName` 才是 Windows 侧目录名的权威字段——`Trae CN` 为 `Trae CN`(`applicationName: trae-cn`)、`TRAE SOLO CN` 为 `TRAE SOLO CN`(`applicationName: trae-solo-cn`)。因此 macOS 拼写大概率正确,但同一产品族在 Linux 用的是小写 `applicationName` 拼写,Windows 实际写哪个未验证。
|
|
83
|
+
- **修法**:新增 `traeWindowsAppNames()`,Windows 侧改为**多候选并列探测**(`Trae CN` + `trae-cn`、`TRAE SOLO CN` + `trae-solo-cn`),与 Linux 侧既有的 `LINUX_APP_NAMES` 处理方式对齐;`src/identity.ts` 读 `product.json` 也共用同一份拼写表,避免「一个文件认这个名字、另一个不认」的不对称。**不再列出仅大小写不同的重复项**——Windows 文件系统不区分大小写,`trae cn` 与 `Trae CN` 是同一个目录,列两遍只会让探测清单和卡片上的「已检查的路径」重复一倍。
|
|
84
|
+
- **明确未解决的部分**:Windows 真机上的实际目录名**仍然没有验证**(本机为 macOS,无 Windows 环境)。多候选只是把「猜错即失败」降级为「多一次失败的 `readFile`」,并不是验证。`docs/WINDOWS_TOKEN_PROBE.md` 保留为待真机回报的清单,并把「第二步:确认数据目录名」标为仍需执行。
|
|
85
|
+
- **回归测试**(+2 例,全仓 319 → 322):`paths.spec.ts`「探测 Windows 两种拼写」「不列出仅大小写不同的重复项」;`identity.spec.ts`「备用拼写(`trae-solo-cn`)下也能读到 `product.json`」。变异验证:`paths.ts` 退回单一目录名 → 1 条失败;`identity.ts` 退回单一拼写 → 1 条失败。
|
|
86
|
+
|
|
87
|
+
- **`src/model-cache.ts` 的缓存路径与凭据路径各自为政,在真机上指向一个不存在的目录**(Windows 真机适配检查时发现,属**潜伏缺陷**):
|
|
88
|
+
- **症状**:该模块把 `state.vscdb` 路径**硬编码为单一拼写 `Trae CN`**(macOS 与 Windows 两分支都写死这一个名字),而 `paths.ts` 早已是**多候选拼写**探测(`Trae CN` / `trae-cn`、`TRAE SOLO CN` / `trae-solo-cn`)。它**自己重算了一遍目录**,没有复用 `traeStorageCandidates`——即 2.3.1 修「多候选」时只修了登录路径,漏了这个兄弟模块。
|
|
89
|
+
- **真机证据**(Windows 10.0.22621 + TRAE SOLO CN):真实数据目录是 `TRAE SOLO CN`,而旧代码拼出的是 `%APPDATA%\Trae CN\User\globalStorage\state.vscdb` —— **该文件根本不存在**(实测 `existsSync` 为 false,而 `TRAE SOLO CN\...\state.vscdb` 存在)。**即便装了 `sqlite3` 也读不到**,与「装了 `sqlite3` 且小写拼写才失效」的初步判断相比,实际失效面**更大**:只要装的不是目录名恰好为 `Trae CN` 的版本就一定失败。
|
|
90
|
+
- **为什么一直没被发现**:Windows 上 `sqlite3` 通常不存在,该调用**必然**先抛 `ENOENT`;调用方 `.catch(() => undefined)` 兜底后功能退化为「无缓存」,于是**一个错误的路径被另一个看似合理的错误(依赖缺失)掩盖**。这类「两个错误互相掩护」的情形,单看错误码永远排查不到——只有把期望的路径与磁盘实际路径对照才暴露。
|
|
91
|
+
- **修法**:不再拼写目录,改为**从凭据路径推导**——`state.vscdb` 与 `storage.json` 同在 `globalStorage`,故同一个候选目录既管登录也管缓存;`traeStateDatabaseCandidates()` 复用 `traeStorageCandidates()`(单一事实来源),`raw-resolver.ts` 把命中的候选传下去。**明确带 `candidate` 时只看该 edition**:多版本共存时读另一个安装的数据库会拿到**别的账号**的模型表。选择顺序是「先看存在的,都不存在才用最可能的那条」,这样 `sqlite3` 缺失时调用方仍能看到**真实**失败,而不是被换成一个猜出来的错误。
|
|
92
|
+
- **回归测试**(+7 例,全仓 338 → 345):① 每个凭据候选的兄弟 `state.vscdb` 都必须可探测(**断言「与凭据表同源」而非硬编码期望字符串**——跟着写死拼写的测试会与 bug 一致地通过);② Windows 上必须能找到 `TRAE SOLO CN`(旧的写死实现永远够不到);③ 带 `candidate` 时它排第一且不越出自己的 edition;④ 大小写不同不重复列;⑤ macOS / Linux 也从各自凭据表推导;⑥ **选中的路径真的传给了执行器**(用注入的 `runSqlite` 断言 argv,否则「列表正确但查错条目」无法被发现);⑦ 全都不存在时仍发起一次查询以暴露真实失败。
|
|
93
|
+
- **变异验证**:把实现退回「硬编码单一拼写」→ **9 条中 5 条失败**;恢复后全绿。(首版变异脚本因文件是 CRLF 而静默未生效,导致测试「通过」——已加断言令变异未落地时**直接报错**,否则这种假阴性会被误读成「测试抓不到这个 bug」。)
|
|
94
|
+
- **顺带修掉一个测试自身的问题**:⑥ 首版用 `path.includes('Library/Application Support')` 断言 macOS 路径,而 `join()` 在 Windows 上产出反斜杠——该测试在作者机器(macOS)通过而会在 CI 的 `windows-latest` 上失败。现先归一化分隔符再断言,这正是本项目「Windows 必须真机/win runner 验证」这条约定要防的错。
|
|
95
|
+
|
|
96
|
+
- **切换账号后无法领取签到:把「本机已签到」误判成「该账号已领取」**(用户上报)。2.3.0 的卡片与路由把 `checked_in` 与 `did_checked_in` **一并**当作「今日已领取」,于是切换账号后新账号显示「今日已领取」、按钮置灰、点不动。
|
|
97
|
+
- **根因(2026-09-26 实测,两个 CN 账号 + 同一台机器 + 同一北京日)**:两个字段判定维度不同——`checked_in` 是**账号**口径(该账号今天的奖励包已存在),`did_checked_in` 是**设备**口径(这台机器的今日名额已用掉,由 `x-device-id` 决定,与是哪个账号用的无关)。切换账号后正是后者:设备名额已用,而当前账号**没有拿到任何奖励**——此时说「今日已领取」把「没领到」说成了「已领到」。(详见 `docs/USAGE_API_RESEARCH.md` 新增的「决定性修正」一节,含四组请求头对照表。)
|
|
98
|
+
- **实测要点**:账号 A(当天已领取)与账号 B(当天未领取,无 `checkin_20260926_*` 奖励包)带**同一真实设备号**读状态,两者都返回 `did_checked_in:true`;不带该头、带合成号、或带本机另一安装的设备号,两者都返回 `false`;同一合成号连读三次也不会变 `true`(所以它不是「读过就算」的标记)。账号 B 带真实设备号 `claim` → `{"code":9095,"message":"当前设备今日已经签到,请明日再来哦~"}`,**未发放**,且 `total/consumed` 前后逐字节相同(`1900 / 1522.85`)。官方 NLS 文案 `2345 -> 该设备今日已参与签到` 亦为设备口径。
|
|
99
|
+
- **修法**:`checked_in` 成为唯一的「今日已领取」(账号口径)判据——已领取才直接返回 `alreadyCheckedIn` 且不发请求;`did_checked_in` 且未领取时,路由返回新的 `deviceCheckedIn: true`(不再谎报 `alreadyCheckedIn`),卡片保持按钮禁用但改说「本机今日的签到已用掉」并说明可换设备 / 明天再来;上游以 `9095` 拒绝时(窗口竞争)同样按 `deviceCheckedIn` 处理,**不当成失败**——那是一条上游规则,不是插件故障。
|
|
100
|
+
- **回归测试**(+4 例,全仓 316 → 319):路由侧「设备已签但账号未领 → 不发请求且返回 `deviceCheckedIn:true` / `alreadyCheckedIn:false` / `code:9095`」「上游 9095 → 按设备已满上报而非错误」,卡片侧「设备已签不显示『今日已领取』且显示设备说明」「路由回 9095 时显示设备说明而非『签到失败』」。做过**变异验证**:把路由的 `checkedIn || didCheckedIn` 复原 → 1 条失败;把卡片的两个字段合并 → 1 条失败;删掉路由的设备守卫(照发请求)→ 1 条失败。
|
|
101
|
+
|
|
102
|
+
### Tests
|
|
103
|
+
|
|
104
|
+
- **Windows 真机验证脚本的三处可用性缺陷**(在真正按「Windows 用户拉代码 → 跑脚本」的流程演练时发现,全部是脚本自身的问题):
|
|
105
|
+
- **空机器误报「解出 1 个账号」**(最严重):`TraeCredentialStore` 不传 `storagePath` 时会按 `process.platform` 扫描**宿主**目录,于是在一个刻意清空的 HOME 上,脚本从开发者本机解出了凭证——即「断言在一台没有任何 Trae 的机器上找到了 Trae」。这是本脚本可能产出的最误导性结果,因为它恰好会被读成「Windows 能用了」。修法:每个候选都**绑定到它自己的路径**,绝不回退到宿主扫描;并新增 4 条回归测试(`tests/verify-windows.spec.ts`)锁死该性质,变异验证:让 store 忽略固定路径 → 2 条失败。
|
|
106
|
+
- **`[1]` 与 `[2]` 可能描述不同机器**:`[1]` 列的是 win32 候选,`[2]` 却扫宿主平台目录。现在两段同源,且 `[2]` 逐候选诊断并给出**命中路径**归属。
|
|
107
|
+
- **未构建时的报错不可用**:`lib/` 不进版本库,拉代码后直接跑脚本会撞 `ERR_MODULE_NOT_FOUND` 堆栈。现在明确提示「这是源码 checkout、需要先 `pnpm install && pnpm run build`」,并以退出码 1 结束(实测)。
|
|
108
|
+
- **CLI-only 场景的两处误判**:① 身份解析误把 CLI token 当 `storage.json` 解析而抛 JSON 错——`resolveTraeIdentity` 的 CLI 兜底**只在文件不存在时**触发,故 CLI 候选改用 `readTraeCliIdentity` 直连;② `x-app-version` 缺失被计为失败,但那只是少一个请求头(登录/聊天/签到均不受影响),会让一台完全可用的机器报「未通过」,现改为 `[INFO]`,并对 CLI 候选改用不同措辞(CLI 从 `ide_version.json` 取版本,而非 `product.json`)。
|
|
109
|
+
- **修复 Windows CI 的冷启动超时抖动**(CI #60 失败,同一份代码在 #59 / #61 通过)。`windows-latest` 上每个测试文件的**第一个**用例会吃到一次性的模块转换成本,超过 vitest 默认的 5000ms:
|
|
110
|
+
- 实测(#60,该次仅改 `.md`,代码与通过的那次逐字节相同):`identity.spec.ts` 首个用例 5179ms 超时、同文件后续用例 6–60ms;`card-checkin.spec.tsx` 首个用例 9435ms 超时、后续 50–196ms;该次总计 `transform 17.71s / import 24.28s`。
|
|
111
|
+
- **判定为抖动而非缺陷的依据**:只有每个文件的第一个用例受影响,同文件其余用例均在毫秒级;且同一份代码在前后两次 CI 均通过。
|
|
112
|
+
- **修法**:`vitest.config.ts` 设 `testTimeout: 20_000`,**不动任何断言**。已验证超时机制仍生效(用一个必然挂起的探测用例确认仍会失败,探测文件随后删除)。
|
|
113
|
+
- **明写在配置注释里**:不得用「放宽断言」的方式消除超时——若某个原本很快的用例在**所有**平台上都开始超时,那是真实回归,改这个数字不是答案。
|
|
114
|
+
|
|
115
|
+
### Docs
|
|
116
|
+
|
|
117
|
+
- **Windows 真机验证取得首个通过结果,README / 验证指引的「未验证项」措辞随之收敛**:此前 README(中英双语)与 `docs/WINDOWS_VERIFY_GUIDE.md` 都以「Windows 上 Trae 数据目录的实际名称尚未在真机确认过」为前提写成,而现在已有真机证据,把这句话留着就是反向的失真(本项目对「未验证项不得写成既成事实」有明确要求,反过来把**已验证项**写成未验证同样会误导)。
|
|
118
|
+
- **实测结果**(Windows 10.0.22621 + TRAE SOLO CN,`node scripts/verify-windows.mjs` 退出码 0):命中 `%APPDATA%\TRAE SOLO CN\User\globalStorage\storage.json`;解出 1 个账号(`solo`/`cn`);`x-device-type: windows`;`x-os-version: Windows 10.0.22621`;`x-device-id` 为 16 位纯数字(来自数据目录,**非**兜底哈希);`x-app-version: 0.1.56`(取自 `%LOCALAPPDATA%\Programs\TRAE SOLO CN\resources\app\product.json`)。
|
|
119
|
+
- **确认的是**:`product.json` 的 `win32DirName` 作为 Windows 目录名依据**是对的**——`TRAE SOLO CN` 确实是真机上的实际目录名;安装路径推导与设备指纹四个字段也都成立。即「Windows 上插件读得到 Trae 登录」由真机证据支持,不再是推断。
|
|
120
|
+
- **仍然未验证的是**:`Trae CN` / `trae-cn`(中国版)两个拼写——那台机器只装了 SOLO 版。README 与验证指引都**明确保留**这一条并说明「它在此机器上不存在只反映本机没装该版本,不代表拼写有误」,避免读者把「本机没命中」误读成「拼写错了」。
|
|
121
|
+
- 因此平台支持表的 Windows 行由「目录名见下方『未验证项』」改为「**已在一台真机通过验证**」,并新增「真机验证结果」小节逐项列出实测值;`docs/WINDOWS_VERIFY_GUIDE.md` 删去「本机(macOS)无法执行这一步」的旧前提,改为状态节 + 实测输出样例;`docs/WINDOWS_TOKEN_PROBE.md` 文首改用状态更新说明,并在第二节与「已知问题」逐条标注哪些已由真机确认。
|
|
122
|
+
- `docs/WINDOWS_VERIFY_GUIDE.md` 的样例输出**改为真实输出的形态**(含 `[2]` 段一行一个候选的写法与退出码 0/1/2 的含义),此前的样例是示意性的,与实际输出格式已有偏差。
|
|
123
|
+
- **验证脚本的检查范围被明确划出,并补跑脚本之外的主流程实测**:`verify-windows.mjs` 的六项**只覆盖路径探测与设备指纹**,把它读成「整个插件在 Windows 上没问题」是过度外推。因此同一台真机上另外直接调用 `lib/` 导出跑了主流程:
|
|
124
|
+
- `TraeCredentialStore.resolve()` ✅ 返回凭据(host `https://api.trae.cn`);`identityHeaders()` ✅ 10 个请求头全部生成;只读用量查询 ✅ 9 个积分包(总额 2050 / 已用 1522.85);只读模型目录 ✅ **42 个模型**;`writeFileAtomic` ✅ 写入回读一致;并发 `withFileLock` ✅ **8 并发串行化且无丢失更新**(此前 README 只写「宿主原生处理 Windows 独占创建语义」,现改为**实测成立**);`SSE` 解码 ✅ 按 `\r?\n` 切分,CRLF 与分块均支持。
|
|
125
|
+
- **实测确认了一处已知限制的真实行为**:`model-cache` 的 `sqlite3` 在本机不存在,抛 `ENOENT`(`spawn sqlite3 ENOENT`),调用方 `.catch(() => undefined)` 正确兜底,主流程不受影响——README 原写「会失败并安全回退」,现补上错误码作为依据。
|
|
126
|
+
- **顺带发现并修掉一处真实精度缺口**(详见下方 Bug Fixes):`src/model-cache.ts` 在 Windows 上把 `state.vscdb` 路径**硬编码为单一拼写 `Trae CN`**(`paths.ts` 是多候选 `Trae CN` / `trae-cn`),且该模块自己重算了一遍目录而没复用 `traeStorageCandidates`。本机真实目录名是 `TRAE SOLO CN`,旧代码指向的 `...\Trae CN\...\state.vscdb` **根本不存在**;只因 `sqlite3` 缺失、该调用必然失败并兜底,才把一个**错误的路径**藏在「依赖缺失」这个看似合理的错误后面。
|
|
127
|
+
- 以上「脚本之外」的实测结果同时写入 README(中英)与 `docs/WINDOWS_VERIFY_GUIDE.md`,与脚本自身那六项**分表列出**,不让读者混淆两者的证据强度。
|
|
128
|
+
- **`docs/WINDOWS_TOKEN_PROBE.md` 的「已知的其他 Windows 问题」整节已过期**:其中两条(`product.json` 硬编码 macOS 路径、`osVersion` 拼成 `win32 <release>`)**在代码里早已修复**,文档却仍写成未解决的缺陷——按它排查会把 Windows 用户引向错误方向。已复核并改写为「已处理 + 当前实现」,同时补上「Windows 安装/数据目录名仍未验证」这一条真实缺口,以及 `mode: 0o600` 在 Windows 被忽略属已知无害。
|
|
129
|
+
- 该文档**改为「先跑脚本」**:手工五步降级为「脚本跑不起来时的备选路径」,并把第二步(数据目录名)更新为当前的**多候选拼写**清单与依据,明确「这一条是本次要查的核心」。
|
|
130
|
+
- **README 明确 Windows 支持,并把「以后都要考虑 Windows」固化为开发约定**("我们的项目要支持 Windows 的" / "之后都要考虑对 Windows 的影响")。
|
|
131
|
+
- README(中英双语)新增**平台支持表**(macOS ✅ 完全支持 / Windows ✅ 支持 / Linux ✅ 支持),并在开头段落明示「支持 macOS、Windows 与 Linux」+ CI 双平台矩阵,让 Windows 支持成为对外可见的承诺而非隐含行为。
|
|
132
|
+
- 「Windows 说明」节**改写为与代码逐条对应**:账号目录(多候选拼写探测 + 与安装目录无关)、应用版本头(含 `LOCALAPPDATA` 回退)、设备指纹(`x-device-type: windows`、`Windows <release>`)、文件锁(宿主原生处理)、Raw Chat 的 `sqlite3` 限制、权限位被忽略。**每条都核对了代码位置后写入**。
|
|
133
|
+
- **删掉一句未经验证的断言**:原文写「目录名与 macOS 一致,**无需额外配置**」——而 `docs/WINDOWS_TOKEN_PROBE.md` 自己就说该目录名从未在真机确认。这正是本项目反复强调的「不得把未验证项写成既成事实」,故改为「未验证项(欢迎回报)」小节,说明依据是 `product.json` 的 `win32DirName`、兜底是 `applicationName` 拼写,并给出回报路径与 `authFile` 临时绕法。
|
|
134
|
+
- `DEVELOPMENT.md` 新增「**多平台要求(Windows 为长期支持目标)**」一节:给出平台相关面/无关面的分区表(路径解析与设备指纹为相关面,其余共用),以及 6 条改动强制检查项——新增路径必须带平台分支且多候选、不得用仅大小写不同的重复候选、新增外部命令依赖须确认 Windows 行为并登记限制、CI 必须在 `windows-latest` 通过、平台结论必须区分已验证/未验证、文档同步。这条约定使「考虑 Windows 影响」成为可执行的检查项而非口头要求。
|
|
135
|
+
- **顺带修掉一处链接会在 npm 页 404 的问题**:本次新增的「未验证项」指引用户去看 `docs/WINDOWS_TOKEN_PROBE.md`,但 `package.json` 的 `files` 白名单(`RELEASING.md` 有明确记载,属有意的包体控制)只放行 `docs/assets/` 下那张截图,**所有 `docs/*.md` 都不进 npm 包**——写成相对 Markdown 链接会在 npm 页面上 404。既有引用(`docs/IMPLEMENTATION_PLAN.md` 等)用的是反引号纯文本故不受影响,本次改为指向 GitHub 的绝对链接(实测 HTTP 200)。
|
|
136
|
+
- `RELEASING.md` 的打包内容清单同步更新(新增 `scripts/verify-windows.mjs`),并记下两条纪律:该脚本必须随包(否则用户无法执行验证步骤)、`docs/*.md` 不进包故 README 引用必须用绝对链接。
|
|
137
|
+
|
|
138
|
+
|
|
3
139
|
## 2.3.0 (2026-09-25)
|
|
4
140
|
|
|
5
141
|
### Breaking Changes
|
|
@@ -93,8 +229,8 @@
|
|
|
93
229
|
- **领取前的守卫比上游更强**:路由 `POST /plugins/dsh-connect-trae/checkin` 仅接受 POST、仅接受回环来源、仅服务 `cn` 区域,并且**先读状态**——已领取(`checked_in` 或 `did_checked_in` 任一为真)就直接返回 `alreadyCheckedIn` 且**根本不发领取请求**;活动关闭答 409。国际版直接答 404 并说明原因,而不是把上游的 HTML 404 当成网络故障抛给用户。
|
|
94
230
|
- **已实测上游按北京自然日幂等**:重复领取仍回 `code: 0`,而权益总额(`total=1600, consumed=390.8`)逐字节不变,**不会重复发放**。插件仍然自行守卫——「不会重复发放」是上游的行为,不是插件可以依赖的保证。
|
|
95
231
|
- **业务拒绝不当成故障**:`claimed: false` + 原始业务码与 message 原样回给卡片并显示出来,而不是包成 500。否则用户只会看到「领取失败」,而丢掉唯一能解释原因的 `9004`。
|
|
96
|
-
- **对齐官方客户端的状态判定**:`checked_in` 与 `did_checked_in`
|
|
97
|
-
- 卡片新增 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
|
|
232
|
+
- **对齐官方客户端的状态判定**:`checked_in` 与 `did_checked_in` 是两个独立字段,官方在「今日已领取」判定上也参考后者,插件据此保持按钮禁用。~~插件在卡片与路由两侧都按同一规则判定~~ —— **注:这里的口径在 2.3.1 被修正**。当时把两者一并当作「今日已领取」是**错的**:`did_checked_in` 实际是**设备**口径(`x-device-id`),不是账号口径,切换账号后会谎报「已领取」。以 2.3.1 为准;`extra_credits` 的展示(避免把当天实际到账的 200 说成 150)保持不变。
|
|
233
|
+
- 卡片新增 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 条失败。(其中针对 `did_checked_in` 的两条断言在 2.3.1 按其真实语义重写。)
|
|
98
234
|
- 取证与接口契约:`docs/USAGE_API_RESEARCH.md` 新增「每日签到领取」一节(含决定性请求头对照表、幂等性实测表、区域限制与官方字段语义)。
|
|
99
235
|
|
|
100
236
|
## 2.0.6 (2026-09-22)
|
package/README.en.md
CHANGED
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
|
|
27
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
28
|
|
|
29
|
+
**Runs on macOS, Windows, and Linux**: CI runs the full typecheck / test / build matrix on `ubuntu-latest` and `windows-latest`, and Windows gets purpose-built handling for directory probing, app version headers, and lock behavior — see [Windows notes](#windows-notes).
|
|
30
|
+
|
|
29
31
|
> 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.
|
|
30
32
|
|
|
31
33
|
## Features
|
|
@@ -37,9 +39,10 @@ A [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) bund
|
|
|
37
39
|
- **DSH local tool loop** — gets pending structured `tool_calls` from Trae `llm_utils_chat`, lets DSH execute its own local tools, then returns tool results to the model.
|
|
38
40
|
- **Dual-region detection (CN / international)** — detects local sign-ins across Trae CN, TRAE SOLO CN, Trae, and TRAE SOLO installs. The region is derived from the credential's own `userRegion` claim (host suffix and edition label as fallbacks), with no manual switch.
|
|
39
41
|
- **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.
|
|
42
|
+
- **Vision follows Trae's own declaration** — when Trae marks a model `multimodal`, the plugin advertises it as such with no manual tick per model (issue #16). The rule is "**models you tick ∪ models Trae marks multimodal**": models Trae marks `false`, or does not mark at all, stay text-only and can still be turned on by hand. A row enabled automatically is shown ticked and read-only, with the reason. See "Image input" below.
|
|
40
43
|
- **Account switching** — refreshes the token list and lets users select an account without storing tokens in DSH settings.
|
|
41
44
|
- **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
|
|
45
|
+
- **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 that account has been paid). This is the plugin's **only** operation that changes account state, and it runs only when you click. Trae allows **one check-in per device per day**: after switching accounts, if this machine's slot is already spent (by any account), the selected account cannot claim here that day — the card explains that rather than pretending the account was paid. The international side has no check-in activity, so no button is shown there.
|
|
43
46
|
- **Secure loopback shim** — one random port + in-process random secret per region; the real Trae token is never handed to pi-ai.
|
|
44
47
|
|
|
45
48
|
## How it works
|
|
@@ -57,28 +60,47 @@ DSH PiAiAdapter (one stack per provider)
|
|
|
57
60
|
|
|
58
61
|
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**.
|
|
59
62
|
|
|
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).
|
|
63
|
+
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). That device id is also what Trae keys the "one check-in per device per day" rule on, so switching accounts does not reset the day's slot. 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).
|
|
61
64
|
|
|
62
65
|
> See `docs/IMPLEMENTATION_PLAN.md`, `docs/SOLO_ROUTE_DECISION.md`, `docs/USAGE_API_RESEARCH.md`.
|
|
63
66
|
|
|
67
|
+
## Image input
|
|
68
|
+
|
|
69
|
+
The plugin declares whether a model accepts images, and DSH uses that to decide whether an attachment is **sent as-is** or **degraded into a text description**. That makes the declaration a hard switch, not a cosmetic label.
|
|
70
|
+
|
|
71
|
+
The rule is the **union of two sources** (issue #16):
|
|
72
|
+
|
|
73
|
+
| Source | Meaning |
|
|
74
|
+
|---|---|
|
|
75
|
+
| Trae's own `multimodal` field | Trae's own answer to "does this model read images". The Trae IDE's attach-image button is driven purely by this field, with no user switch |
|
|
76
|
+
| Your manual `imageModelIds` ticks | Explicit choice. Even if Trae later stops reporting the flag for that model, your tick is never revoked |
|
|
77
|
+
|
|
78
|
+
Notes:
|
|
79
|
+
|
|
80
|
+
- **Only `multimodal === true` enables it automatically.** A missing field (a directory saved by an older release, a fallback row) or an explicit `false` stays text-only — better to not send than to advertise a capability we cannot confirm.
|
|
81
|
+
- **Automatic does not mean automatic sending.** It only lets DSH attach an image to that model; you still choose to attach one per message. The plugin never sends an image on your behalf.
|
|
82
|
+
- **An automatically enabled row is shown ticked and read-only.** Under the union, unticking it would be re-applied immediately, so offering a clickable box would be a lie. There is currently no per-model "force off" escape hatch; if you want one, say so on issue #16.
|
|
83
|
+
- Manual ticks still work for models Trae marks `false` (or does not mark), which is the escape hatch for "upstream does not say so, but it actually works".
|
|
84
|
+
|
|
64
85
|
## Model coverage
|
|
65
86
|
|
|
66
87
|
This plugin serves models from Trae's **SOLO channel** (`DeepSeek-V4-Flash-Official`, `DeepSeek-V4-Pro-Official`, `GLM-5.3`, `GLM-5.2`, `Kimi-K3`, `MiniMax-M3`, `Qwen3.8-Max`, `Doubao-Seed-*`, …), for both the domestic and international sides.
|
|
67
88
|
|
|
68
|
-
The following
|
|
89
|
+
The following three models currently come from the **TraeCode (Trae IDE) channel** and have **not** been opened up to the SOLO channel, so this plugin does **not** support them yet:
|
|
69
90
|
|
|
70
91
|
| Model |
|
|
71
92
|
| --- |
|
|
72
|
-
| `deepseek-v4.1-flash` |
|
|
73
93
|
| `glm-5.3-flash` |
|
|
74
94
|
| `kimi-k2.8-preview` |
|
|
75
95
|
| `qwen3.8-flash` |
|
|
76
96
|
|
|
77
|
-
These
|
|
97
|
+
These three are reachable only inside the Trae IDE client; there is no callable channel for them on the plugin side. **They can only be supported once Trae officially opens them up to the SOLO channel.**
|
|
98
|
+
|
|
99
|
+
> `deepseek-v4.1-flash` used to be listed here — `docs/DS41_CALLABILITY.md` (evidence gathered 2026-09-15) measured `4001 param is invalid` from all eight SOLO functions. **Trae has since opened it up to the SOLO channel**: it now ships in the live directory with `wireFunction: solo_work_remote` and calls succeed (verified against a live refresh). This page had not caught up, so it is removed from the table.
|
|
78
100
|
|
|
79
101
|
> Note the distinction: **`GLM-5.3` is supported** (it goes through the SOLO channel), but **`glm-5.3-flash` is not** — they are different models.
|
|
80
102
|
|
|
81
|
-
To use these
|
|
103
|
+
To use these three models today, use the **Trae IDE itself**.
|
|
82
104
|
|
|
83
105
|
## Install
|
|
84
106
|
|
|
@@ -97,6 +119,23 @@ Restart the DSH process after install/update/uninstall.
|
|
|
97
119
|
## DSH version compatibility
|
|
98
120
|
|
|
99
121
|
**Requires DSH 0.1.7-rc.1 and up** (since 2.3.0 the plugin no longer targets hosts older than 0.1.7; the peer dependency ranges are narrowed accordingly).
|
|
122
|
+
**The peer upper bound is `<0.3.0-0`, so both the 0.1.7 and the 0.2 line are supported.**
|
|
123
|
+
|
|
124
|
+
The 0.2.0 line breaks **no** contract this plugin consumes — a symbol-by-symbol check confirmed every API it imports is unchanged ([full 0.2.0-rc.1 impact check](https://github.com/dingminhua/dsh-connect-trae/blob/main/docs/DSH_0.2.0_RC1_IMPACT_CHECK.md)). But since 0.1.7 DSH enforces a **bundle-level peer gate**: a bundle whose peers are unsatisfied is skipped **whole**, with one line on stderr and nothing in the UI — the plugin simply disappears.
|
|
125
|
+
|
|
126
|
+
### Why the upper bound is not `<0.2.0`
|
|
127
|
+
|
|
128
|
+
2.3.1 wrote `<0.2.0-0`, meaning "not the 0.2.0 release yet". That spelling is **wrong**: in SemVer `0.2.0-rc.1 < 0.2.0-0`, so it excluded the entire 0.2.0 **prerelease** line, and the plugin went dead the moment a user installed the first 0.2.0 prerelease.
|
|
129
|
+
|
|
130
|
+
| Spelling | 0.1.7 line | 0.2.0 alpha/rc | 0.2.0 release | 0.3.0 |
|
|
131
|
+
| --- | --- | --- | --- | --- |
|
|
132
|
+
| `>=0.1.7-rc.1 <0.2.0-0` ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
133
|
+
| `>=0.1.7-rc.1 <0.2.0` | ✅ | ✅ | ❌ | ❌ |
|
|
134
|
+
| `>=0.1.7-rc.1 <0.3.0-0` ✅ | ✅ | ✅ | ✅ | ❌ |
|
|
135
|
+
|
|
136
|
+
`tests/dsh-peer-range.spec.ts` pins this invariant: every peer range must admit the 0.1.7 and 0.2 lines, must reject the 0.3 line, and `<0.2.0-0` may not come back.
|
|
137
|
+
|
|
138
|
+
> **Why this needs a dedicated test**: a wrong range passes `tsc`, every unit test, `pnpm install`, and CI — only a real host refuses to load it. That is exactly how 2.3.1 shipped green.
|
|
100
139
|
|
|
101
140
|
0.1.7 rebuilt the settings machinery, and the plugin follows the 0.1.7 contract:
|
|
102
141
|
|
|
@@ -106,12 +145,65 @@ Restart the DSH process after install/update/uninstall.
|
|
|
106
145
|
- **Schema writable marker**: writable fields must be marked `volatile()` (`asVolatile`), or 0.1.7's write gate rejects every write; 0.1.7 delivers config values as `{get(): T}` live references, so every read and merge path unwraps them first (`unwrapVolatile` / `unwrapVolatileDeep`).
|
|
107
146
|
- **The collapse caret is pure CSS**, with no static primitives icon import — icon naming changes across releases, so a static import is not a stable contract.
|
|
108
147
|
|
|
148
|
+
## Platform support
|
|
149
|
+
|
|
150
|
+
| Platform | Status | Notes |
|
|
151
|
+
| --- | --- | --- |
|
|
152
|
+
| **macOS** | ✅ Fully supported | Development and verification environment; directory names confirmed on a real host |
|
|
153
|
+
| **Windows** | ✅ Supported | CI runs `windows-latest`; account directory, app version headers, and lock behavior all handled. **Verified on a real host** (Windows 10.0.22621 + TRAE SOLO CN) — see below |
|
|
154
|
+
| **Linux** | ✅ Supported | Includes the WSL2 + Trae CLI case (issue #5); directory names probed as multiple candidates |
|
|
155
|
+
|
|
156
|
+
All three platforms share one implementation: account discovery, decryption, region routing, check-in, and usage queries are platform-independent. Platform differences are confined to **path resolution** (`src/paths.ts`) and the **device fingerprint** (`src/identity.ts`).
|
|
157
|
+
|
|
109
158
|
## Windows notes
|
|
110
159
|
|
|
111
|
-
- **Account data directory**: the plugin reads `%APPDATA
|
|
112
|
-
- **App version headers**:
|
|
113
|
-
- **
|
|
114
|
-
-
|
|
160
|
+
- **Account data directory**: the plugin reads `%APPDATA%\<directory name>\User\globalStorage\storage.json` (unrelated to the **install** directory — this Electron family keeps user data under `%APPDATA%` even when installed on another drive). It probes several directory spellings **in parallel** (`Trae CN` and `trae-cn`, `TRAE SOLO CN` and `trae-solo-cn`), and any hit works. Windows file systems are case-insensitive, so the same directory is never probed twice.
|
|
161
|
+
- **App version headers**: read from `<LOCALAPPDATA>\Programs\<install directory name>\resources\app\product.json` (falling back to `<home>\AppData\Local` when `LOCALAPPDATA` is unset) and sent as `x-app-version` / `x-ide-version`; if unreadable those headers are omitted and nothing else is affected.
|
|
162
|
+
- **Device fingerprint**: `x-device-type` is sent as `windows` and `x-os-version` as `Windows <release>`. The machine id comes from the data directory's `telemetry.machineId` (or a sibling `machineid` file), which holds on Windows.
|
|
163
|
+
- **File locking**: the host's `dsh-atomic-write` handles Windows exclusive-create semantics (`EPERM` / `EBUSY` retries) natively; no plugin-side adaptation is needed.
|
|
164
|
+
- **Raw Chat probing (known limitation)**: `model-cache` depends on the `sqlite3` command-line tool, which is not installed by default on Windows; that probe fails and **falls back safely**. Raw Chat is off by default and does not affect the main flow.
|
|
165
|
+
- **Permission bits**: credential copies are written with `mode: 0o600` / `dirMode: 0o700`, which Windows ignores — no error, documented as harmless.
|
|
166
|
+
|
|
167
|
+
### Real-host verification result (2026-09-26, Windows 10.0.22621 + TRAE SOLO CN)
|
|
168
|
+
|
|
169
|
+
`node scripts/verify-windows.mjs` passed **everything** (exit code 0):
|
|
170
|
+
|
|
171
|
+
| Check | Observed |
|
|
172
|
+
| --- | --- |
|
|
173
|
+
| Directory probe | Hit `%APPDATA%\TRAE SOLO CN\User\globalStorage\storage.json` (all other candidates missing) |
|
|
174
|
+
| Account resolution | 1 account resolved, `solo` / `cn` region |
|
|
175
|
+
| `x-device-type` | `windows` ✅ |
|
|
176
|
+
| `x-os-version` | `Windows 10.0.22621` ✅ |
|
|
177
|
+
| `x-device-id` | 16 digits, all numeric (read from the data directory, not the fallback hash) ✅ |
|
|
178
|
+
| `x-app-version` | `0.1.56`, read from `%LOCALAPPDATA%\Programs\TRAE SOLO CN\resources\app\product.json` ✅ |
|
|
179
|
+
|
|
180
|
+
**What this confirms**: using `win32DirName` as the authority is **correct** — the `TRAE SOLO CN` spelling really is the directory name on a real Windows host, and both the `product.json` install-path derivation and every device-fingerprint field hold. In other words "the plugin can read Trae's sign-in on Windows" is now backed by real evidence rather than inference.
|
|
181
|
+
|
|
182
|
+
#### Runtime paths beyond the script (same host, 2026-09-26)
|
|
183
|
+
|
|
184
|
+
The six checks above cover **path probing and the device fingerprint only** — that is not the same as "the whole plugin works on Windows". So the plugin's main flows were exercised separately, calling the `lib/` exports directly (i.e. the product code itself):
|
|
185
|
+
|
|
186
|
+
| Runtime path | Observed |
|
|
187
|
+
| --- | --- |
|
|
188
|
+
| `TraeCredentialStore.resolve()` | ✅ returned a credential, host `https://api.trae.cn`, token expiry parsed |
|
|
189
|
+
| `identityHeaders()` (all headers) | ✅ all 10 headers produced (`x-device-id` / `x-machine-id` / `x-device-cpu` / `x-ide-version-code`, …) |
|
|
190
|
+
| Usage query (read-only, consumes no credits) | ✅ 9 credit packs, total 2050 / consumed 1522.85 |
|
|
191
|
+
| Model directory fetch (read-only) | ✅ **42 models** returned |
|
|
192
|
+
| `writeFileAtomic` | ✅ write + read-back identical |
|
|
193
|
+
| Concurrent `withFileLock` | ✅ 8 concurrent writers serialized correctly with **no lost updates** (Windows exclusive-create semantics are handled by the host — now verified rather than assumed) |
|
|
194
|
+
| `SSE` decoding | ✅ splits on `\r?\n`, so CRLF and chunk splits are both handled |
|
|
195
|
+
|
|
196
|
+
In short: sign-in, identity, usage, model directory, and concurrent writes are all **verified working on Windows**.
|
|
197
|
+
|
|
198
|
+
> **Known limitation (its behaviour confirmed on this host)**: `model-cache` depends on the `sqlite3` command-line tool, which is not installed here; it threw `ENOENT` (`spawn sqlite3 ENOENT`) and the caller's `.catch(() => undefined)` fell back correctly — **the main flow is unaffected**, and Raw Chat is off by default.
|
|
199
|
+
>
|
|
200
|
+
> **That module's directory-spelling gap has been fixed** (found and fixed in the same round): it used to **hardcode the single spelling `Trae CN`** for the `state.vscdb` path while `paths.ts` probes multiple candidates, and it re-derived the directory instead of reusing `traeStorageCandidates`. On this host — whose real directory name is `TRAE SOLO CN` — the old code pointed at `...\Trae CN\...\state.vscdb`, which **does not exist at all**; only because `sqlite3` is absent did that call necessarily fail and fall back, hiding a **wrong path** behind a different, plausible-looking error (a missing dependency).
|
|
201
|
+
>
|
|
202
|
+
> The fix derives the cache path **from the credential path**: `state.vscdb` and `storage.json` share one `globalStorage` directory, so the candidate that yields the sign-in also owns the cache. `raw-resolver` now passes the resolved candidate down, so a machine with several editions installed cannot read another installation's model map. Re-measured on the real host: the new code selects `%APPDATA%\TRAE SOLO CN\User\globalStorage\state.vscdb` (which exists), where the old code only ever looked at `Trae CN` (which does not).
|
|
203
|
+
|
|
204
|
+
> **What remains unverified**: this host only had TRAE SOLO CN installed, so the `Trae CN` / `trae-cn` spellings are **still unconfirmed on a real host** — their absence here only means this build is not installed, not that the spelling is wrong. Windows users running Trae China Edition are still welcome to run the command above and report.
|
|
205
|
+
|
|
206
|
+
> **Running Trae China Edition, or a different setup? One command reports it**: `node scripts/verify-windows.mjs` — it uses the plugin's own build output to list every path it probes on this machine, whether an account can be resolved, and the device fingerprint. Its output is **already redacted** (user name becomes `<user>`; account and device ids are reported as shape only) and can be pasted into an issue as-is. **Full steps and how to read the result are in [`docs/WINDOWS_VERIFY_GUIDE.md`](https://github.com/dingminhua/dsh-connect-trae/blob/main/docs/WINDOWS_VERIFY_GUIDE.md)**; step-by-step manual troubleshooting is in [`docs/WINDOWS_TOKEN_PROBE.md`](https://github.com/dingminhua/dsh-connect-trae/blob/main/docs/WINDOWS_TOKEN_PROBE.md) (**it only asks for directory and key names, never for tokens**). If the directory name still does not match, `authFile` + `edition` can point at the exact path as a workaround.
|
|
115
207
|
|
|
116
208
|
## Development
|
|
117
209
|
|
package/README.md
CHANGED
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
|
|
27
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
28
|
|
|
29
|
+
**支持 macOS、Windows 与 Linux**:CI 在 `ubuntu-latest` 与 `windows-latest` 上跑完整 typecheck / 测试 / 构建;Windows 侧的目录探测、应用版本头与锁行为均有针对性处理,详见 [Windows 说明](#windows-说明)。
|
|
30
|
+
|
|
29
31
|
> 除每日签到领取外,插件的所有查询都是只读的、不消耗额度;签到领取也只在你点击按钮时才发生。
|
|
30
32
|
|
|
31
33
|
## 功能特性
|
|
@@ -37,9 +39,10 @@
|
|
|
37
39
|
- **DSH 本地工具循环** —— 通过 Trae `llm_utils_chat` 获取待执行的结构化 `tool_calls`,交由 DSH 自带的本地工具执行,再将工具结果回传模型。
|
|
38
40
|
- **国内国际双区域自动识别** —— 自动发现 Trae CN / TRAE SOLO CN / Trae / TRAE SOLO 四个本地安装的登录账号;区域由凭证自带的 `userRegion` 声明自动判定(host 后缀与 edition 标签兜底),无需手动指定。
|
|
39
41
|
- **目录与勾选按区域隔离** —— 国内版与国际版各一套模型目录、勾选、图片开关与上下文预算,两个供应商各自读各自的槽位。
|
|
42
|
+
- **识图能力跟随 Trae 自身的标注** —— Trae 把某个模型标为 `multimodal` 时,插件直接按多模态对外声明,无需你在卡片上逐一手勾(issue #16)。判据是「**你手动勾选的 ∪ Trae 标注多模态的**」:Trae 明确标 `false` 或未标注的模型保持纯文本,你可以照旧手动为它们打开图片输入。卡片上被自动开启的那一行显示为已勾选且只读,并说明原因。详见下方「图片输入(识图)」。
|
|
40
43
|
- **多账号切换** —— 支持重新读取 Token 列表并选择账号;Token 不写入 DSH 设置。
|
|
41
44
|
- **只读用量与模型管理** —— 国内版查看 Work 积分与通用积分,国际版查看订阅/试用状态;刷新/启用 Trae 模型;只读查询不消耗额度。
|
|
42
|
-
- **每日签到领取** ——
|
|
45
|
+
- **每日签到领取** —— 国内版卡片上一键领取当日签到积分(按钮显示每日奖励,该账号已领取则显示「今日已领取」并禁用);这是插件里**唯一会改变账号状态**的操作,由你主动点击触发。Trae 的签到按「**每台设备每天一次**」限制:切换账号后如果本机今天的名额已经用掉(无论是哪个账号用的),当前账号在本机当天无法再签,卡片会说明原因而不是谎报「已领取」。国际版没有签到活动,因此不显示该按钮。
|
|
43
46
|
- **安全 loopback shim** —— 每区域一个随机端口 + 进程内随机 secret,真实 Trae token 不交给 pi-ai。
|
|
44
47
|
|
|
45
48
|
## 工作原理
|
|
@@ -57,28 +60,49 @@ DSH PiAiAdapter(每个 provider 一套)
|
|
|
57
60
|
|
|
58
61
|
国内版与国际版各持一套完整的运行时栈——凭据 store、模型 catalog、wire 映射、上游客户端、回环 shim、adapter——按凭证自带的区域声明隔离可见账号,所以**两个区域的账号可以同时在线、同时被不同会话使用**。
|
|
59
62
|
|
|
60
|
-
用量概览走 `https://api.trae.cn/trae/api/v2/pay/*` 与 `/trae/api/v2/ug/*` 只读接口(国际账号走其自有网关的订阅状态端点);每日签到领取是其中唯一的写操作,走 `POST /trae/api/v2/ug/checkin_credits/claim`,需要携带本机安装的设备号(`x-device-id
|
|
63
|
+
用量概览走 `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` 作为迁移来源保留读取)。
|
|
61
64
|
|
|
62
65
|
> 详见 `docs/IMPLEMENTATION_PLAN.md`、`docs/SOLO_ROUTE_DECISION.md`、`docs/USAGE_API_RESEARCH.md`。
|
|
63
66
|
|
|
67
|
+
## 图片输入(识图)
|
|
68
|
+
|
|
69
|
+
插件对外声明「这个模型是否接受图片」,DSH 据此决定附图是**原样发出**还是**降级成文字描述**。所以这条声明是硬开关,不是一个无关紧要的标记。
|
|
70
|
+
|
|
71
|
+
判据是**两个来源的并集**(issue #16):
|
|
72
|
+
|
|
73
|
+
| 来源 | 含义 |
|
|
74
|
+
|---|---|
|
|
75
|
+
| Trae 目录自带的 `multimodal` 字段 | Trae 自己对「这模型读不读图」的回答。Trae IDE 本体的附图按钮就是纯粹由这个字段驱动的,没有用户开关 |
|
|
76
|
+
| 你在卡片上手动勾选的 `imageModelIds` | 显式选择。即便 Trae 后来不再标注该模型,你的勾选也不会被撤掉 |
|
|
77
|
+
|
|
78
|
+
要点:
|
|
79
|
+
|
|
80
|
+
- **只有 `multimodal === true` 才自动开启。** 字段缺失(旧版本保存的目录、兜底目录)或明确 `false`,都按纯文本处理——宁可不发,也不对一个能力不确定的模型谎报「支持图片」。
|
|
81
|
+
- **自动开启并不等于自动发图。** 它只是让 DSH 允许你给该模型附图;图片始终由你在对话里自己贴,插件不会替你发送任何东西。
|
|
82
|
+
- **被自动开启的行在卡片上显示为已勾选且只读。** 因为并集语义下取消勾选会被立刻重新加上,给它一个可点的框是在撒谎。目前不为单个模型提供「强制关闭」的出口;如果你需要这个开关,可以在 issue #16 下提出。
|
|
83
|
+
- 手动勾选对 Trae 标注 `false`(或未标注)的模型依然有效,这是为那些「上游没标但实际可用」的情况留的口子。
|
|
84
|
+
|
|
64
85
|
## 模型覆盖范围
|
|
65
86
|
|
|
66
87
|
插件提供的是 Trae **SOLO 通道**的模型(`DeepSeek-V4-Flash-Official`、`DeepSeek-V4-Pro-Official`、`GLM-5.3`、`GLM-5.2`、`Kimi-K3`、`MiniMax-M3`、`Qwen3.8-Max`、`Doubao-Seed-*` 等),国内版与国际版均走这条通道。
|
|
67
88
|
|
|
68
|
-
以下
|
|
89
|
+
以下 3 个模型目前来自 **TraeCode(Trae IDE)通道**,尚未开放到 SOLO 通道,因此本插件**暂不支持**:
|
|
69
90
|
|
|
70
91
|
| 模型 |
|
|
71
92
|
| --- |
|
|
72
|
-
| `deepseek-v4.1-flash` |
|
|
73
93
|
| `glm-5.3-flash` |
|
|
74
94
|
| `kimi-k2.8-preview` |
|
|
75
95
|
| `qwen3.8-flash` |
|
|
76
96
|
|
|
77
|
-
这
|
|
97
|
+
这 3 个模型只在 Trae IDE 客户端内可用,插件侧没有可调用的通道。**需要等官方把它们开放到 SOLO 通道后,插件才能支持。**
|
|
98
|
+
|
|
99
|
+
> `deepseek-v4.1-flash` 曾在此列——`docs/DS41_CALLABILITY.md`(2026-09-15 取证)实测 8 个 SOLO function
|
|
100
|
+
> 全部返回 `4001 param is invalid`。**上游后来把它开放到了 SOLO 通道**:当前实时目录里它带
|
|
101
|
+
> `wireFunction: solo_work_remote`,可正常调用(已实测刷新确认)。文档此前未同步,现已从表中移除。
|
|
78
102
|
|
|
79
103
|
> 注意区分:**`GLM-5.3` 是支持的**(走 SOLO 通道),但 **`glm-5.3-flash` 不支持**——两者是不同的模型。
|
|
80
104
|
|
|
81
|
-
如果你现在就要用这
|
|
105
|
+
如果你现在就要用这 3 个模型,请直接在 **Trae IDE 本体**中使用。
|
|
82
106
|
|
|
83
107
|
## 安装
|
|
84
108
|
|
|
@@ -99,6 +123,28 @@ npm install dsh-connect-trae
|
|
|
99
123
|
## DSH 版本兼容
|
|
100
124
|
|
|
101
125
|
**要求 DSH 0.1.7-rc.1 及以上**(2.3.0 起不再支持 0.1.7 之前的宿主;peer 依赖范围已按此收窄)。
|
|
126
|
+
**peer 上界为 `<0.3.0-0`:0.1.7 与 0.2 两条线均受支持。**
|
|
127
|
+
|
|
128
|
+
0.2.0 线对本插件**没有契约破坏**——逐符号核对确认插件消费的 API 全部未变([完整的 0.2.0-rc.1 影响面核对](https://github.com/dingminhua/dsh-connect-trae/blob/main/docs/DSH_0.2.0_RC1_IMPACT_CHECK.md))。但 0.1.7 起 DSH 有一道**bundle 级 peer 门禁**:
|
|
129
|
+
peer 范围不满足的 bundle 会被**整体跳过**,只往 stderr 写一行,界面上表现为「插件凭空消失」。
|
|
130
|
+
|
|
131
|
+
### peer 上界为什么不写 `<0.2.0`
|
|
132
|
+
|
|
133
|
+
2.3.1 曾写 `<0.2.0-0`,意图是「先不支持 0.2.0 正式版」。这个写法**是错的**:
|
|
134
|
+
SemVer 里 `0.2.0-rc.1 < 0.2.0-0`,于是它把**整个 0.2.0 预发布线**也一并排除,
|
|
135
|
+
用户装上第一个 0.2.0 预发布版后插件立即整体失效(2.3.1 的实际故障)。
|
|
136
|
+
|
|
137
|
+
| 写法 | 0.1.7 线 | 0.2.0 alpha/rc | 0.2.0 正式 | 0.3.0 |
|
|
138
|
+
| --- | --- | --- | --- | --- |
|
|
139
|
+
| `>=0.1.7-rc.1 <0.2.0-0` ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
140
|
+
| `>=0.1.7-rc.1 <0.2.0` | ✅ | ✅ | ❌ | ❌ |
|
|
141
|
+
| `>=0.1.7-rc.1 <0.3.0-0` ✅ | ✅ | ✅ | ✅ | ❌ |
|
|
142
|
+
|
|
143
|
+
`tests/dsh-peer-range.spec.ts` 钉住这条不变式:逐条断言每个 peer 范围**必须**放行
|
|
144
|
+
0.1.7 与 0.2 两条线、**必须**挡住 0.3 线,并禁止 `<0.2.0-0` 这种写法回归。
|
|
145
|
+
|
|
146
|
+
> **为什么需要专门的测试**:范围写错时 `tsc` 通过、全部单测通过、`pnpm install` 通过、
|
|
147
|
+
> CI 全绿——只有真实宿主会拒绝加载。这正是 2.3.1 一路绿着上线的原因。
|
|
102
148
|
|
|
103
149
|
0.1.7 把设置体系整体重建,插件按 0.1.7 的契约工作:
|
|
104
150
|
|
|
@@ -108,12 +154,65 @@ npm install dsh-connect-trae
|
|
|
108
154
|
- **schema 可写声明**:可写字段必须标 `volatile()`(`asVolatile`),否则写入被 0.1.7 的写入门直接拒绝;0.1.7 以 `{get(): T}` 活引用交付配置值,所有读取与合并路径都先解包(`unwrapVolatile` / `unwrapVolatileDeep`)。
|
|
109
155
|
- **折叠箭头用纯 CSS**,不静态导入任何 primitives 图标——图标命名族随版本变动,静态导入不是稳定契约。
|
|
110
156
|
|
|
157
|
+
## 平台支持
|
|
158
|
+
|
|
159
|
+
| 平台 | 状态 | 说明 |
|
|
160
|
+
| --- | --- | --- |
|
|
161
|
+
| **macOS** | ✅ 完全支持 | 开发与验证环境;目录名均经真机确认 |
|
|
162
|
+
| **Windows** | ✅ 支持 | CI 跑 `windows-latest`;账号目录、应用版本头、锁行为均有专门处理。**已在一台真机(Windows 10.0.22621 + TRAE SOLO CN)通过验证**,见下方 |
|
|
163
|
+
| **Linux** | ✅ 支持 | 含 WSL2 + Trae CLI 场景(issue #5);目录名按多候选探测 |
|
|
164
|
+
|
|
165
|
+
三个平台共享同一套逻辑:账号读取、解密、区域判定、签到与用量查询均与平台无关,平台差异只集中在**路径解析**(`src/paths.ts`)与**设备指纹**(`src/identity.ts`)两处。
|
|
166
|
+
|
|
111
167
|
## Windows 说明
|
|
112
168
|
|
|
113
|
-
- **账号数据目录**:插件读取 `%APPDATA
|
|
114
|
-
- **应用版本头**:插件从安装目录 `<LOCALAPPDATA>\Programs
|
|
115
|
-
-
|
|
116
|
-
-
|
|
169
|
+
- **账号数据目录**:插件读取 `%APPDATA%\<目录名>\User\globalStorage\storage.json`(与**安装目录无关**——Electron 系应用的用户数据固定放 `%APPDATA%`,装到 D 盘也一样)。插件会**并列探测**多个目录拼写(`Trae CN` 与 `trae-cn`、`TRAE SOLO CN` 与 `trae-solo-cn`),命中任一即可;Windows 文件名不区分大小写,因此不会重复探测同一目录。
|
|
170
|
+
- **应用版本头**:插件从安装目录 `<LOCALAPPDATA>\Programs\<安装目录名>\resources\app\product.json` 读取 `appVersion`(`LOCALAPPDATA` 缺失时回退 `<home>\AppData\Local`),随请求发送 `x-app-version` / `x-ide-version`;读不到时这些头不发送,不影响登录与聊天。
|
|
171
|
+
- **设备指纹**:`x-device-type` 发 `windows`,`x-os-version` 为 `Windows <版本>`。机器 ID 取自数据目录的 `telemetry.machineId`(或同目录 `machineid`),该推导在 Windows 上成立。
|
|
172
|
+
- **文件锁**:宿主 `dsh-atomic-write` 原生处理 Windows 的独占创建语义(`EPERM` / `EBUSY` 重试),无需插件侧适配。
|
|
173
|
+
- **Raw Chat 探测(已知限制)**:`model-cache` 依赖 `sqlite3` 命令行,Windows 默认未安装,该探测会失败并**安全回退**。Raw Chat 默认关闭,不影响主流程。
|
|
174
|
+
- **权限位**:写凭据副本时传 `mode: 0o600` / `dirMode: 0o700`,Windows 忽略 POSIX 权限位——不报错,属已知且无害。
|
|
175
|
+
|
|
176
|
+
### 真机验证结果(2026-09-26,Windows 10.0.22621 + TRAE SOLO CN)
|
|
177
|
+
|
|
178
|
+
`node scripts/verify-windows.mjs` 实测**全部通过**(退出码 0):
|
|
179
|
+
|
|
180
|
+
| 检查项 | 实测结果 |
|
|
181
|
+
| --- | --- |
|
|
182
|
+
| 目录探测 | 命中 `%APPDATA%\TRAE SOLO CN\User\globalStorage\storage.json`(其余候选为 missing) |
|
|
183
|
+
| 账号解析 | 解出 1 个账号,`solo` / `cn` 区域 |
|
|
184
|
+
| `x-device-type` | `windows` ✅ |
|
|
185
|
+
| `x-os-version` | `Windows 10.0.22621` ✅ |
|
|
186
|
+
| `x-device-id` | 16 位纯数字(来自数据目录,非兜底哈希)✅ |
|
|
187
|
+
| `x-app-version` | `0.1.56`,取自 `%LOCALAPPDATA%\Programs\TRAE SOLO CN\resources\app\product.json` ✅ |
|
|
188
|
+
|
|
189
|
+
**这确认了什么**:`win32DirName` 作依据是**正确**的——`TRAE SOLO CN` 这个拼写确实是 Windows 真机上的实际目录名,且 `product.json` 的安装路径推导与设备指纹各字段都成立。即「Windows 上插件读得到 Trae 登录」已由真机证据支持,不再是推断。
|
|
190
|
+
|
|
191
|
+
#### 脚本之外的运行路径(同一台真机,2026-09-26)
|
|
192
|
+
|
|
193
|
+
上表四项只覆盖**路径探测与设备指纹**,不等于「整个插件在 Windows 上跑得通」。因此另外单独实测了插件的主流程(直接调用 `lib/` 的导出,即产品代码本身):
|
|
194
|
+
|
|
195
|
+
| 运行路径 | 实测结果 |
|
|
196
|
+
| --- | --- |
|
|
197
|
+
| `TraeCredentialStore.resolve()` | ✅ 返回凭据,host `https://api.trae.cn`,token 有效期正常解析 |
|
|
198
|
+
| `identityHeaders()` 全部请求头 | ✅ 10 个头全部生成(`x-device-id` / `x-machine-id` / `x-device-cpu` / `x-ide-version-code` 等) |
|
|
199
|
+
| 用量查询(只读,不消耗积分) | ✅ 拿到 9 个积分包,总额 2050 / 已用 1522.85 |
|
|
200
|
+
| 模型目录拉取(只读) | ✅ 拉到 **42 个模型** |
|
|
201
|
+
| 原子写 `writeFileAtomic` | ✅ 写入 + 回读一致 |
|
|
202
|
+
| 并发文件锁 `withFileLock` | ✅ 8 个并发写串行化正确,**无丢失更新**(Windows 独占创建语义由宿主处理,验证成立) |
|
|
203
|
+
| `SSE` 解码 | ✅ 代码按 `\r?\n` 切分,CRLF 与分块均支持 |
|
|
204
|
+
|
|
205
|
+
也就是说:登录、身份、用量、模型目录、并发写入这些**主流程在 Windows 上均实测可用**。
|
|
206
|
+
|
|
207
|
+
> **已知限制(本机实测确认其行为)**:`model-cache` 依赖 `sqlite3` 命令行,本机未安装,实测抛 `ENOENT`(`spawn sqlite3 ENOENT`),调用方 `.catch(() => undefined)` 正确兜底——**不影响主流程**,Raw Chat 默认关闭。
|
|
208
|
+
>
|
|
209
|
+
> **该模块的目录拼写缺口已修复**(同轮发现并修掉):它原先把 `state.vscdb` 路径**硬编码为 `Trae CN` 单一拼写**,而 `paths.ts` 是多候选,且它自己重算了一遍目录而没复用 `traeStorageCandidates`。在本机(真实目录名为 `TRAE SOLO CN`)上,旧代码指向的 `...\Trae CN\...\state.vscdb` **根本不存在**;只因 `sqlite3` 缺失、该调用必然失败并兜底,才把一个**错误的路径**藏在了另一个看似合理的错误(依赖缺失)后面。
|
|
210
|
+
>
|
|
211
|
+
> 修法是让缓存路径**从凭据路径推导**——`state.vscdb` 与 `storage.json` 同在 `globalStorage`,因而同一个候选目录既管登录也管缓存;`raw-resolver` 现在把命中的候选传下去,多版本共存时不会读到另一个安装的模型表。真机复测:新代码正确选中 `%APPDATA%\TRAE SOLO CN\User\globalStorage\state.vscdb`(存在),旧代码只找 `Trae CN`(不存在)。
|
|
212
|
+
|
|
213
|
+
> **仍然未验证的部分**:本机只装了 TRAE SOLO CN,因此 `Trae CN` / `trae-cn` 这两个拼写**仍未经真机确认**——但它们在此机器上不存在只反映「本机没装这个版本」,不代表拼写有误。装了 Trae 中国版的 Windows 用户仍欢迎跑一次上面的命令回报。
|
|
214
|
+
|
|
215
|
+
> **安装 Trae 中国版或别的情形?一条命令即可回报**:`node scripts/verify-windows.mjs` —— 它用插件自身的构建产物列出在这台机器上探测的每条路径、能否解出账号、以及设备指纹,输出**已脱敏**(用户名替换为 `<user>`,账号名与设备号只给长度),可直接贴进 issue。**完整的验证步骤与结论判读见 [`docs/WINDOWS_VERIFY_GUIDE.md`](https://github.com/dingminhua/dsh-connect-trae/blob/main/docs/WINDOWS_VERIFY_GUIDE.md)**;手工逐步排查见 [`docs/WINDOWS_TOKEN_PROBE.md`](https://github.com/dingminhua/dsh-connect-trae/blob/main/docs/WINDOWS_TOKEN_PROBE.md)(**只要目录名与 key 名,不要 token**)。若目录名仍对不上,可用 `authFile` + `edition` 直接指定完整路径救急。
|
|
117
216
|
|
|
118
217
|
## 开发
|
|
119
218
|
|