dsh-connect-trae 4.10.0 → 4.15.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 +598 -5
- package/README.en.md +17 -1
- package/README.md +16 -1
- package/docs/assets/dsh-connect-trae-composer-credits.png +0 -0
- package/docs/assets/dsh-connect-trae-sidebar-page.png +0 -0
- package/docs/assets/dsh-connect-trae-usage-card.png +0 -0
- package/lib/client.js +1186 -477
- package/lib/index.d.ts +82 -1
- package/lib/index.js +608 -109
- package/package.json +8 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,598 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.15.0 (2026-11-08)
|
|
4
|
+
|
|
5
|
+
### 新增:模型选择器里的胶囊,改成「谁都能显示」
|
|
6
|
+
|
|
7
|
+
按同族插件的做法(`dsh-connect-workbuddy` 的 `docs/MODEL-LABEL-SPEC.md`)补完两件事。
|
|
8
|
+
两件是**同一条链的两头**,缺一头都不成立。
|
|
9
|
+
|
|
10
|
+
**背景一句话**:`conversation.input.model.label` 是 `single` 槽,只渲染一条注册,
|
|
11
|
+
而赢家的组件会被**所有** provider 的行走一遍。所以:
|
|
12
|
+
|
|
13
|
+
| 位置 | 谁能画 | 靠什么 |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| 自己的行 | 自己的组件 | 自己判定的促销胶囊(时钟判定,已有) |
|
|
16
|
+
| 别人的行 | **槽位赢家** | 只有 `model.badge`——别人**发布过**的东西 |
|
|
17
|
+
|
|
18
|
+
4.8.1 只做了下半张表(赢家复现别人的 `badge`),本版补上半张:**本插件也开始发布**。
|
|
19
|
+
不发布的话,一旦 qoder / workbuddy 抢先占了那格,本插件的行就什么装饰都没有——
|
|
20
|
+
这正是那两家各自胶囊消失时看到的现象。
|
|
21
|
+
|
|
22
|
+
**一、adapter 发布 `badge`(`src/model-badge.ts` + `src/adapter.ts`)**
|
|
23
|
+
|
|
24
|
+
覆盖 `listModels()`,只挂 `badge`,别的一个字段都不动:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
const now = new Date() // ONE clock for the whole catalog
|
|
28
|
+
return models.map((model) => {
|
|
29
|
+
const badge = this.badgeOf(model.id, now)
|
|
30
|
+
return { ...model, ...(badge === undefined ? {} : { badge }) }
|
|
31
|
+
})
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
三条都是踩过的坑,写死在测试里:
|
|
35
|
+
|
|
36
|
+
1. **必须用展开(`...`)而不是字面量直写 `badge`。** 0.1.x 的 `LlmModelInfo` 没有这个
|
|
37
|
+
字段,字面量会被 tsc 判为未知属性;展开一个变量不受这条检查约束,同一份代码在两个
|
|
38
|
+
宿主线上都能编译(已用 0.2.2-rc.1 的类型实测)。旧宿主会在注册表里**静默丢掉**它,
|
|
39
|
+
不报错、不崩,行退化成原来的名字。
|
|
40
|
+
2. **「没有 badge」是 `undefined`,不是 `null`。** 注册表对传入值直接做 `badge.text`,
|
|
41
|
+
`null` 会在 `listModels` 里抛错,而那个错误是**按 provider 的**——`trae` 的**全部**
|
|
42
|
+
模型会一起从目录消失(进 `failures`),不只是带胶囊那一行。
|
|
43
|
+
3. **文案 ≤ 12 个 grapheme、`tone` 只能用四个字面量、翻译表必须含 `en`。** 这条链上
|
|
44
|
+
没有任何一步校验它们:注册表只做规范化,宿主的 `Tag` 只把 tone 写成 `data-tone`。
|
|
45
|
+
写错的后果都是**安静的**——缺 `en` 是一枚空胶囊,错的 tone 是一枚没有配色的胶囊。
|
|
46
|
+
|
|
47
|
+
**说的是哪一件事:此刻正在跑的促销。** 与卡片上、与自己行上画的那枚胶囊**同一份数据、
|
|
48
|
+
同一个判定**(`selectTraePromoBadge`)——`5 折` / `闲时 5 折` / `限时 5 折`,中文照上游
|
|
49
|
+
的「折」,英文改写成百分比(英文没有「折」)。`subsidy` / `limited` 落 `warning`,
|
|
50
|
+
`offPeak` 落 `info`,与 `PROMO_TONES` 一致。
|
|
51
|
+
|
|
52
|
+
**时钟在 `listModels()` 里读,这是规范说的「where it is drawn」那一刻**,而**不是**
|
|
53
|
+
把促销冻进目录:`activityDiscount` 一直是以未判定的形态挂在模型条目上的,判定发生在
|
|
54
|
+
画的那一刻,窗口一关就不再出现。整个目录**只取一次时间**——否则一次刷新期间窗口边界
|
|
55
|
+
落下,同一张菜单里会有一行带胶囊、隔壁那行不带。
|
|
56
|
+
|
|
57
|
+
**这一版推翻了两小时前的一稿,原因值得记下来。** 第一稿放的是「免费」
|
|
58
|
+
(`creditMultiplier === 0` → `Free`/`免费`,`success`),理由是「badge 是目录数据,
|
|
59
|
+
不该装随时钟变的东西」。两处都错:
|
|
60
|
+
|
|
61
|
+
| 错在哪 | 实测 |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| **那个判据根本不可达** | 两条解析路径都把上游的 `consumption_rate` 过一遍 `finitePositive`(`src/solo.ts:103`、`src/model-metadata.ts:54`),它要求 `> 0`,所以 `rate: 0` 进来是 `undefined`。`creditMultiplier` 恰好为 `0` 只能来自**已保存的** `lastCatalog`,永远不来自实时目录。喂 `rate: 0` / `0.0` / `'0'` 给真实解析器,三个都是 `undefined` |
|
|
64
|
+
| **我把规范的边界读反了** | 规范「三个已知边界」第 2 条反对的是**分钟级负载态**(负载等级、剩余积分、队列长度),促销恰恰是第 1 步 ✅ 示例的头一项;而 workbuddy 自己的 `resolveModelBadge` 放的就是 `selectPromoBadge(info.promos, Date.now())` |
|
|
65
|
+
|
|
66
|
+
第一稿能活下来,是因为**夹具是我手写的** `creditMultiplier: 0`——一个没有任何解析
|
|
67
|
+
路径能产生的值。测试因此全绿,功能其实一行都不会显示。现在补了一条守卫直接喂真实
|
|
68
|
+
解析器(`tests/model-badge.spec.ts` 的 `why a price-derived badge is impossible`),
|
|
69
|
+
把这条不可达性钉住。
|
|
70
|
+
|
|
71
|
+
**英文措辞按最坏情况定的。** 12 grapheme 的上限要按 `percent = 100` 检查,不是按好看的
|
|
72
|
+
`50`:`Off-peak 100% off` 是 17,会撑破一行;`Offpeak 100%` 正好 12。测试专门构造了
|
|
73
|
+
100% 的夹具,只测 50 会漏掉真实的溢出。
|
|
74
|
+
|
|
75
|
+
**二、别人的行:按宿主的样子复现(`src/client/TraeModelLabel.tsx`)**
|
|
76
|
+
|
|
77
|
+
非本插件的行本来就会复现 `badge`(4.8.1),本版按宿主真值校正了三处,并让这一支
|
|
78
|
+
**对任何形状都不抛**:
|
|
79
|
+
|
|
80
|
+
- **顺序**:`trigger` 时胶囊在**前**、名字在后;`menu` / `option` 时名字在前、胶囊在
|
|
81
|
+
后。`selected` 不参与。
|
|
82
|
+
- **配色**:`info` 是 `state-business-primary`(不是 `brand-primary`)、`warning` 是
|
|
83
|
+
`state-warn-primary`(不是 `state-warn-secondary`),底色是各自颜色 10%(`warning`
|
|
84
|
+
12%)的 `color-mix`,**没有描边**。原来的实现这两个 token 都抄错了,而且加了描边。
|
|
85
|
+
差一个 token 仍然「看着像蓝的」,所以这种错能活过一次 review——但同一行两颗胶囊是
|
|
86
|
+
并排比较的。
|
|
87
|
+
- **几何**:`padding: 1px 8px`、`font-size: 11px`、`line-height: 17px`、`999px` 圆角,
|
|
88
|
+
且**不截断**(原来加了 `max-width` + 省略号,宿主不截断,截了反而在同宽度下显示成
|
|
89
|
+
「限时免…」)。
|
|
90
|
+
- **不抛**:`null`、未知 tone、非字符串 `text`、根本不是对象的 `badge`,一律退化成
|
|
91
|
+
纯名字。这一支崩了不是丢一枚胶囊——`entriesOfSlot` 会把**整个组件**从那一格摘掉,
|
|
92
|
+
于是选择器里**每一行**的模型名都跟着没。
|
|
93
|
+
|
|
94
|
+
这一支仍然是纯函数:不发请求、不读快照、不起定时器(一个菜单可能同时有二十行别家的
|
|
95
|
+
模型)。
|
|
96
|
+
|
|
97
|
+
**没改的**:`model.name` 一个字符都没动。名字还被宿主用作菜单 `title`、触发器的
|
|
98
|
+
`aria-label` 和搜索排序键,把胶囊语法塞进去会从这三个地方漏出来。
|
|
99
|
+
|
|
100
|
+
### 修复:面板里那个空框
|
|
101
|
+
|
|
102
|
+
积分面板的表格和「最近刷新」之间会画出一个**空白的框**——有边框、有高度,里面一个字
|
|
103
|
+
都没有。截图报告的正是它。
|
|
104
|
+
|
|
105
|
+
**根因是守卫问错了问题。** 渲染条件是 `signedIn?.credits?.expiry !== undefined`,只要
|
|
106
|
+
上游把四个桶发下来就成立;而真正决定「有没有话说」的是 `expiryNotice()`,它在四个桶
|
|
107
|
+
**全部 `packs: 0`** 时返回 `undefined`——没有时间范围、没有金额、也没有包数可报。
|
|
108
|
+
两层判断不一致,于是 `<div class="dsm-trae-expiry">` 照样渲染,里面那个 `<span>` 是
|
|
109
|
+
空的,边框用的还是 `tone ?? 'lapsed'` 的兜底值:
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
<div ...><span></span></div> ← 测试里真实拿到的东西
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**这个形状不是异常数据。** `/usage` 返回一个 `expiry` 块、而所有包要么不在 7 天窗口内
|
|
116
|
+
要么已经用尽,是普通账号的常态——每个这样的账号都会顶着一个空框。
|
|
117
|
+
|
|
118
|
+
**修法**:把「取 notice」收成一个函数 `expiryNoticeOf()`,渲染条件直接读它的返回值。
|
|
119
|
+
这样「有没有框」和「有没有句子」是同一个布尔值,不可能再对不上。卡片那侧的
|
|
120
|
+
`TraeAccountPanel` 本来就是对的(`notice === undefined` 直接 `return null`),所以这次
|
|
121
|
+
只动了面板一处;两处共用的层级判定仍在 `expiry-ladder.ts`。
|
|
122
|
+
|
|
123
|
+
**测试**:先写了一条会失败的用例(`expiry` 对象在、四个桶全空 → 断言
|
|
124
|
+
`.dsm-trae-expiry` 为 `null`),确认它在修之前红,输出就是上面那个空 div。修完转绿,
|
|
125
|
+
同时断言 `.dsm-trae-composer-panel-foot` 仍在——证明是「框没了」,不是「整个面板没渲染」。
|
|
126
|
+
|
|
127
|
+
### 测试
|
|
128
|
+
|
|
129
|
+
全仓 **110 个测试文件 / 1425 例**通过(新增 2 个文件 / 36 例)。
|
|
130
|
+
|
|
131
|
+
- `tests/model-badge.spec.ts`(15 例):促销行带 badge、无促销行**连键都没有**
|
|
132
|
+
(`'badge' in model`)、名字不变、`en` 必填、tone 在四个字面量内、时钟判定(窗口内
|
|
133
|
+
有、窗口外没有)、与卡片措辞一致;**并按最坏情况(`percent = 100`)**断言 12 grapheme
|
|
134
|
+
上限——只测 50 会漏掉 `Off-peak 100% off` 那个真实的溢出。
|
|
135
|
+
另有一条 `why a price-derived badge is impossible`:喂真实解析器 `rate: 0` / `0.0` /
|
|
136
|
+
`'0'`,断言三者都解析成 `undefined`。这是这次翻车的那条路,留着防止有人再回头用它。
|
|
137
|
+
还有一条守着「英文**故意**比卡片短」:把卡片那句 `Off-peak 100% off` 拿来断言它确实
|
|
138
|
+
超过 12,再断言 badge 的英文不是它——将来谁想「统一」两处文案,会先在这里失败。
|
|
139
|
+
- `tests/composer-points.spec.tsx` 新增 1 例(空桶不画框,见上)。
|
|
140
|
+
- `tests/model-label-host-badge.spec.tsx`(19 例):三种 placement 的顺序与那个空格、
|
|
141
|
+
四个 tone 的 token、无描边、宿主几何、不截断、未知 tone 落 `neutral`,以及 `null` /
|
|
142
|
+
非对象 / 非字符串 `text` / 空白串全部退化成纯名字且**不抛**、别家行不发请求不起定时器。
|
|
143
|
+
- `tests/settings-integration.spec.ts` 新增 1 例端到端:经**真实**的 `LlmRuntime` 走一遍
|
|
144
|
+
设置 schema → 目录 → `listModels()`,断言促销行拿到 badge、普通行没有这个键。
|
|
145
|
+
- 反向验证(不是只在修好之后断言,六次全部被抓):`placement` 两个分支对调 → 6 例红;
|
|
146
|
+
`badge` 改成 `null` → 1 例红;展开写法改成 `{ ...model, badge }` → 2 例红;忽略传入的
|
|
147
|
+
时钟(写死一个日期)→ 1 例红;去掉 `en` → 4 例红;`offPeak` 的 tone 改成 `brand` →
|
|
148
|
+
1 例红。
|
|
149
|
+
- **一次没抓住的变异,记在这里**:把 `promoZhe` 的 `Number.isInteger(zhe) ? String(zhe)
|
|
150
|
+
: zhe.toFixed(1)` 改成 `String(zhe)`,14 例全绿。实测 `percent` 取遍 1–100,两种写法
|
|
151
|
+
的输出**完全相同**,所以任何断言都抓不到它。这一条不是测试不够,是该分支在当前取值
|
|
152
|
+
域里等价;注释里已写明这个重复实现为什么不会漂移。
|
|
153
|
+
|
|
154
|
+
## 4.14.3 (2026-11-07)
|
|
155
|
+
|
|
156
|
+
### 修复:CI 连续 8 次失败——两处都是配置,与代码无关
|
|
157
|
+
|
|
158
|
+
`main` 上从 4.13.0 起连续 8 个提交 CI 全红(ubuntu + windows 同时),但
|
|
159
|
+
`pnpm test` 本地一直是绿的。两处原因都在安装阶段,代码从未被跑到。
|
|
160
|
+
|
|
161
|
+
**第一处:pnpm 11 把「忽略构建脚本」当成错误。**
|
|
162
|
+
|
|
163
|
+
CI 用 pnpm 11,本地用 pnpm 10。同一个 `pnpm install --frozen-lockfile`:
|
|
164
|
+
|
|
165
|
+
| 版本 | 遇到未声明的构建脚本 |
|
|
166
|
+
| --- | --- |
|
|
167
|
+
| pnpm 10(本地) | 只警告,继续装 |
|
|
168
|
+
| pnpm 11(CI) | `ERR_PNPM_IGNORED_BUILDS`,**exit 1** |
|
|
169
|
+
|
|
170
|
+
`pnpm-workspace.yaml` 里的 `allowBuilds` 从初始提交起就有,但只列了
|
|
171
|
+
`@google/genai` 和 `protobufjs`,没列 `tesseract.js`。现在显式写 `false`:
|
|
172
|
+
它的 postinstall 只是 `opencollective-postinstall`(捐款横幅),OCR 不需要它。
|
|
173
|
+
|
|
174
|
+
**为什么本地永远复现不出来**:pnpm 版本不同。这条教训值得记住——本地绿不等于
|
|
175
|
+
CI 绿,而这里的差异来自**包管理器版本**,不是代码。
|
|
176
|
+
|
|
177
|
+
**第二处:CI 的 OCR 语言包用了会崩的版本。**
|
|
178
|
+
|
|
179
|
+
修好安装后,隐私门禁会立刻暴露出下一个问题。CI 下载的是
|
|
180
|
+
`4.0.0_best/eng.traineddata.gz`,tesseract.js 7 加载它直接 abort:
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
Aborted(missing function: _ZN9tesseract13DotProductSSEEPKfS1_i)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
换成 `4.0.0`(tesseract.js 自己会去取的那个版本)就正常。实测对比:
|
|
187
|
+
|
|
188
|
+
| 文件 | 大小 | 结果 |
|
|
189
|
+
| --- | --- | --- |
|
|
190
|
+
| `4.0.0_best` | 12,821,300 | abort,门禁记 SKIP |
|
|
191
|
+
| `4.0.0` | 10,923,060 | OCR 正常,读出 768 字符 |
|
|
192
|
+
|
|
193
|
+
这个 SKIP 是个陷阱:`verify-privacy.mjs` 把后端失败记成「无法验证」而不是
|
|
194
|
+
「通过」,所以它不会误报绿——但会挡住发布,且提示语指向 `DSH_TESSDATA`,
|
|
195
|
+
容易让人以为是本地环境问题而不是数据文件问题。
|
|
196
|
+
|
|
197
|
+
### 验证
|
|
198
|
+
|
|
199
|
+
在临时副本里用 **pnpm 11.28.11** 复刻 CI 全流程(不是本地 pnpm 10):
|
|
200
|
+
|
|
201
|
+
- `pnpm install --frozen-lockfile` → 通过(去掉 `tesseract.js: false` 立刻复现
|
|
202
|
+
`ERR_PNPM_IGNORED_BUILDS`,证明这条是必需的)
|
|
203
|
+
- `typecheck` / `test`(108 文件 1389 例)/ `build` → 全部通过
|
|
204
|
+
- `verify:privacy` + `DSH_TESSDATA` → 17 项,**无 SKIP**
|
|
205
|
+
|
|
206
|
+
两处修复分别单独验证过:缺任一条,对应的步骤就失败。
|
|
207
|
+
|
|
208
|
+
## 4.14.2 (2026-11-07)
|
|
209
|
+
|
|
210
|
+
### 修复:非 loopback 绑定下,同源请求被当成外站(issue #78)
|
|
211
|
+
|
|
212
|
+
用 `dsh web --host 100.64.0.2` 这类**具体 LAN 地址**启动时,浏览器里所有写操作都
|
|
213
|
+
返回 `403 origin-not-trusted`:刷新账号、签到、导入导出、模型刷新、模型测试、账号池
|
|
214
|
+
写入。页面本身能打开,一点按钮就失败。
|
|
215
|
+
|
|
216
|
+
**根因**:`loopbackOrigin()` 只认 loopback 主机名,于是把「同源」判成了「外站」。
|
|
217
|
+
浏览器对同源 POST **一定**带 `Origin`,这个值就是页面自己的地址;而 host 信任层
|
|
218
|
+
(`--trusted-host`)早就接受了同一个地址。两层判断割裂:host 层放行、插件层拒绝。
|
|
219
|
+
|
|
220
|
+
**为什么一直没被发现**:`curl` 和插件自身的 `fetch` 都不发 `Origin`,走的正是
|
|
221
|
+
`origin === undefined` 那条放行分支。**故障只在真实浏览器里出现**——这也解释了它
|
|
222
|
+
为什么能躲过当时的测试。9 条路由共用这一个判定,所以一处坏、九处全坏。
|
|
223
|
+
|
|
224
|
+
**修法**:判定从「是不是 loopback」改成「是不是同一个源」,按三种拼写接受——
|
|
225
|
+
loopback、与 `Host` 头主机名相同、与 `req.socket.localAddress` 相同。只比主机名
|
|
226
|
+
不比端口,所以反向代理换端口不受影响。
|
|
227
|
+
|
|
228
|
+
比同生态的 dsh-connect-workbuddy 5.13.1 多出「命中 `Host` 头」这一条:反向代理
|
|
229
|
+
可能把 `Host` 改写成另一个已受信拼写,此时 `Origin` 与 `localAddress` 都不一定
|
|
230
|
+
同拼写。issue 作者已在本机验证过这个组合。
|
|
231
|
+
|
|
232
|
+
**比较用精确相等,不用 `endsWith`/`startsWith`**:前缀比较会放过
|
|
233
|
+
`100.64.0.20`,后缀比较会放过 `evil-100.64.0.2`。测试里这两条都钉住了。
|
|
234
|
+
|
|
235
|
+
### 修复:刷新账号失败时误报「设置未能保存」
|
|
236
|
+
|
|
237
|
+
同一次 403 会同时打出两条错误,其中 `row.settingsWriteFailed` 渲染成
|
|
238
|
+
「设置未能保存:…」——但这条路径**根本没有写设置**,它刷新的是账号列表。
|
|
239
|
+
issue 作者自述先跑去确认 settings 持久化,然后才回头找到真正的原因。
|
|
240
|
+
现在这条路径只更新 `rescanNote`;`writeError` 留给真正会写的路由。
|
|
241
|
+
|
|
242
|
+
### 验证
|
|
243
|
+
|
|
244
|
+
- 全仓 **108 文件 / 1389 例**通过(新增 7 例)。
|
|
245
|
+
- 新增测试按 issue 的行为矩阵逐条覆盖:LAN 同源放行、代理改写 `Host` 放行、
|
|
246
|
+
三种 loopback 拼写放行、无 `Origin` 放行;外站、前缀欺骗、后缀欺骗、
|
|
247
|
+
`Origin: null`、非法 `Origin` 一律拒绝。
|
|
248
|
+
- **先写测试并确认它失败**(3 例红:两条 LAN 同源、一条空 `Origin`),再修到全绿。
|
|
249
|
+
|
|
250
|
+
### 未做
|
|
251
|
+
|
|
252
|
+
issue 备注里提到的第二处差异——trae 的 web 路由没有 `Host` 校验(workbuddy 是
|
|
253
|
+
Host + Origin 双闸)——**按 issue 作者的建议没有捆绑**。加 Host 闸会改变行为,
|
|
254
|
+
前置代理改写 Host 的用户会被拒,应当单独评估。
|
|
255
|
+
|
|
256
|
+
## 4.14.1 (2026-11-07)
|
|
257
|
+
|
|
258
|
+
### 变更
|
|
259
|
+
|
|
260
|
+
**图标样式移进「能力」分栏;默认改回本插件自己的图标。**
|
|
261
|
+
|
|
262
|
+
两处都关于「这个选项该待在哪」。
|
|
263
|
+
|
|
264
|
+
**位置**:它原先挂在账号池设置的标题行里,也就是「账号」分栏。那是错误的分类——
|
|
265
|
+
旁边每一样东西都在讲**哪些账号在供能**(谁进了池、草稿保存后会怎样),而这个开关
|
|
266
|
+
改的是插件**长什么样**,与账号无关。它也不需要保存,所以更不该待在一条写着
|
|
267
|
+
「点保存才生效」的区块里。
|
|
268
|
+
|
|
269
|
+
现在它是「能力」的第四行,排在三个开关之后:上面三条改行为,这一条改一张图。
|
|
270
|
+
|
|
271
|
+
**默认值**:原先未设置时读作 `claude`,意味着每个新用户**什么都没选**就先看到一枚
|
|
272
|
+
重绘的剪影,而插件自己的图标成了需要主动去找的选项。一个「未设置 = 非默认」的
|
|
273
|
+
偏好是糟糕的默认值——它把产品自己的身份变成了要用户opt-in的东西。
|
|
274
|
+
|
|
275
|
+
现在未设置读作 `original`(本插件图标)。存储里明确写过 `claude` 的仍然保持
|
|
276
|
+
Claude 风格,**只有「从未设置」这一种情况变了**。
|
|
277
|
+
|
|
278
|
+
### 细节
|
|
279
|
+
|
|
280
|
+
- `.dsm-trae-prefs .dsm-icon-preference{padding:0}`:这个组件在独立摆放时自带
|
|
281
|
+
12px 块内边距,放进 14px 间距的偏好列表后会让这一行明显偏离它的三个邻居
|
|
282
|
+
(那三条都是无内边距的裸行)。只在能力分栏里抹掉,独立场景的间距仍然正确。
|
|
283
|
+
- `.dsm-trae-prefs .dsm-icon-preference-copy{min-width:0}`:原先的 180px 下限是
|
|
284
|
+
按英文短词定的,中文提示句在 564px 列里会把它撑出去,而不是换行。
|
|
285
|
+
|
|
286
|
+
### 测试
|
|
287
|
+
|
|
288
|
+
全仓 **108 个测试文件 / 1382 例**通过(新增 2 例)。
|
|
289
|
+
|
|
290
|
+
`tests/plugin-icon.spec.tsx` 的三条按新默认值重写,其中「存储被拒」那条现在选的是
|
|
291
|
+
**非默认项**——如果拒绝后回落到默认值,旧写法会把它读成成功,测试绿着而图标已经
|
|
292
|
+
悄悄变回去了。新增 `tests/card-panels.spec.tsx` 一例:图标选择必须在「能力」里、
|
|
293
|
+
不在「账号」里,且是偏好列表的最后一行。
|
|
294
|
+
|
|
295
|
+
## 4.14.0 (2026-11-07)
|
|
296
|
+
|
|
297
|
+
### 合并 PR #79(作者 @kiwaro)
|
|
298
|
+
|
|
299
|
+
自动签到(默认关闭)、图标样式选择、同系列布局统一。
|
|
300
|
+
|
|
301
|
+
**合并时发现并修掉的两处特异性冲突**——这是本次最值得记下来的部分。PR 引入的
|
|
302
|
+
`series-layout.ts` 把规则**追加在样式表末尾**,而它用的选择器(`.dsm-trae-usage X`)
|
|
303
|
+
与本仓库既有规则**同权重(0,2,0)**,靠源码顺序取胜。表现是「测试全绿、界面已坏」:
|
|
304
|
+
|
|
305
|
+
| 现象 | 实测 | 原因 |
|
|
306
|
+
| --- | --- | --- |
|
|
307
|
+
| 分栏条不再吸附 | `top` 从 `-1px` 变 `0px`,多出 8px 上内边距 | 被 `.dsm-trae-usage .dsm-trae-panels` 覆盖 |
|
|
308
|
+
| 两行布局退回 flex | 行高 75 → 111px,签到掉回第二行 | 被 `.dsm-trae-usage .dsm-trae-pool-row{display:flex}` 覆盖 |
|
|
309
|
+
|
|
310
|
+
第二处尤其隐蔽:网格的**列**规则仍然生效(`grid-template-columns` 还是三列),
|
|
311
|
+
只有 `display` 变回 flex,所以看起来像内边距问题而不是层叠问题。
|
|
312
|
+
|
|
313
|
+
修法都是给既有选择器补上 `.dsm-trae-usage` 这一跳,把权重提到 0,3,0。测试也一并
|
|
314
|
+
改成**断言这一跳本身**——原来只断言声明文本存在,覆盖发生时它们仍然全绿,这是
|
|
315
|
+
1380 条测试没能发现布局被改坏的原因。
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
- 统一同系列功能说明与账号池布局,增加本地保存的图标样式选择。顶部页签与保存栏仅在吸附时展开边界,底部背景覆盖滚动视口内边距。保留 Trae 的共享凭据和设备限额差异。
|
|
319
|
+
|
|
320
|
+
- 新增默认关闭的国内个人账号池每日自动签到,保存后按所选成员运行,失败退避重试,并保存当天进度以便重启恢复。领取沿用已有串行流程;成功领取或设备已领取响应会终止当天后台领取,避免将设备限额误当作所有账号均已领取。
|
|
321
|
+
- 当天已确认完成后,重新打开页面不再查询绑定账号的签到状态,也不会把查询失败显示为领取失败。跨天后恢复资格查询。
|
|
322
|
+
|
|
323
|
+
## 4.13.1 (2026-11-07)
|
|
324
|
+
|
|
325
|
+
### 修复
|
|
326
|
+
|
|
327
|
+
**模型选择器里的胶囊一直不显示——不是样式问题,是座位优先级被压住了。**
|
|
328
|
+
|
|
329
|
+
`conversation.input.model.label` 是宿主的 **single** 槽位:只渲染**优先级最低**的
|
|
330
|
+
那一条注册。三个连接器插件的实际取值(从各自源码读出,不是凭记忆):
|
|
331
|
+
|
|
332
|
+
| 插件 | 优先级 | 结果 |
|
|
333
|
+
| --- | --- | --- |
|
|
334
|
+
| dsh-connect-qoder | `-101` | 渲染 |
|
|
335
|
+
| dsh-connect-workbuddy | `-100` | 不渲染 |
|
|
336
|
+
| dsh-connect-trae | `-0.25` | **永远不渲染** |
|
|
337
|
+
|
|
338
|
+
本插件是三家里优先级**最高**的,所以胶囊组件和样式都没问题,只是轮不到它画。
|
|
339
|
+
|
|
340
|
+
根因是一段**读到过时信息的注释**:代码里写的是「qoder `-1`、workbuddy `-0.5`,
|
|
341
|
+
`-0.25` 夹在中间所以不冲突」。两个数字都已过时——两家各自往下挪了一百,而这段
|
|
342
|
+
注释还在描述它们离开时的站位。这种错误能活过一次 review,是因为它写得很有把握。
|
|
343
|
+
|
|
344
|
+
现在改为 **`-102`**,比 qoder 低一格。
|
|
345
|
+
|
|
346
|
+
**为什么不取一个更极端的数(比如 `-1000`)**:只需要这一个座位,而一个没必要的
|
|
347
|
+
极端值会把将来任何选了合理数字的插件永久压到身后。qoder 当时也是这么做的——
|
|
348
|
+
它取 `-101` 也只是贴在 workbuddy 的 `-100` 之下。
|
|
349
|
+
|
|
350
|
+
### 代价(已写进代码注释)
|
|
351
|
+
|
|
352
|
+
抢到 single 槽位意味着**败者在这里完全不渲染**:本插件注册后,qoder 自己的胶囊会
|
|
353
|
+
从选择器里消失。这是该槽位记录在案的取舍,不是 bug——也正因为如此,
|
|
354
|
+
`TraeModelLabel` 对**非本插件的模型行必须原样复现宿主的呈现**(目录名 + 该模型
|
|
355
|
+
自带的徽标),而不是什么都不画。一整个选择器因为某个连接器赢了座位而丢掉每一行
|
|
356
|
+
的模型名,比丢一个促销胶囊要糟得多。
|
|
357
|
+
|
|
358
|
+
### 测试
|
|
359
|
+
|
|
360
|
+
全仓 **105 个测试文件 / 1369 例**通过。新增一条守卫:优先级必须低于 qoder 的
|
|
361
|
+
`-101` 与 workbuddy 的 `-100`,必须高于 `-200`(只争一格),且不能与任何一家相等
|
|
362
|
+
(同优先级会让宿主**抛异常**,那是 issue #74 报过的现象)。
|
|
363
|
+
|
|
364
|
+
## 4.13.0 (2026-11-07)
|
|
365
|
+
|
|
366
|
+
### 变更
|
|
367
|
+
|
|
368
|
+
**账号池每行收敛为 2 行,签到回到第一行。**
|
|
369
|
+
|
|
370
|
+
布局从 flex-wrap 改成**显式两行网格**:
|
|
371
|
+
|
|
372
|
+
```
|
|
373
|
+
第一行 [勾选 · 账号名 · 标签] [签到状态 + 按钮] [积分]
|
|
374
|
+
第二行 [状态 · 测得时间 · 来源] [测速] [删除]
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### 为什么不用 flex-wrap
|
|
378
|
+
|
|
379
|
+
两个目标都关于**位置**,而 flex-wrap 由内容决定位置:账号名短的时候签到会飘到自己的
|
|
380
|
+
一行;账号名长的时候积分落到行中间(实测 x=588,而邻居是 773)。同一张表里三行分别
|
|
381
|
+
占了 4 / 4 / 3 个纵向位置,看起来像三种不同的行。
|
|
382
|
+
|
|
383
|
+
网格把每个事实钉在格子里,于是无论账号名多长:
|
|
384
|
+
|
|
385
|
+
- 积分永远停在同一右缘(实测三行全部 773);
|
|
386
|
+
- 签到永远和名字同一行;
|
|
387
|
+
- 状态与按钮永远在第二行。
|
|
388
|
+
|
|
389
|
+
列的定义:`1fr` 会伸缩的那一列(账号名、状态),`auto` 给签到(宽度由文字决定:
|
|
390
|
+
「已签到」「未签到」「本设备今日已用」),`auto` 给积分(贴右缘、`flex:none`
|
|
391
|
+
不许压缩)。数字用等宽字形,小数点因此也对齐。
|
|
392
|
+
|
|
393
|
+
行高 **92px → 76px**(第三行 74px,那一行没有签到按钮)。
|
|
394
|
+
|
|
395
|
+
### 测试
|
|
396
|
+
|
|
397
|
+
全仓 **105 个测试文件 / 1369 例**通过。
|
|
398
|
+
|
|
399
|
+
`tests/settings-section.spec.tsx` 里那条锁定布局的用例重写了:它原先钉的是 flex
|
|
400
|
+
实现(`flex:1 1 auto` + auto 外边距),现在钉网格——行必须是 `display:grid` 且
|
|
401
|
+
`grid-template-columns:minmax(0,1fr) auto auto`,账号/签到/积分必须 `grid-row:1`,
|
|
402
|
+
状态与按钮必须 `grid-row:2`,按钮要 `grid-column:2/-1` 才能贴到积分下方。
|
|
403
|
+
|
|
404
|
+
## 4.12.2 (2026-11-07)
|
|
405
|
+
|
|
406
|
+
### 变更
|
|
407
|
+
|
|
408
|
+
**「能力」分栏移到「模型」之后。** 分栏顺序由「账号 / 能力 / 模型 / 诊断」改为
|
|
409
|
+
「账号 / 模型 / 能力 / 诊断」。
|
|
410
|
+
|
|
411
|
+
这不是随手对调,而是让阅读顺序合成一条线:**谁在服务 → 能服务什么 → 插件可以拿它
|
|
412
|
+
做什么 → 排障要看的事实**。「能力」里那三个开关(输入框读数、显示位置、侧边栏行)
|
|
413
|
+
作用于「模型」启用出来的东西,放在它前面等于先讲用法再讲对象。
|
|
414
|
+
|
|
415
|
+
### 测试
|
|
416
|
+
|
|
417
|
+
全仓 **105 个测试文件 / 1368 例**通过。新增一条用例把顺序钉住,断言取的是**分栏 id**
|
|
418
|
+
而不是标签文字——标签是本地化文本,一条顺序测试不该在还没发布的语言下失败。
|
|
419
|
+
## 4.12.1 (2026-11-07)
|
|
420
|
+
|
|
421
|
+
### 修复
|
|
422
|
+
|
|
423
|
+
**账号池的积分改成独立右对齐。** 余额是这一行里唯一会被**跨行横向比对**的数字
|
|
424
|
+
(「哪个账号还剩得多」),而它原先紧跟在账号名后面按内容摆放——三行的积分分别落在
|
|
425
|
+
x=593 / 614 / 498,眼睛没法竖着扫。
|
|
426
|
+
|
|
427
|
+
改法:账号行吃掉剩余宽度,积分以 `flex:none` + 自动外边距被推到该行末端。实测三行
|
|
428
|
+
全部落在右缘 773,与账号名同处第一行;数字用等宽字形(`tabular-nums`),小圆点因此
|
|
429
|
+
在竖向上也对齐。
|
|
430
|
+
|
|
431
|
+
状态 / 签到 / 按钮三样整体移到第二行:它们讲的是「能不能用」,与「还剩多少」不是
|
|
432
|
+
同一个问题,全挤进第一行只会把账号名压窄。
|
|
433
|
+
|
|
434
|
+
行高 71px → 92px。这是这次改动**唯一的代价**,换来的是一个可以纵向扫描的余额列。
|
|
435
|
+
|
|
436
|
+
### 过程中的一个错误
|
|
437
|
+
|
|
438
|
+
第一版我给积分加了 `order:-1`,本意是让它「独立」成列,实际把它排到了行首
|
|
439
|
+
(左缘 235)。是浏览器实测的坐标抓到的——截图里积分看着确实「变整齐了」,但它们
|
|
440
|
+
整齐地待在**左边**。已移除该属性,只保留自动外边距。
|
|
441
|
+
|
|
442
|
+
### 测试
|
|
443
|
+
|
|
444
|
+
全仓 **105 个测试文件 / 1367 例**通过。`tests/settings-section.spec.tsx` 新增一条
|
|
445
|
+
用例锁定这次布局:积分必须 `flex:none` + `text-align:right` + `tabular-nums`,
|
|
446
|
+
账号行必须是 `flex:1 1 auto`(不能是 `100%`,否则旁边没有空间给积分),
|
|
447
|
+
且状态块必须 `flex:1 1 100%` 独占第二行。
|
|
448
|
+
|
|
449
|
+
## 4.12.0 (2026-11-07)
|
|
450
|
+
|
|
451
|
+
### 新增
|
|
452
|
+
|
|
453
|
+
**「能力」分栏:三个偏好开关从账号分栏里搬出来。** 参考同族 `dsh-connect-workbuddy`
|
|
454
|
+
的 `panelCapabilities`。
|
|
455
|
+
|
|
456
|
+
卡片的分栏从「账号 / 模型 / 诊断」变成「账号 / 能力 / 模型 / 诊断」。
|
|
457
|
+
|
|
458
|
+
理由是这三个开关回答的问题和账号分栏不是同一个:「在输入框工具栏显示积分读数」
|
|
459
|
+
「显示位置」「在侧边栏显示 Trae」讲的是**插件可以替你做什么**,而账号分栏讲的是
|
|
460
|
+
**现在是谁在服务、还剩多少**。混在一起时,用户读余额要越过三条设置往下滚;
|
|
461
|
+
而这三条设置又夹在账号列表和账号池之间,两边都不是它们的归属。
|
|
462
|
+
|
|
463
|
+
分栏顶部有一行说明(「这些开关点一下立即生效 —— 开关没有保存按钮」):一个放着
|
|
464
|
+
设置却没有保存按钮的分区,是读者第一个会怀疑的地方。
|
|
465
|
+
|
|
466
|
+
**实现上复用同一个组件。** `TraeAccountPanel` 新增 `section` 属性,`'prefs'` 只渲染
|
|
467
|
+
开关块,`'accounts'` 跳过它,`'all'`(默认)两者都给——卡片与侧边栏整页没有分栏导航,
|
|
468
|
+
在那里拆开会把开关藏进一个无法抵达的位置。
|
|
469
|
+
|
|
470
|
+
### 修复
|
|
471
|
+
|
|
472
|
+
**账号池每行的按钮不再独占一行。** 参考 workbuddy 的同位做法。
|
|
473
|
+
|
|
474
|
+
窄列下账号池每行是折行的 flex 行,而按钮块原先带 `flex:1 1 100%`,所以它必定换到
|
|
475
|
+
下一行、靠左,整行因此多出一行高度。workbuddy 的解法是让按钮**跟着名称行走**:
|
|
476
|
+
名称行本来就是行内最短的一行,右侧总有空位。
|
|
477
|
+
|
|
478
|
+
- 基础规则加 `margin-inline-start:auto`(推到该行末端)与 `flex:none`(按钮可以被
|
|
479
|
+
挤到折行,但不能被压窄到点不动)。
|
|
480
|
+
- 窄列里账号行从 `flex:1 1 100%` 改成 `flex:0 1 auto`,把右侧空间让给按钮。
|
|
481
|
+
|
|
482
|
+
实测:单行高度 **102px → 71px**,按钮落到账号行右端、与「已签到」同排。
|
|
483
|
+
内容特别长的行仍会折到第二行——这是正确的:按钮不压缩,宁可折行。
|
|
484
|
+
|
|
485
|
+
### 测试
|
|
486
|
+
|
|
487
|
+
全仓 **105 个测试文件 / 1367 例**通过。
|
|
488
|
+
|
|
489
|
+
新增用例锁定分栏边界:开关必须在「能力」页、**不在**账号页,且能力页没有保存页脚。
|
|
490
|
+
同时修掉一个真实缺陷——第一版只把开关块用条件包起来,账号表与签到块仍在渲染,
|
|
491
|
+
于是能力页出现**第二份账号列表**,12 个测试报「Found multiple elements with the
|
|
492
|
+
text: demo-user」。改成 `section: 'prefs'` 时提前 return,只渲染开关块。
|
|
493
|
+
|
|
494
|
+
`tests/sidebar-panel-optin.spec.tsx` 的 6 条用例改为先打开「能力」页再到找那个开关:
|
|
495
|
+
开关从默认分栏不可达,正是这次搬家的目的,不是搬家带来的副作用。
|
|
496
|
+
|
|
497
|
+
## 4.11.0 (2026-11-07)
|
|
498
|
+
|
|
499
|
+
### 修复
|
|
500
|
+
|
|
501
|
+
**积分浮层的「设置」按钮在老外壳上会停在错误的分栏。** 参考同族 `dsh-connect-workbuddy`
|
|
502
|
+
5.20.4 的做法,改成两条路径。
|
|
503
|
+
|
|
504
|
+
原实现只有一条路:读设置外壳发布在 `sidebar.settings` 上的 store 座位,调
|
|
505
|
+
`create().actions.openSection(id)`。这条路在当代外壳上是对的,但它对**只有 `open()`
|
|
506
|
+
没有 `openSection()` 的旧外壳**处理错了——调用 `open()` 打开面板后**直接返回 `true`**,
|
|
507
|
+
于是按钮报告成功,用户却停在设置面板的默认分栏(第一个),而不是本插件那一页。
|
|
508
|
+
按钮说的事和它做的事不一致,而且没有任何提示。
|
|
509
|
+
|
|
510
|
+
现在按优先级两条路:
|
|
511
|
+
|
|
512
|
+
1. **座位** —— 一次调用,无重试、无文案依赖、不会先停在错误页面再跳。
|
|
513
|
+
2. **驱动 chrome** —— 点宿主的设置入口,等面板挂载,再按**标签文字**点本插件那一行。
|
|
514
|
+
|
|
515
|
+
座位只有 `open()` 时,仍调 `open()` 打开面板,然后**继续走第 2 条**去点本插件那一行,
|
|
516
|
+
所以在老外壳上按钮也会落到名字所指的那一页。第 2 条保留而不是删掉:本插件声明的宿主
|
|
517
|
+
版本跨度里可能有更早的外壳没有座位,删掉等于拿一个可用的兜底换一个空。
|
|
518
|
+
|
|
519
|
+
### 新增
|
|
520
|
+
|
|
521
|
+
- **`onGiveUp` 兜底回调**:宿主完全没有设置面板(或侧边栏收成 rail)时,
|
|
522
|
+
不再留一个按了没反应的按钮,而是交给调用方一个兜底落点。
|
|
523
|
+
- **重试预算 5 次约 300ms**:面板是同步挂载的,但导航行的标签要等一次 locale 快照,
|
|
524
|
+
同帧查询常常早一拍。早于预算耗尽就放弃,会把「正在渲染」误判成「没有这一行」。
|
|
525
|
+
|
|
526
|
+
### 细节
|
|
527
|
+
|
|
528
|
+
- **按**精确等值**匹配导航行,不是包含匹配**:「模型」是「模型与预设」的子串,
|
|
529
|
+
松匹配会点错行,用户会莫名其妙落到别人的页面。
|
|
530
|
+
- **标签是 thunk,每次按下重新求值**:注册时捕获的字符串在用户切换语言后就再也对不上;
|
|
531
|
+
这与 `settings.section` 注册里 `label: () => t('nav.panel')` 用的是同一个键。
|
|
532
|
+
- **已展开的入口不会去点**:点一个 `aria-expanded="true"` 的入口会**关闭**面板,
|
|
533
|
+
那会读成"设置窗口闪一下就没了"。
|
|
534
|
+
- **永不抛出**:每条路都跑在渲染路径的点击处理里,抛异常会带走整块读数而不只是一个按钮,
|
|
535
|
+
所以每一段都自吞失败。
|
|
536
|
+
|
|
537
|
+
### 测试
|
|
538
|
+
|
|
539
|
+
全仓 **105 个测试文件 / 1366 例**通过。`tests/composer-settings-route.spec.tsx` 重写为
|
|
540
|
+
两条路各一组,新增覆盖:旧外壳上仍落到正确页面、精确等值匹配、已展开入口不点、
|
|
541
|
+
`onGiveUp` 在**最后一次**重试之后才触发、标签每次重新求值、以及一个会抛异常的
|
|
542
|
+
`Document` 不会让按钮崩掉。
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
## 4.10.1 (2026-11-06)
|
|
546
|
+
|
|
547
|
+
### 安全
|
|
548
|
+
|
|
549
|
+
**README 产品图中的真实账号标识已清除,并补上发布前的隐私门禁。**
|
|
550
|
+
三条产品图里,`docs/assets/dsh-connect-trae-sidebar-page.png` 与
|
|
551
|
+
`dsh-connect-trae-composer-credits.png` 以**明文**渲染了两个真实的 Trae 账号名
|
|
552
|
+
(一个数字账号 id 与一个昵称);`dsh-connect-trae-usage-card.png` 给昵称打了马赛克,
|
|
553
|
+
但账号名旁边就是真实余额。同一条字符串也躺在生成这些图的 `tools/*-shot.tsx` 夹具里,
|
|
554
|
+
所以它们随 GitHub、第三方市场与已发布的 npm 版本一起分发了。
|
|
555
|
+
|
|
556
|
+
- **根因是夹具,不是图片。** 图片是产物,`tools/` 里的 demo 数据是原因;只涂图片的话,
|
|
557
|
+
下次重出图会把同一个名字原样带回来。现在真实账号名集中替换为
|
|
558
|
+
`tools/demo-accounts.ts` 导出的 `DEMO_NUMERIC_ID` / `DEMO_NICKNAME`,六个 shot 脚本
|
|
559
|
+
全部改为引用它——保持与真实数据同量级的长度,因为那些图是用来量版式的。
|
|
560
|
+
- **余额也一并换成编造值。** 只换账号名是不够的:余额能把一个具体账号钉在一个具体时刻,
|
|
561
|
+
所以它与账号名同属可识别信息。首版修复只改了名字,六个夹具里的余额仍是原样,`684`/`784`
|
|
562
|
+
这类数字紧挨着账号名出现在重出的图里——本版把它们全部换掉。
|
|
563
|
+
`tools/demo-accounts.ts` 里那份「编造的」余额当时其实也是真值,一并纠正。
|
|
564
|
+
- **测试夹具里的真实昵称同样清掉了**(约 115 处,29 个 spec)。它们不进 npm 包,但在公开仓库里,
|
|
565
|
+
而且与图片泄露的是同一类关联信息。本机用户路径 `/Users/<name>/` 一并换成 `/Users/<user>/`。
|
|
566
|
+
- **三张产品图已重出。** 首图同时升级到当前 UI(原图还是 v1.1.0 时期的界面,已落后
|
|
567
|
+
约 80 个版本),并改为 2x 渲染。
|
|
568
|
+
- **两道自动检查,都是新增的**:
|
|
569
|
+
- `tests/privacy.spec.ts`(单测):夹具里的 `accountName` 字面量必须是 `DEMO_*` 或
|
|
570
|
+
明显的合成形状;`CHANGELOG.md` 不得含真实账号 id。
|
|
571
|
+
- `scripts/verify-privacy.mjs`(`pnpm run verify:privacy`,并挂在 `prepublishOnly`):
|
|
572
|
+
检查**发布用 PNG 的字节流与渲染文本**、随包文本,以及版权署名是否仍在。
|
|
573
|
+
像素 OCR 对旧图实测能准确报出两个账号名(数字 id 与昵称各一);对重出的图全部通过。
|
|
574
|
+
没有 OCR 后端时它**明确打印 SKIP 并计为缺口**,不会静默算作通过。
|
|
575
|
+
- **首个版本起就存在的残留**:改动只作用于当前树(HEAD)与之后的版本。已发布的 80 个
|
|
576
|
+
版本、历史 tag 与 fork 无法回收,本项目**不重写历史**——重写会让这些 tag 失去意义,
|
|
577
|
+
而泄露的是账号名与余额,不是凭据。此前版本中的同名内容按此如实记录。
|
|
578
|
+
- **文档**:新增 [`SECURITY.md`](SECURITY.md) 说明「什么不能进入仓库与发布物」,
|
|
579
|
+
[`RELEASING.md`](RELEASING.md) 新增第 1.5 节把这条规则定为**发布阻断项**。
|
|
580
|
+
- **规则落到入口层**:新增 [`AGENTS.md`](AGENTS.md)。此前这条纪律只写在
|
|
581
|
+
`SECURITY.md`(参考文档)与 `DEVELOPMENT.md`(开发说明)里,都需要"有人主动去读";
|
|
582
|
+
AI 助手进仓库时按惯例先读的是 `AGENTS.md`,而本仓库此前没有这个文件(同目录其他
|
|
583
|
+
项目都有)。规则写在没人会先打开的地方,等于没有规则。
|
|
584
|
+
- **守卫文件自己曾带明文。** `tests/privacy.spec.ts` 的注释里举了真实值当"粘连形态
|
|
585
|
+
的例子",包括那个 9 位账号 id 与另一个账号名——哈希表本身是干净的,但注释把黑名单
|
|
586
|
+
的原文摆在了旁边。已改为不含值的描述。写 `AGENTS.md` 时又踩了同一个坑:在「必须
|
|
587
|
+
保留署名」一节直接写出了那个昵称,被 `tests/privacy.spec.ts` 当场判为泄露(这正是
|
|
588
|
+
它该做的事),改为指代 `LICENSE` 的版权行。
|
|
589
|
+
- **已知的检查盲区**(实测确认,不是推测):按值哈希挡不住 Base64 编码、全角数字、
|
|
590
|
+
数字间插空格三种形态;OCR 对低对比度与异常字距会漏。这三类都需要"新写入"或
|
|
591
|
+
"OCR 异常"才会出现,不是仓库里已存在的泄露,但新增图片与文本时不能只依赖门禁。
|
|
592
|
+
|
|
593
|
+
> 顺带:`usage-card` 从 v1.0.0 起就在包里,`sidebar-page` 从 v4.7.0 起。
|
|
594
|
+
> `pnpm run verify:privacy` 现在是发布流程第 1 步的一部分,不再是可选项。
|
|
595
|
+
|
|
3
596
|
## 4.10.0 (2026-11-06)
|
|
4
597
|
|
|
5
598
|
### 变更
|
|
@@ -106,7 +699,7 @@
|
|
|
106
699
|
- 根因:本文件所有 `@media` 断点测的都是**视口**,而设置面板的内容列只有 564px,视口媒体
|
|
107
700
|
查询在那里永不触发——五列表格被塞进 564px,固定轨道 `128+96+128` 加上四条 10px 间隙与
|
|
108
701
|
两倍 14px 内边距后,两条弹性轨道只剩约 144px,**账号名列实测被压到 76px**,
|
|
109
|
-
|
|
702
|
+
一个 9 位数字账号名被截成只剩前 5 个字符。
|
|
110
703
|
- 用 `container-type:inline-size` 让断点改测**表格自身**的宽度。特意放在表格上而不是祖先上:
|
|
111
704
|
`container-type` 会让该元素成为 `position:fixed` 后代的包含块,而忙碌遮罩
|
|
112
705
|
`.dsm-trae-veil` 与账号导入对话框都是这张表的**兄弟**节点,放在表格上让它们继续相对视口
|
|
@@ -119,7 +712,7 @@
|
|
|
119
712
|
`.dsm-trae-pool-row` 改成可换行的 flex 行,但真正竖排的是更里层——
|
|
120
713
|
`.dsm-trae-pool-probe-cell` 自己带 `flex-direction:column`,`.dsm-trae-pool-account`
|
|
121
714
|
自己是三行网格。它们不改,状态列仍然是「正常 / 测得 3 天前 / 来自测速 /
|
|
122
|
-
upstream answered
|
|
715
|
+
upstream answered」四行,账号名反而被挤成前几个字符——行更高且更窄。
|
|
123
716
|
(这个缺陷是用户截图发现的:本地 fixture 里三个账号的探针都是「尚未测速」,
|
|
124
717
|
只渲染一行,正好把问题掩盖掉了。fixture 已补上真实探针记录。)
|
|
125
718
|
- 实测:账号名列 76px → 517px,不再截断;状态列 4 行 → 1 行(17px);
|
|
@@ -164,7 +757,7 @@
|
|
|
164
757
|
### 修复
|
|
165
758
|
|
|
166
759
|
- **积分浮层里两个账号显示同一个数字**(用户报告,2026-11-03)。截图里
|
|
167
|
-
|
|
760
|
+
两个账号都显示 647,而它们在上游其实是两个不同的余额。
|
|
168
761
|
- **根因:表格里「已选中」那一行走的是共享客户端**,而共享客户端读的是
|
|
169
762
|
`store.resolve()` —— 也就是**当前绑定的账号**,不是这一行自己的账号。于是这一行
|
|
170
763
|
报出的是另一个账号的数字。实测复现:两个账号都显示 `644.2932`,而上游按 id 读
|
|
@@ -2266,7 +2859,7 @@ trae 一家,原因:
|
|
|
2266
2859
|
开着的请求就是这种)按钮原来会永久禁用、只能刷新页面;现在到点自动取消、按钮恢复,日志给出
|
|
2267
2860
|
`row.poolLogTimeout`。
|
|
2268
2861
|
- **一次点击只记一条测速日志。** Host 的 `test` 流先逐账号推 `{row}`,末尾再推一份 `{rows}` 汇总重复
|
|
2269
|
-
|
|
2862
|
+
同样的行;卡片把两处都当新结果,于是一次点击出现两条「某账号:正常。」。现在按 `accountId` 去重,
|
|
2270
2863
|
第二次只刷新单元格、不再记日志(单号测速路径同样去重)。
|
|
2271
2864
|
- **移除之后按钮不再永远停在「移除中…」。** 移除成功后写下的标记,与按钮显示「移除中…」读的是同一个
|
|
2272
2865
|
状态,于是按钮从此一直是转圈的禁用态——点成功了也长得像点坏了。现在进行中的账号只在一个集合里停留
|
|
@@ -3045,7 +3638,7 @@ trae 一家,原因:
|
|
|
3045
3638
|
|
|
3046
3639
|
### 修复(「刷新账号与 Token」显示已绑定,设置里却是空的)
|
|
3047
3640
|
|
|
3048
|
-
- **刷新账号后显示的账号,就是真正被写进设置的账号**。报告的现象是:刷新后提示「找到 1 个账号 · 仍绑定
|
|
3641
|
+
- **刷新账号后显示的账号,就是真正被写进设置的账号**。报告的现象是:刷新后提示「找到 1 个账号 · 仍绑定 demo-user」,而设置文档里 `accounts.cn` **一直是空的**。
|
|
3049
3642
|
- 根因是把路由的 `selected` 标志读成了「已经绑定过」。该标志回答的是「**哪个账号正在使用**」,而不是「哪个账号被显式保存过」:没有保存过绑定时,存储层会用**探测到的默认值**(Trae App 当前登录的那个)来回答。于是「从未显式绑定」被读成「检测到的默认值正在使用,无需写入」——刷新恰好**什么都没保存**,而卡片却把它显示成已绑定;下一次存储重建(重启、或 Trae 换登录)就静默改回别的账号。
|
|
3050
3643
|
- 现在按「**这个区域实际在用的账号**(存储层的裁决,也正是卡片显示的那个)写盘」,仅在路由完全没给出裁决时才退回该区域自己的名册;**已经是保存中绑定**的行不重复写——一次点击不该为无变化重写设置文档(那会触发 `loader/volatile-update`、重跑 `applySelection` 并重建两个存储)。
|
|
3051
3644
|
- 提示语随之区分两件事:`已绑定 X`(这次真的写入了)与 `仍绑定 X`(这次没写,X 是既有绑定)。旧措辞把「保存了」和「本来就没动」压成同一句话,正是这个缺口看不见的原因。
|