dsh-connect-trae 4.15.0 → 4.16.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 +43 -0
- package/lib/client.js +162 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.16.0 (2026-11-08)
|
|
4
|
+
|
|
5
|
+
### 模型标签槽:按宿主的声明注册,为 `keyed` 做好准备
|
|
6
|
+
|
|
7
|
+
`conversation.input.model.label` 的 kind 写在宿主包里(`ui-model-selection`)。
|
|
8
|
+
在它改口之前是 `single`:只有一格,三家连接器只能按 `-101` / `-102` / `-105`
|
|
9
|
+
互相压价。目标形态是 `keyed`——每个 `options.key` 自成一格,宿主按 `provider.id`
|
|
10
|
+
派发 `{ entryKey }`,没人贡献的 provider 落回宿主自己的 fallback。
|
|
11
|
+
|
|
12
|
+
**本版做的是一件插件侧唯一该做的事:注册几条跟着宿主声明的 kind 走。**
|
|
13
|
+
|
|
14
|
+
- 新增 `src/client/model-label-seat.ts`:运行时读 `ctx.slots.spec(key)?.kind`,
|
|
15
|
+
`keyed` 就为 `trae` / `trae-enterprise` / `trae-global` 各注册一格,其余情况
|
|
16
|
+
(含读不到)保持历史上的单条。**一个构建同时服务两条宿主线**,宿主改口那天
|
|
17
|
+
不需要重新发版。
|
|
18
|
+
- `registerSlotEntry` 现在接受「多个 disposer」,并把它们合成一个按**反序**释放的
|
|
19
|
+
组合 disposer:注册是一条命,拆开释放会留下「某几格还占着、兄弟格已消失」的半死状态。
|
|
20
|
+
|
|
21
|
+
**为什么不能只改宿主。** 两个方向都会**静默**失败,而本仓库的注册路径把一个
|
|
22
|
+
try/catch 包在回调里(issue #74 的修复),于是它们都变成一行 `console.error`:
|
|
23
|
+
|
|
24
|
+
| 情况 | 结果 |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| keyed 的两条打到 `single` 宿主 | 第二条抛 `already has a registration`,第一条已落地 → 半注册 |
|
|
27
|
+
| 无 key 的一条打到 `keyed` 宿主 | 抛 `keyed slot … requires options.key` → 标签消失 |
|
|
28
|
+
|
|
29
|
+
`keyed` **强制要求 `key`**,这是上游提案里没写的第一道陷阱:**此前已发布**的本族每个
|
|
30
|
+
版本都注册一条无 key 的条目,只改宿主声明会让它们一起失效(本版之后的新构建会跟着
|
|
31
|
+
声明走,所以受影响的是「已经装在用户机器上」的那些版本)。第二道陷阱见下一节。
|
|
32
|
+
|
|
33
|
+
**改声明不够:宿主还必须传 `entryKey`。** 复核实机产物
|
|
34
|
+
(`ui-model-selection@0.2.2-rc.1`)发现,渲染该槽只有一个调用点,第三个参数只有
|
|
35
|
+
`fallback`、**没有 `entryKey`**;而 keyed 分支是
|
|
36
|
+
`find(e => e.options.key === opts?.entryKey)`——`undefined` 匹配不到任何条目,于是
|
|
37
|
+
**每一行**都落到 fallback。本插件的注册会成功、回调也跑、无异常无日志,只是组件再也
|
|
38
|
+
不被调用。上游的最小改动是**两处**:声明 + 调用点补 `{ entryKey: provider.id }`。
|
|
39
|
+
|
|
40
|
+
`MODEL_LABEL_PRIORITY = -102` 不改。keyed 下它不再决定可见性,只剩「同一格内谁先」;
|
|
41
|
+
三家之间不再有压制关系——停战靠的是换槽的基数,不是再压一次价。
|
|
42
|
+
|
|
43
|
+
外来行分支保留:keyed 下运行时走不到,但一个构建要同时服务两种宿主,而 `single`
|
|
44
|
+
宿主上它就是整行文字。它同时也是最便宜的哨兵——自己的行若走进那一支,说明 `key` 写错了。
|
|
45
|
+
|
|
3
46
|
## 4.15.0 (2026-11-08)
|
|
4
47
|
|
|
5
48
|
### 新增:模型选择器里的胶囊,改成「谁都能显示」
|
package/lib/client.js
CHANGED
|
@@ -9554,6 +9554,17 @@ ${u} ${p}-pool .sw input:checked+.sw-track::after{transform:translateX(16px)}
|
|
|
9554
9554
|
*
|
|
9555
9555
|
* @module dsh-connect-trae/client/TraeModelLabel
|
|
9556
9556
|
*/
|
|
9557
|
+
/**
|
|
9558
|
+
* Every provider id this plugin owns, in panel order.
|
|
9559
|
+
*
|
|
9560
|
+
* Exported because on a `keyed` host the label seat is one registration PER
|
|
9561
|
+
* provider id — the registration side needs the same list the component uses to
|
|
9562
|
+
* decide "is this row mine", and a second literal would drift from
|
|
9563
|
+
* `providerOfPanel`. Derived rather than typed out: `trae-enterprise` is the id
|
|
9564
|
+
* most easily forgotten, and forgetting it means that panel's rows silently fall
|
|
9565
|
+
* through to the host's plain fallback.
|
|
9566
|
+
*/
|
|
9567
|
+
const TRAE_MODEL_LABEL_PROVIDERS = TRAE_PANELS.map(providerOfPanel);
|
|
9557
9568
|
/** How long a fetched snapshot is trusted, in milliseconds. */
|
|
9558
9569
|
const CACHE_MS = 6e4;
|
|
9559
9570
|
/** How often a mounted label re-reads the clock, in milliseconds. */
|
|
@@ -9825,6 +9836,17 @@ ${u} ${p}-pool .sw input:checked+.sw-track::after{transform:translateX(16px)}
|
|
|
9825
9836
|
* host's own presentation — catalog name plus that model's optional badge — for
|
|
9826
9837
|
* any provider that is not ours.
|
|
9827
9838
|
*
|
|
9839
|
+
* WHAT CHANGES WHEN THE HOST KEYS THE SLOT — and why the number stays. On a host
|
|
9840
|
+
* that declares this slot `keyed`, the seat is registered once per provider id
|
|
9841
|
+
* (`./model-label-seat.ts`), each `key` is its own cell, and priority stops
|
|
9842
|
+
* deciding WHO IS VISIBLE: qoder's `-101` and workbuddy's `-105` no longer share
|
|
9843
|
+
* a cell with this `-102`, so none of the three can shadow another and the
|
|
9844
|
+
* armistice below stops being a race. Priority keeps exactly one meaning inside
|
|
9845
|
+
* a cell — the tie-breaker between two registrations claiming the SAME provider
|
|
9846
|
+
* id, where an equal value throws and the lowest renders. The value is therefore
|
|
9847
|
+
* kept as this plugin's rank for its own rows, not as a bid in an auction that
|
|
9848
|
+
* no longer happens.
|
|
9849
|
+
*
|
|
9828
9850
|
* WHY A DISTINCT PRIORITY IS NOT ENOUGH ON ITS OWN. It removes the cross-plugin
|
|
9829
9851
|
* collision, but issue #74's error was a SECOND registration of THIS plugin
|
|
9830
9852
|
* against its own first one (the report's stack shows
|
|
@@ -9833,7 +9855,9 @@ ${u} ${p}-pool .sw input:checked+.sw-track::after{transform:translateX(16px)}
|
|
|
9833
9855
|
* slot's declaration lifetime collapses and re-declares, so a callback that
|
|
9834
9856
|
* registers unconditionally double-registers. `index.tsx` therefore also
|
|
9835
9857
|
* disposes its previous entry before every attempt. Priority alone would have
|
|
9836
|
-
* made the throw rarer and harder to read, not absent.
|
|
9858
|
+
* made the throw rarer and harder to read, not absent. (On a `keyed` host the
|
|
9859
|
+
* same rule applies per key: re-registering one provider's seat twice at one
|
|
9860
|
+
* priority throws just as loudly.)
|
|
9837
9861
|
*/
|
|
9838
9862
|
const MODEL_LABEL_PRIORITY = -102;
|
|
9839
9863
|
/** The four tones the host understands, as a lookup for narrowing. */
|
|
@@ -10030,6 +10054,114 @@ ${u} ${p}-pool .sw input:checked+.sw-track::after{transform:translateX(16px)}
|
|
|
10030
10054
|
});
|
|
10031
10055
|
}
|
|
10032
10056
|
//#endregion
|
|
10057
|
+
//#region src/client/model-label-seat.ts
|
|
10058
|
+
/**
|
|
10059
|
+
* Read the declared kind of a slot, or `undefined` when it cannot be read.
|
|
10060
|
+
*
|
|
10061
|
+
* `spec()` is the documented read-only accessor (`SlotRegistry.spec`), present
|
|
10062
|
+
* on 0.1.7 and on 0.2.2. It is also the only way to learn the kind: the
|
|
10063
|
+
* declaration lives in the host package, and a plugin's own `SlotMap`
|
|
10064
|
+
* augmentation says what this plugin HOPES the slot is, not what the running
|
|
10065
|
+
* host declared.
|
|
10066
|
+
*
|
|
10067
|
+
* Anything unexpected — no `spec` method on an older line, a throw from a
|
|
10068
|
+
* service that is not ready, a kind this build does not know — returns
|
|
10069
|
+
* `undefined`, and the caller falls back to the single-cell shape. That is the
|
|
10070
|
+
* conservative direction: it is what every released version of this plugin
|
|
10071
|
+
* already does.
|
|
10072
|
+
*
|
|
10073
|
+
* @param slots - The `slots` service (typed loosely: the accessor is optional).
|
|
10074
|
+
* @param key - The slot key to inspect.
|
|
10075
|
+
* @returns The declared kind, or `undefined`.
|
|
10076
|
+
*/
|
|
10077
|
+
function declaredSlotKind(slots, key) {
|
|
10078
|
+
const face = slots;
|
|
10079
|
+
if (face === void 0 || typeof face.spec !== "function") return void 0;
|
|
10080
|
+
let spec;
|
|
10081
|
+
try {
|
|
10082
|
+
spec = face.spec(key);
|
|
10083
|
+
} catch {
|
|
10084
|
+
return;
|
|
10085
|
+
}
|
|
10086
|
+
const kind = spec?.kind;
|
|
10087
|
+
if (kind === "single" || kind === "list" || kind === "keyed" || kind === "chain") return kind;
|
|
10088
|
+
}
|
|
10089
|
+
/**
|
|
10090
|
+
* The registrations this plugin should make for the model-label seat.
|
|
10091
|
+
*
|
|
10092
|
+
* `keyed` yields ONE seat per provider id: each is its own cell, each renders
|
|
10093
|
+
* only the rows whose owner props carry that provider id, and a provider this
|
|
10094
|
+
* plugin does not own falls through to the owner's own fallback (the catalog
|
|
10095
|
+
* name, plus that model's `badge` on hosts that have one).
|
|
10096
|
+
*
|
|
10097
|
+
* Every other kind — including `undefined`, i.e. a host that cannot be
|
|
10098
|
+
* inspected — yields the single historical seat. That is not a degraded mode:
|
|
10099
|
+
* it is what `single` requires, and on a not-yet-declared slot it is what the
|
|
10100
|
+
* inject callback is about to find.
|
|
10101
|
+
*
|
|
10102
|
+
* An empty provider list under `keyed` would silently disable the label, so it
|
|
10103
|
+
* falls back to the single seat instead: a wrong-shaped registration that still
|
|
10104
|
+
* draws something beats a correct-shaped registration that draws nothing.
|
|
10105
|
+
*
|
|
10106
|
+
* @param params - Slot key, declared kind, this plugin's provider ids, priority.
|
|
10107
|
+
* @returns One or more `register()` option objects, in registration order.
|
|
10108
|
+
*/
|
|
10109
|
+
function modelLabelSeats(params) {
|
|
10110
|
+
const { name, kind, providerIds, priority } = params;
|
|
10111
|
+
if (kind === "list" || kind === "chain") return [];
|
|
10112
|
+
if (kind === "keyed" && providerIds.length > 0) return providerIds.map((id) => ({
|
|
10113
|
+
name,
|
|
10114
|
+
key: id,
|
|
10115
|
+
priority
|
|
10116
|
+
}));
|
|
10117
|
+
return [{
|
|
10118
|
+
name,
|
|
10119
|
+
priority
|
|
10120
|
+
}];
|
|
10121
|
+
}
|
|
10122
|
+
/**
|
|
10123
|
+
* Claim every planned seat, atomically, and hand back ONE disposer.
|
|
10124
|
+
*
|
|
10125
|
+
* WHY ATOMIC. The seat list is 2-3 entries on a `keyed` host, and `register()`
|
|
10126
|
+
* can throw on any ONE of them — a rival plugin claiming the same provider id at
|
|
10127
|
+
* the same priority, or the declaration collapsing mid-loop. A plain `.map()`
|
|
10128
|
+
* would then leave the earlier seats landed on the ledger with their disposers
|
|
10129
|
+
* unreachable (they were only ever held by the array that the throw discarded),
|
|
10130
|
+
* which is worse than failing outright: the component stays mounted, the caller
|
|
10131
|
+
* believes registration failed, and nothing will ever release those cells. On a
|
|
10132
|
+
* `single` host it also means the identity keeps a live entry that the next
|
|
10133
|
+
* `inject` re-run cannot displace without a priority clash.
|
|
10134
|
+
*
|
|
10135
|
+
* So a partial failure is rolled back in reverse order before the error is
|
|
10136
|
+
* rethrown, exactly as if it had never been attempted. The rethrow is important:
|
|
10137
|
+
* each caller already has a catch that logs, and swallowing here would hide the
|
|
10138
|
+
* refusal from the diagnostics that exist to explain it.
|
|
10139
|
+
*
|
|
10140
|
+
* A disposal that itself throws must not mask the original error, hence the
|
|
10141
|
+
* inner catch — the registry treats a second dispose as a no-op, and a broken
|
|
10142
|
+
* one must not turn one failure into a crash.
|
|
10143
|
+
*
|
|
10144
|
+
* @param seats - The plan from {@link modelLabelSeats}, in registration order.
|
|
10145
|
+
* @param register - Claims one seat and returns its disposer.
|
|
10146
|
+
* @returns One disposer that releases every seat, in reverse registration order.
|
|
10147
|
+
*/
|
|
10148
|
+
function registerModelLabelSeats(seats, register) {
|
|
10149
|
+
const claimed = [];
|
|
10150
|
+
try {
|
|
10151
|
+
for (const seat of seats) claimed.push(register(seat));
|
|
10152
|
+
} catch (error) {
|
|
10153
|
+
for (const dispose of [...claimed].reverse()) try {
|
|
10154
|
+
dispose();
|
|
10155
|
+
} catch {}
|
|
10156
|
+
throw error;
|
|
10157
|
+
}
|
|
10158
|
+
return () => {
|
|
10159
|
+
for (const dispose of [...claimed].reverse()) try {
|
|
10160
|
+
dispose();
|
|
10161
|
+
} catch {}
|
|
10162
|
+
};
|
|
10163
|
+
}
|
|
10164
|
+
//#endregion
|
|
10033
10165
|
//#region src/client/index.tsx
|
|
10034
10166
|
/** Stable browser-plugin name. */
|
|
10035
10167
|
const name = "dsh-connect-trae-client";
|
|
@@ -10081,10 +10213,19 @@ ${u} ${p}-pool .sw input:checked+.sw-track::after{transform:translateX(16px)}
|
|
|
10081
10213
|
* the same priority. That refusal is OUR fault and we can fix it rather than
|
|
10082
10214
|
* report it, so each run releases whatever this identity already holds.
|
|
10083
10215
|
*
|
|
10216
|
+
* ONE DISPOSER PER CALL, HOWEVER MANY ENTRIES IT OWNS. A `keyed` host makes this
|
|
10217
|
+
* plugin claim one cell per provider id (see `./model-label-seat.ts`), and those
|
|
10218
|
+
* cells must live and die together — a partial teardown would leave a cell
|
|
10219
|
+
* occupied by a component whose siblings are gone, visible as some rows
|
|
10220
|
+
* decorated and others not. Folding them into one disposer is
|
|
10221
|
+
* `registerModelLabelSeats`'s job, so the contract here stays the simple one:
|
|
10222
|
+
* whatever `register` returns is the whole ownership of this identity, and
|
|
10223
|
+
* `SlotHold` keeps exactly one.
|
|
10224
|
+
*
|
|
10084
10225
|
* @param ctx - The applying plugin's context (module scope has none).
|
|
10085
10226
|
* @param identity - Stable key for this seat.
|
|
10086
10227
|
* @param slotKey - The host slot to register into.
|
|
10087
|
-
* @param register - Performs the registration, returning
|
|
10228
|
+
* @param register - Performs the registration(s), returning ONE disposer for all.
|
|
10088
10229
|
* @param describe - Names the seat in the failure log.
|
|
10089
10230
|
*/
|
|
10090
10231
|
function registerSlotEntry(ctx, identity, slotKey, register, describe) {
|
|
@@ -10349,16 +10490,31 @@ ${u} ${p}-pool .sw input:checked+.sw-track::after{transform:translateX(16px)}
|
|
|
10349
10490
|
* non-interactive inline content such as a coloured name or badge" while the
|
|
10350
10491
|
* picker keeps owning clicks, focus and check marks.
|
|
10351
10492
|
*
|
|
10493
|
+
* HOW MANY SEATS, AND WHY THAT IS READ AT MOUNT. On a `single` host this slot
|
|
10494
|
+
* holds ONE cell: a second registration at the same priority throws, a second
|
|
10495
|
+
* registration at any other priority is silently discarded, and whoever holds
|
|
10496
|
+
* the cell must draw EVERY provider's rows. On a `keyed` host each
|
|
10497
|
+
* `options.key` is its own cell, so one seat per provider id gives this
|
|
10498
|
+
* plugin exactly its own rows and leaves every other provider to the owner's
|
|
10499
|
+
* own fallback. Which shape is legal is the HOST's declaration, so the kind
|
|
10500
|
+
* is read from `ctx.slots.spec(...)` at mount and the seat list is derived
|
|
10501
|
+
* from it (`./model-label-seat.ts`). One build serves both lines.
|
|
10502
|
+
*
|
|
10352
10503
|
* Registered unconditionally: the component renders the plain name for a
|
|
10353
10504
|
* provider that is not ours, so there is nothing to gate on. Its own
|
|
10354
10505
|
* try/catch keeps a host that does not serve this slot from taking the card
|
|
10355
10506
|
* down with it.
|
|
10356
10507
|
*/
|
|
10357
10508
|
const registerModelLabel = () => {
|
|
10358
|
-
registerSlotEntry(ctx, "conversation.input.model.label", "conversation.input.model.label", () =>
|
|
10359
|
-
|
|
10360
|
-
|
|
10361
|
-
|
|
10509
|
+
registerSlotEntry(ctx, "conversation.input.model.label", "conversation.input.model.label", () => {
|
|
10510
|
+
const registry = ctx.slots;
|
|
10511
|
+
return registerModelLabelSeats(modelLabelSeats({
|
|
10512
|
+
name: "conversation.input.model.label",
|
|
10513
|
+
kind: declaredSlotKind(ctx.slots, "conversation.input.model.label"),
|
|
10514
|
+
providerIds: TRAE_MODEL_LABEL_PROVIDERS,
|
|
10515
|
+
priority: MODEL_LABEL_PRIORITY
|
|
10516
|
+
}), (seat) => registry.register(seat, TraeModelLabel));
|
|
10517
|
+
}, "model label slot");
|
|
10362
10518
|
};
|
|
10363
10519
|
registerModelLabel();
|
|
10364
10520
|
/**
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-connect-trae",
|
|
3
3
|
"displayName": "DSH Connect Trae",
|
|
4
4
|
"description": "把本机登录的 Trae 模型接入 DeepSeek Harness:国内版与国际版双供应商并行,提供用量/积分概览与每日签到领取。",
|
|
5
|
-
"version": "4.
|
|
5
|
+
"version": "4.16.0",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|