dsh-connect-trae 2.1.0 → 2.3.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 CHANGED
@@ -1,5 +1,87 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.3.0 (2026-09-25)
4
+
5
+ ### Breaking Changes
6
+
7
+ - **只支持 DSH 0.1.7-rc.1 及以上,不再支持 0.1.7 之前的宿主**。2.2.0 用「双线 + 运行期能力探测」同时服务 0.1.5 与 0.1.7;0.1.5 线的设置面(`installSection` / `settingsScope` / `settings.plugin.item` / 非 volatile 写入)与 0.1.7 的新模型**互不相交**,保留它意味着每条路径都要背两套契约。从本版起删除全部 0.1.5 分支,`peerDependencies` 与 `devDependencies` 一并收窄到 `>=0.1.7-rc.1` / `0.1.7-rc.1`(`cordis >=4.0.4`、`schemastery >=3.18.4`)。做法对齐同级 `dsh-connect-workbuddy`(其 2.1.0 起的 0.1.7-only 改造)。
8
+
9
+ ### Bug Fixes
10
+
11
+ - **0.1.7 上 provider 被判「未配置」——命名空间必须用宿主服务的那个**(对齐 workbuddy 2.0.16 的实测)。0.1.7 的 `SettingsForms.describe()` 以 **Loader 条目 id** 为键,harness 对 provider 的命名空间做**精确匹配**查表(`namespaces.get(entry.settingsNs)`)。此前硬编码 `settingsNs: 'trae'`,而桌面宿主实际以 `dsh-connect-trae`(或 `include:dsh-connect-trae`)挂载条目,于是查表落空、provider 读作「未配置」,**配置入口与模型发现静默失效**、不报错。
12
+ - **修法**:新增 `settingsNamespaceOf(ctx)`,采用一线插件(`dsh-llm-pi-ai`)的权威范式 `const settingsNs = ctx.fiber.entry?.options.id ?? TRAE_SETTINGS_NS`,`registerConfigurableProviders` 与 `registerModelDiscovery` 全部改用解析值;`ctx.fiber.entry` 由 Loader 注入(非 Cordis 公共类型),保留探测 + 回落到 `'trae'`。
13
+ - **回归测试**(`tests/settings-integration.spec.ts`,+4 例):采纳 Loader 条目 id、无条目/空 id/非字符串时回落、以及**目录实际宣告的就是解析值**(断言目录条目而非常量,否则恒过)。
14
+ - **移除宿主端 `installSection` 双路径,`configure({auto}, owner)` 成为唯一路径**(对齐 workbuddy 的 0.1.7-only 写法)。0.1.7 的 `SettingsForms` 删除了 `installSection`;2.2.0 按能力探测两条路径,本版起无条件调用 `configure`,且把返回的 disposer 挂进 `ctx.effect()`——否则展示策略泄漏到插件销毁之后(一线插件同款:`child.effect(() => child.settings.configure({ auto: false }, …))`)。随 `installSection` 一起删除 `legacyInstallSettingsSection` / `sectionHooks` 及其对 `@deepseek-ai/dsh-settings` 命名空间的依赖。
15
+ - **客户端设置面只走 `configForms`**(对齐 workbuddy 的 0.1.7-only 写法):删除 `settingsScope.bind` 回退分支与 `settings.plugin.item` 槽位注册,只保留 `configForms.get(entryId)` + `plugins.bundle.config` / `plugins.row.config` 两个槽位。`inject` 维持 `['slots', 'locale']`,设置面仍经 `ctx.get()` 软探测。
16
+ - **卡片的 `view` 行为不再按双线区分**:`page` 视图默认展开、无 `view` 默认折叠;`set()` 契约收窄为 `Promise<boolean>`(0.1.7 表单返回显式布尔,`false` 即拒绝)。
17
+ - **区域开关等设置保存被宿主拒绝(`No configurable plugin entry "trae"`)——客户端命名空间绑定必须跟随 describe 镜像**。卡片此前在 `apply()` 时**一次性**解析命名空间:describe 镜像异步加载,且在运行中的 profile 里新增插件条目(本插件正是这种场景)时,镜像首次 describe 可能早于条目出现;`/trae/i` 匹配落空就**永久**回落到声明的 `'trae'`。`configForms.get('trae')` 把表单控制器永久绑到该 ns——宿主 `configEditor.entries()` 里没有 `options.id === 'trae'` 的条目,每次写入都抛 `No configurable plugin entry "trae"` 被拒;而控件仍可点(controller 对「ns 不在镜像」的派生沿用全局 `writable=true`),表现为「开关翻过去又弹回」。
18
+ - **修法**:设置面改为**可重绑代理**——`getSnapshot` / `set` 转发到「当前」控制器;别名解析(`served.ns`,`/trae/i` 匹配)在 apply 时尝试一次,并订阅镜像(`describe().subscribe`)在每次变化后重绑;镜像已就绪但缺条目时**一次性**触发 `describe().load()` 补拉(此后宿主 `settings/document-updated` 事件保持新鲜)。绑定前代理返回 `writable: false`,卡片只读而非提供必定失败的控件。
19
+ - **回归测试**(+3 例):激活测试的镜像桩改为真实形状(`ns: 'dsh-connect-trae'`,即 patch id,非声明的 fallback),断言绑定到 `dsh-connect-trae`;新增「镜像在 apply 后才出现条目 → 重绑到正确 ns」与「一次性 nudge 补拉镜像」两例。测试桩此前的 `ns: 'trae'` 恰好把 fallback 当成了正解,是这条缺陷没被 CI 拦住的原因之一。
20
+ - **插件管理页 / 市场页的 logo 显示不对**(默认占位/空白,对齐 workbuddy 2.0.17 的实测)。0.1.7 的插件管理页从插件包 `package.json` 的 `icon` 字段读图标(`dsh-client-ui-plugin-manager` 渲染 `row.meta?.icon`),此前没有声明该字段 → 宿主无图可用。
21
+ - **修法**(照同级 `dsh-connect-workbuddy` 的做法):新增 `icons/` 目录(64px + 128px 真实 PNG,其中 64px 与既有卡片内嵌 base64 逐字节一致——同族 LD logo),`package.json` 声明 `"icon": "icons/dsh-connect-trae-128.png"`,并把 `icons` 加入 `files` 白名单随包分发。
22
+ - **验证**:`npm pack --dry-run` 确认两个 PNG 进包(12 → 14 文件)。
23
+ - **模型倍率改回「wire 权威、Remote 兜底」,恢复折后价显示**(回归:b8f09bc 重构时误删 1.4.2 的 `display_contact_config` 解析)。`get_detail_param` 每行的 `display_contact_config`(第二层 JSON)里的 `consumption_rate.data.rate` 才是 Trae IDE 渲染的**折后价**;Remote 目录的 `consumption_rate` 在限时折扣下会报**未折价**(Seed-2.1-Pro:IDE `x0.08` vs Remote `0.80`,差 10 倍)。b8f09bc 改成多 function 并集时把这个解析连带删掉,倍率从此退化成 Remote 值。
24
+ - **修法**:`solo.ts` 恢复 `wireCreditMultiplier()`(读 `display_contact_config.consumption_rate.data.rate`,`enable` 为真且为正有限数时取值,缺失/损坏留空不虚构),`TraeSoloModel` / `TraeWireModel` 增列 `creditMultiplier`;`mergeTraeModelSources` 合并时 **wire 优先、Remote 兜底**(wire 未提供才用 Remote)。
25
+ - **国际版(ai)区域**:实测(2026-09-25 只读探测)国际版上游任何目录都不含 `consumption_rate`(订阅制,只有 `cost` 标签与 `manual_usage: 0|1`,官方 App 也只渲染「Lite-friendly」这类标签而非数值倍率),因此 ai 模型行按「解析不出则留空」保持不显示倍率——不虚构数值。
26
+ - **回归测试**(+2 例):`solo.spec.ts` 断言 wire 解析出折后价、缺失时不虚构;`catalog.spec.ts` 断言合并时 wire 优先、Remote 兜底、双缺留空。变异验证:把合并改回只读 Remote → 新用例立即失败。
27
+
28
+ ### Dependencies
29
+
30
+ - `peerDependencies`:全部 `@deepseek-ai/dsh-*` 收窄为 `>=0.1.7-rc.1 <0.2.0-0`;`@deepseek-ai/cordis` 收窄为 `>=4.0.4 <5.0.0`;`@deepseek-ai/schemastery` 收窄为 `>=3.18.4 <4.0.0`(`volatile()` 自 3.18.3 起才有)。
31
+ - `devDependencies`:全部 `@deepseek-ai/dsh-*` 与 `cordis` / `schemastery` 升到 0.1.7-rc.1 线,开发树与用户解析一致。
32
+ - `tests/settings-integration.spec.ts` 的 settings 测试替身改为 **0.1.7 形状**(`SettingsForms` 语义的内存服务:`configure` + `describe` + 只写 volatile 字段的 `update` + `loader/volatile-update`),插件以**活引用配置**(volatile 字段 = `{get(): T}` 指向同一份文档)挂载,模拟真实宿主的写入→重放路径;原先写非 volatile 扁平字段(`lastCatalog` / `imageModelIds` / `enabledModelIds`)的用例改为写 `regions.cn` 槽位——正是 0.1.7 写入门接受且卡片实际采用的形状。
33
+
34
+ ### Tests
35
+
36
+ - 310 → 316。移除 0.1.5 形态用例(client-activation 的 `settingsScope` 宿主、client-fallback 的 `settingsScope.bind` 回退、card-region-switch 的 void 返回契约),新增 4 例 `settingsNamespaceOf`、3 例命名空间重绑/补拉、2 例 wire 倍率解析与合并优先级。`pnpm run check`(typecheck + test + build)全绿。
37
+
38
+ ## 2.2.0 (2026-09-24)
39
+
40
+ ### Bug Fixes
41
+
42
+ - **支持 DSH 0.1.7 线,且同一个构建同时服务 0.1.5 与 0.1.7**(修复 [issue #13](https://github.com/dingminhua/dsh-connect-trae/issues/13))。这不是「多支持一个版本」的可选增强——**在 0.1.7 上插件的浏览器半侧此前根本不激活**:
43
+
44
+ ```
45
+ web boot: 1 entry did not activate
46
+ dsh-connect-trae: pending (waiting for service: settingsScope)
47
+ ```
48
+
49
+ - **根因:`settingsScope` 被整个移除,而 Cordis 的依赖闸门是硬闸。** DSH **0.1.7-alpha.1** 起客户端设置服务换成 `configForms`,`settingsScope` **不再存在**(不是弃用、没有兼容层;0.1.6-alpha.2 还有,0.1.7-alpha.1 起包内已无此符号)。插件 `inject` 里仍声明它,于是 fiber 永远停在 `PENDING`——**不是某个功能坏掉,是整个客户端插件不激活**,因此「重新登录」「重装插件」一概无效。修法与 workbuddy 一致:`inject` 收窄为两条线都有的 `['slots', 'locale']`,设置面改由 `ctx.get()` **按能力软探测**(属性访问会抛 `cannot get property X without inject`,`?.` 挡不住;`ctx.get` 对缺失服务返回 `undefined`)。
50
+ - **0.1.7 上另有三处断裂,issue 只报了第一处。** 逐项对照 workbuddy 的实测(该仓库已用同一套适配跑通 0.1.7):
51
+
52
+ | # | 位置 | 0.1.5 | 0.1.7 |
53
+ |---|---|---|---|
54
+ | 1 | 客户端设置服务 | `settingsScope` | `configForms` |
55
+ | 2 | 客户端槽位名 | `settings.plugin.item` | `plugins.bundle.config` / `plugins.row.config` |
56
+ | 3 | 宿主端注册 | `installSection()` | `configure({auto}, owner)` |
57
+ | 4 | schema 可写声明 | 无需 | 必须标 `volatile()` |
58
+ | 5 | primitives 图标名 | `…Outline14` | `…OutlineRegular` |
59
+
60
+ - **第 2 条(槽位名)最容易被漏掉**:两条线声明的槽位集合**互不相交**(0.1.5 只有 `settings.plugin.item`;0.1.7 只有 `plugins.*`)。就算设置服务改对了,槽位名不改卡片依然渲染不出来。现在三个槽位**各自独立 try/catch** 逐个注册——一条线上不存在的槽位不能把另一条线的注册一起带走。
61
+ - **第 3 条(宿主端)在 0.1.7 上是静默失败**:`installSection` 被 `SettingsForms.configure()` 取代。由于该调用位于嵌套的 `ctx.inject` 回调内,抛出的 `installSection is not a function` 落在**那个子 fiber** 上——外层插件照常加载、两个 provider 照常注册,**但 `trae` 设置命名空间从未注册**,表现为卡片设置区无声消失。现在按能力探测:有 `configure` 走 `configure`,否则回落 `installSection`。
62
+ - **第 4 条(易失声明)**:0.1.7 的 settings 写入门要求插件 schema 把可写字段标为 volatile,否则**写入被直接拒绝**(`Plugin entry "trae" has no volatile fields`),而 `scope.set()` 却正常 resolve——用户会看到开关翻过去又静默复原。新增 `asVolatile()`:运行时探测 `schema.volatile()`,**有则调用、无则退化为 identity no-op**(`volatile()` 自 schemastery 3.18.3 起才有,0.1.5 线锁在 3.18.2)。**故意不手写 `meta.volatile = true`**——那会绕过 schemastery 自身的 `validateVolatileSchema` 校验,产出一个它自己都不认的 schema。
63
+ - **0.1.7 以「活引用」交付配置值。** 该线把 volatile 字段以 `{get(): T}` 形式交给消费方;不解包则 `config.regions`、`value.accounts[region]`、`config.authFile` 静默变成对象或 `undefined`,表现为「设置明明写了却读不到」。更隐蔽的是**展开一个活引用得到的是 `{get: <function>}` 而不是值**——卡片「写一个区域、保留另一个」的合并会因此**丢掉兄弟区域并把函数泄进设置文档**。新增 `unwrapVolatile()` / `unwrapVolatileDeep()` 并在所有读取与合并路径上解包,同时监听 `loader/volatile-update` 在每次写入后重读选择。
64
+ - **第 5 条(图标)会让整个客户端包挂掉**:两条线 primitives 的图标名不重叠(`IconChevronDownOutline14` vs `IconChevronDownOutlineRegular`),**没有任何一个静态 import 能同时服务两边**——静态导入 0.1.5 的名字会让 0.1.7 上的客户端包解析失败,整个卡片随之消失。折叠箭头改为**纯 CSS caret**(`border` + `rotate`),与版本无关。
65
+ - **设置写入改为「写入后回读校验」**:`set()` 的 promise resolve **不代表值已落盘**(0.1.7 上被拒绝的写入会重新加载 Host 状态然后正常返回,0.1.5 上则可能被文件占用丢弃)。四处写入(账号选择、账号重扫、区域开关、模型目录)统一走 `writeSettingsField()`:写入后回读,未落盘即抛错并在卡片上明确显示,而不是「点了没反应」。0.1.7 的 `set()` 还会返回显式布尔值,`false` 同样按拒绝处理。
66
+ - **卡片的 `view` 属性**:0.1.7 的插件管理器通过 owner props 向每个配置条目索取两种视图(`summary` 一句话简介 / `page` 带保存控件的完整表单)。卡片接受该属性并在 `page` 下默认展开——否则在 0.1.7 的插件页里卡片会是折叠的一行。
67
+ - **开发环境的 schemastery 从 3.18.2 提到 3.18.4**(`pnpm-workspace.yaml` 的 override,与 `devDependencies` 对齐)。此前 dev 树锁在 3.18.2,那里没有 `volatile()`,`asVolatile()` 退化为 no-op——**于是 volatile 真正生效的那条路径(每个 0.1.7 用户都会走的那条)在本地从未被执行过**,这类缺陷可以同时躲过评审与 CI。这与 workbuddy 记录的教训同源。
68
+
69
+ ### Tests
70
+
71
+ 测试总数 282 → 310。
72
+
73
+ - **新增 `tests/client-activation.spec.tsx`(3 例,本次最关键的一组)**:用真实的 cordis `Context` **启动真实的客户端入口模块**,分别对 0.1.7 形态(有 `configForms`、**无** `settingsScope`)、0.1.5 形态(有 `settingsScope`、无 `configForms`)、以及**两者都没有**的形态断言 fiber 到达 `ACTIVE`、槽位注册齐全、scope 取自正确的服务。之所以能直接导入真实入口,是因为它运行期只依赖 React 与本地文件,所有 DSH 包都是**类型导入**、构建时即被擦除。
74
+ - 此前 `client-fallback.spec.ts` 抓不到这个缺陷:它**手工镜像**入口函数体,因此验证的是「思路」而**从不执行真实的 `inject` 数组**——而恰恰是那个数组让 fiber 卡在 PENDING。
75
+ - **变异验证**:把 `inject` 改回 `['slots','locale','settingsScope']`,0.1.7 与「无设置面」两例立即变红(fiber 停在 0/1,永远到不了 `ACTIVE`),0.1.5 那例仍绿——精确复现了 issue 报的现象。
76
+ - **新增 `tests/dsh-017-compat.spec.ts`(13 例)**:活引用的单层/深层解包、数组与嵌套、**不就地改写调用方对象**;`regionEnabledOf` / `regionStateOf` / `regionEnabled` 穿透活引用读值;合并时**保留兄弟区域且不泄露函数**;`asVolatile()` 的两条分支按运行时能力确定性断言;以及**宿主端 `configure()` 优先于 `installSection()`**(用一个只有 `configure` 的真 Service 装配,断言走的是 `configure({auto:true})` 且没有 `installSection` 报错)。
77
+ - **变异验证**:把 `configure` 探测改回无保护裸调 `installSection` → 1 条立即变红;把 `nextRegionSlots` 的深解包去掉 → 1 条立即变红。
78
+ - `client-fallback.spec.ts` 的镜像同步更新,并补 2 例:**两条线都提供时 `configForms` 优先**(0.1.7 的写入闸门只认它),以及 **`configForms` 缺席时回落 `settingsScope.bind()`**。
79
+ - **`card-region-switch.spec.tsx` 新增 8 例**(渲染真组件、点真按钮):穿透活引用读区域开关;合并时**保留兄弟区域且不泄露函数**;Host 明确拒绝(`set()` 返回 `false`)与**「接受了但没落盘」**(`set()` 返回 `true` 而文档未变)两种失败都必须显式报错;0.1.5 的 `void` 返回视为成功;`view: 'page'` 时默认展开、无 `view` 时默认折叠;无设置面时渲染为只读。
80
+ - **变异验证**:去掉 `regionsMapOf` 的解包 → 1 条失败;去掉 `view` 判断 → 1 条失败;去掉 `false` 判断 → 0 条失败(回读校验兜住了,说明两层防护确实互补);去掉回读校验 → 1 条失败;**两层都去掉 → 1 条失败**。
81
+ - 顺带移除了该文件对 `dsh-client-ui-primitives` 的整包 `vi.mock`:图标已改纯 CSS,不再需要这个桩。
82
+
83
+ **双版本验证**:同一份代码在 schemastery **3.18.4**(volatile 生效)与 **3.18.2**(no-op 分支)下**均为 310 通过**。
84
+
3
85
  ## 2.1.0 (2026-09-24)
4
86
 
5
87
  ### Features
package/README.en.md CHANGED
@@ -94,6 +94,18 @@ npm install dsh-connect-trae
94
94
 
95
95
  Restart the DSH process after install/update/uninstall.
96
96
 
97
+ ## DSH version compatibility
98
+
99
+ **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).
100
+
101
+ 0.1.7 rebuilt the settings machinery, and the plugin follows the 0.1.7 contract:
102
+
103
+ - **Host registration**: `SettingsForms.configure({auto}, owner)` (`installSection` was removed in 0.1.7), and the namespace is the **Loader entry id the host actually serves** (`ctx.fiber.entry?.options.id`, falling back to `trae`) — the harness looks a provider's namespace up by exact match, so hard-coding `trae` made the provider read as "not configured".
104
+ - **Client settings surface**: `configForms` (`settingsScope` was removed in 0.1.7). `inject` declares only `slots` / `locale`; the settings surface is probed with `ctx.get()`. Cordis' dependency gate is hard — any `inject` entry the running line does not provide keeps `apply()` from ever running (this is why 2.2.0 and earlier sat at `pending (waiting for service: settingsScope)` on 0.1.7).
105
+ - **Config card slots**: `plugins.bundle.config` / `plugins.row.config` (`settings.plugin.item` was removed).
106
+ - **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
+ - **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
+
97
109
  ## Windows notes
98
110
 
99
111
  - **Account data directory**: the plugin reads `%APPDATA%\Trae CN` / `%APPDATA%\TRAE SOLO CN` → `User\globalStorage\storage.json` (unrelated to the install directory). The folder names match macOS, so no extra config is needed; if the name does not match, point the `authFile` + `edition` plugin options at the exact path.
package/README.md CHANGED
@@ -96,6 +96,18 @@ npm install dsh-connect-trae
96
96
 
97
97
  安装、更新或卸载 bundle 后,需要重启对应的 DSH 进程。
98
98
 
99
+ ## DSH 版本兼容
100
+
101
+ **要求 DSH 0.1.7-rc.1 及以上**(2.3.0 起不再支持 0.1.7 之前的宿主;peer 依赖范围已按此收窄)。
102
+
103
+ 0.1.7 把设置体系整体重建,插件按 0.1.7 的契约工作:
104
+
105
+ - **宿主端注册**:`SettingsForms.configure({auto}, owner)`(0.1.7 起 `installSection` 已删除),命名空间取**宿主实际服务的 Loader 条目 id**(`ctx.fiber.entry?.options.id`,回落到 `trae`)——harness 按精确匹配查找 provider 的命名空间,写死 `trae` 会让 provider 被判「未配置」。
106
+ - **客户端设置面**:`configForms`(0.1.7 起 `settingsScope` 已移除)。`inject` 只声明 `slots` / `locale`,设置面用 `ctx.get()` 软探测——Cordis 的依赖闸门是硬闸,`inject` 里任一服务在运行线上不存在,`apply()` 就永远不会执行(这正是 2.2.0 及更早版本在 0.1.7 上卡在 `pending (waiting for service: settingsScope)` 的原因)。
107
+ - **配置卡片槽位**:`plugins.bundle.config` / `plugins.row.config`(`settings.plugin.item` 已删除)。
108
+ - **schema 可写声明**:可写字段必须标 `volatile()`(`asVolatile`),否则写入被 0.1.7 的写入门直接拒绝;0.1.7 以 `{get(): T}` 活引用交付配置值,所有读取与合并路径都先解包(`unwrapVolatile` / `unwrapVolatileDeep`)。
109
+ - **折叠箭头用纯 CSS**,不静态导入任何 primitives 图标——图标命名族随版本变动,静态导入不是稳定契约。
110
+
99
111
  ## Windows 说明
100
112
 
101
113
  - **账号数据目录**:插件读取 `%APPDATA%\Trae CN` / `%APPDATA%\TRAE SOLO CN` 下的 `User\globalStorage\storage.json`(与安装目录无关)。目录名与 macOS 一致,无需额外配置;若目录名对不上,可用插件配置项 `authFile` + `edition` 直接指定完整路径。
Binary file
Binary file
package/lib/client.js CHANGED
@@ -5,7 +5,6 @@ window.__ModuleLoader__.load({
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  let react = require("react");
8
- let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
8
  let react_jsx_runtime = require("react/jsx-runtime");
10
9
  //#region src/status-paths.ts
11
10
  /** Plugin-owned usage endpoint consumed by its browser half. */
@@ -31,6 +30,42 @@ window.__ModuleLoader__.load({
31
30
  return `${path}?${TRAE_REGION_PARAM}=${region}`;
32
31
  }
33
32
  /**
33
+ * Peel ONE `{get(): T}` live reference, the shape DSH 0.1.7 delivers a
34
+ * volatile-marked settings field as (see {@link unwrapVolatileDeep}).
35
+ */
36
+ function unwrapVolatile(value) {
37
+ if (value !== null && typeof value === "object" && typeof value.get === "function") return value.get();
38
+ return value;
39
+ }
40
+ /**
41
+ * Deep copy of a value with every `{get(): T}` live reference replaced by the
42
+ * value it resolves to.
43
+ *
44
+ * DSH 0.1.7 hands volatile-marked fields back as live references, and a live
45
+ * reference is still `typeof === 'object'` — so it passes a naive object check
46
+ * and every lookup on it is `undefined`. Worse, spreading one (`{ ...ref }`)
47
+ * does NOT read the field: it produces `{ get: <function> }`. Any merge that
48
+ * spreads a resolved field to preserve its siblings — the card's "write one
49
+ * region, keep the other" write, or the account map's spread — would otherwise
50
+ * DROP every sibling and leak a function into the document.
51
+ *
52
+ * Both halves therefore unwrap before touching a resolved field. This lives in
53
+ * the node-free host↔client bridge because the browser half needs it too.
54
+ *
55
+ * Non-reference values are recursed into so a nested volatile field is caught
56
+ * as well; arrays and objects are rebuilt rather than mutated, so the caller's
57
+ * value is never touched.
58
+ */
59
+ function unwrapVolatileDeep(value) {
60
+ if (value === null || typeof value !== "object") return value;
61
+ if (typeof value.get === "function") return unwrapVolatileDeep(value.get());
62
+ if (Array.isArray(value)) return value.map((entry) => unwrapVolatileDeep(entry));
63
+ const source = value;
64
+ const out = {};
65
+ for (const key of Object.keys(source)) out[key] = unwrapVolatileDeep(source[key]);
66
+ return out;
67
+ }
68
+ /**
34
69
  * Build the next `regions` settings value for the card's save. The write
35
70
  * targets ONLY the signed-in account's region slot; every other region's slot
36
71
  * is carried over untouched, so switching accounts never clobbers the other
@@ -38,8 +73,9 @@ window.__ModuleLoader__.load({
38
73
  * from an empty document.
39
74
  */
40
75
  function nextRegionSlots(regions, region, slot) {
76
+ const unwrapped = unwrapVolatileDeep(regions);
41
77
  return {
42
- ...typeof regions === "object" && regions !== null && !Array.isArray(regions) ? regions : {},
78
+ ...typeof unwrapped === "object" && unwrapped !== null && !Array.isArray(unwrapped) ? unwrapped : {},
43
79
  [region]: slot
44
80
  };
45
81
  }
@@ -50,17 +86,23 @@ window.__ModuleLoader__.load({
50
86
  * where the map was expected was a real shipped bug — the lookup then read
51
87
  * `section['cn']` (absent), so the card's checkbox reported `true` forever and
52
88
  * clicking it appeared to do nothing even though the write succeeded.
89
+ *
90
+ * Unwraps live references first: on DSH 0.1.7 the `regions` field arrives as a
91
+ * `{get(): T}` reference, which passes the `typeof === 'object'` check below
92
+ * and would otherwise be returned as if it were the map — making every region
93
+ * read back as absent (and the enabled flag read as "on" forever).
53
94
  */
54
95
  function regionsMapOf(value) {
55
- if (typeof value !== "object" || value === null || Array.isArray(value)) return {};
56
- const record = value;
96
+ const unwrapped = unwrapVolatileDeep(value);
97
+ if (typeof unwrapped !== "object" || unwrapped === null || Array.isArray(unwrapped)) return {};
98
+ const record = unwrapped;
57
99
  const nested = record["regions"];
58
100
  if (typeof nested === "object" && nested !== null && !Array.isArray(nested)) return nested;
59
101
  return record;
60
102
  }
61
103
  /** One region's stored slot as a plain object; any other shape reads as empty. */
62
104
  function regionSlotOf(value, region) {
63
- const slot = regionsMapOf(value)[region];
105
+ const slot = unwrapVolatile(regionsMapOf(value)[region]);
64
106
  return typeof slot === "object" && slot !== null && !Array.isArray(slot) ? slot : {};
65
107
  }
66
108
  /**
@@ -123,7 +165,11 @@ window.__ModuleLoader__.load({
123
165
  .dsm-plugin-card-head{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}
124
166
  .dsm-plugin-card-title{color:var(--dsw-alias-label-primary,#e6e6e6);font-size:15px;font-weight:600;line-height:1.4}
125
167
  .dsm-plugin-card-description{color:var(--dsw-alias-label-tertiary,#999);font-size:13px;line-height:1.5}
126
- .dsm-plugin-card-chevron{color:var(--dsw-alias-label-tertiary,#999);flex:none;display:inline-flex;transition:transform .16s}
168
+ /* Pure-CSS caret: the host primitives' chevron icon names are not part of any
169
+ stable contract across DSH releases, so no static import is used — a border
170
+ caret in the plugin's own CSS is version-proof. */
171
+ .dsm-plugin-card-chevron{color:var(--dsw-alias-label-tertiary,#999);flex:none;width:16px;height:16px;position:relative;transition:transform .16s}
172
+ .dsm-plugin-card-chevron::before{content:"";display:block;position:absolute;left:4px;top:5px;width:7px;height:7px;border-right:1.6px solid currentColor;border-bottom:1.6px solid currentColor;transform:rotate(45deg)}
127
173
  .dsm-plugin-card-chevron-open{transform:rotate(180deg)}
128
174
  .dsm-plugin-card-body{border-top:1px solid var(--dsw-alias-border-l2,#36373b);margin:0 16px;padding:0 0 8px}
129
175
  .dsm-plugin-card-icon{width:32px;height:32px;flex:none;border-radius:7px}
@@ -230,11 +276,49 @@ window.__ModuleLoader__.load({
230
276
  */
231
277
  const POLL_INTERVAL_MS = 6e4;
232
278
  const TRAE_GITHUB_URL = "https://github.com/dingminhua/dsh-connect-trae";
233
- /** Read the per-region account selections out of the settings snapshot. */
279
+ /**
280
+ * Read the per-region account selections out of the settings snapshot.
281
+ *
282
+ * Deep-unwraps first: on DSH 0.1.7 a volatile field arrives as a `{get(): T}`
283
+ * live reference, which passes the `typeof === 'object'` check below and would
284
+ * be returned as if it were the map. Every lookup on it is then `undefined`,
285
+ * and the card's merge (`{ ...configured, [region]: id }`) would spread a
286
+ * reference into `{get: <function>}` — dropping the other region's selection
287
+ * and leaking a function into the settings document.
288
+ */
234
289
  function configuredAccountsOf(configured) {
235
- const accounts = configured?.accounts;
290
+ const accounts = unwrapVolatileDeep(configured)?.accounts;
236
291
  return typeof accounts === "object" && accounts !== null ? accounts : {};
237
292
  }
293
+ /**
294
+ * Write one settings field and confirm the value actually landed.
295
+ *
296
+ * `configForms.set()` resolving is NOT proof that anything was stored: a write
297
+ * can be accepted by the transport and still not materialize — a rejected
298
+ * write reloads Host state and merely RETURNS, so the caller's `await`
299
+ * succeeds while the document is unchanged. The form answers with an explicit
300
+ * boolean for exactly this reason.
301
+ *
302
+ * Either way the caller must not report success on an unpersisted write: the
303
+ * user would see the control flip and then silently revert.
304
+ *
305
+ * @throws {TraeSettingsWriteError} when the write was refused or did not land.
306
+ */
307
+ var TraeSettingsWriteError = class extends Error {
308
+ constructor(field, detail) {
309
+ super(detail === void 0 || detail === "" ? `trae: settings field "${field}" was not persisted by the settings write` : `trae: settings field "${field}" was not persisted by the settings write (${detail})`);
310
+ this.name = "TraeSettingsWriteError";
311
+ }
312
+ };
313
+ async function writeSettingsField(scope, field, value, landed) {
314
+ if (await scope.set(field, value) === false) throw new TraeSettingsWriteError(field, "the Host refused the write");
315
+ if (!landed(unwrapVolatileDeep(scope.getSnapshot().value))) throw new TraeSettingsWriteError(field);
316
+ }
317
+ /** Read one region's stored `regions` slot out of an unwrapped settings value. */
318
+ function regionSlotsOf(value) {
319
+ const regions = value?.regions;
320
+ return typeof regions === "object" && regions !== null ? regions : {};
321
+ }
238
322
  /** Inject the shared card CSS once. */
239
323
  if (typeof document !== "undefined") {
240
324
  const cssId = "dsh-connect-trae/client.css";
@@ -271,9 +355,9 @@ window.__ModuleLoader__.load({
271
355
  return { background: status === "signed-in" ? "var(--dsw-alias-state-success-primary, #22a06b)" : status === "error" ? "var(--dsw-alias-state-error-primary, #d92d20)" : "var(--dsw-alias-label-dimmed, #9aa0a6)" };
272
356
  }
273
357
  /** Render Trae sign-in state and the total usage summary as one expandable card. */
274
- function TraeUsageCard({ t, settingsScope }) {
358
+ function TraeUsageCard({ t, settingsScope, view }) {
275
359
  if (t === void 0) throw new Error("Trae usage card requires its translation function");
276
- const [open, setOpen] = (0, react.useState)(false);
360
+ const [open, setOpen] = (0, react.useState)(view === "page");
277
361
  /** The region whose tab is on screen; each tab is its own provider stack. */
278
362
  const [activeRegion, setActiveRegion] = (0, react.useState)("cn");
279
363
  /** Last-known usage per region, so tab dots survive tab switches. */
@@ -304,6 +388,12 @@ window.__ModuleLoader__.load({
304
388
  const [claiming, setClaiming] = (0, react.useState)(false);
305
389
  /** Last claim refusal, shown until the next successful refresh. */
306
390
  const [claimError, setClaimError] = (0, react.useState)(void 0);
391
+ /**
392
+ * A refused or unpersisted settings write, surfaced instead of silently
393
+ * reverting. On 0.1.7 a rejected write resolves normally, so without this
394
+ * the control would flip and then quietly snap back on the next snapshot.
395
+ */
396
+ const [writeError, setWriteError] = (0, react.useState)(void 0);
307
397
  const mounted = (0, react.useRef)(true);
308
398
  (0, react.useEffect)(() => {
309
399
  mounted.current = true;
@@ -399,6 +489,7 @@ window.__ModuleLoader__.load({
399
489
  ]);
400
490
  const rescanAccounts = async () => {
401
491
  setBusy(true);
492
+ setWriteError(void 0);
402
493
  try {
403
494
  const response = await fetch(withTraeRegion(TRAE_ACCOUNTS_REFRESH_PATH, activeRegion), {
404
495
  method: "POST",
@@ -409,11 +500,13 @@ window.__ModuleLoader__.load({
409
500
  if (!response.ok || !Array.isArray(body.accounts)) throw new Error(`HTTP ${response.status}`);
410
501
  const selected = body.accounts.find((account) => account.selected)?.id;
411
502
  const configured = configuredAccountsOf(settingsScope?.getSnapshot().value);
412
- if (selected !== void 0 && selected !== configured[activeRegion] && settingsScope?.getSnapshot().writable === true) await settingsScope.set("accounts", {
503
+ if (selected !== void 0 && selected !== configured[activeRegion] && settingsScope !== void 0 && settingsScope.getSnapshot().writable === true) await writeSettingsField(settingsScope, "accounts", {
413
504
  ...configured,
414
505
  [activeRegion]: selected
415
- });
506
+ }, (readBack) => configuredAccountsOf(readBack)[activeRegion] === selected);
416
507
  await refreshUsage(activeRegion);
508
+ } catch (error) {
509
+ if (mounted.current) setWriteError(error instanceof Error ? error.message : t("row.requestFailed"));
417
510
  } finally {
418
511
  if (mounted.current) setBusy(false);
419
512
  }
@@ -421,13 +514,15 @@ window.__ModuleLoader__.load({
421
514
  const switchAccount = async (accountId) => {
422
515
  if (settingsScope === void 0) return;
423
516
  setSwitchingAccount(true);
517
+ setWriteError(void 0);
424
518
  try {
425
- const configured = configuredAccountsOf(settingsScope.getSnapshot().value);
426
- await settingsScope.set("accounts", {
427
- ...configured,
519
+ await writeSettingsField(settingsScope, "accounts", {
520
+ ...configuredAccountsOf(settingsScope.getSnapshot().value),
428
521
  [activeRegion]: accountId
429
- });
522
+ }, (readBack) => configuredAccountsOf(readBack)[activeRegion] === accountId);
430
523
  await refreshUsage(activeRegion);
524
+ } catch (error) {
525
+ if (mounted.current) setWriteError(error instanceof Error ? error.message : t("row.requestFailed"));
431
526
  } finally {
432
527
  if (mounted.current) setSwitchingAccount(false);
433
528
  }
@@ -474,8 +569,11 @@ window.__ModuleLoader__.load({
474
569
  const toggleRegion = async (item, enabled) => {
475
570
  if (settingsScope === void 0 || settingsScope.getSnapshot().writable !== true) return;
476
571
  setTogglingRegion(item);
572
+ setWriteError(void 0);
477
573
  try {
478
- await settingsScope.set("regions", nextRegionEnabled(settingsScope.getSnapshot().value, item, enabled));
574
+ await writeSettingsField(settingsScope, "regions", nextRegionEnabled(settingsScope.getSnapshot().value, item, enabled), (readBack) => regionSlotsOf(readBack)[item] !== void 0 && regionSlotsOf(readBack)[item].enabled === enabled);
575
+ } catch (error) {
576
+ if (mounted.current) setWriteError(error instanceof Error ? error.message : t("row.requestFailed"));
479
577
  } finally {
480
578
  if (mounted.current) setTogglingRegion(void 0);
481
579
  }
@@ -523,7 +621,7 @@ window.__ModuleLoader__.load({
523
621
  if (mounted.current) setBusy(false);
524
622
  }
525
623
  };
526
- const settingsValue = settingsScope?.getSnapshot().value;
624
+ const settingsValue = unwrapVolatileDeep(settingsScope?.getSnapshot().value);
527
625
  const region = activeRegion;
528
626
  const configuredRegions = typeof settingsValue === "object" && settingsValue !== null && typeof settingsValue.regions === "object" && settingsValue.regions !== null ? settingsValue.regions : {};
529
627
  const regionSlot = configuredRegions[region];
@@ -594,9 +692,10 @@ window.__ModuleLoader__.load({
594
692
  const saveModels = async () => {
595
693
  if (settingsScope === void 0) return;
596
694
  setSaving(true);
695
+ setWriteError(void 0);
597
696
  try {
598
697
  if (status.status !== "signed-in") return;
599
- await settingsScope.set("regions", nextRegionSlots(configuredRegions, activeRegion, {
698
+ const nextSlot = {
600
699
  enabled: regionOn(activeRegion),
601
700
  lastCatalog: visibleModels.map((model) => ({
602
701
  ...model,
@@ -605,9 +704,15 @@ window.__ModuleLoader__.load({
605
704
  enabledModelIds: [...activeEnabledIds],
606
705
  imageModelIds: [...activeImageIds].filter((id) => activeEnabledIds.has(id)),
607
706
  contextBudgets: activeContextBudgets
608
- }));
707
+ };
708
+ await writeSettingsField(settingsScope, "regions", nextRegionSlots(configuredRegions, activeRegion, nextSlot), (readBack) => {
709
+ const ids = regionSlotsOf(readBack)[activeRegion]?.enabledModelIds;
710
+ return Array.isArray(ids) && ids.length === nextSlot.enabledModelIds.length && nextSlot.enabledModelIds.every((id) => ids.includes(id));
711
+ });
609
712
  discardModels();
610
713
  await refreshUsage(activeRegion);
714
+ } catch (error) {
715
+ if (mounted.current) setWriteError(error instanceof Error ? error.message : t("row.requestFailed"));
611
716
  } finally {
612
717
  if (mounted.current) setSaving(false);
613
718
  }
@@ -642,8 +747,7 @@ window.__ModuleLoader__.load({
642
747
  }),
643
748
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
644
749
  "aria-hidden": "true",
645
- className: `dsm-plugin-card-chevron${open ? " dsm-plugin-card-chevron-open" : ""}`,
646
- children: (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 14 })
750
+ className: `dsm-plugin-card-chevron${open ? " dsm-plugin-card-chevron-open" : ""}`
647
751
  })
648
752
  ]
649
753
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -695,6 +799,11 @@ window.__ModuleLoader__.load({
695
799
  className: "dsm-trae-tab-hint",
696
800
  children: t("row.tabHint")
697
801
  }),
802
+ writeError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
803
+ className: "dsm-trae-usage-error",
804
+ role: "alert",
805
+ children: t("row.settingsWriteFailed", { message: writeError })
806
+ }),
698
807
  regionOn(activeRegion) ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
699
808
  className: "dsm-trae-tab-off-notice",
700
809
  role: "status",
@@ -1069,6 +1178,7 @@ window.__ModuleLoader__.load({
1069
1178
  "row.tabSwitchHint": "Uncheck to switch this provider off. Its models disappear from the DSH model picker; its account, usage, and model settings are kept and return when you check it again.",
1070
1179
  "row.tabSwitchAria": "Offer the {region} provider to DSH",
1071
1180
  "row.tabOffNotice": "This provider is switched off: none of its models are offered to DSH. Your account, usage, and model settings are kept — check the box above the tab to bring it back.",
1181
+ "row.settingsWriteFailed": "The setting was not saved: {message}",
1072
1182
  "row.signedOut": "Not signed in",
1073
1183
  "row.signedOutHint": "Sign in once in the Trae desktop app; this plugin follows that sign-in automatically.",
1074
1184
  "row.searchedTitle": "Paths checked",
@@ -1146,6 +1256,7 @@ window.__ModuleLoader__.load({
1146
1256
  "row.tabSwitchHint": "取消勾选即关闭该供应商:它的模型不再出现在 DSH 模型选择器里;账号、用量与模型设置都会保留,重新勾选即恢复。",
1147
1257
  "row.tabSwitchAria": "是否向 DSH 提供{region}供应商",
1148
1258
  "row.tabOffNotice": "该供应商已关闭:它的模型不会提供给 DSH。账号、用量与模型设置均已保留——勾选上方复选框即可恢复。",
1259
+ "row.settingsWriteFailed": "设置未能保存:{message}",
1149
1260
  "row.signedOut": "未登录",
1150
1261
  "row.signedOutHint": "在 Trae 桌面 App 里登录一次即可,插件会自动跟随当前登录的账号。",
1151
1262
  "row.searchedTitle": "已检查的路径",
@@ -1216,12 +1327,16 @@ window.__ModuleLoader__.load({
1216
1327
  //#region src/client/index.tsx
1217
1328
  /** Stable browser-plugin name. */
1218
1329
  const name = "dsh-connect-trae-client";
1219
- /** Client services required by the Plugin configuration contribution. */
1220
- const inject = [
1221
- "slots",
1222
- "locale",
1223
- "settingsScope"
1224
- ];
1330
+ /**
1331
+ * Client services required by the Plugin configuration contribution.
1332
+ *
1333
+ * Deliberately only the two services the 0.1.7 line provides unconditionally:
1334
+ * `slots` (card injection) and `locale` (copy). The settings surface is probed
1335
+ * through `ctx.get()` (which returns undefined, never throws, for an absent
1336
+ * service), so the card degrades to read-only rather than failing to load if
1337
+ * configForms is not served yet.
1338
+ */
1339
+ const inject = ["slots", "locale"];
1225
1340
  /**
1226
1341
  * Register card copy and the Trae card under Plugin configuration.
1227
1342
  *
@@ -1234,6 +1349,8 @@ window.__ModuleLoader__.load({
1234
1349
  * NOTE: the try/catch boundary of this function is mirrored (duplicated) in
1235
1350
  * `tests/client-fallback.spec.ts`, because the real client entry imports
1236
1351
  * browser-only DSH packages that cannot load in the Node test environment.
1352
+ * If you change the guarded body, the soft probe, or the per-slot
1353
+ * registration here, update that mirror too.
1237
1354
  */
1238
1355
  function apply(ctx) {
1239
1356
  try {
@@ -1243,16 +1360,93 @@ window.__ModuleLoader__.load({
1243
1360
  en
1244
1361
  }), "dsh-connect-trae: settings copy");
1245
1362
  const t = ctx.locale.bind(namespace);
1246
- const settingsScope = ctx.settingsScope.bind({ namespace: "trae" });
1247
- ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
1248
- name: "settings.plugin.item",
1249
- key: "trae",
1250
- priority: 30,
1251
- inject: () => ({
1252
- t,
1253
- settingsScope
1254
- })
1255
- }, TraeUsageCard));
1363
+ const softGet = (name) => ctx.get(name);
1364
+ const forms = softGet("configForms");
1365
+ /**
1366
+ * The card's settings scope, bound to whichever namespace the Host actually
1367
+ * serves — resolved from the describe mirror, and RE-BOUND whenever the
1368
+ * mirror changes.
1369
+ *
1370
+ * The mirror answers asynchronously relative to this plugin's `apply()`
1371
+ * (and, on a profile reload, may gain namespaces that did not exist at
1372
+ * boot — this plugin's entry is exactly such a case). Resolving the
1373
+ * namespace ONCE at apply time and falling back to the declared constant
1374
+ * is a real failure: the form controller is bound to its namespace forever,
1375
+ * so a card whose entry appeared after the mirror's first load writes to
1376
+ * `trae` — a namespace the host does not serve — and every write is
1377
+ * refused (`No configurable plugin entry "trae"`), while the controls stay
1378
+ * enabled (the mirror reports global `writable` even when the namespace is
1379
+ * missing).
1380
+ *
1381
+ * The scope therefore forwards every read/write to the CURRENT controller:
1382
+ * once the mirror lists the trae namespace (any name matching `/trae/i`,
1383
+ * e.g. `dsh-connect-trae` or `include:dsh-connect-trae`), the card lands on
1384
+ * the right namespace and re-renders through its own subscription. Before
1385
+ * the mirror answers, the scope reports `writable: false` so the card is
1386
+ * read-only rather than offering controls that cannot save.
1387
+ */
1388
+ const settingsScope = (() => {
1389
+ let current;
1390
+ let currentOff;
1391
+ let refreshedOnce = false;
1392
+ const listeners = /* @__PURE__ */ new Set();
1393
+ const notify = () => {
1394
+ for (const listener of [...listeners]) listener();
1395
+ };
1396
+ const scope = {
1397
+ getSnapshot: () => current?.getSnapshot() ?? {
1398
+ status: "unavailable",
1399
+ value: void 0,
1400
+ writable: false
1401
+ },
1402
+ subscribe: (listener) => {
1403
+ listeners.add(listener);
1404
+ return () => {
1405
+ listeners.delete(listener);
1406
+ };
1407
+ },
1408
+ set: (field, value) => current !== void 0 ? current.set(field, value) : Promise.resolve(false)
1409
+ };
1410
+ const rebind = () => {
1411
+ let served;
1412
+ try {
1413
+ served = (forms?.describe().getSnapshot().view?.namespaces ?? []).find((entry) => entry.ns === "trae" || /trae/i.test(entry.ns));
1414
+ } catch {}
1415
+ const next = served === void 0 || forms === void 0 ? void 0 : forms.get(served.ns);
1416
+ if (next !== current) {
1417
+ currentOff?.();
1418
+ current = next;
1419
+ currentOff = current?.subscribe(notify);
1420
+ notify();
1421
+ }
1422
+ if (next === void 0 && !refreshedOnce) {
1423
+ refreshedOnce = true;
1424
+ try {
1425
+ (forms?.describe()).load?.();
1426
+ } catch {}
1427
+ }
1428
+ };
1429
+ rebind();
1430
+ forms?.describe().subscribe?.(rebind);
1431
+ return scope;
1432
+ })();
1433
+ const registerCard = (slotName, key) => {
1434
+ try {
1435
+ ctx.slots.inject(slotName, () => ctx.slots.register({
1436
+ name: slotName,
1437
+ key,
1438
+ priority: 30,
1439
+ inject: () => ({
1440
+ t,
1441
+ settingsScope
1442
+ })
1443
+ }, TraeUsageCard));
1444
+ } catch (error) {
1445
+ console.error(`[dsh-connect-trae] card slot "${slotName}" failed to register (host provider unaffected):`, error);
1446
+ }
1447
+ };
1448
+ registerCard("plugins.bundle.config", "dsh-connect-trae");
1449
+ registerCard("plugins.row.config", "dsh-connect-trae#dsh-connect-trae");
1256
1450
  } catch (error) {
1257
1451
  console.error("[dsh-connect-trae] client card failed to load (host provider unaffected):", error);
1258
1452
  }
package/lib/index.d.ts CHANGED
@@ -175,6 +175,13 @@ interface TraeWireModel {
175
175
  contextWindow?: number;
176
176
  maxTokens?: number;
177
177
  reasoning?: TraeReasoningCapability;
178
+ /**
179
+ * Effective (post-discount) credit multiplier, read from the row's
180
+ * `display_contact_config` — the figure the Trae IDE renders (see
181
+ * `wireCreditMultiplier` in solo.ts). Wins over the Remote directory's own
182
+ * `consumption_rate`, which reports the undiscounted value.
183
+ */
184
+ creditMultiplier?: number;
178
185
  /**
179
186
  * The `get_detail_param` function this config_name came from. Trae splits its
180
187
  * callable roster across several functions (SOLO modes), and a model is only
@@ -191,12 +198,19 @@ interface TraeWireModel {
191
198
  * skeleton: it supplies the display id, display name, context windows, credit
192
199
  * multiplier, reasoning and multimodal flags. `wire` (from `get_detail_param`)
193
200
  * supplies the real `llm_utils_chat` `config_name` — the only id the chat
194
- * endpoint actually accepts. A remote row is only callable when it maps to a
195
- * wire `config_name`, so a remote row with no wire match is DROPPED (it would
196
- * otherwise be sent as an invalid `config_name` and rejected with 4001
197
- * "param is invalid"). Verified 2026-08-30: the Remote directory advertises
198
- * `Doubao-Seed-Code` and `glm-5.3`, neither of which is a current `config_name`;
199
- * both fail every request, so they must not be exposed.
201
+ * endpoint actually accepts — plus the authoritative post-discount credit
202
+ * multiplier when `display_contact_config` carries one. A remote row is only
203
+ * callable when it maps to a wire `config_name`, so a remote row with no wire
204
+ * match is DROPPED (it would otherwise be sent as an invalid `config_name` and
205
+ * rejected with 4001 "param is invalid"). Verified 2026-08-30: the Remote
206
+ * directory advertises `Doubao-Seed-Code` and `glm-5.3`, neither of which is a
207
+ * current `config_name`; both fail every request, so they must not be exposed.
208
+ *
209
+ * Credit multiplier precedence: the wire's `display_contact_config` rate wins
210
+ * whenever present (it is the post-discount figure the Trae IDE renders — the
211
+ * Remote directory can report up to 10x the undiscounted value, see
212
+ * `wireCreditMultiplier` in solo.ts); the Remote `consumption_rate` is the
213
+ * fallback when the wire row carries none.
200
214
  *
201
215
  * Joining is two-tier, in priority order:
202
216
  * 1. `wire.id` (the `config_name`) equals the remote id — the model's display
@@ -1054,6 +1068,8 @@ interface TraeSoloModel {
1054
1068
  contextWindow?: number;
1055
1069
  maxTokens?: number;
1056
1070
  reasoning?: TraeReasoningCapability;
1071
+ /** Effective (post-discount) credit multiplier, as the Trae IDE shows it. */
1072
+ creditMultiplier?: number;
1057
1073
  /** The directory function that listed this config (replayed when calling it). */
1058
1074
  function?: string;
1059
1075
  }
@@ -1435,6 +1451,31 @@ interface TraeWebSearchPath {
1435
1451
  reason: 'missing' | 'unreadable' | 'invalid';
1436
1452
  message?: string;
1437
1453
  }
1454
+ /**
1455
+ * Peel ONE `{get(): T}` live reference, the shape DSH 0.1.7 delivers a
1456
+ * volatile-marked settings field as (see {@link unwrapVolatileDeep}).
1457
+ */
1458
+ declare function unwrapVolatile<T>(value: T): T;
1459
+ /**
1460
+ * Deep copy of a value with every `{get(): T}` live reference replaced by the
1461
+ * value it resolves to.
1462
+ *
1463
+ * DSH 0.1.7 hands volatile-marked fields back as live references, and a live
1464
+ * reference is still `typeof === 'object'` — so it passes a naive object check
1465
+ * and every lookup on it is `undefined`. Worse, spreading one (`{ ...ref }`)
1466
+ * does NOT read the field: it produces `{ get: <function> }`. Any merge that
1467
+ * spreads a resolved field to preserve its siblings — the card's "write one
1468
+ * region, keep the other" write, or the account map's spread — would otherwise
1469
+ * DROP every sibling and leak a function into the document.
1470
+ *
1471
+ * Both halves therefore unwrap before touching a resolved field. This lives in
1472
+ * the node-free host↔client bridge because the browser half needs it too.
1473
+ *
1474
+ * Non-reference values are recursed into so a nested volatile field is caught
1475
+ * as well; arrays and objects are rebuilt rather than mutated, so the caller's
1476
+ * value is never touched.
1477
+ */
1478
+ declare function unwrapVolatileDeep<T>(value: T): T;
1438
1479
  /**
1439
1480
  * Build the next `regions` settings value for the card's save. The write
1440
1481
  * targets ONLY the signed-in account's region slot; every other region's slot
@@ -1573,13 +1614,30 @@ declare const name = "dsh-connect-trae";
1573
1614
  declare const inject: string[];
1574
1615
  /**
1575
1616
  * Settings namespace, as a plain lowercase literal. DSH 0.1.2 removed the
1576
- * `settingsNamespace` brand constructor from the npm package (the Desktop
1577
- * host still ships it as a legacy shim), and every consumer of this constant —
1578
- * `registerModelDiscovery`, `registerConfigurableProviders`,
1579
- * `settings.installSection`, the settings test — takes it as a kebab string,
1580
- * so no branding is needed for either host generation.
1617
+ * `settingsNamespace` brand constructor from the npm package, and every
1618
+ * consumer of this constant takes it as a kebab string — so no branding is
1619
+ * needed. It is the FALLBACK namespace; the namespace the host actually serves
1620
+ * comes from {@link settingsNamespaceOf} (0.1.7 keys settings by the Loader
1621
+ * entry id, which a profile patch may mount as `dsh-connect-trae` or
1622
+ * `include:dsh-connect-trae`).
1581
1623
  */
1582
1624
  declare const TRAE_SETTINGS_NS = "trae";
1625
+ /**
1626
+ * The namespace the HOST actually serves this plugin under.
1627
+ *
1628
+ * On DSH 0.1.7 `SettingsForms.describe()` keys every namespace by the Loader
1629
+ * entry id, and the harness looks a provider's namespace up by EXACT match —
1630
+ * advertising `trae` while the host serves `include:dsh-connect-trae` made the
1631
+ * provider read as "not configured": its configure affordance and model
1632
+ * discovery both silently went dead.
1633
+ *
1634
+ * `ctx.fiber.entry` is added by the Loader, not by Cordis itself, so it is not
1635
+ * in Cordis's public types and is absent on hosts that mount a plugin without a
1636
+ * Loader entry (a test harness, or `ctx.plugin()` called directly). Hence the
1637
+ * probe plus the documented fallback, mirroring the first-party plugins:
1638
+ * `const settingsNs = ctx.fiber.entry?.options.id ?? NS`.
1639
+ */
1640
+ declare function settingsNamespaceOf(ctx: unknown): string;
1583
1641
  /** One region's saved model state: its own directory and the user's selection within it. */
1584
1642
  interface TraeRegionState {
1585
1643
  /**
@@ -1655,7 +1713,22 @@ declare function regionStateOf(config: Config, region: TraeRegion): TraeRegionSt
1655
1713
  * disagree about a region's state.
1656
1714
  */
1657
1715
  declare function regionEnabled(config: Config, region: TraeRegion): boolean;
1716
+ /**
1717
+ * Mark a schema's field as volatile.
1718
+ *
1719
+ * `volatile()` exists from schemastery 3.18.3; since 2.3.0 the peer range
1720
+ * requires `>=3.18.4`, so it is always present on a supported host. The probe
1721
+ * is kept as a safety net for a consumer that resolves an older schemastery
1722
+ * anyway (where the helper must degrade to identity — hand-writing
1723
+ * `meta.volatile = true` would bypass schemastery's own `validateVolatileSchema`
1724
+ * checks) and to keep both arms deterministically testable.
1725
+ *
1726
+ * Without the marker, 0.1.7's settings write gate REJECTS every write with
1727
+ * `Plugin entry "trae" has no volatile fields` while the scope's `set()` still
1728
+ * resolves — so the card would look like it saved and silently revert.
1729
+ */
1730
+ declare function asVolatile<S>(schema: S): S;
1658
1731
  declare const Config: z<Config>;
1659
1732
  declare function apply(ctx: Context, config: Config): void;
1660
1733
  //#endregion
1661
- export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, REGION_GATEWAYS, type RawChatDelta, type RawChatMessage, type RawChatTool, SseDecoder, type SseEvent, TRAE_AI_PROVIDER, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROVIDER, TRAE_PROVIDERS, TRAE_PROVIDER_DISPLAY_NAMES, TRAE_RAW_CHAT_V1_PATH, TRAE_RAW_CHAT_V2_PATH, TRAE_REASONING_EFFORTS, TRAE_REGIONS, TRAE_REGION_PARAM, TRAE_SETTINGS_NS, TRAE_SOLO_CHAT_PATH, TRAE_SOLO_FUNCTION, TRAE_SOLO_MODELS_PATH, TRAE_SOLO_REMOTE_BASE, TRAE_STREAM_IDLE_TIMEOUT_MS, TRAE_USAGE_PATH, TRAE_VERSION_CODE_FALLBACK, type TraeActivityRule, type TraeAdapter, type TraeCachedModelConfig, TraeCatalog, type TraeChatResult, type TraeCheckinClaim, type TraeCheckinStatus, type TraeContextBudget, type TraeCredential, TraeCredentialStore, TraeDelegatingUpstreamClient, type TraeDiscoveredModel, type TraeDiscoveredReasoning, type TraeEdition, TraeFallbackUpstreamClient, type TraeFallbackUpstreamOptions, type TraeFusionRawChatEnvelope, TraeGatedUpstreamClient, type TraeGatedUpstreamOptions, type TraeIdentity, type TraeInputModality, type TraeModelDetailRequest, type TraeModelInfo, type TraeObservedModelConfig, TraeRawCapabilityController, type TraeRawCapabilityControllerOptions, type TraeRawCapabilitySnapshot, TraeRawCapabilityState, type TraeRawChatBehaviorConfig, type TraeRawChatCapability, type TraeRawChatClientOptions, type TraeRawChatConfig, type TraeRawChatFailureReason, type TraeRawChatRuntimeConfig, TraeRawChatUpstreamClient, type TraeRawDiagnostic, type TraeRawDiagnosticState, type TraeRawGateway, type TraeRawGatewayOptions, type TraeRawResolverResult, type TraeReasoningCapability, type TraeReasoningEffort, type TraeRefreshDevice, type TraeRegion, type TraeRegionGateways, TraeRegionState, type TraeShim, TraeSoloBridge, type TraeSoloClientOptions, TraeSoloRemoteCatalogClient, type TraeSoloRemoteCatalogOptions, TraeSoloUpstreamClient, type TraeStorageCandidate, type TraeStreamEvent, type TraeUpstreamClient, TraeUsageClient, type TraeUsageOptions, type TraeUsagePack, type TraeUsageRouteOptions, type TraeUsageSnapshot, type TraeUsageSummary, type TraeUsageView, type TraeWebActivity, type TraeWebCheckin, type TraeWebCheckinClaim, type TraeWebCredits, type TraeWebUsage, type TraeWireModel, UnconfiguredTraeUpstreamClient, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyTraeRawChatFailure, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, discoveredCatalog, fallbackModelsFor, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, mergeTraeModelSources, name, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeTraeRawChatCapability, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeCliIdentity, readTraeIdentity, refreshTraeCredential, regionEnabled, regionEnabledOf, regionOfCredential, regionOfEdition, regionOfHost, regionOfTraeProvider, regionOfTraeStatusUrl, regionOfUserRegion, regionStateOf, registerTraeUsageRoute, resolveTraeIdentity, resolveTraeRawRuntime, sanitizeCatalog, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStorageCandidates, traeWebUsage, withTraeRegion };
1734
+ export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, REGION_GATEWAYS, type RawChatDelta, type RawChatMessage, type RawChatTool, SseDecoder, type SseEvent, TRAE_AI_PROVIDER, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROVIDER, TRAE_PROVIDERS, TRAE_PROVIDER_DISPLAY_NAMES, TRAE_RAW_CHAT_V1_PATH, TRAE_RAW_CHAT_V2_PATH, TRAE_REASONING_EFFORTS, TRAE_REGIONS, TRAE_REGION_PARAM, TRAE_SETTINGS_NS, TRAE_SOLO_CHAT_PATH, TRAE_SOLO_FUNCTION, TRAE_SOLO_MODELS_PATH, TRAE_SOLO_REMOTE_BASE, TRAE_STREAM_IDLE_TIMEOUT_MS, TRAE_USAGE_PATH, TRAE_VERSION_CODE_FALLBACK, type TraeActivityRule, type TraeAdapter, type TraeCachedModelConfig, TraeCatalog, type TraeChatResult, type TraeCheckinClaim, type TraeCheckinStatus, type TraeContextBudget, type TraeCredential, TraeCredentialStore, TraeDelegatingUpstreamClient, type TraeDiscoveredModel, type TraeDiscoveredReasoning, type TraeEdition, TraeFallbackUpstreamClient, type TraeFallbackUpstreamOptions, type TraeFusionRawChatEnvelope, TraeGatedUpstreamClient, type TraeGatedUpstreamOptions, type TraeIdentity, type TraeInputModality, type TraeModelDetailRequest, type TraeModelInfo, type TraeObservedModelConfig, TraeRawCapabilityController, type TraeRawCapabilityControllerOptions, type TraeRawCapabilitySnapshot, TraeRawCapabilityState, type TraeRawChatBehaviorConfig, type TraeRawChatCapability, type TraeRawChatClientOptions, type TraeRawChatConfig, type TraeRawChatFailureReason, type TraeRawChatRuntimeConfig, TraeRawChatUpstreamClient, type TraeRawDiagnostic, type TraeRawDiagnosticState, type TraeRawGateway, type TraeRawGatewayOptions, type TraeRawResolverResult, type TraeReasoningCapability, type TraeReasoningEffort, type TraeRefreshDevice, type TraeRegion, type TraeRegionGateways, TraeRegionState, type TraeShim, TraeSoloBridge, type TraeSoloClientOptions, TraeSoloRemoteCatalogClient, type TraeSoloRemoteCatalogOptions, TraeSoloUpstreamClient, type TraeStorageCandidate, type TraeStreamEvent, type TraeUpstreamClient, TraeUsageClient, type TraeUsageOptions, type TraeUsagePack, type TraeUsageRouteOptions, type TraeUsageSnapshot, type TraeUsageSummary, type TraeUsageView, type TraeWebActivity, type TraeWebCheckin, type TraeWebCheckinClaim, type TraeWebCredits, type TraeWebUsage, type TraeWireModel, UnconfiguredTraeUpstreamClient, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, asVolatile, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyTraeRawChatFailure, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, discoveredCatalog, fallbackModelsFor, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, mergeTraeModelSources, name, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeTraeRawChatCapability, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeCliIdentity, readTraeIdentity, refreshTraeCredential, regionEnabled, regionEnabledOf, regionOfCredential, regionOfEdition, regionOfHost, regionOfTraeProvider, regionOfTraeStatusUrl, regionOfUserRegion, regionStateOf, registerTraeUsageRoute, resolveTraeIdentity, resolveTraeRawRuntime, sanitizeCatalog, settingsNamespaceOf, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStorageCandidates, traeWebUsage, unwrapVolatile, unwrapVolatileDeep, withTraeRegion };
package/lib/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import z from "@deepseek-ai/schemastery";
2
- import * as dshSettings from "@deepseek-ai/dsh-settings";
3
2
  import { createProvider } from "@earendil-works/pi-ai";
4
3
  import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
5
4
  import { resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
@@ -150,12 +149,19 @@ function displayKey(name) {
150
149
  * skeleton: it supplies the display id, display name, context windows, credit
151
150
  * multiplier, reasoning and multimodal flags. `wire` (from `get_detail_param`)
152
151
  * supplies the real `llm_utils_chat` `config_name` — the only id the chat
153
- * endpoint actually accepts. A remote row is only callable when it maps to a
154
- * wire `config_name`, so a remote row with no wire match is DROPPED (it would
155
- * otherwise be sent as an invalid `config_name` and rejected with 4001
156
- * "param is invalid"). Verified 2026-08-30: the Remote directory advertises
157
- * `Doubao-Seed-Code` and `glm-5.3`, neither of which is a current `config_name`;
158
- * both fail every request, so they must not be exposed.
152
+ * endpoint actually accepts — plus the authoritative post-discount credit
153
+ * multiplier when `display_contact_config` carries one. A remote row is only
154
+ * callable when it maps to a wire `config_name`, so a remote row with no wire
155
+ * match is DROPPED (it would otherwise be sent as an invalid `config_name` and
156
+ * rejected with 4001 "param is invalid"). Verified 2026-08-30: the Remote
157
+ * directory advertises `Doubao-Seed-Code` and `glm-5.3`, neither of which is a
158
+ * current `config_name`; both fail every request, so they must not be exposed.
159
+ *
160
+ * Credit multiplier precedence: the wire's `display_contact_config` rate wins
161
+ * whenever present (it is the post-discount figure the Trae IDE renders — the
162
+ * Remote directory can report up to 10x the undiscounted value, see
163
+ * `wireCreditMultiplier` in solo.ts); the Remote `consumption_rate` is the
164
+ * fallback when the wire row carries none.
159
165
  *
160
166
  * Joining is two-tier, in priority order:
161
167
  * 1. `wire.id` (the `config_name`) equals the remote id — the model's display
@@ -177,12 +183,13 @@ function mergeTraeModelSources(remote, wire) {
177
183
  for (const model of remote) {
178
184
  const wireModel = wireById.get(displayKey(model.id)) ?? wireByName.get(displayKey(model.name));
179
185
  if (wireModel === void 0) continue;
186
+ const creditMultiplier = wireModel.creditMultiplier ?? model.creditMultiplier;
180
187
  result.push({
181
188
  id: model.id,
182
189
  name: model.name,
183
190
  ...model.contextWindow === void 0 ? {} : { contextWindow: model.contextWindow },
184
191
  ...model.maxContextWindow === void 0 ? {} : { maxContextWindow: model.maxContextWindow },
185
- ...model.creditMultiplier === void 0 ? {} : { creditMultiplier: model.creditMultiplier },
192
+ ...creditMultiplier === void 0 ? {} : { creditMultiplier },
186
193
  input: ["text"],
187
194
  reasoningSupported: model.reasoningSupported,
188
195
  ...model.reasoning === void 0 ? {} : {
@@ -1934,6 +1941,39 @@ function prepareSoloBody(source, defaultModel = "glm-5.2", functionName) {
1934
1941
  }
1935
1942
  return JSON.stringify(body);
1936
1943
  }
1944
+ /**
1945
+ * Read the effective credit multiplier from a `get_detail_param` row.
1946
+ *
1947
+ * The rate the Trae IDE renders lives in `display_contact_config` — a *string*
1948
+ * holding a second JSON document — and its `consumption_rate.data.rate` is
1949
+ * already the post-discount value (verified 2026-09-13, commit 1.4.2: the
1950
+ * Remote directory reports the undiscounted figure, e.g. `0.8`, while the IDE
1951
+ * and this field both say `0.08` under a 限时 1 折 promotion — up to a 10x
1952
+ * difference). The wire figure therefore wins over the Remote one during the
1953
+ * merge; this parser is what feeds it.
1954
+ *
1955
+ * The international (ai) gateway serves no `consumption_rate` anywhere (its
1956
+ * subscription models carry only `features.cost` tags), so rows without the
1957
+ * field keep their bare name — parsed-but-absent, never fabricated.
1958
+ */
1959
+ function wireCreditMultiplier(config) {
1960
+ const raw = config["display_contact_config"];
1961
+ if (typeof raw !== "string" || raw === "") return void 0;
1962
+ let parsed;
1963
+ try {
1964
+ parsed = JSON.parse(raw);
1965
+ } catch {
1966
+ return;
1967
+ }
1968
+ if (typeof parsed !== "object" || parsed === null) return void 0;
1969
+ const consumption = parsed["consumption_rate"];
1970
+ if (typeof consumption !== "object" || consumption === null) return void 0;
1971
+ const entry = consumption;
1972
+ if (entry["enable"] !== true) return void 0;
1973
+ const data = entry["data"];
1974
+ if (typeof data !== "object" || data === null) return void 0;
1975
+ return finitePositive$1(data["rate"]);
1976
+ }
1937
1977
  var TraeSoloUpstreamClient = class {
1938
1978
  options;
1939
1979
  fetchImpl;
@@ -2012,6 +2052,7 @@ var TraeSoloUpstreamClient = class {
2012
2052
  ...config,
2013
2053
  ...detail
2014
2054
  });
2055
+ const creditMultiplier = wireCreditMultiplier(config);
2015
2056
  if (byId.has(id)) continue;
2016
2057
  byId.set(id, {
2017
2058
  id,
@@ -2019,6 +2060,7 @@ var TraeSoloUpstreamClient = class {
2019
2060
  ...contextWindow === void 0 ? {} : { contextWindow },
2020
2061
  ...maxTokens === void 0 ? {} : { maxTokens },
2021
2062
  ...reasoning === void 0 ? {} : { reasoning },
2063
+ ...creditMultiplier === void 0 ? {} : { creditMultiplier },
2022
2064
  function: directoryFunction
2023
2065
  });
2024
2066
  }
@@ -3269,6 +3311,42 @@ function regionOfTraeStatusUrl(url) {
3269
3311
  return TRAE_REGIONS.includes(value) ? value : void 0;
3270
3312
  }
3271
3313
  /**
3314
+ * Peel ONE `{get(): T}` live reference, the shape DSH 0.1.7 delivers a
3315
+ * volatile-marked settings field as (see {@link unwrapVolatileDeep}).
3316
+ */
3317
+ function unwrapVolatile(value) {
3318
+ if (value !== null && typeof value === "object" && typeof value.get === "function") return value.get();
3319
+ return value;
3320
+ }
3321
+ /**
3322
+ * Deep copy of a value with every `{get(): T}` live reference replaced by the
3323
+ * value it resolves to.
3324
+ *
3325
+ * DSH 0.1.7 hands volatile-marked fields back as live references, and a live
3326
+ * reference is still `typeof === 'object'` — so it passes a naive object check
3327
+ * and every lookup on it is `undefined`. Worse, spreading one (`{ ...ref }`)
3328
+ * does NOT read the field: it produces `{ get: <function> }`. Any merge that
3329
+ * spreads a resolved field to preserve its siblings — the card's "write one
3330
+ * region, keep the other" write, or the account map's spread — would otherwise
3331
+ * DROP every sibling and leak a function into the document.
3332
+ *
3333
+ * Both halves therefore unwrap before touching a resolved field. This lives in
3334
+ * the node-free host↔client bridge because the browser half needs it too.
3335
+ *
3336
+ * Non-reference values are recursed into so a nested volatile field is caught
3337
+ * as well; arrays and objects are rebuilt rather than mutated, so the caller's
3338
+ * value is never touched.
3339
+ */
3340
+ function unwrapVolatileDeep(value) {
3341
+ if (value === null || typeof value !== "object") return value;
3342
+ if (typeof value.get === "function") return unwrapVolatileDeep(value.get());
3343
+ if (Array.isArray(value)) return value.map((entry) => unwrapVolatileDeep(entry));
3344
+ const source = value;
3345
+ const out = {};
3346
+ for (const key of Object.keys(source)) out[key] = unwrapVolatileDeep(source[key]);
3347
+ return out;
3348
+ }
3349
+ /**
3272
3350
  * Build the next `regions` settings value for the card's save. The write
3273
3351
  * targets ONLY the signed-in account's region slot; every other region's slot
3274
3352
  * is carried over untouched, so switching accounts never clobbers the other
@@ -3276,8 +3354,9 @@ function regionOfTraeStatusUrl(url) {
3276
3354
  * from an empty document.
3277
3355
  */
3278
3356
  function nextRegionSlots(regions, region, slot) {
3357
+ const unwrapped = unwrapVolatileDeep(regions);
3279
3358
  return {
3280
- ...typeof regions === "object" && regions !== null && !Array.isArray(regions) ? regions : {},
3359
+ ...typeof unwrapped === "object" && unwrapped !== null && !Array.isArray(unwrapped) ? unwrapped : {},
3281
3360
  [region]: slot
3282
3361
  };
3283
3362
  }
@@ -3288,17 +3367,23 @@ function nextRegionSlots(regions, region, slot) {
3288
3367
  * where the map was expected was a real shipped bug — the lookup then read
3289
3368
  * `section['cn']` (absent), so the card's checkbox reported `true` forever and
3290
3369
  * clicking it appeared to do nothing even though the write succeeded.
3370
+ *
3371
+ * Unwraps live references first: on DSH 0.1.7 the `regions` field arrives as a
3372
+ * `{get(): T}` reference, which passes the `typeof === 'object'` check below
3373
+ * and would otherwise be returned as if it were the map — making every region
3374
+ * read back as absent (and the enabled flag read as "on" forever).
3291
3375
  */
3292
3376
  function regionsMapOf(value) {
3293
- if (typeof value !== "object" || value === null || Array.isArray(value)) return {};
3294
- const record = value;
3377
+ const unwrapped = unwrapVolatileDeep(value);
3378
+ if (typeof unwrapped !== "object" || unwrapped === null || Array.isArray(unwrapped)) return {};
3379
+ const record = unwrapped;
3295
3380
  const nested = record["regions"];
3296
3381
  if (typeof nested === "object" && nested !== null && !Array.isArray(nested)) return nested;
3297
3382
  return record;
3298
3383
  }
3299
3384
  /** One region's stored slot as a plain object; any other shape reads as empty. */
3300
3385
  function regionSlotOf(value, region) {
3301
- const slot = regionsMapOf(value)[region];
3386
+ const slot = unwrapVolatile(regionsMapOf(value)[region]);
3302
3387
  return typeof slot === "object" && slot !== null && !Array.isArray(slot) ? slot : {};
3303
3388
  }
3304
3389
  /**
@@ -3718,14 +3803,34 @@ const name = "dsh-connect-trae";
3718
3803
  const inject = ["llm"];
3719
3804
  /**
3720
3805
  * Settings namespace, as a plain lowercase literal. DSH 0.1.2 removed the
3721
- * `settingsNamespace` brand constructor from the npm package (the Desktop
3722
- * host still ships it as a legacy shim), and every consumer of this constant —
3723
- * `registerModelDiscovery`, `registerConfigurableProviders`,
3724
- * `settings.installSection`, the settings test — takes it as a kebab string,
3725
- * so no branding is needed for either host generation.
3806
+ * `settingsNamespace` brand constructor from the npm package, and every
3807
+ * consumer of this constant takes it as a kebab string — so no branding is
3808
+ * needed. It is the FALLBACK namespace; the namespace the host actually serves
3809
+ * comes from {@link settingsNamespaceOf} (0.1.7 keys settings by the Loader
3810
+ * entry id, which a profile patch may mount as `dsh-connect-trae` or
3811
+ * `include:dsh-connect-trae`).
3726
3812
  */
3727
3813
  const TRAE_SETTINGS_NS = "trae";
3728
3814
  /**
3815
+ * The namespace the HOST actually serves this plugin under.
3816
+ *
3817
+ * On DSH 0.1.7 `SettingsForms.describe()` keys every namespace by the Loader
3818
+ * entry id, and the harness looks a provider's namespace up by EXACT match —
3819
+ * advertising `trae` while the host serves `include:dsh-connect-trae` made the
3820
+ * provider read as "not configured": its configure affordance and model
3821
+ * discovery both silently went dead.
3822
+ *
3823
+ * `ctx.fiber.entry` is added by the Loader, not by Cordis itself, so it is not
3824
+ * in Cordis's public types and is absent on hosts that mount a plugin without a
3825
+ * Loader entry (a test harness, or `ctx.plugin()` called directly). Hence the
3826
+ * probe plus the documented fallback, mirroring the first-party plugins:
3827
+ * `const settingsNs = ctx.fiber.entry?.options.id ?? NS`.
3828
+ */
3829
+ function settingsNamespaceOf(ctx) {
3830
+ const id = ctx?.fiber?.entry?.options?.id;
3831
+ return typeof id === "string" && id !== "" ? id : TRAE_SETTINGS_NS;
3832
+ }
3833
+ /**
3729
3834
  * One region's saved state. A config written before the region split has only
3730
3835
  * the flat fields: those were always captured from the CN endpoint (the
3731
3836
  * plugin had no international support), so they are read as the CN state and
@@ -3735,7 +3840,7 @@ const TRAE_SETTINGS_NS = "trae";
3735
3840
  * picks (the same failure workbuddy fixed with its region split).
3736
3841
  */
3737
3842
  function regionStateOf(config, region) {
3738
- const stored = config.regions?.[region];
3843
+ const stored = unwrapVolatile(unwrapVolatile(config.regions)?.[region]);
3739
3844
  if (stored !== void 0) return stored;
3740
3845
  if (region !== "cn") return {};
3741
3846
  return {
@@ -3756,6 +3861,24 @@ function regionStateOf(config, region) {
3756
3861
  function regionEnabled(config, region) {
3757
3862
  return regionStateOf(config, region).enabled !== false;
3758
3863
  }
3864
+ /**
3865
+ * Mark a schema's field as volatile.
3866
+ *
3867
+ * `volatile()` exists from schemastery 3.18.3; since 2.3.0 the peer range
3868
+ * requires `>=3.18.4`, so it is always present on a supported host. The probe
3869
+ * is kept as a safety net for a consumer that resolves an older schemastery
3870
+ * anyway (where the helper must degrade to identity — hand-writing
3871
+ * `meta.volatile = true` would bypass schemastery's own `validateVolatileSchema`
3872
+ * checks) and to keep both arms deterministically testable.
3873
+ *
3874
+ * Without the marker, 0.1.7's settings write gate REJECTS every write with
3875
+ * `Plugin entry "trae" has no volatile fields` while the scope's `set()` still
3876
+ * resolves — so the card would look like it saved and silently revert.
3877
+ */
3878
+ function asVolatile(schema) {
3879
+ if (typeof schema.volatile === "function") return schema.volatile();
3880
+ return schema;
3881
+ }
3759
3882
  const modelConfig = z.object({
3760
3883
  id: z.string().required(),
3761
3884
  name: z.string().required(),
@@ -3777,17 +3900,17 @@ const accountSelectionConfig = z.object({
3777
3900
  ai: z.string().description("Selected international account id (never a token)")
3778
3901
  });
3779
3902
  const Config = z.object({
3780
- authFile: z.string().description("Optional Trae storage.json path override"),
3781
- edition: z.union([
3903
+ authFile: asVolatile(z.string().description("Optional Trae storage.json path override")),
3904
+ edition: asVolatile(z.union([
3782
3905
  "auto",
3783
3906
  "cn",
3784
3907
  "sg",
3785
3908
  "solo",
3786
3909
  "solo-sg"
3787
- ]).default("auto").description("Trae edition hint"),
3910
+ ]).default("auto").description("Trae edition hint")),
3788
3911
  accountId: z.string().description("Deprecated: pre-split account selector, attributed to its own region"),
3789
- accounts: accountSelectionConfig.description("Per-region account selections, keyed cn | ai"),
3790
- regions: z.dict(regionStateConfig).default({}).description("Per-region model directory and selection, keyed cn | ai"),
3912
+ accounts: asVolatile(accountSelectionConfig.description("Per-region account selections, keyed cn | ai")),
3913
+ regions: asVolatile(z.dict(regionStateConfig).default({}).description("Per-region model directory and selection, keyed cn | ai")),
3791
3914
  lastCatalog: z.array(modelConfig).description("Deprecated: pre-region-split CN model directory"),
3792
3915
  enabledModelIds: z.array(z.string()).default([]).description("Deprecated: pre-region-split CN selection"),
3793
3916
  contextBudgets: z.dict(z.number().step(1).min(1)).default({}).description("Deprecated: pre-region-split CN context budgets"),
@@ -3797,6 +3920,7 @@ const Config = z.object({
3797
3920
  /** Every region, in card tab order. */
3798
3921
  const REGION_KEYS = ["cn", "ai"];
3799
3922
  function apply(ctx, config) {
3923
+ const settingsNs = settingsNamespaceOf(ctx);
3800
3924
  let current = () => config;
3801
3925
  const enabledSet = (value, region) => new Set(regionStateOf(value, region).enabledModelIds ?? []);
3802
3926
  const imageSet = (value, region) => new Set(regionStateOf(value, region).imageModelIds ?? []);
@@ -3846,7 +3970,7 @@ function apply(ctx, config) {
3846
3970
  */
3847
3971
  let legacyAccountRegion;
3848
3972
  const effectiveAccountFor = (region, value) => {
3849
- const explicit = value.accounts?.[region];
3973
+ const explicit = unwrapVolatile(value.accounts)?.[region];
3850
3974
  if (explicit !== void 0) return explicit;
3851
3975
  return legacyAccountRegion === region ? value.accountId : void 0;
3852
3976
  };
@@ -3887,10 +4011,12 @@ function apply(ctx, config) {
3887
4011
  try {
3888
4012
  preferred = (await store.current())?.edition;
3889
4013
  } catch {}
3890
- const hint = (config.edition !== void 0 && config.edition !== "auto" ? config.edition : void 0) ?? preferred;
3891
- return resolveTraeIdentity(config.authFile === void 0 ? traeStorageCandidates().filter((item) => item.source === "desktop" && (hint !== void 0 ? item.edition === hint : regionOfEdition(item.edition) === region)) : [{
4014
+ const authFile = unwrapVolatile(config.authFile);
4015
+ const edition = unwrapVolatile(config.edition);
4016
+ const hint = (edition !== void 0 && edition !== "auto" ? edition : void 0) ?? preferred;
4017
+ return resolveTraeIdentity(authFile === void 0 ? traeStorageCandidates().filter((item) => item.source === "desktop" && (hint !== void 0 ? item.edition === hint : regionOfEdition(item.edition) === region)) : [{
3892
4018
  edition: hint ?? (region === "ai" ? "sg" : "solo"),
3893
- path: config.authFile,
4019
+ path: authFile,
3894
4020
  source: "desktop"
3895
4021
  }], hint ?? (region === "ai" ? "solo-sg" : "cn"));
3896
4022
  };
@@ -3912,8 +4038,8 @@ function apply(ctx, config) {
3912
4038
  };
3913
4039
  const store = new TraeCredentialStore({
3914
4040
  region,
3915
- ...config.authFile === void 0 ? {} : { storagePath: config.authFile },
3916
- edition: config.edition ?? "auto",
4041
+ ...unwrapVolatile(config.authFile) === void 0 ? {} : { storagePath: unwrapVolatile(config.authFile) },
4042
+ edition: unwrapVolatile(config.edition) ?? "auto",
3917
4043
  refresh: async (credential) => refreshTraeCredential(credential, void 0, await refreshDeviceSafe())
3918
4044
  });
3919
4045
  const solo = new TraeSoloUpstreamClient({
@@ -4032,7 +4158,7 @@ function apply(ctx, config) {
4032
4158
  registration.cn.directory?.replace(REGION_KEYS.filter((region) => regionEnabled(value, region)).map((region) => ({
4033
4159
  provider: TRAE_PROVIDERS[region],
4034
4160
  displayName: TRAE_PROVIDER_DISPLAY_NAMES[region],
4035
- settingsNs: TRAE_SETTINGS_NS,
4161
+ settingsNs,
4036
4162
  settingsPath: [],
4037
4163
  declared: false
4038
4164
  })));
@@ -4041,20 +4167,12 @@ function apply(ctx, config) {
4041
4167
  const applySelection = (value) => {
4042
4168
  for (const region of REGION_KEYS) {
4043
4169
  const stack = stacks[region];
4044
- stack.store.setSource(value.authFile, value.edition ?? "auto", effectiveAccountFor(region, value));
4170
+ stack.store.setSource(unwrapVolatile(value.authFile), unwrapVolatile(value.edition) ?? "auto", effectiveAccountFor(region, value));
4045
4171
  stack.catalog.set(configuredModels(value, region));
4046
4172
  stack.invalidateAdapter();
4047
4173
  }
4048
4174
  syncRegionRegistration(value);
4049
4175
  };
4050
- const sectionHooks = {
4051
- setSource(source) {
4052
- current = source;
4053
- },
4054
- onChange() {
4055
- applySelection(current());
4056
- }
4057
- };
4058
4176
  applySelection(config);
4059
4177
  (async () => {
4060
4178
  const id = current().accountId;
@@ -4067,10 +4185,12 @@ function apply(ctx, config) {
4067
4185
  }
4068
4186
  } catch {}
4069
4187
  })();
4070
- const legacyInstallSettingsSection = dshSettings.installSettingsSection;
4071
- if (typeof legacyInstallSettingsSection === "function") legacyInstallSettingsSection(ctx, TRAE_SETTINGS_NS, Config, config, sectionHooks);
4072
- else ctx.inject(["settings"], (settingsCtx) => {
4073
- settingsCtx.settings.installSection(ctx, TRAE_SETTINGS_NS, Config, config, sectionHooks);
4188
+ ctx.inject(["settings"], (settingsCtx) => {
4189
+ const settings = settingsCtx.settings;
4190
+ ctx.effect(() => settings.configure({ auto: true }, ctx.fiber));
4191
+ });
4192
+ ctx.on("loader/volatile-update", () => {
4193
+ applySelection(current());
4074
4194
  });
4075
4195
  let stopped = false;
4076
4196
  ctx.effect(() => () => {
@@ -4094,7 +4214,7 @@ function apply(ctx, config) {
4094
4214
  };
4095
4215
  registration[region].adapter = region === "cn" ? ctx.llm.registerAdapter([TRAE_PROVIDER], trae.adapter) : ctx.llm.registerAdapter([TRAE_AI_PROVIDER], trae.adapter);
4096
4216
  }
4097
- ctx.llm.registerModelDiscovery(TRAE_SETTINGS_NS, async (request, signal) => {
4217
+ ctx.llm.registerModelDiscovery(settingsNs, async (request, signal) => {
4098
4218
  const region = regionOfTraeProvider(request.provider ?? "");
4099
4219
  if (region === void 0) return [];
4100
4220
  if (!regionEnabled(current(), region)) return [];
@@ -4110,7 +4230,7 @@ function apply(ctx, config) {
4110
4230
  registration.cn.directory = ctx.llm.registerConfigurableProviders(REGION_KEYS.map((region) => ({
4111
4231
  provider: TRAE_PROVIDERS[region],
4112
4232
  displayName: TRAE_PROVIDER_DISPLAY_NAMES[region],
4113
- settingsNs: TRAE_SETTINGS_NS,
4233
+ settingsNs,
4114
4234
  settingsPath: [],
4115
4235
  declared: false
4116
4236
  })));
@@ -4153,4 +4273,4 @@ function apply(ctx, config) {
4153
4273
  });
4154
4274
  }
4155
4275
  //#endregion
4156
- export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, REGION_GATEWAYS, SseDecoder, TRAE_AI_PROVIDER, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROVIDER, TRAE_PROVIDERS, TRAE_PROVIDER_DISPLAY_NAMES, TRAE_RAW_CHAT_V1_PATH, TRAE_RAW_CHAT_V2_PATH, TRAE_REASONING_EFFORTS, TRAE_REGIONS, TRAE_REGION_PARAM, TRAE_SETTINGS_NS, TRAE_SOLO_CHAT_PATH, TRAE_SOLO_FUNCTION, TRAE_SOLO_MODELS_PATH, TRAE_SOLO_REMOTE_BASE, TRAE_STREAM_IDLE_TIMEOUT_MS, TRAE_USAGE_PATH, TRAE_VERSION_CODE_FALLBACK, TraeCatalog, TraeCredentialStore, TraeDelegatingUpstreamClient, TraeFallbackUpstreamClient, TraeGatedUpstreamClient, TraeRawCapabilityController, TraeRawCapabilityState, TraeRawChatUpstreamClient, TraeSoloBridge, TraeSoloRemoteCatalogClient, TraeSoloUpstreamClient, TraeUsageClient, UnconfiguredTraeUpstreamClient, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyTraeRawChatFailure, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, discoveredCatalog, fallbackModelsFor, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, mergeTraeModelSources, name, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeTraeRawChatCapability, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeCliIdentity, readTraeIdentity, refreshTraeCredential, regionEnabled, regionEnabledOf, regionOfCredential, regionOfEdition, regionOfHost, regionOfTraeProvider, regionOfTraeStatusUrl, regionOfUserRegion, regionStateOf, registerTraeUsageRoute, resolveTraeIdentity, resolveTraeRawRuntime, sanitizeCatalog, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStorageCandidates, traeWebUsage, withTraeRegion };
4276
+ export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, REGION_GATEWAYS, SseDecoder, TRAE_AI_PROVIDER, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROVIDER, TRAE_PROVIDERS, TRAE_PROVIDER_DISPLAY_NAMES, TRAE_RAW_CHAT_V1_PATH, TRAE_RAW_CHAT_V2_PATH, TRAE_REASONING_EFFORTS, TRAE_REGIONS, TRAE_REGION_PARAM, TRAE_SETTINGS_NS, TRAE_SOLO_CHAT_PATH, TRAE_SOLO_FUNCTION, TRAE_SOLO_MODELS_PATH, TRAE_SOLO_REMOTE_BASE, TRAE_STREAM_IDLE_TIMEOUT_MS, TRAE_USAGE_PATH, TRAE_VERSION_CODE_FALLBACK, TraeCatalog, TraeCredentialStore, TraeDelegatingUpstreamClient, TraeFallbackUpstreamClient, TraeGatedUpstreamClient, TraeRawCapabilityController, TraeRawCapabilityState, TraeRawChatUpstreamClient, TraeSoloBridge, TraeSoloRemoteCatalogClient, TraeSoloUpstreamClient, TraeUsageClient, UnconfiguredTraeUpstreamClient, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, asVolatile, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyTraeRawChatFailure, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, discoveredCatalog, fallbackModelsFor, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, mergeTraeModelSources, name, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeTraeRawChatCapability, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeCliIdentity, readTraeIdentity, refreshTraeCredential, regionEnabled, regionEnabledOf, regionOfCredential, regionOfEdition, regionOfHost, regionOfTraeProvider, regionOfTraeStatusUrl, regionOfUserRegion, regionStateOf, registerTraeUsageRoute, resolveTraeIdentity, resolveTraeRawRuntime, sanitizeCatalog, settingsNamespaceOf, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStorageCandidates, traeWebUsage, unwrapVolatile, unwrapVolatileDeep, withTraeRegion };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-connect-trae",
3
3
  "displayName": "DSH Connect Trae",
4
4
  "description": "Connect locally signed-in Trae models to DeepSeek Harness, with a usage/credits summary and daily check-in claiming.",
5
- "version": "2.1.0",
5
+ "version": "2.3.0",
6
6
  "author": "dingminhua",
7
7
  "copyright": "Copyright (c) 2026 LaoDing",
8
8
  "license": "MIT",
@@ -20,6 +20,7 @@
20
20
  },
21
21
  "main": "lib/index.js",
22
22
  "types": "lib/index.d.ts",
23
+ "icon": "icons/dsh-connect-trae-128.png",
23
24
  "exports": {
24
25
  ".": {
25
26
  "types": "./lib/index.d.ts",
@@ -31,6 +32,7 @@
31
32
  },
32
33
  "files": [
33
34
  "lib",
35
+ "icons",
34
36
  "docs/assets/dsh-connect-trae-usage-card.png",
35
37
  "screenshots.json",
36
38
  "cordis.patch.yml",
@@ -72,35 +74,35 @@
72
74
  "url": "https://github.com/dingminhua/dsh-connect-trae/issues"
73
75
  },
74
76
  "peerDependencies": {
75
- "@deepseek-ai/cordis": "^4.0.1-rc.1",
76
- "@deepseek-ai/dsh-atomic-write": ">=0.1.5-0 <0.2.0-0",
77
- "@deepseek-ai/dsh-attachment": ">=0.1.5-0 <0.2.0-0",
78
- "@deepseek-ai/dsh-client-ui-primitives": ">=0.1.5-0 <0.2.0-0",
79
- "@deepseek-ai/dsh-home-paths": ">=0.1.5-0 <0.2.0-0",
80
- "@deepseek-ai/dsh-host-webserver": ">=0.1.5-0 <0.2.0-0",
81
- "@deepseek-ai/dsh-llm": ">=0.1.5-0 <0.2.0-0",
82
- "@deepseek-ai/dsh-llm-pi-ai": ">=0.1.5-0 <0.2.0-0",
83
- "@deepseek-ai/dsh-settings": ">=0.1.5-0 <0.2.0-0",
84
- "@deepseek-ai/schemastery": "^3.18.1-rc.1",
77
+ "@deepseek-ai/cordis": ">=4.0.4 <5.0.0",
78
+ "@deepseek-ai/dsh-atomic-write": ">=0.1.7-rc.1 <0.2.0-0",
79
+ "@deepseek-ai/dsh-attachment": ">=0.1.7-rc.1 <0.2.0-0",
80
+ "@deepseek-ai/dsh-client-ui-primitives": ">=0.1.7-rc.1 <0.2.0-0",
81
+ "@deepseek-ai/dsh-home-paths": ">=0.1.7-rc.1 <0.2.0-0",
82
+ "@deepseek-ai/dsh-host-webserver": ">=0.1.7-rc.1 <0.2.0-0",
83
+ "@deepseek-ai/dsh-llm": ">=0.1.7-rc.1 <0.2.0-0",
84
+ "@deepseek-ai/dsh-llm-pi-ai": ">=0.1.7-rc.1 <0.2.0-0",
85
+ "@deepseek-ai/dsh-settings": ">=0.1.7-rc.1 <0.2.0-0",
86
+ "@deepseek-ai/schemastery": ">=3.18.4 <4.0.0",
85
87
  "@earendil-works/pi-ai": ">=0.85.1",
86
88
  "react": "^18.2.0"
87
89
  },
88
90
  "devDependencies": {
89
- "@deepseek-ai/cordis": "^4.0.2",
90
- "@deepseek-ai/dsh-atomic-write": "0.1.5-rc.2",
91
- "@deepseek-ai/dsh-attachment": "0.1.5-rc.2",
92
- "@deepseek-ai/dsh-client-locale": "0.1.5-rc.2",
93
- "@deepseek-ai/dsh-client-ui-primitives": "0.1.5-rc.2",
94
- "@deepseek-ai/dsh-client-ui-renderer": "0.1.5-rc.2",
95
- "@deepseek-ai/dsh-client-ui-settings": "0.1.5-rc.2",
96
- "@deepseek-ai/dsh-client-ui-settings-plugins": "0.1.5-rc.2",
97
- "@deepseek-ai/dsh-client-ui-slots": "0.1.5-rc.2",
98
- "@deepseek-ai/dsh-home-paths": "0.1.5-rc.2",
99
- "@deepseek-ai/dsh-host-webserver": "0.1.5-rc.2",
100
- "@deepseek-ai/dsh-llm": "0.1.5-rc.2",
101
- "@deepseek-ai/dsh-llm-pi-ai": "0.1.5-rc.2",
102
- "@deepseek-ai/dsh-settings": "0.1.5-rc.2",
103
- "@deepseek-ai/schemastery": "3.18.2",
91
+ "@deepseek-ai/cordis": "^4.0.4",
92
+ "@deepseek-ai/dsh-atomic-write": "0.1.7-rc.1",
93
+ "@deepseek-ai/dsh-attachment": "0.1.7-rc.1",
94
+ "@deepseek-ai/dsh-client-locale": "0.1.7-rc.1",
95
+ "@deepseek-ai/dsh-client-ui-primitives": "0.1.7-rc.1",
96
+ "@deepseek-ai/dsh-client-ui-renderer": "0.1.7-rc.1",
97
+ "@deepseek-ai/dsh-client-ui-settings": "0.1.7-rc.1",
98
+ "@deepseek-ai/dsh-client-ui-settings-plugins": "0.1.7-rc.1",
99
+ "@deepseek-ai/dsh-client-ui-slots": "0.1.7-rc.1",
100
+ "@deepseek-ai/dsh-home-paths": "0.1.7-rc.1",
101
+ "@deepseek-ai/dsh-host-webserver": "0.1.7-rc.1",
102
+ "@deepseek-ai/dsh-llm": "0.1.7-rc.1",
103
+ "@deepseek-ai/dsh-llm-pi-ai": "0.1.7-rc.1",
104
+ "@deepseek-ai/dsh-settings": "0.1.7-rc.1",
105
+ "@deepseek-ai/schemastery": "3.18.4",
104
106
  "@earendil-works/pi-ai": "^0.85.1",
105
107
  "@testing-library/dom": "^10.4.2",
106
108
  "@testing-library/react": "^16.3.3",