dsh-connect-trae 2.2.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,40 @@
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
+
3
38
  ## 2.2.0 (2026-09-24)
4
39
 
5
40
  ### Bug Fixes
package/README.en.md CHANGED
@@ -96,22 +96,15 @@ Restart the DSH process after install/update/uninstall.
96
96
 
97
97
  ## DSH version compatibility
98
98
 
99
- **One build serves both the DSH 0.1.5 and 0.1.7 lines** — no per-version package.
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
100
 
101
- The two lines differ in their settings machinery, and the plugin probes for capability at runtime instead of assuming either:
101
+ 0.1.7 rebuilt the settings machinery, and the plugin follows the 0.1.7 contract:
102
102
 
103
- | | 0.1.5 line | 0.1.7 line |
104
- |---|---|---|
105
- | Client settings service | `settingsScope` | `configForms` |
106
- | Config card slots | `settings.plugin.item` | `plugins.bundle.config` / `plugins.row.config` |
107
- | Host registration | `installSection()` | `configure({auto}, owner)` |
108
- | Schema writable marker | not needed | `volatile()` required |
109
- | Primitives icon names | `…Outline14` | `…OutlineRegular` |
110
-
111
- Two decisions matter most:
112
-
113
- - **`inject` declares only the services BOTH lines provide** (`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 (0.1.7 removed `settingsScope`, which is why 2.1.0 and earlier sat at `pending (waiting for service: settingsScope)` on that line).
114
- - **The collapse caret is pure CSS**, with no static icon import: the two lines' icon names do not overlap, so any static import fails to resolve on one of them.
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.
115
108
 
116
109
  ## Windows notes
117
110
 
package/README.md CHANGED
@@ -98,22 +98,15 @@ npm install dsh-connect-trae
98
98
 
99
99
  ## DSH 版本兼容
100
100
 
101
- **同一个构建同时服务 DSH 0.1.5 与 0.1.7 两条线**,无需按版本安装不同的包。
101
+ **要求 DSH 0.1.7-rc.1 及以上**(2.3.0 起不再支持 0.1.7 之前的宿主;peer 依赖范围已按此收窄)。
102
102
 
103
- 两条线的设置体系不同,插件在运行期按能力探测,而不是假定某一条:
103
+ 0.1.7 把设置体系整体重建,插件按 0.1.7 的契约工作:
104
104
 
105
- | | 0.1.5 线 | 0.1.7 线 |
106
- |---|---|---|
107
- | 客户端设置服务 | `settingsScope` | `configForms` |
108
- | 配置卡片槽位 | `settings.plugin.item` | `plugins.bundle.config` / `plugins.row.config` |
109
- | 宿主端注册 | `installSection()` | `configure({auto}, owner)` |
110
- | schema 可写声明 | 无需 | 必须标 `volatile()` |
111
- | primitives 图标名 | `…Outline14` | `…OutlineRegular` |
112
-
113
- 两处关键做法:
114
-
115
- - **`inject` 只声明两条线都有的服务**(`slots` / `locale`),设置面用 `ctx.get()` 软探测。Cordis 的依赖闸门是硬闸——`inject` 里任一服务在运行线上不存在,`apply()` 就永远不会执行(0.1.7 上 `settingsScope` 已被移除,这正是 2.1.0 及更早版本在 0.1.7 上卡在 `pending (waiting for service: settingsScope)` 的原因)。
116
- - **折叠箭头用纯 CSS**,不静态导入任何图标:两条线的图标名不重叠,静态导入必然在其中一条线上解析失败。
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 图标——图标命名族随版本变动,静态导入不是稳定契约。
117
110
 
118
111
  ## Windows 说明
119
112
 
Binary file
Binary file
package/lib/client.js CHANGED
@@ -165,10 +165,8 @@ window.__ModuleLoader__.load({
165
165
  .dsm-plugin-card-head{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}
166
166
  .dsm-plugin-card-title{color:var(--dsw-alias-label-primary,#e6e6e6);font-size:15px;font-weight:600;line-height:1.4}
167
167
  .dsm-plugin-card-description{color:var(--dsw-alias-label-tertiary,#999);font-size:13px;line-height:1.5}
168
- /* Pure-CSS caret: the host primitives' chevron icon names differ per DSH line
169
- (0.1.5 IconChevronDownOutline14 vs 0.1.7 IconChevronDownOutlineRegular),
170
- so no static import can serve both — importing the 0.1.5 name makes the
171
- client bundle fail to resolve on 0.1.7, taking the whole card down. A border
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
172
170
  caret in the plugin's own CSS is version-proof. */
173
171
  .dsm-plugin-card-chevron{color:var(--dsw-alias-label-tertiary,#999);flex:none;width:16px;height:16px;position:relative;transition:transform .16s}
174
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)}
@@ -295,11 +293,11 @@ window.__ModuleLoader__.load({
295
293
  /**
296
294
  * Write one settings field and confirm the value actually landed.
297
295
  *
298
- * `settingsScope.set()` / `configForms.set()` resolving is NOT proof that
299
- * anything was stored. On DSH 0.1.7 a write can be accepted by the transport
300
- * and still not materialize — a rejected write reloads Host state and merely
301
- * RETURNS, so the caller's `await` succeeds while the document is unchanged.
302
- * The 0.1.7 form also answers with an explicit boolean for exactly this reason.
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.
303
301
  *
304
302
  * Either way the caller must not report success on an unpersisted write: the
305
303
  * user would see the control flip and then silently revert.
@@ -1332,12 +1330,11 @@ window.__ModuleLoader__.load({
1332
1330
  /**
1333
1331
  * Client services required by the Plugin configuration contribution.
1334
1332
  *
1335
- * Deliberately only the two services that exist on BOTH host lines. Cordis'
1336
- * dependency gate is hard: any `inject` entry the running line does not provide
1337
- * keeps `apply` from ever running at all — which is exactly how this plugin
1338
- * ended up `pending (waiting for service: settingsScope)` on 0.1.7. Probing the
1339
- * settings surface through `ctx.get()` (which returns undefined, never throws,
1340
- * for an absent service) is what lets one build serve both lines.
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.
1341
1338
  */
1342
1339
  const inject = ["slots", "locale"];
1343
1340
  /**
@@ -1364,27 +1361,85 @@ window.__ModuleLoader__.load({
1364
1361
  }), "dsh-connect-trae: settings copy");
1365
1362
  const t = ctx.locale.bind(namespace);
1366
1363
  const softGet = (name) => ctx.get(name);
1367
- let settingsScope;
1368
1364
  const forms = softGet("configForms");
1369
- const legacy = softGet("settingsScope");
1370
- if (forms !== void 0) {
1371
- let ns = "trae";
1372
- try {
1373
- const served = (forms.describe().getSnapshot().view?.namespaces ?? []).find((entry) => entry.ns === "trae" || /trae/i.test(entry.ns));
1374
- if (served !== void 0) ns = served.ns;
1375
- } catch {}
1376
- settingsScope = forms.get(ns);
1377
- } else if (legacy !== void 0) settingsScope = legacy.bind({ namespace: "trae" });
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
+ })();
1378
1433
  const registerCard = (slotName, key) => {
1379
1434
  try {
1380
1435
  ctx.slots.inject(slotName, () => ctx.slots.register({
1381
1436
  name: slotName,
1382
1437
  key,
1383
1438
  priority: 30,
1384
- inject: () => settingsScope === void 0 ? { t } : {
1439
+ inject: () => ({
1385
1440
  t,
1386
1441
  settingsScope
1387
- }
1442
+ })
1388
1443
  }, TraeUsageCard));
1389
1444
  } catch (error) {
1390
1445
  console.error(`[dsh-connect-trae] card slot "${slotName}" failed to register (host provider unaffected):`, error);
@@ -1392,7 +1447,6 @@ window.__ModuleLoader__.load({
1392
1447
  };
1393
1448
  registerCard("plugins.bundle.config", "dsh-connect-trae");
1394
1449
  registerCard("plugins.row.config", "dsh-connect-trae#dsh-connect-trae");
1395
- registerCard("settings.plugin.item", "trae");
1396
1450
  } catch (error) {
1397
1451
  console.error("[dsh-connect-trae] client card failed to load (host provider unaffected):", error);
1398
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
  }
@@ -1598,13 +1614,30 @@ declare const name = "dsh-connect-trae";
1598
1614
  declare const inject: string[];
1599
1615
  /**
1600
1616
  * Settings namespace, as a plain lowercase literal. DSH 0.1.2 removed the
1601
- * `settingsNamespace` brand constructor from the npm package (the Desktop
1602
- * host still ships it as a legacy shim), and every consumer of this constant —
1603
- * `registerModelDiscovery`, `registerConfigurableProviders`,
1604
- * `settings.installSection`, the settings test — takes it as a kebab string,
1605
- * 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`).
1606
1623
  */
1607
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;
1608
1641
  /** One region's saved model state: its own directory and the user's selection within it. */
1609
1642
  interface TraeRegionState {
1610
1643
  /**
@@ -1681,26 +1714,21 @@ declare function regionStateOf(config: Config, region: TraeRegion): TraeRegionSt
1681
1714
  */
1682
1715
  declare function regionEnabled(config: Config, region: TraeRegion): boolean;
1683
1716
  /**
1684
- * Mark a schema's field as volatile on the DSH lines that support it.
1717
+ * Mark a schema's field as volatile.
1685
1718
  *
1686
- * `volatile()` exists from schemastery 3.18.3 (the DSH 0.1.7 line, which is the
1687
- * only line whose settings write gate reads the marker). Older pinning (3.18.2,
1688
- * the 0.1.5 line) has no such method, and the schema must stay byte-identical
1689
- * to the unmarked original there: hand-writing `meta.volatile = true` would
1690
- * bypass schemastery's own `validateVolatileSchema` checks and produce a schema
1691
- * no 0.1.5 consumer understands — so on that line this degrades to an identity
1692
- * no-op.
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.
1693
1725
  *
1694
1726
  * Without the marker, 0.1.7's settings write gate REJECTS every write with
1695
1727
  * `Plugin entry "trae" has no volatile fields` while the scope's `set()` still
1696
1728
  * resolves — so the card would look like it saved and silently revert.
1697
- *
1698
- * Exported so BOTH arms are testable on any machine: the capability is decided
1699
- * by whichever schemastery the dependency tree resolves, so without a seam the
1700
- * no-op arm would silently stop being exercised the moment the pin moved up.
1701
1729
  */
1702
1730
  declare function asVolatile<S>(schema: S): S;
1703
1731
  declare const Config: z<Config>;
1704
1732
  declare function apply(ctx: Context, config: Config): void;
1705
1733
  //#endregion
1706
- 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, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStorageCandidates, traeWebUsage, unwrapVolatile, unwrapVolatileDeep, 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
  }
@@ -3761,14 +3803,34 @@ const name = "dsh-connect-trae";
3761
3803
  const inject = ["llm"];
3762
3804
  /**
3763
3805
  * Settings namespace, as a plain lowercase literal. DSH 0.1.2 removed the
3764
- * `settingsNamespace` brand constructor from the npm package (the Desktop
3765
- * host still ships it as a legacy shim), and every consumer of this constant —
3766
- * `registerModelDiscovery`, `registerConfigurableProviders`,
3767
- * `settings.installSection`, the settings test — takes it as a kebab string,
3768
- * 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`).
3769
3812
  */
3770
3813
  const TRAE_SETTINGS_NS = "trae";
3771
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
+ /**
3772
3834
  * One region's saved state. A config written before the region split has only
3773
3835
  * the flat fields: those were always captured from the CN endpoint (the
3774
3836
  * plugin had no international support), so they are read as the CN state and
@@ -3800,23 +3862,18 @@ function regionEnabled(config, region) {
3800
3862
  return regionStateOf(config, region).enabled !== false;
3801
3863
  }
3802
3864
  /**
3803
- * Mark a schema's field as volatile on the DSH lines that support it.
3865
+ * Mark a schema's field as volatile.
3804
3866
  *
3805
- * `volatile()` exists from schemastery 3.18.3 (the DSH 0.1.7 line, which is the
3806
- * only line whose settings write gate reads the marker). Older pinning (3.18.2,
3807
- * the 0.1.5 line) has no such method, and the schema must stay byte-identical
3808
- * to the unmarked original there: hand-writing `meta.volatile = true` would
3809
- * bypass schemastery's own `validateVolatileSchema` checks and produce a schema
3810
- * no 0.1.5 consumer understands — so on that line this degrades to an identity
3811
- * no-op.
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.
3812
3873
  *
3813
3874
  * Without the marker, 0.1.7's settings write gate REJECTS every write with
3814
3875
  * `Plugin entry "trae" has no volatile fields` while the scope's `set()` still
3815
3876
  * resolves — so the card would look like it saved and silently revert.
3816
- *
3817
- * Exported so BOTH arms are testable on any machine: the capability is decided
3818
- * by whichever schemastery the dependency tree resolves, so without a seam the
3819
- * no-op arm would silently stop being exercised the moment the pin moved up.
3820
3877
  */
3821
3878
  function asVolatile(schema) {
3822
3879
  if (typeof schema.volatile === "function") return schema.volatile();
@@ -3863,6 +3920,7 @@ const Config = z.object({
3863
3920
  /** Every region, in card tab order. */
3864
3921
  const REGION_KEYS = ["cn", "ai"];
3865
3922
  function apply(ctx, config) {
3923
+ const settingsNs = settingsNamespaceOf(ctx);
3866
3924
  let current = () => config;
3867
3925
  const enabledSet = (value, region) => new Set(regionStateOf(value, region).enabledModelIds ?? []);
3868
3926
  const imageSet = (value, region) => new Set(regionStateOf(value, region).imageModelIds ?? []);
@@ -4100,7 +4158,7 @@ function apply(ctx, config) {
4100
4158
  registration.cn.directory?.replace(REGION_KEYS.filter((region) => regionEnabled(value, region)).map((region) => ({
4101
4159
  provider: TRAE_PROVIDERS[region],
4102
4160
  displayName: TRAE_PROVIDER_DISPLAY_NAMES[region],
4103
- settingsNs: TRAE_SETTINGS_NS,
4161
+ settingsNs,
4104
4162
  settingsPath: [],
4105
4163
  declared: false
4106
4164
  })));
@@ -4115,14 +4173,6 @@ function apply(ctx, config) {
4115
4173
  }
4116
4174
  syncRegionRegistration(value);
4117
4175
  };
4118
- const sectionHooks = {
4119
- setSource(source) {
4120
- current = source;
4121
- },
4122
- onChange() {
4123
- applySelection(current());
4124
- }
4125
- };
4126
4176
  applySelection(config);
4127
4177
  (async () => {
4128
4178
  const id = current().accountId;
@@ -4135,18 +4185,9 @@ function apply(ctx, config) {
4135
4185
  }
4136
4186
  } catch {}
4137
4187
  })();
4138
- const legacyInstallSettingsSection = dshSettings.installSettingsSection;
4139
- if (typeof legacyInstallSettingsSection === "function") legacyInstallSettingsSection(ctx, TRAE_SETTINGS_NS, Config, config, sectionHooks);
4140
- else ctx.inject(["settings"], (settingsCtx) => {
4188
+ ctx.inject(["settings"], (settingsCtx) => {
4141
4189
  const settings = settingsCtx.settings;
4142
- if (typeof settings.configure === "function") {
4143
- settings.configure({ auto: true }, ctx.fiber);
4144
- return;
4145
- }
4146
- if (typeof settings.installSection === "function") {
4147
- const entry = unwrapVolatileDeep(config);
4148
- settings.installSection(ctx, TRAE_SETTINGS_NS, Config, entry, sectionHooks);
4149
- }
4190
+ ctx.effect(() => settings.configure({ auto: true }, ctx.fiber));
4150
4191
  });
4151
4192
  ctx.on("loader/volatile-update", () => {
4152
4193
  applySelection(current());
@@ -4173,7 +4214,7 @@ function apply(ctx, config) {
4173
4214
  };
4174
4215
  registration[region].adapter = region === "cn" ? ctx.llm.registerAdapter([TRAE_PROVIDER], trae.adapter) : ctx.llm.registerAdapter([TRAE_AI_PROVIDER], trae.adapter);
4175
4216
  }
4176
- ctx.llm.registerModelDiscovery(TRAE_SETTINGS_NS, async (request, signal) => {
4217
+ ctx.llm.registerModelDiscovery(settingsNs, async (request, signal) => {
4177
4218
  const region = regionOfTraeProvider(request.provider ?? "");
4178
4219
  if (region === void 0) return [];
4179
4220
  if (!regionEnabled(current(), region)) return [];
@@ -4189,7 +4230,7 @@ function apply(ctx, config) {
4189
4230
  registration.cn.directory = ctx.llm.registerConfigurableProviders(REGION_KEYS.map((region) => ({
4190
4231
  provider: TRAE_PROVIDERS[region],
4191
4232
  displayName: TRAE_PROVIDER_DISPLAY_NAMES[region],
4192
- settingsNs: TRAE_SETTINGS_NS,
4233
+ settingsNs,
4193
4234
  settingsPath: [],
4194
4235
  declared: false
4195
4236
  })));
@@ -4232,4 +4273,4 @@ function apply(ctx, config) {
4232
4273
  });
4233
4274
  }
4234
4275
  //#endregion
4235
- 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, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStorageCandidates, traeWebUsage, unwrapVolatile, unwrapVolatileDeep, 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.2.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,34 +74,34 @@
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",
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",
103
105
  "@deepseek-ai/schemastery": "3.18.4",
104
106
  "@earendil-works/pi-ai": "^0.85.1",
105
107
  "@testing-library/dom": "^10.4.2",