dsh-connect-trae 4.9.1 → 4.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,417 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.14.2 (2026-11-07)
4
+
5
+ ### 修复:非 loopback 绑定下,同源请求被当成外站(issue #78)
6
+
7
+ 用 `dsh web --host 100.64.0.2` 这类**具体 LAN 地址**启动时,浏览器里所有写操作都
8
+ 返回 `403 origin-not-trusted`:刷新账号、签到、导入导出、模型刷新、模型测试、账号池
9
+ 写入。页面本身能打开,一点按钮就失败。
10
+
11
+ **根因**:`loopbackOrigin()` 只认 loopback 主机名,于是把「同源」判成了「外站」。
12
+ 浏览器对同源 POST **一定**带 `Origin`,这个值就是页面自己的地址;而 host 信任层
13
+ (`--trusted-host`)早就接受了同一个地址。两层判断割裂:host 层放行、插件层拒绝。
14
+
15
+ **为什么一直没被发现**:`curl` 和插件自身的 `fetch` 都不发 `Origin`,走的正是
16
+ `origin === undefined` 那条放行分支。**故障只在真实浏览器里出现**——这也解释了它
17
+ 为什么能躲过当时的测试。9 条路由共用这一个判定,所以一处坏、九处全坏。
18
+
19
+ **修法**:判定从「是不是 loopback」改成「是不是同一个源」,按三种拼写接受——
20
+ loopback、与 `Host` 头主机名相同、与 `req.socket.localAddress` 相同。只比主机名
21
+ 不比端口,所以反向代理换端口不受影响。
22
+
23
+ 比同生态的 dsh-connect-workbuddy 5.13.1 多出「命中 `Host` 头」这一条:反向代理
24
+ 可能把 `Host` 改写成另一个已受信拼写,此时 `Origin` 与 `localAddress` 都不一定
25
+ 同拼写。issue 作者已在本机验证过这个组合。
26
+
27
+ **比较用精确相等,不用 `endsWith`/`startsWith`**:前缀比较会放过
28
+ `100.64.0.20`,后缀比较会放过 `evil-100.64.0.2`。测试里这两条都钉住了。
29
+
30
+ ### 修复:刷新账号失败时误报「设置未能保存」
31
+
32
+ 同一次 403 会同时打出两条错误,其中 `row.settingsWriteFailed` 渲染成
33
+ 「设置未能保存:…」——但这条路径**根本没有写设置**,它刷新的是账号列表。
34
+ issue 作者自述先跑去确认 settings 持久化,然后才回头找到真正的原因。
35
+ 现在这条路径只更新 `rescanNote`;`writeError` 留给真正会写的路由。
36
+
37
+ ### 验证
38
+
39
+ - 全仓 **108 文件 / 1389 例**通过(新增 7 例)。
40
+ - 新增测试按 issue 的行为矩阵逐条覆盖:LAN 同源放行、代理改写 `Host` 放行、
41
+ 三种 loopback 拼写放行、无 `Origin` 放行;外站、前缀欺骗、后缀欺骗、
42
+ `Origin: null`、非法 `Origin` 一律拒绝。
43
+ - **先写测试并确认它失败**(3 例红:两条 LAN 同源、一条空 `Origin`),再修到全绿。
44
+
45
+ ### 未做
46
+
47
+ issue 备注里提到的第二处差异——trae 的 web 路由没有 `Host` 校验(workbuddy 是
48
+ Host + Origin 双闸)——**按 issue 作者的建议没有捆绑**。加 Host 闸会改变行为,
49
+ 前置代理改写 Host 的用户会被拒,应当单独评估。
50
+
51
+ ## 4.14.1 (2026-11-07)
52
+
53
+ ### 变更
54
+
55
+ **图标样式移进「能力」分栏;默认改回本插件自己的图标。**
56
+
57
+ 两处都关于「这个选项该待在哪」。
58
+
59
+ **位置**:它原先挂在账号池设置的标题行里,也就是「账号」分栏。那是错误的分类——
60
+ 旁边每一样东西都在讲**哪些账号在供能**(谁进了池、草稿保存后会怎样),而这个开关
61
+ 改的是插件**长什么样**,与账号无关。它也不需要保存,所以更不该待在一条写着
62
+ 「点保存才生效」的区块里。
63
+
64
+ 现在它是「能力」的第四行,排在三个开关之后:上面三条改行为,这一条改一张图。
65
+
66
+ **默认值**:原先未设置时读作 `claude`,意味着每个新用户**什么都没选**就先看到一枚
67
+ 重绘的剪影,而插件自己的图标成了需要主动去找的选项。一个「未设置 = 非默认」的
68
+ 偏好是糟糕的默认值——它把产品自己的身份变成了要用户opt-in的东西。
69
+
70
+ 现在未设置读作 `original`(本插件图标)。存储里明确写过 `claude` 的仍然保持
71
+ Claude 风格,**只有「从未设置」这一种情况变了**。
72
+
73
+ ### 细节
74
+
75
+ - `.dsm-trae-prefs .dsm-icon-preference{padding:0}`:这个组件在独立摆放时自带
76
+ 12px 块内边距,放进 14px 间距的偏好列表后会让这一行明显偏离它的三个邻居
77
+ (那三条都是无内边距的裸行)。只在能力分栏里抹掉,独立场景的间距仍然正确。
78
+ - `.dsm-trae-prefs .dsm-icon-preference-copy{min-width:0}`:原先的 180px 下限是
79
+ 按英文短词定的,中文提示句在 564px 列里会把它撑出去,而不是换行。
80
+
81
+ ### 测试
82
+
83
+ 全仓 **108 个测试文件 / 1382 例**通过(新增 2 例)。
84
+
85
+ `tests/plugin-icon.spec.tsx` 的三条按新默认值重写,其中「存储被拒」那条现在选的是
86
+ **非默认项**——如果拒绝后回落到默认值,旧写法会把它读成成功,测试绿着而图标已经
87
+ 悄悄变回去了。新增 `tests/card-panels.spec.tsx` 一例:图标选择必须在「能力」里、
88
+ 不在「账号」里,且是偏好列表的最后一行。
89
+
90
+ ## 4.14.0 (2026-11-07)
91
+
92
+ ### 合并 PR #79(作者 @kiwaro)
93
+
94
+ 自动签到(默认关闭)、图标样式选择、同系列布局统一。
95
+
96
+ **合并时发现并修掉的两处特异性冲突**——这是本次最值得记下来的部分。PR 引入的
97
+ `series-layout.ts` 把规则**追加在样式表末尾**,而它用的选择器(`.dsm-trae-usage X`)
98
+ 与本仓库既有规则**同权重(0,2,0)**,靠源码顺序取胜。表现是「测试全绿、界面已坏」:
99
+
100
+ | 现象 | 实测 | 原因 |
101
+ | --- | --- | --- |
102
+ | 分栏条不再吸附 | `top` 从 `-1px` 变 `0px`,多出 8px 上内边距 | 被 `.dsm-trae-usage .dsm-trae-panels` 覆盖 |
103
+ | 两行布局退回 flex | 行高 75 → 111px,签到掉回第二行 | 被 `.dsm-trae-usage .dsm-trae-pool-row{display:flex}` 覆盖 |
104
+
105
+ 第二处尤其隐蔽:网格的**列**规则仍然生效(`grid-template-columns` 还是三列),
106
+ 只有 `display` 变回 flex,所以看起来像内边距问题而不是层叠问题。
107
+
108
+ 修法都是给既有选择器补上 `.dsm-trae-usage` 这一跳,把权重提到 0,3,0。测试也一并
109
+ 改成**断言这一跳本身**——原来只断言声明文本存在,覆盖发生时它们仍然全绿,这是
110
+ 1380 条测试没能发现布局被改坏的原因。
111
+
112
+
113
+ - 统一同系列功能说明与账号池布局,增加本地保存的图标样式选择。顶部页签与保存栏仅在吸附时展开边界,底部背景覆盖滚动视口内边距。保留 Trae 的共享凭据和设备限额差异。
114
+
115
+ - 新增默认关闭的国内个人账号池每日自动签到,保存后按所选成员运行,失败退避重试,并保存当天进度以便重启恢复。领取沿用已有串行流程;成功领取或设备已领取响应会终止当天后台领取,避免将设备限额误当作所有账号均已领取。
116
+ - 当天已确认完成后,重新打开页面不再查询绑定账号的签到状态,也不会把查询失败显示为领取失败。跨天后恢复资格查询。
117
+
118
+ ## 4.13.1 (2026-11-07)
119
+
120
+ ### 修复
121
+
122
+ **模型选择器里的胶囊一直不显示——不是样式问题,是座位优先级被压住了。**
123
+
124
+ `conversation.input.model.label` 是宿主的 **single** 槽位:只渲染**优先级最低**的
125
+ 那一条注册。三个连接器插件的实际取值(从各自源码读出,不是凭记忆):
126
+
127
+ | 插件 | 优先级 | 结果 |
128
+ | --- | --- | --- |
129
+ | dsh-connect-qoder | `-101` | 渲染 |
130
+ | dsh-connect-workbuddy | `-100` | 不渲染 |
131
+ | dsh-connect-trae | `-0.25` | **永远不渲染** |
132
+
133
+ 本插件是三家里优先级**最高**的,所以胶囊组件和样式都没问题,只是轮不到它画。
134
+
135
+ 根因是一段**读到过时信息的注释**:代码里写的是「qoder `-1`、workbuddy `-0.5`,
136
+ `-0.25` 夹在中间所以不冲突」。两个数字都已过时——两家各自往下挪了一百,而这段
137
+ 注释还在描述它们离开时的站位。这种错误能活过一次 review,是因为它写得很有把握。
138
+
139
+ 现在改为 **`-102`**,比 qoder 低一格。
140
+
141
+ **为什么不取一个更极端的数(比如 `-1000`)**:只需要这一个座位,而一个没必要的
142
+ 极端值会把将来任何选了合理数字的插件永久压到身后。qoder 当时也是这么做的——
143
+ 它取 `-101` 也只是贴在 workbuddy 的 `-100` 之下。
144
+
145
+ ### 代价(已写进代码注释)
146
+
147
+ 抢到 single 槽位意味着**败者在这里完全不渲染**:本插件注册后,qoder 自己的胶囊会
148
+ 从选择器里消失。这是该槽位记录在案的取舍,不是 bug——也正因为如此,
149
+ `TraeModelLabel` 对**非本插件的模型行必须原样复现宿主的呈现**(目录名 + 该模型
150
+ 自带的徽标),而不是什么都不画。一整个选择器因为某个连接器赢了座位而丢掉每一行
151
+ 的模型名,比丢一个促销胶囊要糟得多。
152
+
153
+ ### 测试
154
+
155
+ 全仓 **105 个测试文件 / 1369 例**通过。新增一条守卫:优先级必须低于 qoder 的
156
+ `-101` 与 workbuddy 的 `-100`,必须高于 `-200`(只争一格),且不能与任何一家相等
157
+ (同优先级会让宿主**抛异常**,那是 issue #74 报过的现象)。
158
+
159
+ ## 4.13.0 (2026-11-07)
160
+
161
+ ### 变更
162
+
163
+ **账号池每行收敛为 2 行,签到回到第一行。**
164
+
165
+ 布局从 flex-wrap 改成**显式两行网格**:
166
+
167
+ ```
168
+ 第一行 [勾选 · 账号名 · 标签] [签到状态 + 按钮] [积分]
169
+ 第二行 [状态 · 测得时间 · 来源] [测速] [删除]
170
+ ```
171
+
172
+ ### 为什么不用 flex-wrap
173
+
174
+ 两个目标都关于**位置**,而 flex-wrap 由内容决定位置:账号名短的时候签到会飘到自己的
175
+ 一行;账号名长的时候积分落到行中间(实测 x=588,而邻居是 773)。同一张表里三行分别
176
+ 占了 4 / 4 / 3 个纵向位置,看起来像三种不同的行。
177
+
178
+ 网格把每个事实钉在格子里,于是无论账号名多长:
179
+
180
+ - 积分永远停在同一右缘(实测三行全部 773);
181
+ - 签到永远和名字同一行;
182
+ - 状态与按钮永远在第二行。
183
+
184
+ 列的定义:`1fr` 会伸缩的那一列(账号名、状态),`auto` 给签到(宽度由文字决定:
185
+ 「已签到」「未签到」「本设备今日已用」),`auto` 给积分(贴右缘、`flex:none`
186
+ 不许压缩)。数字用等宽字形,小数点因此也对齐。
187
+
188
+ 行高 **92px → 76px**(第三行 74px,那一行没有签到按钮)。
189
+
190
+ ### 测试
191
+
192
+ 全仓 **105 个测试文件 / 1369 例**通过。
193
+
194
+ `tests/settings-section.spec.tsx` 里那条锁定布局的用例重写了:它原先钉的是 flex
195
+ 实现(`flex:1 1 auto` + auto 外边距),现在钉网格——行必须是 `display:grid` 且
196
+ `grid-template-columns:minmax(0,1fr) auto auto`,账号/签到/积分必须 `grid-row:1`,
197
+ 状态与按钮必须 `grid-row:2`,按钮要 `grid-column:2/-1` 才能贴到积分下方。
198
+
199
+ ## 4.12.2 (2026-11-07)
200
+
201
+ ### 变更
202
+
203
+ **「能力」分栏移到「模型」之后。** 分栏顺序由「账号 / 能力 / 模型 / 诊断」改为
204
+ 「账号 / 模型 / 能力 / 诊断」。
205
+
206
+ 这不是随手对调,而是让阅读顺序合成一条线:**谁在服务 → 能服务什么 → 插件可以拿它
207
+ 做什么 → 排障要看的事实**。「能力」里那三个开关(输入框读数、显示位置、侧边栏行)
208
+ 作用于「模型」启用出来的东西,放在它前面等于先讲用法再讲对象。
209
+
210
+ ### 测试
211
+
212
+ 全仓 **105 个测试文件 / 1368 例**通过。新增一条用例把顺序钉住,断言取的是**分栏 id**
213
+ 而不是标签文字——标签是本地化文本,一条顺序测试不该在还没发布的语言下失败。
214
+ ## 4.12.1 (2026-11-07)
215
+
216
+ ### 修复
217
+
218
+ **账号池的积分改成独立右对齐。** 余额是这一行里唯一会被**跨行横向比对**的数字
219
+ (「哪个账号还剩得多」),而它原先紧跟在账号名后面按内容摆放——三行的积分分别落在
220
+ x=593 / 614 / 498,眼睛没法竖着扫。
221
+
222
+ 改法:账号行吃掉剩余宽度,积分以 `flex:none` + 自动外边距被推到该行末端。实测三行
223
+ 全部落在右缘 773,与账号名同处第一行;数字用等宽字形(`tabular-nums`),小圆点因此
224
+ 在竖向上也对齐。
225
+
226
+ 状态 / 签到 / 按钮三样整体移到第二行:它们讲的是「能不能用」,与「还剩多少」不是
227
+ 同一个问题,全挤进第一行只会把账号名压窄。
228
+
229
+ 行高 71px → 92px。这是这次改动**唯一的代价**,换来的是一个可以纵向扫描的余额列。
230
+
231
+ ### 过程中的一个错误
232
+
233
+ 第一版我给积分加了 `order:-1`,本意是让它「独立」成列,实际把它排到了行首
234
+ (左缘 235)。是浏览器实测的坐标抓到的——截图里积分看着确实「变整齐了」,但它们
235
+ 整齐地待在**左边**。已移除该属性,只保留自动外边距。
236
+
237
+ ### 测试
238
+
239
+ 全仓 **105 个测试文件 / 1367 例**通过。`tests/settings-section.spec.tsx` 新增一条
240
+ 用例锁定这次布局:积分必须 `flex:none` + `text-align:right` + `tabular-nums`,
241
+ 账号行必须是 `flex:1 1 auto`(不能是 `100%`,否则旁边没有空间给积分),
242
+ 且状态块必须 `flex:1 1 100%` 独占第二行。
243
+
244
+ ## 4.12.0 (2026-11-07)
245
+
246
+ ### 新增
247
+
248
+ **「能力」分栏:三个偏好开关从账号分栏里搬出来。** 参考同族 `dsh-connect-workbuddy`
249
+ 的 `panelCapabilities`。
250
+
251
+ 卡片的分栏从「账号 / 模型 / 诊断」变成「账号 / 能力 / 模型 / 诊断」。
252
+
253
+ 理由是这三个开关回答的问题和账号分栏不是同一个:「在输入框工具栏显示积分读数」
254
+ 「显示位置」「在侧边栏显示 Trae」讲的是**插件可以替你做什么**,而账号分栏讲的是
255
+ **现在是谁在服务、还剩多少**。混在一起时,用户读余额要越过三条设置往下滚;
256
+ 而这三条设置又夹在账号列表和账号池之间,两边都不是它们的归属。
257
+
258
+ 分栏顶部有一行说明(「这些开关点一下立即生效 —— 开关没有保存按钮」):一个放着
259
+ 设置却没有保存按钮的分区,是读者第一个会怀疑的地方。
260
+
261
+ **实现上复用同一个组件。** `TraeAccountPanel` 新增 `section` 属性,`'prefs'` 只渲染
262
+ 开关块,`'accounts'` 跳过它,`'all'`(默认)两者都给——卡片与侧边栏整页没有分栏导航,
263
+ 在那里拆开会把开关藏进一个无法抵达的位置。
264
+
265
+ ### 修复
266
+
267
+ **账号池每行的按钮不再独占一行。** 参考 workbuddy 的同位做法。
268
+
269
+ 窄列下账号池每行是折行的 flex 行,而按钮块原先带 `flex:1 1 100%`,所以它必定换到
270
+ 下一行、靠左,整行因此多出一行高度。workbuddy 的解法是让按钮**跟着名称行走**:
271
+ 名称行本来就是行内最短的一行,右侧总有空位。
272
+
273
+ - 基础规则加 `margin-inline-start:auto`(推到该行末端)与 `flex:none`(按钮可以被
274
+ 挤到折行,但不能被压窄到点不动)。
275
+ - 窄列里账号行从 `flex:1 1 100%` 改成 `flex:0 1 auto`,把右侧空间让给按钮。
276
+
277
+ 实测:单行高度 **102px → 71px**,按钮落到账号行右端、与「已签到」同排。
278
+ 内容特别长的行仍会折到第二行——这是正确的:按钮不压缩,宁可折行。
279
+
280
+ ### 测试
281
+
282
+ 全仓 **105 个测试文件 / 1367 例**通过。
283
+
284
+ 新增用例锁定分栏边界:开关必须在「能力」页、**不在**账号页,且能力页没有保存页脚。
285
+ 同时修掉一个真实缺陷——第一版只把开关块用条件包起来,账号表与签到块仍在渲染,
286
+ 于是能力页出现**第二份账号列表**,12 个测试报「Found multiple elements with the
287
+ text: demo-user」。改成 `section: 'prefs'` 时提前 return,只渲染开关块。
288
+
289
+ `tests/sidebar-panel-optin.spec.tsx` 的 6 条用例改为先打开「能力」页再到找那个开关:
290
+ 开关从默认分栏不可达,正是这次搬家的目的,不是搬家带来的副作用。
291
+
292
+ ## 4.11.0 (2026-11-07)
293
+
294
+ ### 修复
295
+
296
+ **积分浮层的「设置」按钮在老外壳上会停在错误的分栏。** 参考同族 `dsh-connect-workbuddy`
297
+ 5.20.4 的做法,改成两条路径。
298
+
299
+ 原实现只有一条路:读设置外壳发布在 `sidebar.settings` 上的 store 座位,调
300
+ `create().actions.openSection(id)`。这条路在当代外壳上是对的,但它对**只有 `open()`
301
+ 没有 `openSection()` 的旧外壳**处理错了——调用 `open()` 打开面板后**直接返回 `true`**,
302
+ 于是按钮报告成功,用户却停在设置面板的默认分栏(第一个),而不是本插件那一页。
303
+ 按钮说的事和它做的事不一致,而且没有任何提示。
304
+
305
+ 现在按优先级两条路:
306
+
307
+ 1. **座位** —— 一次调用,无重试、无文案依赖、不会先停在错误页面再跳。
308
+ 2. **驱动 chrome** —— 点宿主的设置入口,等面板挂载,再按**标签文字**点本插件那一行。
309
+
310
+ 座位只有 `open()` 时,仍调 `open()` 打开面板,然后**继续走第 2 条**去点本插件那一行,
311
+ 所以在老外壳上按钮也会落到名字所指的那一页。第 2 条保留而不是删掉:本插件声明的宿主
312
+ 版本跨度里可能有更早的外壳没有座位,删掉等于拿一个可用的兜底换一个空。
313
+
314
+ ### 新增
315
+
316
+ - **`onGiveUp` 兜底回调**:宿主完全没有设置面板(或侧边栏收成 rail)时,
317
+ 不再留一个按了没反应的按钮,而是交给调用方一个兜底落点。
318
+ - **重试预算 5 次约 300ms**:面板是同步挂载的,但导航行的标签要等一次 locale 快照,
319
+ 同帧查询常常早一拍。早于预算耗尽就放弃,会把「正在渲染」误判成「没有这一行」。
320
+
321
+ ### 细节
322
+
323
+ - **按**精确等值**匹配导航行,不是包含匹配**:「模型」是「模型与预设」的子串,
324
+ 松匹配会点错行,用户会莫名其妙落到别人的页面。
325
+ - **标签是 thunk,每次按下重新求值**:注册时捕获的字符串在用户切换语言后就再也对不上;
326
+ 这与 `settings.section` 注册里 `label: () => t('nav.panel')` 用的是同一个键。
327
+ - **已展开的入口不会去点**:点一个 `aria-expanded="true"` 的入口会**关闭**面板,
328
+ 那会读成"设置窗口闪一下就没了"。
329
+ - **永不抛出**:每条路都跑在渲染路径的点击处理里,抛异常会带走整块读数而不只是一个按钮,
330
+ 所以每一段都自吞失败。
331
+
332
+ ### 测试
333
+
334
+ 全仓 **105 个测试文件 / 1366 例**通过。`tests/composer-settings-route.spec.tsx` 重写为
335
+ 两条路各一组,新增覆盖:旧外壳上仍落到正确页面、精确等值匹配、已展开入口不点、
336
+ `onGiveUp` 在**最后一次**重试之后才触发、标签每次重新求值、以及一个会抛异常的
337
+ `Document` 不会让按钮崩掉。
338
+
339
+
340
+ ## 4.10.1 (2026-11-06)
341
+
342
+ ### 安全
343
+
344
+ **README 产品图中的真实账号标识已清除,并补上发布前的隐私门禁。**
345
+ 三条产品图里,`docs/assets/dsh-connect-trae-sidebar-page.png` 与
346
+ `dsh-connect-trae-composer-credits.png` 以**明文**渲染了两个真实的 Trae 账号名
347
+ (一个数字账号 id 与一个昵称);`dsh-connect-trae-usage-card.png` 给昵称打了马赛克,
348
+ 但账号名旁边就是真实余额。同一条字符串也躺在生成这些图的 `tools/*-shot.tsx` 夹具里,
349
+ 所以它们随 GitHub、第三方市场与已发布的 npm 版本一起分发了。
350
+
351
+ - **根因是夹具,不是图片。** 图片是产物,`tools/` 里的 demo 数据是原因;只涂图片的话,
352
+ 下次重出图会把同一个名字原样带回来。现在真实账号名集中替换为
353
+ `tools/demo-accounts.ts` 导出的 `DEMO_NUMERIC_ID` / `DEMO_NICKNAME`,六个 shot 脚本
354
+ 全部改为引用它——保持与真实数据同量级的长度,因为那些图是用来量版式的。
355
+ - **余额也一并换成编造值。** 只换账号名是不够的:余额能把一个具体账号钉在一个具体时刻,
356
+ 所以它与账号名同属可识别信息。首版修复只改了名字,六个夹具里的余额仍是原样,`684`/`784`
357
+ 这类数字紧挨着账号名出现在重出的图里——本版把它们全部换掉。
358
+ `tools/demo-accounts.ts` 里那份「编造的」余额当时其实也是真值,一并纠正。
359
+ - **测试夹具里的真实昵称同样清掉了**(约 115 处,29 个 spec)。它们不进 npm 包,但在公开仓库里,
360
+ 而且与图片泄露的是同一类关联信息。本机用户路径 `/Users/<name>/` 一并换成 `/Users/<user>/`。
361
+ - **三张产品图已重出。** 首图同时升级到当前 UI(原图还是 v1.1.0 时期的界面,已落后
362
+ 约 80 个版本),并改为 2x 渲染。
363
+ - **两道自动检查,都是新增的**:
364
+ - `tests/privacy.spec.ts`(单测):夹具里的 `accountName` 字面量必须是 `DEMO_*` 或
365
+ 明显的合成形状;`CHANGELOG.md` 不得含真实账号 id。
366
+ - `scripts/verify-privacy.mjs`(`pnpm run verify:privacy`,并挂在 `prepublishOnly`):
367
+ 检查**发布用 PNG 的字节流与渲染文本**、随包文本,以及版权署名是否仍在。
368
+ 像素 OCR 对旧图实测能准确报出两个账号名(数字 id 与昵称各一);对重出的图全部通过。
369
+ 没有 OCR 后端时它**明确打印 SKIP 并计为缺口**,不会静默算作通过。
370
+ - **首个版本起就存在的残留**:改动只作用于当前树(HEAD)与之后的版本。已发布的 80 个
371
+ 版本、历史 tag 与 fork 无法回收,本项目**不重写历史**——重写会让这些 tag 失去意义,
372
+ 而泄露的是账号名与余额,不是凭据。此前版本中的同名内容按此如实记录。
373
+ - **文档**:新增 [`SECURITY.md`](SECURITY.md) 说明「什么不能进入仓库与发布物」,
374
+ [`RELEASING.md`](RELEASING.md) 新增第 1.5 节把这条规则定为**发布阻断项**。
375
+ - **规则落到入口层**:新增 [`AGENTS.md`](AGENTS.md)。此前这条纪律只写在
376
+ `SECURITY.md`(参考文档)与 `DEVELOPMENT.md`(开发说明)里,都需要"有人主动去读";
377
+ AI 助手进仓库时按惯例先读的是 `AGENTS.md`,而本仓库此前没有这个文件(同目录其他
378
+ 项目都有)。规则写在没人会先打开的地方,等于没有规则。
379
+ - **守卫文件自己曾带明文。** `tests/privacy.spec.ts` 的注释里举了真实值当"粘连形态
380
+ 的例子",包括那个 9 位账号 id 与另一个账号名——哈希表本身是干净的,但注释把黑名单
381
+ 的原文摆在了旁边。已改为不含值的描述。写 `AGENTS.md` 时又踩了同一个坑:在「必须
382
+ 保留署名」一节直接写出了那个昵称,被 `tests/privacy.spec.ts` 当场判为泄露(这正是
383
+ 它该做的事),改为指代 `LICENSE` 的版权行。
384
+ - **已知的检查盲区**(实测确认,不是推测):按值哈希挡不住 Base64 编码、全角数字、
385
+ 数字间插空格三种形态;OCR 对低对比度与异常字距会漏。这三类都需要"新写入"或
386
+ "OCR 异常"才会出现,不是仓库里已存在的泄露,但新增图片与文本时不能只依赖门禁。
387
+
388
+ > 顺带:`usage-card` 从 v1.0.0 起就在包里,`sidebar-page` 从 v4.7.0 起。
389
+ > `pnpm run verify:privacy` 现在是发布流程第 1 步的一部分,不再是可选项。
390
+
391
+ ## 4.10.0 (2026-11-06)
392
+
393
+ ### 变更
394
+
395
+ **模型行的「上下文预算」从一组单选圆点改成下拉框。** 界面行为与写入口径完全不变
396
+ (同一个 `contextBudgets` 字段、同一套 `contextBudgetOptions` 判定、同一个「保存」),
397
+ 只换了控件的形态。
398
+
399
+ 理由是两个实测问题,都来自「每行一个控件组」这个形态:
400
+
401
+ - **行高会随选项数变化。** 一个模型可能有 1 个、2 个或 3 个可选窗口(开发窗口 /
402
+ 500K / Max,`maxContextWindow` 是否够宽决定后两个)。圆点组把 1-3 个
403
+ 「圆点 + 文字」并排放到行的右边缘,模型名、图片勾选框、圆点组三者争同一行;
404
+ 在 564px 的设置面板列里,圆点组会被挤到模型名下方另起一行,行就长高了。
405
+ 实测下拉框在 1/2/3 个选项下宽高恒为 96×24,行高恒为 68px。
406
+ - **列表看起来参差不齐。** 圆点组是内容撑宽的,3 个选项的行比 1 个选项的行宽,
407
+ 18 行的列表因此有肉眼可见的锯齿。
408
+
409
+ 下拉框还把「当前窗口」变成了一个**值**而不是「一堆圆点里被选中的那个」,这与它
410
+ 下方那行「最大上下文 1M / 400K / 200K」读的是同一件事。
411
+
412
+ 没有 Max 窗口的模型保持原来的只读形态:下拉框只有一项、已选中、禁用——控件仍然
413
+ **显示**窗口,而不是消失。
414
+
3
415
  ## 4.9.1 (2026-11-06)
4
416
 
5
417
  ### 修复
@@ -82,7 +494,7 @@
82
494
  - 根因:本文件所有 `@media` 断点测的都是**视口**,而设置面板的内容列只有 564px,视口媒体
83
495
  查询在那里永不触发——五列表格被塞进 564px,固定轨道 `128+96+128` 加上四条 10px 间隙与
84
496
  两倍 14px 内边距后,两条弹性轨道只剩约 144px,**账号名列实测被压到 76px**,
85
- 「用户7520607992」显示成「用户75」。
497
+ 一个 9 位数字账号名被截成只剩前 5 个字符。
86
498
  - 用 `container-type:inline-size` 让断点改测**表格自身**的宽度。特意放在表格上而不是祖先上:
87
499
  `container-type` 会让该元素成为 `position:fixed` 后代的包含块,而忙碌遮罩
88
500
  `.dsm-trae-veil` 与账号导入对话框都是这张表的**兄弟**节点,放在表格上让它们继续相对视口
@@ -95,7 +507,7 @@
95
507
  `.dsm-trae-pool-row` 改成可换行的 flex 行,但真正竖排的是更里层——
96
508
  `.dsm-trae-pool-probe-cell` 自己带 `flex-direction:column`,`.dsm-trae-pool-account`
97
509
  自己是三行网格。它们不改,状态列仍然是「正常 / 测得 3 天前 / 来自测速 /
98
- upstream answered」四行,账号名反而被挤到「LaoDin」「用户75」——行更高且更窄。
510
+ upstream answered」四行,账号名反而被挤成前几个字符——行更高且更窄。
99
511
  (这个缺陷是用户截图发现的:本地 fixture 里三个账号的探针都是「尚未测速」,
100
512
  只渲染一行,正好把问题掩盖掉了。fixture 已补上真实探针记录。)
101
513
  - 实测:账号名列 76px → 517px,不再截断;状态列 4 行 → 1 行(17px);
@@ -140,7 +552,7 @@
140
552
  ### 修复
141
553
 
142
554
  - **积分浮层里两个账号显示同一个数字**(用户报告,2026-11-03)。截图里
143
- 「用户7520607992」和「LaoDing」都是 647,而它们在上游其实是两个不同的余额。
555
+ 两个账号都显示 647,而它们在上游其实是两个不同的余额。
144
556
  - **根因:表格里「已选中」那一行走的是共享客户端**,而共享客户端读的是
145
557
  `store.resolve()` —— 也就是**当前绑定的账号**,不是这一行自己的账号。于是这一行
146
558
  报出的是另一个账号的数字。实测复现:两个账号都显示 `644.2932`,而上游按 id 读
@@ -2242,7 +2654,7 @@ trae 一家,原因:
2242
2654
  开着的请求就是这种)按钮原来会永久禁用、只能刷新页面;现在到点自动取消、按钮恢复,日志给出
2243
2655
  `row.poolLogTimeout`。
2244
2656
  - **一次点击只记一条测速日志。** Host 的 `test` 流先逐账号推 `{row}`,末尾再推一份 `{rows}` 汇总重复
2245
- 同样的行;卡片把两处都当新结果,于是一次点击出现两条「LaoDing:正常。」。现在按 `accountId` 去重,
2657
+ 同样的行;卡片把两处都当新结果,于是一次点击出现两条「某账号:正常。」。现在按 `accountId` 去重,
2246
2658
  第二次只刷新单元格、不再记日志(单号测速路径同样去重)。
2247
2659
  - **移除之后按钮不再永远停在「移除中…」。** 移除成功后写下的标记,与按钮显示「移除中…」读的是同一个
2248
2660
  状态,于是按钮从此一直是转圈的禁用态——点成功了也长得像点坏了。现在进行中的账号只在一个集合里停留
@@ -3021,7 +3433,7 @@ trae 一家,原因:
3021
3433
 
3022
3434
  ### 修复(「刷新账号与 Token」显示已绑定,设置里却是空的)
3023
3435
 
3024
- - **刷新账号后显示的账号,就是真正被写进设置的账号**。报告的现象是:刷新后提示「找到 1 个账号 · 仍绑定 丁敏华」,而设置文档里 `accounts.cn` **一直是空的**。
3436
+ - **刷新账号后显示的账号,就是真正被写进设置的账号**。报告的现象是:刷新后提示「找到 1 个账号 · 仍绑定 demo-user」,而设置文档里 `accounts.cn` **一直是空的**。
3025
3437
  - 根因是把路由的 `selected` 标志读成了「已经绑定过」。该标志回答的是「**哪个账号正在使用**」,而不是「哪个账号被显式保存过」:没有保存过绑定时,存储层会用**探测到的默认值**(Trae App 当前登录的那个)来回答。于是「从未显式绑定」被读成「检测到的默认值正在使用,无需写入」——刷新恰好**什么都没保存**,而卡片却把它显示成已绑定;下一次存储重建(重启、或 Trae 换登录)就静默改回别的账号。
3026
3438
  - 现在按「**这个区域实际在用的账号**(存储层的裁决,也正是卡片显示的那个)写盘」,仅在路由完全没给出裁决时才退回该区域自己的名册;**已经是保存中绑定**的行不重复写——一次点击不该为无变化重写设置文档(那会触发 `loader/volatile-update`、重跑 `applySelection` 并重建两个存储)。
3027
3439
  - 提示语随之区分两件事:`已绑定 X`(这次真的写入了)与 `仍绑定 X`(这次没写,X 是既有绑定)。旧措辞把「保存了」和「本来就没动」压成同一句话,正是这个缺口看不见的原因。
package/README.en.md CHANGED
@@ -2,6 +2,8 @@
2
2
  <img src="docs/assets/dsh-connect-trae-usage-card.png" width="860" alt="dsh-connect-trae settings panel" />
3
3
  </p>
4
4
 
5
+ > Account names, balances and timestamps in this document and its screenshots are **demo data** (`demo-*`); no real account information is shown.
6
+
5
7
  <h1 align="center">dsh-connect-trae</h1>
6
8
 
7
9
  <p align="center"><b>Connect locally signed-in Trae models to DeepSeek Harness with local DSH tools, a credits overview, and daily check-in claiming.</b></p>
@@ -32,6 +34,11 @@ A [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) bund
32
34
 
33
35
  > **Boundary: this plugin does not interoperate with any other plugin.** Accounts, credentials, the model directory, the account pool and the settings namespace belong to this plugin alone; a `trae` failure never borrows another plugin's account or credit, and vice versa. Why, and what that rules out, is in [Relationship to other plugins](#relationship-to-other-plugins).
34
36
 
37
+ ### Icon style
38
+
39
+ In the account pool, open **More settings** and choose **Claude style** or **Default icon**. The sidebar and page icons update immediately. The choice is stored in the current browser; other browsers keep their own choice.
40
+
41
+
35
42
  ## Features
36
43
 
37
44
  - **Three parallel providers** — the CN side registers as the `trae` provider (e.g. `DeepSeek-V4-Flash`, `DeepSeek-V4-Pro`), the enterprise side as `trae-enterprise`, and the international side as `trae-global` (the Gemini/GPT/MiniMax roster); **all three rosters appear in the DSH model picker simultaneously**: different sessions can each pick a side without interfering.
@@ -66,6 +73,10 @@ A [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) bund
66
73
 
67
74
  - **Secure loopback shim** — one random port + in-process random secret per region; the real Trae token is never handed to pi-ai.
68
75
 
76
+ ### Daily automatic check-in
77
+
78
+ The domestic personal account pool offers automatic check-in, disabled by default. Enable and save to check in selected members while DSH runs. Progress survives restarts and failed attempts retry with backoff. This setting is independent of automatic billing selection. Trae limits daily rewards per device, so not every member can receive a reward. Global and enterprise panels do not offer automatic check-in.
79
+
69
80
  ## How it works
70
81
 
71
82
  ```text
@@ -369,6 +380,11 @@ Local dev via a `link:` install to the desktop profile (restart DSH Desktop afte
369
380
  dsh plugin --profile desktop add /path/to/dsh-connect-trae
370
381
  ```
371
382
 
383
+ **Read [AGENTS.md](AGENTS.md) before you start.** It states the two hard rules for this
384
+ repository: what must never enter the repo or the published package (details in
385
+ [SECURITY.md](SECURITY.md)), and how to prove a change is correct. Read it in particular
386
+ before adding or replacing any screenshot under `docs/assets/` — mosaic is not redaction.
387
+
372
388
  ## Acknowledgements
373
389
 
374
390
  - [Wang-JQ77/dsh-trae-api](https://github.com/Wang-JQ77/dsh-trae-api) (MIT) — Reference implementation for research into Trae authentication, sessions, and model protocols.
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
  <img src="docs/assets/dsh-connect-trae-usage-card.png" width="860" alt="dsh-connect-trae settings panel" />
3
3
  </p>
4
4
 
5
+ > 本文与配图中的账号名、额度与时间**均为演示数据**(`demo-*`),不含任何真实账号信息。
6
+
5
7
  <h1 align="center">dsh-connect-trae</h1>
6
8
 
7
9
  <p align="center"><b>把本机登录的 Trae 模型接入 DeepSeek Harness,并提供用量/积分概览与每日签到领取。</b></p>
@@ -32,6 +34,11 @@
32
34
 
33
35
  > **边界:本插件不与任何其它插件联动。** 账号、凭据、模型目录、账号池与设置命名空间都只属于本插件;`trae` 失败不会借用别的插件的账号或额度,反之亦然。理由与具体含义见[与其它插件的关系](#与其它插件的关系)。
34
36
 
37
+ ### 图标样式
38
+
39
+ 在账号池的「更多设置」中选择 Claude 风格或默认图标。选择会立即应用到侧边栏和页面,并保存在当前浏览器中;其他浏览器需要单独设置。
40
+
41
+
35
42
  ## 功能特性
36
43
 
37
44
  - **三供应商并行接入** —— 国内版注册为 DSH 的 `trae` provider(`DeepSeek-V4-Flash`、`DeepSeek-V4-Pro` 等),企业版注册为 `trae-enterprise`,国际版注册为 `trae-global`(Gemini / GPT / MiniMax 阵容),**三边模型同时出现在 DSH 模型选择器里**:不同会话可以各选一边,互不干扰。
@@ -71,6 +78,10 @@
71
78
 
72
79
  - **安全 loopback shim** —— 每区域一个随机端口 + 进程内随机 secret,真实 Trae token 不交给 pi-ai。
73
80
 
81
+ ### 每日自动签到
82
+
83
+ 国内版账号池支持每日自动签到,默认关闭。开启并保存后,插件在 DSH 运行期间为勾选的个人账号签到,重启后保留当天进度,失败自动重试。此开关独立于账号池的自动挑号开关。Trae 对每台设备的每日奖励设有限制,不能保证每个成员都能领取。国际版与企业版不提供此功能。
84
+
74
85
  ## 工作原理
75
86
 
76
87
  ```text
@@ -450,6 +461,10 @@ pnpm run check # typecheck + test + build
450
461
  dsh plugin --profile desktop add /path/to/dsh-connect-trae
451
462
  ```
452
463
 
464
+ **动手前先读 [AGENTS.md](AGENTS.md)**:它列了本仓库的两条硬约束——什么不能进入仓库
465
+ 与发布物(细节见 [SECURITY.md](SECURITY.md))、以及改完怎么验证。新增或替换
466
+ `docs/assets/` 下的截图前尤其要看,马赛克不算脱敏。
467
+
453
468
  ## 致谢
454
469
 
455
470
  - [Wang-JQ77/dsh-trae-api](https://github.com/Wang-JQ77/dsh-trae-api)(MIT)— Trae 认证、会话和模型协议研究的参照实现。