dsh-connect-trae 4.4.1 → 4.4.3

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,175 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.4.3 (2026-10-23)
4
+
5
+ > **模型选择器里也看得见活动优惠;账号池信息架构对齐 workbuddy;顺带修一个会让已保存设置
6
+ > 读回空值的区域映射缺陷,和三处「写了等于没写」的样式。**
7
+ >
8
+ > 合并自 PR #51(issue #50 的落实)。两条从姊妹插件 `dsh-connect-workbuddy` 搬来的账:
9
+ > **模型的活动优惠该在「挑模型的地方」就看得见**,以及一个会让**已保存的设置读回空值**的
10
+ > 区域映射缺陷。搬的是结论与陷阱,不是代码 —— 两边的促销协议不同(Trae 发 `activity_discount`
11
+ > 规则,WorkBuddy 发 `badge` + `displayMode`),所以只有「怎么判、什么时候判、显示几个数字」
12
+ > 这三件事是共通的。
13
+
14
+ ### 新增
15
+
16
+ - **模型选择器里也画活动优惠胶囊**(`feat(client)`,移植自 workbuddy 的 #120)。
17
+ DSH 的模型选择器由宿主编排,插件能塞进去的只有一个字符 —— 而字符的颜色由 emoji 字体决定,
18
+ 不是我们能定的。宿主为此留的唯一缝隙是 `conversation.input.model.label` 插槽(契约原文:
19
+ 「渲染非交互的内联内容,例如带颜色的名称或徽标」,官方自己就用它挂 DeepSeek 的发布徽标)。
20
+ 插件在该插槽注册 `TraeModelLabel`:
21
+ - **只显示一个倍率**。`traeModelDisplayName` 已经把目录倍率拼进名字(`Kimi-K2 · x0.55`),
22
+ 宿主原样渲染该字符串,所以插槽若再追加一个倍率就会印成 `· x0.55 · x0.55` —— 这正是
23
+ workbuddy 的 #120 回归。现在后缀被**按形状**拆回名字之外(`/ · x(\d+(?:\.\d+)?)$/`),
24
+ 再画在那个能承载折后价的 span 里:有折扣时显示折后价,没有时显示目录价,**永远只有一个数字**,
25
+ 且是此刻成立的那个。
26
+ - **在渲染时刻判定,不在解析时刻**。判据是钟点(闲时窗口开合、活动到期),所以快照只取一次、
27
+ 胶囊每次渲染现算,并挂一只**共享的分钟定时器**(20 行标签只花一个 interval);夜间折扣不会
28
+ 在第二天早上继续挂着。
29
+ - **取数走 `detail=fast`**(issue #77)。插槽在卡片之外,读不到卡片的 React state,只能自己问
30
+ 宿主;用的是卡片轮询的同一个 `/usage` 路由,但取**首屏文档**:模型名册、`creditMultiplier`
31
+ 与未解析的 `activityDiscount` 都是本地已知的,所以**打开选择器不会触发一次上游读**。
32
+ 结果缓存 60 秒。
33
+ - **归属按 provider 判**:`panelOfProvider`(`src/region.ts`)把 `trae` / `trae-enterprise` /
34
+ `trae-global` 映射到各自的 panel,**企业版读自己的名册**(同一凭据、不同计费池、不同阵容);
35
+ 别人的 provider 一个徽标都不画、**连请求都不发**。
36
+ - **语言不靠注入的 `t`**:插槽拿不到卡片的 `t`,所以直接读浏览器语言并查同一张
37
+ `locales.ts` 表(中文「5 折」/ 英文「50% off」,两个占位符都传,各自取用的那个)。
38
+ - 关键样式内联兜底:插槽渲染在卡片之外,卡片样式表不一定在位(从未打开过卡片的页面),
39
+ 所以胶囊的颜色/边框/圆角由组件自带,与 `.dsm-trae-model-promo-*` 同源同值。
40
+ - **测试**:`tests/model-label-slot.spec.tsx`(13 条)覆盖「拆后缀」「折扣优先于名字里的倍率」
41
+ 「按形状而非数值拆」「`detail=fast` 与 panel 路由」「无价格就不画」「别人的 provider 不 fetch」
42
+ 与「中文文案」;`tests/client-activation.spec.tsx` 的注册计数 4 → 5。
43
+
44
+ ### 修复
45
+
46
+ - **三处新样式被后定义的基础类吃掉**(`fix(styles)`,真机截图比对时发现)。
47
+ 这三个元素都同时挂两个类,而修饰类的规则写在基础类**之前** —— 特异性相同时后定义者胜,于是
48
+ 「写了的样式」在页面上等于「没写」:
49
+ - `.dsm-trae-pool-modebar`(462 行)被 `.dsm-trae-pool-settings`(518 行)的
50
+ `padding:0` / `border` / `background` 抹平,蓝色描边与淡蓝渐变全部消失,模式条塌成一条普通分隔行;
51
+ - 免费模型缺失的警示语 `.dsm-trae-pool-serving-note`(471 行)被 `.dsm-trae-pool-note`(501 行)
52
+ 的 tertiary 灰覆盖,该显示橙色的地方显示成灰;
53
+ - 账号下拉 `.dsm-trae-pool-account-select`(511 行)的 `max-width:min(320px,50%)`
54
+ 被 `.dsm-trae-select`(600 行)的 `max-width:220px` 覆盖,框比设计值窄。
55
+
56
+ 三处都改成双类选择器(`.a.b`,特异性 0-2-0),与规则顺序解耦。姊妹插件 `promo` 没有这个问题
57
+ —— 是搬样式时把新规则插错位置的连带伤。
58
+ **验证**:写脚本扫两端 JSX 里全部 33 组多类组合,按「交集属性 + 定义行号」判定谁覆盖谁,
59
+ Trae 修完 0 处、promo 本来 0 处;`tsc` 两端静默,`vitest run` 81 文件 / 1014 条全过。
60
+
61
+ - **`regionsMapOf` 不再跳到过期的同名区域**(`fix(region)`,已单独提交,移植自 workbuddy 的 5.5.x)。
62
+ 旧规则是「有 `regions` 键就往下跳」,但 **`regions` map 自己也可能带 `regions` 键** —— 早期构建
63
+ 往 map 里写过旧兄弟字段(真实 profile 里 `accounts` / `regions` / `cliBridge` 就并排躺在
64
+ `cn` 旁边)。跳下去读到的是**同一 panel 的另一份更旧的拷贝**,于是已保存的开关/手动排序读回空值,
65
+ 表现为「设置没保存」,而文件里其实还在。
66
+ - 新规则按**形状**判定:外层只要含 panel id(`cn` / `enterprise` / `ai`)就是 map,不跳;
67
+ 否则才视为整段设置文档。
68
+ - **变异验证**:把判定改回旧行为,新增用例必红(实测 1 条失败);恢复后 22 条全绿。
69
+
70
+ ### 重构
71
+
72
+ - **「更多设置」下面三段说明收进折叠块**(`refactor(pool)`,与 workbuddy 的「本池如何挑选账号」同形)。
73
+ 副本路径、保存规则、删除行为三段原本平铺,在标题下占掉八行,把两个真正的控件(目标测速模型、
74
+ 测试请求体积)顶出首屏。改用 `<details>/<summary>`:键盘可操作、展开状态对屏幕阅读器原生可见,
75
+ 不需要拿按钮和 state 去追 DOM;`open` 受控,因为这张卡片每次轮询都会重渲染,非受控会在用户
76
+ 刚点开时把它收回去。文案一字未删,副本路径那句照样在(「插件留了真副本」是主张,「这两个 id
77
+ 此刻就在这个文件里」是用户能打开来核对的事实)。顺带删掉从未写过样式的死类名
78
+ `dsm-trae-pool-copyfile`。新增双语 key `row.poolMoreRulesTitle`。
79
+
80
+ - **账号池的信息架构重排**(`refactor(pool)`,结论移植自 workbuddy 的 5.4.0,代码按 Trae 的结构重写)。
81
+ 这一批不是改功能,是改**同一件事说几次、在哪里说**。十条原则里 Trae 欠九条,逐条落地:
82
+ 1. **总开关移到标题下方**。它原本是「账号池设置」框的最后一行,被它控制一切的成员表在它上方
83
+ 一百多行;池开/池关意味着「未入池」这一列**有没有意义**,所以选模式的控件必须排在
84
+ 依赖模式的控件之前。开关那一行同时给出**运行中的模式**(自动挑号 / 手动选号)——
85
+ 读的是已保存的值而不是草稿,因为开关本身已经显示待保存的编辑,旁边的「未保存」胶囊
86
+ 说明这件事,模式胶囊因此可以回答另一个问题:池此刻在做什么。
87
+ 2. **手动选号搬进标题行**。原来它自己占一整块,重复写着账号名。现在它与「当前计费:X」
88
+ 徽标**共用一个槽位**:池关闭且有绑定路由时是选择器(控件自带名字),否则是徽标。
89
+ 一个控件回答一个问题,不并存。
90
+ 3. **「派单中」不再与「未入池」同框**。这两件事正交:Host 在池里没有可用成员时会回退到
91
+ 已保存的选择,于是「正在计费的账号」完全可能不在池中。同一行还顶着「已选 0 / 1」,
92
+ 正确状态因此读起来像 bug。现在行内只留成员关系(且跟随**草稿**而非已保存列表),
93
+ 谁在计费由标题回答;两者真的冲突时,模式条下方给**一句话**解释。整行着色保留,
94
+ 计费行仍然一眼可辨。
95
+ 4. **批量按钮搬进成员区**。顺序改为「先声明范围(已选 N / M · 全部 / 清空)→ 再给动作
96
+ (一键测试 / 重新检测)→ 再列账号」。它原先是整块页头工具条,按钮在问「对哪些账号下手」
97
+ 时,回答这个问题的控件还在一整块之外。「为什么按钮是灰的」紧贴动作行,不再隔一屏。
98
+ 5. **保存条 sticky**。两个生效按钮原本在整页最底部:改完一行偏好,按钮在视口之外。
99
+ 6. **「账号池设置」降级为「更多设置」**,它是「剩下那些」而不是页面主标题,不该与顶部的
100
+ 「账号池」争主次。
101
+ 7. **账号导入/导出搬到「更多设置」之上**。它们点一下立即生效,而它们下方那句提示写的是
102
+ 「点保存才生效」——立即生效的动作不能住在只承诺保存的区块里。
103
+ 8. **测试模型只说一遍**。设置区「目标测速模型」、表头「目标模型」、行尾各说一次,
104
+ 同一个名字在一屏里出现三遍。表头改为「**状态**」(它描述的是结果),行尾仅在
105
+ **实际测的模型 ≠ 本池保存的模型**时才点名——那才是看着像 bug 而其实不是的情形。
106
+ 来源(实时请求 / 批量测试)仍然保留,它不在这两个地方说过。
107
+ 9. 顺带删掉因此变死的两条规则(`.dsm-trae-pool-bind`、`.dsm-trae-pool-tag-current`)
108
+ 与一个死键(`row.poolCurrentBadge`);`row.poolCurrentHeader` 原本已定义却从未使用,
109
+ 这次终于用上。
110
+ - **测试相应重写**:`「派单中」 names only the pool's own answer` 那组改为断言**信息搬到了哪里**
111
+ ——标题行的计费账号、池外旁注、以及池关闭时槽位里是选择器而不是徽标。新增三组共 10 条
112
+ 结构断言:开关在成员表之前且标签读运行态;批量按钮在「已选」之后、表格之前;保存条
113
+ `position:sticky`;表头是「状态」、相同模型不复述而不同模型必点名。
114
+ - **变异验证**(通过 ≠ 有效):把 `modelDiffers` 改回「有模型就显示」→ **2 条必红**;
115
+ 把动作条移出成员区 → **1 条必红**;恢复后 50 条全绿。
116
+
117
+ ### 验证
118
+
119
+ - `tsc -p tsconfig.json` 与 `tsc -p tsconfig.client.json` 均无输出。
120
+ - `vitest run` **81 文件 / 1014 条全过**(合并后 main 上复核实测;PR 写作过程中为 1007,末两个提交又加了 7 条)。
121
+ - `tsdown` 两个 build 通过。
122
+
123
+ ## 4.4.2 (2026-10-23)
124
+
125
+ > **带工具的对话在 deepseek-v4.1-flash 上整轮 400/4027 拒绝。这个版本在两个工具出口
126
+ > 把 draft-3 风格的 `required: true` 归一化为 draft-4。**
127
+ >
128
+ > 接 issue #48 的后续证据(issue #49):上游校验**分后端**。同一凭证、同一 SOLO 出口、
129
+ > 同一工具 schema,`glm-5.2`(智谱后端)放行属性内的 `required: true`,而
130
+ > `deepseek-v4.1-flash` 按 JSON Schema draft-4+ 严校验,直接回
131
+ > `4027 … tools.N.function.parameters/properties/X/required: invalid JSON Schema` ——
132
+ > 且 `tools.N` 是**按批校验**,一个不合格即全挂,所以「带工具必 400」。
133
+ > 之前 #48 判「上游已放行」是因为只在宽松的智谱后端上试过。
134
+
135
+ ### 修复
136
+
137
+ - **工具 schema 出口统一归一化为 draft-4**(`fix(protocol)`,issue #49)。
138
+ DSH schemastery 生成的工具参数是 draft-3 风格 —— `required: true` 写在属性**内部**:
139
+
140
+ ```json
141
+ { "type": "object", "properties": { "tool": { "type": "string", "required": true } } }
142
+ ```
143
+
144
+ JSON Schema draft-4 起已把 per-property boolean `required` 移除,改为父级字符串数组
145
+ `{ "required": ["tool"] }`。deepseek-v4.1-flash 这类严校验后端对前者直接判
146
+ `invalid JSON Schema`,整请求被拒。由于模型路由到哪个后端由上游决定、随 config 变化,
147
+ 凡带工具的请求都必须是 draft-4 干净的,与探针上次试过后端无关。
148
+ - 新增 `normalizeTraeToolSchema` / `normalizeTraeToolSchemas`(`src/raw-chat.ts`):
149
+ 递归把属性内 boolean `required` 提升到父级 `required` 数组(与既有数组去重合并)后
150
+ 删除 boolean;覆盖 `items` / `anyOf` / `oneOf` / `allOf` 等任意嵌套。**幂等** ——
151
+ 已是 draft-4 的 schema 原样返回(同一对象,零拷贝),不改变合法载荷。
152
+ - **两个工具出口都套上归一化**:`prepareSoloBody`(SOLO `llm_utils_chat` 主路径,
153
+ `parameters` 在 `JSON.stringify` 前归一化)与 `buildTraeRawChatDraft`(raw chat 路径,
154
+ `structuredClone` 后的 `tools` 归一化)。纯本地、不碰凭据、不改调用方入参。
155
+ - 归一化后的载荷形态与 issue #49 的 D3 对照实验(draft-4 提升后经
156
+ deepseek-v4.1-flash 后端 200 正常 SSE)一致。
157
+
158
+ ### 测试补强
159
+
160
+ - **989 例测试全绿**(`pnpm run test`),新增 **7 例**:draft-3 提升、嵌套
161
+ `items`/`anyOf` 递归、与既有 `required` 数组合并、draft-4 幂等(字节不变)、
162
+ `properties` 之外的 boolean `required` 与 `required: false` 不误伤、调用方 `tools`
163
+ 不被原地修改、SOLO 信封 wire 载荷为 draft-4。
164
+
165
+ ### 已知边界
166
+
167
+ - 归一化只处理 draft-3 boolean `required` 这一种已实测命中的非法形态;上游若日后
168
+ 收紧到拒绝更多 draft-3 残留(如属性内其他 boolean 关键字),需另行取证后处理。
169
+ - 本次修复未经本机真实凭据实测(修复者无 Trae 凭据);上游行为依据 issue #49 报告人
170
+ 的三步对照实验(D1 无工具 200 / D2 draft-3 4027 / D3 draft-4 200)。本地验证为
171
+ 单测 + 构造载荷核验。
172
+
3
173
  ## 4.4.1 (2026-10-22)
4
174
 
5
175
  > **4.4.0 的修复自己藏掉了 `kimi-k2-0905`。这个版本把它捞回来,并补上 4.4.0 缺的实测。**
package/README.md CHANGED
@@ -42,7 +42,7 @@
42
42
  > 池是**显式选择**:默认关闭。它不会替你在没打开池的情况下换账号——「换账号等于换人计费」,所以这件事只能由你点头。池打开后卡片会明确写出**当前是谁在付费**(Host 报告的是「下一个真正发送请求的账号」,不是保存的那个绑定)。
43
43
 
44
44
  - **倍率内嵌模型名** —— 模型名称按 Trae 自身菜单的格式显示积分倍率(如 `GLM-5.2 · x0.79`),倍率随目录刷新更新。
45
- - **活动优惠徽标** —— Trae 把促销规则发在目录里(`activity_discount`:折扣比例、原价、闲时时段与时区),插件按**当前时钟**判定后,在模型名旁边画一枚胶囊:`5 折` / `闲时 5 折` / `限时 5 折`,悬停写明原价、现价与时段。判定放在**渲染时刻**而不是解析时刻,所以夜间折扣不会在第二天早上继续挂着;上游说「此刻没打折」就不画,**不从倍率反推**,也不给没有结束时间的活动编「限时」。这是纯提示:不改请求、不改计费,国际区(订阅制)没有这项数据因此不显示。详见 [docs/PROMO_BADGE.md](docs/PROMO_BADGE.md)。
45
+ - **活动优惠徽标** —— Trae 把促销规则发在目录里(`activity_discount`:折扣比例、原价、闲时时段与时区),插件按**当前时钟**判定后,在模型名旁边画一枚胶囊:`5 折` / `闲时 5 折` / `限时 5 折`,悬停写明原价、现价与时段。判定放在**渲染时刻**而不是解析时刻,所以夜间折扣不会在第二天早上继续挂着;上游说「此刻没打折」就不画,**不从倍率反推**,也不给没有结束时间的活动编「限时」。这是纯提示:不改请求、不改计费,国际区(订阅制)没有这项数据因此不显示。详见 [docs/PROMO_BADGE.md](docs/PROMO_BADGE.md)。**同一枚胶囊也画在 DSH 的模型选择器里**(宿主 `conversation.input.model.label` 插槽),挑模型时就能看见——毕竟徽标画在设置卡片里时,你已经选完了。选择器里**倍率只出现一次**:名字里已有的目录倍率会被拆出来,有折扣时换成**折后价**(与卡片同一套时钟判定,同一份数据),打开选择器不会额外触发上游读。
46
46
  - **DSH 本地工具循环** —— 通过 Trae `llm_utils_chat` 获取待执行的结构化 `tool_calls`,交由 DSH 自带的本地工具执行,再将工具结果回传模型。
47
47
  - **国内国际双区域自动识别** —— 自动发现 Trae CN / TRAE SOLO CN / Trae / TRAE SOLO 四个本地安装的登录账号;区域由凭证自带的 `userRegion` 声明自动判定(host 后缀与 edition 标签兜底),无需手动指定。
48
48
  - **目录与勾选按面板隔离** —— 三个面板各一套模型目录、勾选、图片开关与上下文预算,各自读各自的槽位。名录从**该面板所钉的那条网关**读取——原本钉在企业池的客户端去读消费池名录,会永远发现不了自己池的独占模型。