dsh-connect-trae 4.8.1 → 4.9.1
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 +102 -0
- package/README.en.md +4 -4
- package/README.md +5 -5
- package/lib/client.js +413 -120
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,107 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.9.1 (2026-11-06)
|
|
4
|
+
|
|
5
|
+
### 修复
|
|
6
|
+
|
|
7
|
+
**4.9.0 的两条样式规则漏了作用域,在卡片与整页上造成回归。** 补丁号发布。
|
|
8
|
+
|
|
9
|
+
4.9.0 给设置面板加了两个调整,写法是**无作用域**的全局选择器:
|
|
10
|
+
|
|
11
|
+
- `.dsm-trae-tab-hint` —— 把供应商行的说明视觉隐藏,省下 36px;
|
|
12
|
+
- `.dsm-trae-panels` —— 把分栏条改成 `position:sticky` 并加一层 layer-2 底色。
|
|
13
|
+
|
|
14
|
+
这两个元素是**三个界面共用**的:输入框卡片、侧边栏整页、设置面板画的是同一棵树。
|
|
15
|
+
写的时候只想着设置面板那一列(564px、外壳自带滚动),忘了另外两个界面也会吃到。
|
|
16
|
+
|
|
17
|
+
实测到的两个回归:
|
|
18
|
+
|
|
19
|
+
| 界面 | 回归 | 原因 |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| 卡片 / 整页 | 「企业版与国内版共用同一份 Trae CN 登录」那句话消失 | 卡片本身有 726px 宽、没有纵向空间压力,这句说明不该被裁掉 |
|
|
22
|
+
| 卡片 | 分栏条下方压出一条浅色带 | `position:sticky` 在没有滚动祖先时不生效,但底色留下了;底色取 layer-2,卡片本身是 layer-3 |
|
|
23
|
+
|
|
24
|
+
两条规则现在都限定在 `.dsm-trae-section` 之下——那是"只在设置面板里生效"的结构标记,
|
|
25
|
+
也是这些改动的唯一理由所在(外壳的滚动列与 564px 的宽度)。卡片与整页恢复原样。
|
|
26
|
+
|
|
27
|
+
实测三界面(修复后):
|
|
28
|
+
|
|
29
|
+
| 界面 | 说明行 | 分栏条 |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| 卡片 | 726×18px,可见 | `static`,无底色 |
|
|
32
|
+
| 整页 | 710×18px,可见 | `static`,无底色 |
|
|
33
|
+
| 设置面板 | 1×1px,视觉隐藏(读屏保留) | `sticky`,layer-2 底色 |
|
|
34
|
+
|
|
35
|
+
新增两条用例**双向**锁定:设置页下必须命中,且无作用域的基础规则必须**不带**
|
|
36
|
+
`position:sticky` / `background` / `clip`——只测前者的话,下次再漏作用域仍然会绿。
|
|
37
|
+
|
|
38
|
+
## 4.9.0 (2026-11-06)
|
|
39
|
+
|
|
40
|
+
### 变更
|
|
41
|
+
|
|
42
|
+
- **配置入口从「插件详情页的卡片」搬到「设置」侧边栏的独立页面**。原来这张卡片注册在
|
|
43
|
+
插件市场的 `plugins.bundle.config` / `plugins.row.config` 槽位,也就是插件详情页里、
|
|
44
|
+
README 下方——那一页的职责是介绍和安装一个包,把配置也塞进去,用户得先划过一整篇说明
|
|
45
|
+
才能找到一个勾选框。现在改注册 `settings.section`(DSH 0.2.1-rc.5 起的槽位),入口在
|
|
46
|
+
设置面板左侧导航里,与宿主自己的「账号 / 通用 / 模型 / 插件 / 智能体预设」并列。
|
|
47
|
+
- 页面 id 用包名 `dsh-connect-trae`,`order: 30`(排在宿主的 `account -10`、`general 0`、
|
|
48
|
+
`models 10`、`plugins 15`、`agent-presets 20` 之后,不与任何内置项冲突)。
|
|
49
|
+
- 同时注册 `settings.section.icon`,**id 与页面完全相同**:宿主按 `{ only: row.id }` 过滤
|
|
50
|
+
图标,id 不匹配时图标不显示、默认齿轮静默留下——这类失配在界面上没有任何报错。
|
|
51
|
+
- 页面与图标在**同一个 `inject` 回调**里注册、disposer 成对返回:图标只在页面的 id 存在时
|
|
52
|
+
才有意义,把配对做成结构而不是约定。图标自带独立 `inject` 与 catch——宿主没声明该槽位时
|
|
53
|
+
丢的是图标,不是配置页。
|
|
54
|
+
- **不做能力探测**。`slots.inject()` 本身就是探测:槽位已声明就立即执行回调,未声明就等待,
|
|
55
|
+
宿主永远不声明则什么都不做。外面再套一层 `typeof` 判断只会新增一条「注册永远不发生且无声」
|
|
56
|
+
的失败路径(同族 `dsh-subagent-default-model` 的 2.1.16/2.1.17 就是这么丢过一整页)。
|
|
57
|
+
- **输入框积分浮层恢复「设置」按钮,指路改到设置面板的分栏定位**。上一轮曾把按钮删掉,
|
|
58
|
+
理由写成「宿主没有任何公开的客户端服务能打开设置面板并定位到某个 section」——**这个理由是
|
|
59
|
+
错的**,本轮已撤销。宿主确实没有这样的*服务*,但设置面板把自己的 store 座位钉死在注册声明上
|
|
60
|
+
(`shellStore.create: () => shellInstance`),所以
|
|
61
|
+
`slots.entries('sidebar.settings')[0].store.create().actions.openSection(id)` 是一条真实可用的
|
|
62
|
+
路径,宿主自己的测试就是这么驱动那个对话框的。「刷新」(账号池测试)保留。
|
|
63
|
+
- 探测放在**按下那一刻**而不是注册时:`entries('sidebar.settings')` 在
|
|
64
|
+
`whenAccess('settings/describe')` 通过之前是空的,注册时探测会永久拿到空数组。
|
|
65
|
+
- `.store` 缺失、没有 `create`、或拿到的 `create()` 不带 `actions.openSection`,三者都视为
|
|
66
|
+
「没有这条路」:按钮照常画出,按下后显示一行失败说明,而不是画一个必然失败的按钮。
|
|
67
|
+
- 这是读另一个 bundle 声明的 store 座位,不是宿主发布的插件 API(见 `settings-route.ts` 的
|
|
68
|
+
JSDoc);接口变了,最坏结果是那行失败提示出现,不会崩。
|
|
69
|
+
- **「账号」分栏重新设计,纵向空间还给内容**(参考同族 `dsh-connect-workbuddy` 5.19.0 的
|
|
70
|
+
同一次迁移,它踩过完全一样的坑)。
|
|
71
|
+
- **不接管滚动**。设置面板的 `.options` 列本来就负责滚动,本插件在里面再套一层
|
|
72
|
+
`overflow:auto` 就是两根滚动条。所以既不写 `height:100%`,也不写 `overflow`。
|
|
73
|
+
- **分栏条改为 `position:sticky`**(`top:-1px`,带外壳 layer-2 底色与 `z-index:2`)。
|
|
74
|
+
账号池在折叠线以下,原来滚到那里时「模型 / 诊断」已经跟着滚出视野,想切分栏只能把整页
|
|
75
|
+
滚回顶部。
|
|
76
|
+
- **页头压成两行网格**:标题 18px→15px/22px,简介 13px→12px/18px,行距由 `row-gap:2px`
|
|
77
|
+
精确控制,页头 70px→60px。面板自己已有一层标题栏,这里不需要第二层大标题。
|
|
78
|
+
- **供应商芯片行的说明行视觉隐藏**(`clip` 而非 `display:none`,读屏仍可读到):它是三个
|
|
79
|
+
芯片的注解,独占 36px,只在同时启用两个供应商时才有用,而那正是用户在看芯片的场景。
|
|
80
|
+
- 实测:首个可点控件从 y=351 上移到 y=287。
|
|
81
|
+
- **账号池表格在窄列下改为堆叠卡片**(`@container (max-width:700px)`)。
|
|
82
|
+
- 根因:本文件所有 `@media` 断点测的都是**视口**,而设置面板的内容列只有 564px,视口媒体
|
|
83
|
+
查询在那里永不触发——五列表格被塞进 564px,固定轨道 `128+96+128` 加上四条 10px 间隙与
|
|
84
|
+
两倍 14px 内边距后,两条弹性轨道只剩约 144px,**账号名列实测被压到 76px**,
|
|
85
|
+
「用户7520607992」显示成「用户75」。
|
|
86
|
+
- 用 `container-type:inline-size` 让断点改测**表格自身**的宽度。特意放在表格上而不是祖先上:
|
|
87
|
+
`container-type` 会让该元素成为 `position:fixed` 后代的包含块,而忙碌遮罩
|
|
88
|
+
`.dsm-trae-veil` 与账号导入对话框都是这张表的**兄弟**节点,放在表格上让它们继续相对视口
|
|
89
|
+
定位。表格内部只有表头与成员行,没有 fixed 后代。
|
|
90
|
+
- **一个阈值,不是两个**:中间再插一档「稍微窄一点就少一列」没有价值——设置面板那一列正好
|
|
91
|
+
落在中档里,用户会拿到表头对不上的 2×2 网格。要么放得下这些列,要么放不下。
|
|
92
|
+
- 放不下时每行变成三行堆叠卡片:账号与操作各占满一行,中间三个事实共享一行。整体
|
|
93
|
+
`flex-direction:column` 会把每个单元格推成独立一行,行高翻倍且读起来像表单。
|
|
94
|
+
- **折叠的是内层单元格,不只是行包装**。这是本轮最容易漏的一处:第一版只把
|
|
95
|
+
`.dsm-trae-pool-row` 改成可换行的 flex 行,但真正竖排的是更里层——
|
|
96
|
+
`.dsm-trae-pool-probe-cell` 自己带 `flex-direction:column`,`.dsm-trae-pool-account`
|
|
97
|
+
自己是三行网格。它们不改,状态列仍然是「正常 / 测得 3 天前 / 来自测速 /
|
|
98
|
+
upstream answered」四行,账号名反而被挤到「LaoDin」「用户75」——行更高且更窄。
|
|
99
|
+
(这个缺陷是用户截图发现的:本地 fixture 里三个账号的探针都是「尚未测速」,
|
|
100
|
+
只渲染一行,正好把问题掩盖掉了。fixture 已补上真实探针记录。)
|
|
101
|
+
- 实测:账号名列 76px → 517px,不再截断;状态列 4 行 → 1 行(17px);
|
|
102
|
+
账号池表格 412px → 304px,单行高度 138px → 102px。
|
|
103
|
+
- README 中英文同步更新配置位置说明。
|
|
104
|
+
|
|
3
105
|
## 4.8.1 (2026-11-05)
|
|
4
106
|
|
|
5
107
|
### 适配
|
package/README.en.md
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
<a href="https://dshfind.com/plugins/dingminhua/dsh-connect-trae"><img src="https://dshfind.com/api/badge/dingminhua/dsh-connect-trae" alt="dshfind plugin"></a>
|
|
25
25
|
</p>
|
|
26
26
|
|
|
27
|
-
A [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) bundle plugin that connects locally signed-in Trae models (**CN, enterprise, and international installs**) to the DSH model picker. Trae generates structured tool calls while DSH executes its own local tools, with a usage overview (Work/general credits on CN, tenant allowance on enterprise, subscription status on international), daily check-in claiming, and a model-management panel. **The CN, enterprise, and international sides are three parallel providers (`trae` / `trae-enterprise` / `trae-global`) that can be used at the same time**; the settings
|
|
27
|
+
A [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) bundle plugin that connects locally signed-in Trae models (**CN, enterprise, and international installs**) to the DSH model picker. Trae generates structured tool calls while DSH executes its own local tools, with a usage overview (Work/general credits on CN, tenant allowance on enterprise, subscription status on international), daily check-in claiming, and a model-management panel. **The CN, enterprise, and international sides are three parallel providers (`trae` / `trae-enterprise` / `trae-global`) that can be used at the same time**; the settings page (**Settings → Trae**, its own page in the settings nav) separates them with tabs for convenient management.
|
|
28
28
|
|
|
29
29
|
**Runs on macOS, Windows, and Linux**: CI runs the full typecheck / test / build matrix on `ubuntu-latest` and `windows-latest`, and Windows gets purpose-built handling for directory probing, app version headers, and lock behavior — see [Windows notes](#windows-notes).
|
|
30
30
|
|
|
@@ -54,13 +54,13 @@ A [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) bund
|
|
|
54
54
|
> `4011` is a **rate limit**, not "this model is unavailable" (measured for issue #33): an exhausted balance reports a different code, `4008`, so "I still have plenty of credits" and "I got a 4011" are both true at once. A throttled model answers again after a short wait — do not delete it.
|
|
55
55
|
- **One-click test of all models** — the same probe applied to **every visible model** of the current region, not just the enabled ones. Use it when you want to know whether the models you have switched off could still answer — after a plan change, or after Trae reshuffles its lineup.
|
|
56
56
|
- **Daily check-in claiming (per account)** — on the CN card **every account row carries its own check-in button**, with the day's reward written on it (`Check in +150`). A row reads **Claimed** and disables itself once *that account* has been paid, and reads **Device used** and disables itself when *this machine* has already spent today's slot through some other account — the two are reported honestly, and the second is never dressed up as "claimed". This is the plugin's **only** operation that changes account state, and it runs only when you click; one click targets that one row's account and sends exactly one request. There is deliberately **no "claim for every account"**: Trae allows **one check-in per device per day** (switching accounts does not reset the slot), so a sweep could only manufacture a row of false failures. The international side has no check-in activity, so no button is shown there.
|
|
57
|
-
- **Composer readout** — the composer tool row (between "Expert" and the model selector) shows a clickable readout, refreshing every 5 minutes. **Both regions show it, but they do not show the same quantity, and that is the data's doing**: the CN side is credit-based, so it reads "Trae CN · <general credits>"; the international side is subscription-based and its usage document carries no credit field at all, so it reads "Trae Global · <plan>" (e.g. `Pro` / `Free`). The whole readout is clickable and opens a panel showing a TABLE: one row per account in the region — general credits on CN, plan on international — where clicking a row SWITCHES accounts (through the same merged `accounts` write the card uses, so the other region's selection survives). The panel's top right carries **two controls, Refresh and Settings
|
|
57
|
+
- **Composer readout** — the composer tool row (between "Expert" and the model selector) shows a clickable readout, refreshing every 5 minutes. **Both regions show it, but they do not show the same quantity, and that is the data's doing**: the CN side is credit-based, so it reads "Trae CN · <general credits>"; the international side is subscription-based and its usage document carries no credit field at all, so it reads "Trae Global · <plan>" (e.g. `Pro` / `Free`). The whole readout is clickable and opens a panel showing a TABLE: one row per account in the region — general credits on CN, plan on international — where clicking a row SWITCHES accounts (through the same merged `accounts` write the card uses, so the other region's selection survives). The panel's top right carries **two controls, Refresh and Settings** (as of 4.9.0 the Settings button targets the settings panel; an interim revision removed it on the belief that this was impossible, which was wrong — the host publishes no id-targeted *service*, but the settings shell pins its store seat to the registration so `slots.entries('sidebar.settings')[0].store.create().actions.openSection(id)` is a real route, and the host's own tests drive the dialog exactly that way). While Refresh runs its icon turns: the batch is serial, so the slowest account decides when it stops. **Refresh** asks every account checked into this panel's account pool to answer once (through the pool's `action=test` route — the same batch the pool section's own button runs) and reports the outcome in the panel's foot: `Pool tested · 2 of 2 answered`. **Two things about that press the label cannot say, so both live in the tooltip and in the foot line**: it tests the **pool's checked-in members**, not every account in the table you are looking at, and it is the **only press in this plugin that spends credits** (one minimal ping per member) — a bare "Refresh" reads as free, so the tooltip is worth reading. To change anything, open **Settings → Trae** — the plugin's only configuration surface. The balance needs no refresh button of its own — it re-reads when the panel opens, on every account switch, and every five minutes, which is why that button was replaced rather than joined by another. It appears **only while the session's selected model belongs to this plugin**, and renders nothing for any other provider — which is what keeps two connector plugins from competing for one shared row (the sidebar foot is a shared root row; the composer row is per-session and provider-scoped).
|
|
58
58
|
|
|
59
59
|
<img src="docs/assets/dsh-connect-trae-composer-credits.png" width="560" alt="Trae credits panel: a table of accounts with their general balance, and the composer readout below" />
|
|
60
60
|
|
|
61
61
|
**Where that readout sits is a choice** (learned from the sibling `dsh-connect-workbuddy` issue #62). The default is the **composer toolbar** (the position described above); if another plugin occupies it and the readout does not appear, move it **below the input box** — the row carrying the round / step / token statistics, where the readout is APPENDED after the shipped figures rather than replacing them. The seat is set **per panel** (all three are stored separately), in the card's settings block next to the "show credits in the composer row" checkbox. Why a second seat exists at all: the composer toolbar is a **shared list slot** that the shell's own controls and every other plugin register into, and a shared slot **cannot promise that every occupant is visible** — the report behind that issue is a readout that vanished entirely once a third-party plugin was installed (this plugin has been squeezed out of a shared row once before too, down to a clipped "Tr" at the sidebar foot).
|
|
62
62
|
|
|
63
|
-
- **Optional: put Trae in the sidebar** (learned from the sibling `dsh-connect-workbuddy` PR #188, and **off by default** here too). Ticking "Show Trae in the sidebar" in the card's settings block adds a Trae row to the DSH sidebar; it opens the **full-page version of this same card** — same region tabs, same account / models / diagnostics sections, minus the card frame, with a title, a description and "Back to conversation" at the top. **Why it is off by default**: the sidebar's global rows are the host's own pages, and this row claims a seat **every installed bundle shares** while the
|
|
63
|
+
- **Optional: put Trae in the sidebar** (learned from the sibling `dsh-connect-workbuddy` PR #188, and **off by default** here too). Ticking "Show Trae in the sidebar" in the card's settings block adds a Trae row to the DSH sidebar; it opens the **full-page version of this same card** — same region tabs, same account / models / diagnostics sections, minus the card frame, with a title, a description and "Back to conversation" at the top. **Why it is off by default**: the sidebar's global rows are the host's own pages, and this row claims a seat **every installed bundle shares** while the settings page already reaches the same content — a nav row nobody asked for re-arranges a surface this plugin does not own. **It takes effect on the click** with no Save: writing `sidebarPanel` registers or disposes the row in the same second (it is not a component rendering null — that would leave a blank but still clickable button), and switching it off while the page is open sends the centre column back to the conversation by itself. The page's back control goes through the host's `layout.selectPanel(null)`; on a host without that service the control is **omitted rather than drawn dead**.
|
|
64
64
|
<img src="docs/assets/dsh-connect-trae-sidebar-page.png" width="720" alt="The sidebar page: back control, title and description at the top, then the same card body below" />
|
|
65
65
|
|
|
66
66
|
|
|
@@ -266,7 +266,7 @@ For **this plugin**:
|
|
|
266
266
|
- **One contract break**: `dsh-llm-pi-ai` added two **required** fields to `ResolvedPiAiProviderProfile` — `maxRequestImages` and `imageOffloadCountQuantum`. This plugin builds that profile **by hand** from the live catalog (it bypasses the host's config resolution), so `pnpm typecheck` fails with `TS2739` after the bump. Both are now declared in `REQUEST_IMAGE_BUDGETS` with the host's own defaults, `50 / 10`.
|
|
267
267
|
- **Runtime was never broken**: the host only defaults those fields on its config-resolution path; a hand-built profile that omits them takes the "image count unchecked" branch on 0.2.2. That is why 4.8.0 keeps working there (verified on the desktop host: `GET /plugins/dsh-connect-trae/usage` → 200, `signed-in`).
|
|
268
268
|
- **New capabilities adopted where they matter**: the plugin now emits `llm/models-updated(provider)` when a region's catalog changes (deduplicated by a signature, so a settings write that rebuilds identical rows does not wake every consumer). And because `conversation.input.model.label` is a `single` slot, holding it suppresses the host's built-in "name + `model.badge`" label for **every** provider — so the component now renders another provider's `model.badge` (with language fallback) instead of dropping it. Its own rows are unchanged.
|
|
269
|
-
- **Deliberately not adopted**: the six new UI languages (this plugin keeps zh/en and falls back to English), `
|
|
269
|
+
- **Deliberately not adopted**: the six new UI languages (this plugin keeps zh/en and falls back to English), `llm-pi-ai/request-headers` (no custom inference headers), `supportsMidConvoEffort` (not on the openai-completions route).
|
|
270
270
|
- **The three core values are unaffected**: dual-region providers, usage/credits overview, daily check-in. Their tests are green; this change only touches the image budget, catalog announcements, and the model label.
|
|
271
271
|
- The other six changed packages (`dsh-host-webserver` resource routes, `dsh-attachment` admission variants, the new languages in `dsh-client-locale` / `dsh-client-ui-settings-plugins`, the `dsh-client-ui-settings` icon slot, `dsh-client-ui-primitives` additions) are **additive**; this plugin either does not use them or already has an equivalent. The five unchanged packages (`dsh-atomic-write`, `dsh-client-ui-renderer`, `dsh-client-ui-slots`, `dsh-home-paths`, `dsh-settings`) are **byte-for-byte identical**, so every slot contract this plugin registers is unchanged.
|
|
272
272
|
- The peer range **needs no change**: `0.2.2-rc.1` is admitted by `>=0.1.7-rc.1 <0.3.0-0`, and `MUST_ADMIT` in `tests/dsh-peer-range.spec.ts` now includes it.
|
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
<a href="https://dshfind.com/plugins/dingminhua/dsh-connect-trae"><img src="https://dshfind.com/api/badge/dingminhua/dsh-connect-trae" alt="dshfind plugin"></a>
|
|
25
25
|
</p>
|
|
26
26
|
|
|
27
|
-
一个独立的 [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) bundle 插件。它把本机已登录的 Trae 账号(**国内版、企业版与国际版均支持**)接到 DSH 的模型选择器:模型负责生成结构化工具调用,`bash` / `read` / `write` / `edit` 等工具由 DSH 本地执行;同时提供用量概览(国内版 Work/通用积分、企业版租户额度、国际版订阅状态)、每日签到领取与模型管理界面。**国内版、企业版与国际版是三个并行的供应商(`trae` / `trae-enterprise` / `trae-global
|
|
27
|
+
一个独立的 [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) bundle 插件。它把本机已登录的 Trae 账号(**国内版、企业版与国际版均支持**)接到 DSH 的模型选择器:模型负责生成结构化工具调用,`bash` / `read` / `write` / `edit` 等工具由 DSH 本地执行;同时提供用量概览(国内版 Work/通用积分、企业版租户额度、国际版订阅状态)、每日签到领取与模型管理界面。**国内版、企业版与国际版是三个并行的供应商(`trae` / `trae-enterprise` / `trae-global`),可同时使用**;插件设置页(**设置 → Trae**,侧边栏导航里的独立一页)以 tab 区分三者,方便统一管理。
|
|
28
28
|
|
|
29
29
|
**支持 macOS、Windows 与 Linux**:CI 在 `ubuntu-latest` 与 `windows-latest` 上跑完整 typecheck / 测试 / 构建;Windows 侧的目录探测、应用版本头与锁行为均有针对性处理,详见 [Windows 说明](#windows-说明)。
|
|
30
30
|
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
- **三供应商并行接入** —— 国内版注册为 DSH 的 `trae` provider(`DeepSeek-V4-Flash`、`DeepSeek-V4-Pro` 等),企业版注册为 `trae-enterprise`,国际版注册为 `trae-global`(Gemini / GPT / MiniMax 阵容),**三边模型同时出现在 DSH 模型选择器里**:不同会话可以各选一边,互不干扰。
|
|
38
38
|
- **企业版是独立计费通道,不是国内版的别名** —— 同一张 CN 凭据若属于 `tob_online` 租户,会被**两个**网关同时服务:消费池(扣个人积分)与租户池(扣企业额度)。同一模型、两种付费方,所以「谁付钱」由你显式选,插件不替你猜。`trae` 永不碰企业网关;`trae-enterprise` **不回落**,被拒绝就如实报错,不会悄悄改用个人额度重跑。企业版**默认关闭**——纯个人账号用不了企业池,默认放开等于塞一条一用就失败的 provider。
|
|
39
39
|
- **可单独关闭任一面板** —— 三个 tab 各带一个勾选(国内版与国际版默认勾选,企业版默认不勾):取消勾选即把该面板的供应商**整个从 DSH 模型选择器撤掉**(不是只藏起 tab),并且不再发起任何请求;重新勾选即恢复,账号、目录、勾选与上下文预算全部保留。用不到国际版就把国际版关掉即可。
|
|
40
|
-
-
|
|
40
|
+
- **设置页 tab 切换** —— 设置页顶部为「国内版 / 企业版 / 国际版」三个 tab,各含独立的账号选择、用量概览与模型管理;每个 tab 的账号、目录、勾选与未保存草稿完全隔离——在一个 tab 里切账号或刷新模型,不会触碰另一边的运行时目录与会话。国内版与企业版共用同一份 CN 凭据池,但**各有各的账号绑定与模型勾选**:两个池的名录并不相同(`DeepSeek-V4.1-Flash` 只有企业池列),所以勾选互不覆盖。
|
|
41
41
|
- **卡片内再按「账号 / 模型 / 诊断」分栏**(学自同族 `dsh-connect-workbuddy`)—— 上面那排地区是**作用域**(这张卡片讲的是哪个供应商),这一排是同一张卡片里的**分栏**:账号栏管「谁在服务、还剩多少、签到与账号池」,模型栏管「勾哪些模型、图片输入、上下文预算、保存」,诊断栏只读地列出面板名、供应商 id、服务账号、两种令牌到期时间、目录/已启用模型数、账号池与签到支持与否,以及本面板最近一次报错的**原文**。**4.4.7 起诊断栏还多一行「损坏副本留存」**:读不出来、因此被改名留存的账号副本会在这里被点名(连同它曾经保存了几个账号)——被顶掉的账号「去哪了」这个问题,从此不用再去 `~/.dsh` 里翻文件名;未登录那一屏也会显示同一条通知,因为账号消失之后用户落地的正是那一屏。分栏的理由是**提交语义不同**:账号改动点一下即生效,模型改动是草稿、必须点「保存」——一列长页会把保存按钮放到离它要保存的东西三屏之外。切栏不重新拉取、不丢未保存的模型草稿、账号池日志也不重建;未登录时不出分栏,那一屏说明不会被看不见的 tab 藏起来。**模型栏把所有模型一次性铺开,没有内层滚动条**——不再是一个套在滚动页面里的小窗口,唯一滚动的就是卡片自己那一页。
|
|
42
42
|
- **账号池:自动选号 + 失败换号(默认关闭)** —— 本机读完几个账号后,「该用谁的额度」可以交给插件按规则排序:**先排除不可用的**(凭据被拒、套餐不覆盖、账号已不存在、限流未到重试时间、额度耗尽),再在可用账号里**优先快到期的那点额度**(与其放着过期,不如先花掉),最后才比总额、token 新鲜度,并以账号 id 兜底成**全序**(同样的输入永远得到同样的顺序,不会在两行之间来回抖)。三个面板**各有各的池**:国内版与企业版共用同一份 CN 凭据,但企业版只挑租户账号、国内版不会漂到租户号上。开着池时,发请求前先按上面的顺序选中一个账号,并且**真实请求失败会换下一个账号重试(最多 3 个)**——只有账号级失败才换(`401` 凭据被拒、额度耗尽、限流、上游 5xx);模型 id 写错、请求本身不合法**不换号**,因为那不是账号的问题,换号只会把同一个错误烧两遍、还冤枉一个没机会服务的账号。换号失败的真相会写进池的探针记录,下一次排序直接把它排到后面,而每一次结论都能在卡片上看到:`可用` / `限流` / `额度耗尽` / `凭据被拒` / `套餐不覆盖` / `上游不提供` / `本次未测出`,以及「什么时候可以重试」。**关掉池就回到严格绑定**,不会有任何残留 override。卡片里的「全部测速」会把每个成员按目标模型各测一次,结果**边跑边显示**(一行一个答案,不等整批跑完),并落盘成下次排序的依据。
|
|
43
43
|
|
|
@@ -59,13 +59,13 @@
|
|
|
59
59
|
> `4011` 是**限流**不是「模型不可用」(issue #33 实测):它额度耗尽走的是另一个码 `4008`,所以「额度还有很多却报 4011」两句话可以同时成立。被限流的模型**等你一会儿再试**即可,不要删。
|
|
60
60
|
- **一键测试所有模型** —— 同一套探测,作用于当前 region 的**全部可见模型**(不限于已启用的)。想知道「没启用的那些到底还能不能用」时用它——比如套餐降级后,或 Trae 调整了模型阵容之后。
|
|
61
61
|
- **每日签到领取(逐账号)** —— 国内版**每个账号行各有一个签到按钮**,当日奖励直接写在按钮上(`签到 +150`);该账号当天已领显示「已领取」并禁用,**本机今日名额已被别的账号用掉**时显示「本机已签」并禁用——如实区分「这个账号领了」和「这台设备领过了」,绝不把后者说成「已领取」。这是插件里**唯一会改变账号状态**的操作,由你主动点击触发;一次点击只针对该行账号、只发一次请求。Trae 的签到按「**每台设备每天一次**」计(换账号不会重置当天名额),所以逐账号签到始终是主路径。账号池里另有一个「全部签到」,它**不是**把同一件事盲目重复:一旦上游回 `9095`(本机今日已领),整批立刻停止并把剩余账号如实标成「本机已签」,产出的是真相而不是一排假失败。国际版没有签到活动,因此不显示该按钮。
|
|
62
|
-
- **输入框读数** —— 在输入框工具栏(「专家」与模型选择器之间)显示一块可点击读数,5 分钟自动刷新。**两个区域都显示,但显示的东西不同,这是数据决定的**:国内版是积分制,显示「Trae CN · <通用积分>」;国际版是订阅制,它的用量文档里根本没有积分字段,所以显示「Trae 国际版 · <套餐>」(如 `Pro` / `Free`)。**整块可点击**,弹出浮层显示一张**表格**:本区域**每个账号一行**,国内版列**通用积分**、国际版列**套餐**,**点行即可切换账号**(写入与卡片相同的 `accounts`
|
|
62
|
+
- **输入框读数** —— 在输入框工具栏(「专家」与模型选择器之间)显示一块可点击读数,5 分钟自动刷新。**两个区域都显示,但显示的东西不同,这是数据决定的**:国内版是积分制,显示「Trae CN · <通用积分>」;国际版是订阅制,它的用量文档里根本没有积分字段,所以显示「Trae 国际版 · <套餐>」(如 `Pro` / `Free`)。**整块可点击**,弹出浮层显示一张**表格**:本区域**每个账号一行**,国内版列**通用积分**、国际版列**套餐**,**点行即可切换账号**(写入与卡片相同的 `accounts` 字段、合并时不丢掉另一个区域的选择)。浮层右上角是**「刷新」与「设置」两个控件**(4.9.0 起「设置」改指设置面板;中途曾以为做不到而删掉,是错的——宿主确实没有 id 定向的服务,但设置面板把自己的 store 座位钉在注册声明上,`slots.entries('sidebar.settings')[0].store.create().actions.openSection(id)` 是真实可用的路径,宿主自己的测试就是这么驱动的)。按「刷新」时图标会转,直到这批探测结束——它是串行跑的,最慢的账号决定什么时候结束。**「刷新」**把账号池里勾选的成员逐个问一遍「能不能应答」(走账号池那条 `action=test` 路由,与账号池分栏里的测试是同一个动作),结果直接报在浮层底部:`账号池已测 · 2 of 2 可应答`。**这个按钮有两件事标签说不出来,都写在悬停提示与结果行里**:它测的是**账号池里勾选的成员**,不是你眼前这张表里的每个账号;而且它是**插件里唯一消耗额度**的按钮(每个成员一条最小 `ping`)——一个光秃秃的「刷新」读起来是免费的,所以工具提示必须留着看。要改配置就去「设置 → Trae」:那是本插件唯一一处配置界面。积分本身不需要刷新按钮:**打开浮层、切换账号、每 5 分钟**都会重新读一次,所以那个按钮被换掉而不是并排加进来。**只在当前会话选中的模型属于本插件时出现**——选其它供应商的模型时整块不渲染。这样两个连接器插件不会争同一个位置(侧边栏底部是共享的根级行,输入框工具栏是按会话、按供应商的)。
|
|
63
63
|
|
|
64
64
|
<img src="docs/assets/dsh-connect-trae-composer-credits.png" width="560" alt="Trae credits panel: a table of accounts with their general balance, and the composer readout below" />
|
|
65
65
|
|
|
66
66
|
**读数显示在哪里可以选**(学自同族 `dsh-connect-workbuddy` issue #62)。默认在**输入框工具栏**(即上面说的位置);如果那里被别的插件占了、读数不显示,改成**输入框下方**——那是「轮次 / 步数 / token」统计所在的那一行,读数是**追加**在官方统计之后,不会顶掉它们。位置**按面板分别设置**(三个面板各存各的),在卡片「设置」区紧挨「在输入框工具栏显示积分读数」那个勾选框。之所以要有第二个位置:输入框工具栏是**共享的 list 插槽**,外壳自己的控件和其他插件都注册在那里,而共享插槽**无法保证每个占用者都可见**——同族的报告者装了第三方插件后读数就整个消失了(本插件早年放在侧边栏底部时也被挤成过一个「Tr」的残片)。
|
|
67
67
|
|
|
68
|
-
- **可选:把 Trae 放进侧边栏**(学自同族 `dsh-connect-workbuddy` 的 PR #188,同样**默认关闭**)。在卡片「设置」区勾上「在侧边栏显示 Trae」,DSH 侧边栏就多一个 Trae 行,点开是**这张卡片的整页版**:同样的地区页签、同样的账号 / 模型 /
|
|
68
|
+
- **可选:把 Trae 放进侧边栏**(学自同族 `dsh-connect-workbuddy` 的 PR #188,同样**默认关闭**)。在卡片「设置」区勾上「在侧边栏显示 Trae」,DSH 侧边栏就多一个 Trae 行,点开是**这张卡片的整页版**:同样的地区页签、同样的账号 / 模型 / 诊断分栏,只是没有卡片边框,顶部是标题、说明和「返回会话」。**默认关闭的理由**:侧边栏的全局行是宿主自己的页面,这一行占的是**所有已安装插件共用**的座位,而「设置 → Trae」本来就能看到同样的内容——一行没人要求的导航会改变一个插件并不拥有的界面。**点一下即生效**,不用保存:开关写入 `sidebarPanel` 后,注册与注销在同一秒发生(不是把组件渲染成 null——那会留下一个空白却仍可点的按钮);页面开着时关掉它,中心栏会自己回到会话。整页的「返回会话」走宿主的 `layout.selectPanel(null)`,宿主没这个服务时**不画**这个按钮,而不是画一个点不动的。
|
|
69
69
|
<img src="docs/assets/dsh-connect-trae-sidebar-page.png" width="720" alt="Trae 侧边栏整页版:顶部是返回会话、标题与说明,下面是同一张卡片的地区页签和账号栏" />
|
|
70
70
|
|
|
71
71
|
|
|
@@ -337,7 +337,7 @@ stderr、View 标签的 tooltip 只在文字溢出时显示、More 菜单 toolti
|
|
|
337
337
|
压掉宿主的「名字 + `model.badge`」内置渲染,因此对**别的 provider** 的模型补回了 badge
|
|
338
338
|
(含语言回退),本插件自己的行行为不变。
|
|
339
339
|
- **明确不采纳**:新语言 ja / es / fr / de / ru / pt(只维护 zh / en,其余回退英文)、
|
|
340
|
-
`
|
|
340
|
+
`llm-pi-ai/request-headers`
|
|
341
341
|
(不需要额外请求头)、`supportsMidConvoEffort`(openai-completions 路线用不到)。
|
|
342
342
|
- **三大价值不受影响**:双区域供应商、用量与积分概览、每日签到领取——对应测试全绿,本次
|
|
343
343
|
只碰图像预算、目录公告与模型标签三处。
|
package/lib/client.js
CHANGED
|
@@ -1000,6 +1000,21 @@ window.__ModuleLoader__.load({
|
|
|
1000
1000
|
.dsm-trae-tab-switch{flex:none;margin-left:auto;padding-left:10px;border-left:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.24))}
|
|
1001
1001
|
.dsm-trae-tab-dot{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:7px;vertical-align:baseline}
|
|
1002
1002
|
.dsm-trae-tab-hint{margin:0;padding:0 2px;color:var(--dsw-alias-label-tertiary,#999);font-size:12px;line-height:18px}
|
|
1003
|
+
/* THE TWO SETTINGS-SURFACE TWEAKS, SCOPED (learned the hard way, post-release).
|
|
1004
|
+
*
|
|
1005
|
+
* 下面两条原本是**无作用域**的:一条把供应商说明行视觉隐藏,一条把分栏条改成
|
|
1006
|
+
* sticky 并加一层底色。它们写的时候只想到设置面板那一列(564px、外壳自带滚动),
|
|
1007
|
+
* 但那两处 DOM 是**三个界面共用的**——卡片、整页、设置页画的是同一棵树。
|
|
1008
|
+
*
|
|
1009
|
+
* 结果在卡片上是两个回归(实测):说明行在卡片里也消失了(卡片有自己的宽度、
|
|
1010
|
+
* 有自己的节奏,那里根本没有纵向空间压力),分栏条拿到 position:sticky 却没有
|
|
1011
|
+
* 滚动祖先,且底色取了 layer-2 而卡片本身是 layer-3,于是在卡片上压出一条
|
|
1012
|
+
* 比周围更浅的色带。
|
|
1013
|
+
*
|
|
1014
|
+
* 所以两条都挪到 .dsm-trae-section 之下——那是"只在设置面板里生效"的结构标记,
|
|
1015
|
+
* 也是这些改动的唯一理由所在(外壳的滚动列与 564px 的宽度)。卡片与整页保持
|
|
1016
|
+
* 原样:它们没有面板外壳,也就没有这些约束。 */
|
|
1017
|
+
.dsm-trae-section .dsm-trae-tab-hint{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}
|
|
1003
1018
|
/* SECTION TABS: the card's own top-level split (account / models / diagnostics).
|
|
1004
1019
|
*
|
|
1005
1020
|
* Deliberately NOT the boxed chips above. Those are the REGION scope — a
|
|
@@ -1014,7 +1029,25 @@ window.__ModuleLoader__.load({
|
|
|
1014
1029
|
* inactive panel out of the accessibility tree, which is exactly what
|
|
1015
1030
|
* role="tabpanel" wants — unlike display:none reached by a CSS class, the user
|
|
1016
1031
|
* agent's own rule for [hidden] cannot be overridden by a later declaration. */
|
|
1032
|
+
/* 分栏条钉在外壳的滚动列顶部(STICKY, not a second scroller)。
|
|
1033
|
+
*
|
|
1034
|
+
* 站点:本插件**不**接管滚动。设置面板的 .options 列本来就负责滚动(它自己的
|
|
1035
|
+
* CSS 注释写明了这一点),在里面再套一层 overflow:auto 就是两根滚动条,滚轮
|
|
1036
|
+
* 会在两者之间来回跳。所以这里既不写 height:100%,也不写 overflow。
|
|
1037
|
+
*
|
|
1038
|
+
* 但「不接管滚动」不等于「分栏条可以滚走」:账号池在折叠线以下,滚到那里时
|
|
1039
|
+
* 「模型 / 诊断」已经跟着滚出了视野,想切分栏只能先把整页滚回顶部。sticky 让它
|
|
1040
|
+
* 在任何滚动位置都留在顶部,代价只是 z-index 与一层不透明底色——否则滚上来的
|
|
1041
|
+
* 账号卡片会从字缝里透出来(外壳的 layer-2 正是这一列自己的底色)。
|
|
1042
|
+
*
|
|
1043
|
+
* top:-1px 而不是 0:分栏条自己的 border-bottom 画在盒子外侧,写成 0 会在它上方
|
|
1044
|
+
* 留一条 1px 的缝,滚动的行会从这条缝里露出来。 */
|
|
1017
1045
|
.dsm-trae-panels{display:flex;align-items:stretch;gap:2px;margin:4px 0 0;border-bottom:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.24))}
|
|
1046
|
+
/* 分栏条只在设置面板里钉住(见上方 .dsm-trae-tab-hint 的长注释:这两条原本无
|
|
1047
|
+
* 作用域,在卡片上留下两个回归)。卡片与整页没有"外壳的滚动列",那里的分栏条
|
|
1048
|
+
* 跟着内容正常滚走即可;sticky 在没有滚动祖先时不会生效,却会把底色带进来——
|
|
1049
|
+
* 而底色取 layer-2,卡片本身是 layer-3,色差在卡片上就成了一条浅色带。 */
|
|
1050
|
+
.dsm-trae-section .dsm-trae-panels{position:sticky;top:-1px;z-index:2;background:var(--dsw-alias-bg-layer-2,#24262c)}
|
|
1018
1051
|
.dsm-trae-panel-tab{appearance:none;font:inherit;cursor:pointer;border:0;background:transparent;padding:7px 11px;margin-bottom:-1px;border-bottom:2px solid transparent;color:var(--dsw-alias-label-tertiary,#999);font-size:13px;font-weight:600;line-height:20px;white-space:nowrap;transition:color .15s,border-color .15s}
|
|
1019
1052
|
.dsm-trae-panel-tab:hover:not(.dsm-trae-panel-tab-active){color:var(--dsw-alias-label-primary,#e6e6e6)}
|
|
1020
1053
|
.dsm-trae-panel-tab:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
|
|
@@ -1577,7 +1610,25 @@ window.__ModuleLoader__.load({
|
|
|
1577
1610
|
.dsm-trae-pool-subnote{margin:2px 0 0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary,#9aa0a8)}
|
|
1578
1611
|
.dsm-trae-pool-select-actions{display:flex;flex:none;gap:6px}
|
|
1579
1612
|
.dsm-trae-pool-select-actions .dsm-btn,.dsm-trae-pool-row-actions .dsm-btn{padding:4px 10px;font-size:12px;line-height:1.4}
|
|
1580
|
-
|
|
1613
|
+
/* CONTAINMENT ROOT(learned from workbuddy 5.19.0 的同一次迁移)。
|
|
1614
|
+
*
|
|
1615
|
+
* 这张卡同时画在三处,宽度与窗口毫无关系:输入框卡片、整页版(上限 860px)、
|
|
1616
|
+
* 以及设置面板的内容列(1400px 屏上是 564px)。本文件里所有 @media 断点测的
|
|
1617
|
+
* 都是**视口**,所以在设置面板里它们永远不触发——564px 的列拿到了五列桌面表格,
|
|
1618
|
+
* 每个单元格都在截断(实测账号列被压到 76px,「用户7520607992」只剩「用户75」)。
|
|
1619
|
+
*
|
|
1620
|
+
* container-type:inline-size 让下面的断点改测**这张表**的宽度,那才是行真正要
|
|
1621
|
+
* 塞进去的宽度。
|
|
1622
|
+
*
|
|
1623
|
+
* 特意放在表格上而不是祖先上:container-type 会让这个元素成为 position:fixed
|
|
1624
|
+
* 后代的包含块。忙碌遮罩 .dsm-trae-veil 与账号导入对话框都是这张表的兄弟节点
|
|
1625
|
+
* (挂在 .dsm-trae-pool 或 cardBody 上,不在表格里),放在表格上就让它们继续
|
|
1626
|
+
* 相对视口定位——全屏遮罩本来该在那里。表格内部只有表头与成员行,没有 fixed 后代。
|
|
1627
|
+
*
|
|
1628
|
+
* overflow:hidden 与 container-type 冲突:容器查询要求这个盒子能被"测量",而
|
|
1629
|
+
* overflow 会改变它的块格式化上下文。这里原本就有 overflow:hidden(用来裁圆角),
|
|
1630
|
+
* 二者在本例中兼容——表格自身不滚动,只是裁掉子元素的直角。 */
|
|
1631
|
+
.dsm-trae-pool-table{--dsm-trae-pool-cols:minmax(0,1.4fr) 128px minmax(0,1.2fr) 96px 128px;container-type:inline-size;display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:var(--dsm-trae-pool-radius);overflow:hidden;background:var(--dsw-alias-bg-layer-2,#24262c)}
|
|
1581
1632
|
.dsm-trae-pool-table-no-checkin{--dsm-trae-pool-cols:minmax(0,1.4fr) 128px minmax(0,1.2fr) 128px}
|
|
1582
1633
|
.dsm-trae-pool-row{display:grid;grid-template-columns:var(--dsm-trae-pool-cols);align-items:start;gap:10px;padding:var(--dsm-trae-pool-pad-y) var(--dsm-trae-pool-pad-x);border-top:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.18))}
|
|
1583
1634
|
.dsm-trae-pool-row:first-child{border-top:0}
|
|
@@ -1645,6 +1696,59 @@ window.__ModuleLoader__.load({
|
|
|
1645
1696
|
.dsm-trae-pool-save-row{display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:1px solid var(--dsw-alias-border-l2,#36373b);padding-top:12px;position:sticky;bottom:0;z-index:2;padding-bottom:8px;background:var(--dsw-alias-bg-layer-3,#2a2c33)}
|
|
1646
1697
|
.dsm-trae-pool-save-buttons{display:flex;align-items:center;justify-content:flex-end;gap:8px}
|
|
1647
1698
|
.dsm-trae-pool-save-row .dsm-btn{padding:5px 14px;font-size:13px;line-height:1.5}
|
|
1699
|
+
|
|
1700
|
+
/* THE TABLE'S NARROW FORM — measured against the table, not the window
|
|
1701
|
+
* (learned from workbuddy 5.19.0, which hit the identical bug in the identical
|
|
1702
|
+
* move from a plugin card to a settings.section page).
|
|
1703
|
+
*
|
|
1704
|
+
* 上面的 @media 断点测的都是视口。这张卡现在画在设置面板的内容列里(564px),
|
|
1705
|
+
* 视口媒体查询在那里永远不触发:五列表格被塞进 564px,固定轨道 128+96+128 加
|
|
1706
|
+
* 四条 10px 间隙与两倍 14px 内边距之后,留给账号名与探针两条弹性轨道的只剩
|
|
1707
|
+
* ~144px,实测账号名列 = 76px。
|
|
1708
|
+
*
|
|
1709
|
+
* 一个阈值,不是两个。中间再插一档「稍微窄一点就少一列」是没有价值的分支:
|
|
1710
|
+
* 设置面板那一列正好落在中档里,用户会拿到一个表头对不上的 2×2 网格。表格没有
|
|
1711
|
+
* 「稍微窄」的形态值得保留——要么放得下这些列,要么放不下。
|
|
1712
|
+
*
|
|
1713
|
+
* 放不下的时候,每行变成一张**堆叠卡片**:同样是那五个事实,宽的时候从左往右
|
|
1714
|
+
* 读,现在从上往下读。这是让窄行可读而不只是能塞下的关键。
|
|
1715
|
+
*
|
|
1716
|
+
* 三行,不是五行:整体 flex-direction:column 会把每个单元格推成独立一行,行高翻
|
|
1717
|
+
* 倍、读起来像表单而不是一个账号。账号与操作各占满一整行(最需要辨认的一端与
|
|
1718
|
+
* 最需要操作的一端),中间三个事实共享中间一行,只有真的放不下时才在自己之间换行。
|
|
1719
|
+
*
|
|
1720
|
+
* .dsm-trae-pool-row-head 隐藏:它的标签描述的是已经不存在的轨道。 */
|
|
1721
|
+
@container (max-width:700px){
|
|
1722
|
+
.dsm-trae-pool-row{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 12px;padding:var(--dsm-trae-pool-pad-y) 12px}
|
|
1723
|
+
.dsm-trae-pool-row-head{display:none}
|
|
1724
|
+
.dsm-trae-pool-account{flex:1 1 100%;align-items:center}
|
|
1725
|
+
.dsm-trae-pool-credit{flex:0 1 auto;flex-direction:row;flex-wrap:wrap;align-items:baseline;gap:2px 8px}
|
|
1726
|
+
.dsm-trae-pool-probe{flex:1 1 auto}
|
|
1727
|
+
.dsm-trae-pool-checkin{flex:none}
|
|
1728
|
+
.dsm-trae-pool-row-actions{flex:1 1 100%;justify-content:flex-start;padding-top:4px}
|
|
1729
|
+
/* THE PROBE CELL'S OWN COLUMN, collapsed to one line.
|
|
1730
|
+
*
|
|
1731
|
+
* 这一条是最容易漏掉的:.dsm-trae-pool-probe 只是个 min-width:0 的外层包装,
|
|
1732
|
+
* 真正竖排的是它内部的 .dsm-trae-pool-probe-cell(flex-direction:column)。
|
|
1733
|
+
* 只改外层包装的话,状态列仍然一句一行,被挤成「正常 / 测得 3 天前 / 来自测速 /
|
|
1734
|
+
* upstream answered」四行——旁边只有 12px 间距,整行高度就被这一格撑到 100px
|
|
1735
|
+
* 以上,账号名也跟着被压窄(实测截成「LaoDin」「用户7」)。
|
|
1736
|
+
*
|
|
1737
|
+
* 改成横排并允许在自己内部换行:这四个事实读起来本来就是一句话
|
|
1738
|
+
* (「正常 · 测得 3 天前 · 来自测速 · upstream answered」),宽时从左排到右,
|
|
1739
|
+
* 窄时在它们自己之间折行,而不是各自独占一行。 */
|
|
1740
|
+
.dsm-trae-pool-probe-cell{flex-direction:row;flex-wrap:wrap;align-items:baseline;gap:2px 8px}
|
|
1741
|
+
/* THE ACCOUNT CELL'S OWN GRID, collapsed the same way.
|
|
1742
|
+
*
|
|
1743
|
+
* 宽的时候它是个三行网格(名字 / 标签 / 探针时间),第一列固定给勾选框。
|
|
1744
|
+
* 在窄列里这三行没有理由继续各占一行——它们本来就是一句话
|
|
1745
|
+
* (「LaoDing · 已在池中 · 测试于 11/03, 06:35」),横排后名字与这两条短事实
|
|
1746
|
+
* 共享一行,只有真的放不下时才折行。勾选框仍然排在最前,并保持垂直居中对齐
|
|
1747
|
+
* 到第一行基线(align-self:baseline 会让它沉到行底)。 */
|
|
1748
|
+
.dsm-trae-pool-account{display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 8px}
|
|
1749
|
+
.dsm-trae-pool-account-name,.dsm-trae-pool-account-tags,.dsm-trae-pool-account-note{grid-column:auto;grid-row:auto}
|
|
1750
|
+
.dsm-trae-pool-check{align-self:center}
|
|
1751
|
+
}
|
|
1648
1752
|
.dsm-trae-pool-dirty{padding:0 6px;border-radius:999px;background:var(--dsw-alias-state-warn-tertiary,#fef3c7);color:var(--dsw-alias-state-warn-primary,#b45309);font-size:10px;line-height:16px}
|
|
1649
1753
|
.dsm-trae-pool-save-error{margin:0;font-size:12px;line-height:18px;color:var(--dsw-alias-state-error-primary,#d92d20)}
|
|
1650
1754
|
.dsm-trae-pool-target{display:flex;flex-direction:column;gap:6px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary,#c6c9d0)}
|
|
@@ -1683,6 +1787,24 @@ window.__ModuleLoader__.load({
|
|
|
1683
1787
|
.dsm-trae-page-title{margin:0;font-size:20px;font-weight:600;line-height:1.4}
|
|
1684
1788
|
.dsm-trae-page-desc{margin:0;font-size:13px;line-height:1.5;color:var(--dsw-alias-label-tertiary,#999)}
|
|
1685
1789
|
.dsm-trae-page-body .dsm-trae-usage{padding:20px 20px 8px;border:1px solid var(--dsw-alias-border-l2,#36373b);background:var(--dsw-alias-bg-layer-3,#202126);border-radius:12px}
|
|
1790
|
+
/* The settings.section page. The shell already owns the panel, the nav row and
|
|
1791
|
+
* the scrolling .options column (which brings its own 24px side padding), so
|
|
1792
|
+
* this surface adds NO frame of its own: no second border, no second card, and
|
|
1793
|
+
* deliberately no negative margins to "reclaim" the shell's padding. It is a
|
|
1794
|
+
* plain column so the sections below stack the same way they do in the card. */
|
|
1795
|
+
.dsm-trae-section{display:flex;flex-direction:column;gap:14px;color:var(--dsw-alias-label-primary,#e6e6e6)}
|
|
1796
|
+
/* 页头两行:标题一行,简介一行(learned from workbuddy 5.19.0)。
|
|
1797
|
+
*
|
|
1798
|
+
* 原来是 18px/600 的大标题加一行 13px 说明,在 564px 的列里要占 70px。压到
|
|
1799
|
+
* 15px/22px 的标题加 12px/18px 的说明后是 60px,省下的 10px 直接给了账号池——
|
|
1800
|
+
* 而面板本身已有一层标题栏,「设置」已经写在最上面,这里再喊一遍大标题是本插件
|
|
1801
|
+
* 自己的第二层标题,不是外壳的。
|
|
1802
|
+
*
|
|
1803
|
+
* 用 grid 而不是两个 flex 子项:标题和说明各自成行,中间不留 flex gap,行距由
|
|
1804
|
+
* row-gap:2px 精确控制(flex 的 gap 会同时作用在两者之间,量出来是 6px)。 */
|
|
1805
|
+
.dsm-trae-section-head{display:grid;grid-template-columns:minmax(0,1fr);row-gap:2px;align-items:start}
|
|
1806
|
+
.dsm-trae-section-title{margin:0;font-size:15px;font-weight:600;line-height:22px}
|
|
1807
|
+
.dsm-trae-section-desc{margin:0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary,#999)}
|
|
1686
1808
|
.dsm-trae-panel-icon{display:block;border-radius:4px;flex:none}
|
|
1687
1809
|
`;
|
|
1688
1810
|
/**
|
|
@@ -4538,14 +4660,17 @@ window.__ModuleLoader__.load({
|
|
|
4538
4660
|
function TraeUsageCard({ t, settingsScope, view, surface, backToConversation }) {
|
|
4539
4661
|
if (t === void 0) throw new Error("Trae usage card requires its translation function");
|
|
4540
4662
|
/**
|
|
4541
|
-
*
|
|
4542
|
-
*
|
|
4543
|
-
*
|
|
4544
|
-
* polling effect below stay
|
|
4663
|
+
* The two FULL-BODY surfaces (the sidebar row's `main` panel and the
|
|
4664
|
+
* `settings.section` page) have no card chrome to collapse: they are always
|
|
4665
|
+
* expanded, and the collapse button is not rendered, so `setOpen` is simply
|
|
4666
|
+
* unused there. Keeping ONE `open` value lets every polling effect below stay
|
|
4667
|
+
* untouched.
|
|
4545
4668
|
*/
|
|
4546
4669
|
const pageSurface = surface === "page";
|
|
4670
|
+
const sectionSurface = surface === "section";
|
|
4671
|
+
const fullSurface = pageSurface || sectionSurface;
|
|
4547
4672
|
const [cardOpen, setCardOpen] = (0, react.useState)(view === "page");
|
|
4548
|
-
const open =
|
|
4673
|
+
const open = fullSurface || cardOpen;
|
|
4549
4674
|
/** The region whose tab is on screen; each tab is its own provider stack. */
|
|
4550
4675
|
const [activeRegion, setActiveRegion] = (0, react.useState)("cn");
|
|
4551
4676
|
/**
|
|
@@ -6768,6 +6893,30 @@ window.__ModuleLoader__.load({
|
|
|
6768
6893
|
})
|
|
6769
6894
|
]
|
|
6770
6895
|
}) : null;
|
|
6896
|
+
/**
|
|
6897
|
+
* The section head, drawn only by the `settings.section` page.
|
|
6898
|
+
*
|
|
6899
|
+
* Deliberately NOT {@link pageHead} minus a button: the settings shell already
|
|
6900
|
+
* paints the nav row (which carries the icon registered into
|
|
6901
|
+
* `settings.section.icon`), the close control and the scroll column, so a
|
|
6902
|
+
* second 40px brand mark and a second title here would say the same thing
|
|
6903
|
+
* twice in one dialog. What the shell does NOT supply is a statement of what
|
|
6904
|
+
* the page is for, which is this heading plus the one-line description.
|
|
6905
|
+
*
|
|
6906
|
+
* No back control: the shell's close button is the way out of a modal, and a
|
|
6907
|
+
* "back to conversation" in here would either duplicate it or — worse — move
|
|
6908
|
+
* the centre column behind an open dialog.
|
|
6909
|
+
*/
|
|
6910
|
+
const sectionHead = sectionSurface ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6911
|
+
className: "dsm-trae-section-head",
|
|
6912
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
6913
|
+
className: "dsm-trae-section-title",
|
|
6914
|
+
children: title
|
|
6915
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
6916
|
+
className: "dsm-trae-section-desc",
|
|
6917
|
+
children: t("row.desc")
|
|
6918
|
+
})]
|
|
6919
|
+
}) : null;
|
|
6771
6920
|
if (pageSurface) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6772
6921
|
className: "dsm-trae-page",
|
|
6773
6922
|
children: [
|
|
@@ -6782,6 +6931,17 @@ window.__ModuleLoader__.load({
|
|
|
6782
6931
|
})
|
|
6783
6932
|
]
|
|
6784
6933
|
});
|
|
6934
|
+
if (sectionSurface) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6935
|
+
className: "dsm-trae-section",
|
|
6936
|
+
children: [
|
|
6937
|
+
sectionHead,
|
|
6938
|
+
cardBody,
|
|
6939
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(BusyOverlay, {
|
|
6940
|
+
visible: saving || savingSettings,
|
|
6941
|
+
message: t("row.veilSaving")
|
|
6942
|
+
})
|
|
6943
|
+
]
|
|
6944
|
+
});
|
|
6785
6945
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
6786
6946
|
className: `dsm-plugin-card${open ? " dsm-plugin-card-open" : ""}`,
|
|
6787
6947
|
children: [
|
|
@@ -6961,12 +7121,15 @@ window.__ModuleLoader__.load({
|
|
|
6961
7121
|
* per account in the region and that account's general balance, where a row
|
|
6962
7122
|
* click switches to it.
|
|
6963
7123
|
*
|
|
6964
|
-
* The panel's header carries
|
|
6965
|
-
*
|
|
6966
|
-
*
|
|
6967
|
-
*
|
|
6968
|
-
*
|
|
6969
|
-
*
|
|
7124
|
+
* The panel's header carries the account-pool test batch, labelled 「刷新」, and
|
|
7125
|
+
* a SETTINGS pill that opens this plugin's own page in the wide Settings dialog
|
|
7126
|
+
* (`./settings-route.ts`).
|
|
7127
|
+
*
|
|
7128
|
+
* The pill was briefly removed, on the belief that no host seam could open the
|
|
7129
|
+
* dialog on a given section. That belief was wrong — the settings shell
|
|
7130
|
+
* publishes its state as the `sidebar.settings` store seat, whose `openSection(id)`
|
|
7131
|
+
* is exactly that seam — so the pill is back and now lands on this plugin's own
|
|
7132
|
+
* `settings.section` page. The route module records the finding.
|
|
6970
7133
|
*
|
|
6971
7134
|
* Two deliberate choices in that panel:
|
|
6972
7135
|
*
|
|
@@ -7135,9 +7298,8 @@ window.__ModuleLoader__.load({
|
|
|
7135
7298
|
* pool at once is how the upstream's volume limit gets tripped), so it can run
|
|
7136
7299
|
* for a minute while a credits read would be merely slow.
|
|
7137
7300
|
*
|
|
7138
|
-
* `testFailed` is its own line
|
|
7139
|
-
*
|
|
7140
|
-
* other. `testNote` carries the last outcome summary, which is what makes the
|
|
7301
|
+
* `testFailed` is its own line so the two failures cannot take turns
|
|
7302
|
+
* overwriting each other in one slot. `testNote` carries the last outcome summary, which is what makes the
|
|
7141
7303
|
* press feel answered even when the table's marks are subtle.
|
|
7142
7304
|
*/
|
|
7143
7305
|
const [testing, setTesting] = (0, react.useState)(false);
|
|
@@ -7332,8 +7494,8 @@ window.__ModuleLoader__.load({
|
|
|
7332
7494
|
* The header's settings control: open this plugin's page, then close.
|
|
7333
7495
|
*
|
|
7334
7496
|
* CLOSING IS PART OF THE JOB. The panel is portaled to `document.body` and
|
|
7335
|
-
* floats over the composer;
|
|
7336
|
-
*
|
|
7497
|
+
* floats over the composer; opening the Settings dialog does not remove it, so
|
|
7498
|
+
* leaving it open would park a credits table on top of the dialog the user
|
|
7337
7499
|
* just asked for. It is not reopened on return — a popover that comes back by
|
|
7338
7500
|
* itself after navigation is a popover the user cannot get rid of.
|
|
7339
7501
|
*
|
|
@@ -7751,6 +7913,123 @@ window.__ModuleLoader__.load({
|
|
|
7751
7913
|
});
|
|
7752
7914
|
}
|
|
7753
7915
|
//#endregion
|
|
7916
|
+
//#region src/client/settings-route.ts
|
|
7917
|
+
/**
|
|
7918
|
+
* The route from the composer credit readout to THIS plugin's settings page.
|
|
7919
|
+
*
|
|
7920
|
+
* ## The finding this module encodes (m02517)
|
|
7921
|
+
*
|
|
7922
|
+
* The 4.9.0 line deleted the panel's settings pill on the stated reasoning that
|
|
7923
|
+
* "the host exposes no client service that opens the Settings dialog on a given
|
|
7924
|
+
* section, so the pill had nowhere to go". That reasoning was WRONG, and the
|
|
7925
|
+
* owner said so: clicking through the sidebar DOES open the wide settings on a
|
|
7926
|
+
* chosen page. The route exists; it was simply not the one the old module used.
|
|
7927
|
+
*
|
|
7928
|
+
* The old module (`openBundle` → `layout.selectPanel('plugins')`) targeted the
|
|
7929
|
+
* **Plugins** page, which stopped holding this plugin's configuration the moment
|
|
7930
|
+
* it moved to its own `settings.section` page. So the previous implementation is
|
|
7931
|
+
* not restorable, and a replacement was needed — this is it.
|
|
7932
|
+
*
|
|
7933
|
+
* ## The route: the settings shell's own store seat
|
|
7934
|
+
*
|
|
7935
|
+
* `@deepseek-ai/dsh-client-ui-settings-general` registers the `sidebar.settings`
|
|
7936
|
+
* slot with a **store seat**, and that seat is the shell's public state handle:
|
|
7937
|
+
*
|
|
7938
|
+
* const shellHandle = createSettingsShellStore()
|
|
7939
|
+
* const shellInstance = shellHandle.create()
|
|
7940
|
+
* const shellStore = { ...shellHandle, create: () => shellInstance }
|
|
7941
|
+
* ctx.slots.register({ name: 'sidebar.settings', store: shellStore, … })
|
|
7942
|
+
*
|
|
7943
|
+
* `createSettingsShellStore().actions` contains exactly the four shell
|
|
7944
|
+
* mutations, and `openSection(id)` is the one that answers this button: it sets
|
|
7945
|
+
* `activeId = id` AND `open = true`, i.e. "open the Settings dialog on the
|
|
7946
|
+
* named section" in a single call. `id` is the same string this plugin already
|
|
7947
|
+
* registers the page under (`TRAE_SETTINGS_ID`), so no translation is needed.
|
|
7948
|
+
*
|
|
7949
|
+
* Note the `create: () => shellInstance` override. A bare handle would mint a
|
|
7950
|
+
* FRESH instance per `create()` call — a private copy whose `openSection` moves
|
|
7951
|
+
* a dialog nobody renders. The shell pins the seat to its one live instance
|
|
7952
|
+
* precisely so a reader of the seat gets the dialog the user actually sees. This
|
|
7953
|
+
* module is that reader.
|
|
7954
|
+
*
|
|
7955
|
+
* ## Why reading the seat is legitimate, and not a private reach-through
|
|
7956
|
+
*
|
|
7957
|
+
* - `StoredEntry.store` is a documented public field: "Declared store seat
|
|
7958
|
+
* (instance resolution and lifecycle live with the host machinery)".
|
|
7959
|
+
* - `slots.entries(key)` is a documented public service method, and reading a
|
|
7960
|
+
* seat through it is the pattern the harness's OWN client tests use for this
|
|
7961
|
+
* exact store, e.g. `ui-shortcuts/tests/commands.client.spec.ts`:
|
|
7962
|
+
* `ctx.slots.entries('sidebar.settings')[0].store.create()`.
|
|
7963
|
+
* - No other id-targeted seam exists. `settings.launcher` hands `openSettings`
|
|
7964
|
+
* with no id, `settings.section` receives only `close`, and `openSection` is
|
|
7965
|
+
* otherwise given only to `settings.onboarding` registrants — reaching it
|
|
7966
|
+
* from there would mean registering a fake first-run step, which is far more
|
|
7967
|
+
* intrusive than reading the seat the shell deliberately published.
|
|
7968
|
+
*
|
|
7969
|
+
* ## Why the probe is at PRESS time
|
|
7970
|
+
*
|
|
7971
|
+
* The seat belongs to ANOTHER bundle whose apply order is not ours to assume.
|
|
7972
|
+
* Probing once during `apply()` would capture `undefined` on a page whose
|
|
7973
|
+
* settings bundle applied later, leaving the button dead for the whole session
|
|
7974
|
+
* with nothing to re-evaluate it. `entries()` answers `[]` for a slot that is
|
|
7975
|
+
* not declared yet, so probing per press is free of state and always current.
|
|
7976
|
+
*
|
|
7977
|
+
* ## Why `false` rather than an exception
|
|
7978
|
+
*
|
|
7979
|
+
* The caller is a popover button. There is no supported host line lacking the
|
|
7980
|
+
* shell, but "cannot" must not become a thrown error inside a click handler on
|
|
7981
|
+
* a user's machine; it becomes a `false` the caller reports, and a button that
|
|
7982
|
+
* stays honest about it.
|
|
7983
|
+
*/
|
|
7984
|
+
/**
|
|
7985
|
+
* The `settings.section` id this plugin registers its page under.
|
|
7986
|
+
*
|
|
7987
|
+
* One spelling, in one place: the page registration, the nav row and
|
|
7988
|
+
* `openSection(id)` must agree, or the dialog opens on the shell's fallback
|
|
7989
|
+
* (first) section instead of this plugin's — silently, since an unknown id is
|
|
7990
|
+
* not an error to the store.
|
|
7991
|
+
*/
|
|
7992
|
+
const TRAE_SETTINGS_ID = "dsh-connect-trae";
|
|
7993
|
+
/** The shipped slot whose store seat is the settings shell's state handle. */
|
|
7994
|
+
const SETTINGS_SLOT = "sidebar.settings";
|
|
7995
|
+
/**
|
|
7996
|
+
* Open this plugin's settings page — the wide Settings dialog, on this
|
|
7997
|
+
* plugin's section.
|
|
7998
|
+
*
|
|
7999
|
+
* @param get - the entry's soft service probe (`ctx.get`), which returns
|
|
8000
|
+
* `undefined` for an absent service instead of throwing. Called HERE, on every
|
|
8001
|
+
* invocation, so a service that appears after `apply` is still found.
|
|
8002
|
+
* @returns whether a route was taken. `false` means the caller's control should
|
|
8003
|
+
* report that this host has no way there — see the module note.
|
|
8004
|
+
*/
|
|
8005
|
+
function openPluginSettingsPage(get) {
|
|
8006
|
+
const slots = get("slots");
|
|
8007
|
+
if (slots === void 0 || typeof slots.entries !== "function") return false;
|
|
8008
|
+
let seat;
|
|
8009
|
+
try {
|
|
8010
|
+
seat = slots.entries(SETTINGS_SLOT)[0]?.store;
|
|
8011
|
+
} catch {
|
|
8012
|
+
return false;
|
|
8013
|
+
}
|
|
8014
|
+
if (seat === null || typeof seat !== "object") return false;
|
|
8015
|
+
const handle = seat;
|
|
8016
|
+
if (typeof handle.create !== "function") return false;
|
|
8017
|
+
try {
|
|
8018
|
+
const instance = handle.create();
|
|
8019
|
+
const openSection = instance?.actions?.openSection;
|
|
8020
|
+
if (typeof openSection === "function") {
|
|
8021
|
+
openSection(TRAE_SETTINGS_ID);
|
|
8022
|
+
return true;
|
|
8023
|
+
}
|
|
8024
|
+
const open = instance?.actions?.open;
|
|
8025
|
+
if (typeof open === "function") {
|
|
8026
|
+
open();
|
|
8027
|
+
return true;
|
|
8028
|
+
}
|
|
8029
|
+
} catch {}
|
|
8030
|
+
return false;
|
|
8031
|
+
}
|
|
8032
|
+
//#endregion
|
|
7754
8033
|
//#region src/client/locales.ts
|
|
7755
8034
|
/**
|
|
7756
8035
|
* Plugin-card copy registered under the settings.trae locale namespace.
|
|
@@ -9016,92 +9295,6 @@ window.__ModuleLoader__.load({
|
|
|
9016
9295
|
});
|
|
9017
9296
|
}
|
|
9018
9297
|
//#endregion
|
|
9019
|
-
//#region src/client/settings-route.ts
|
|
9020
|
-
/**
|
|
9021
|
-
* The route from the composer credit readout to THIS plugin's settings page.
|
|
9022
|
-
*
|
|
9023
|
-
* ## Why a module and not three lines inside `apply`
|
|
9024
|
-
*
|
|
9025
|
-
* The entry (`src/client/index.tsx`) imports browser-only DSH packages the Node
|
|
9026
|
-
* test environment cannot load, so anything worth asserting on has to live
|
|
9027
|
-
* outside it — the same seam `panel-switch.ts` provides for the sidebar opt-in.
|
|
9028
|
-
* The spec then exercises the REAL decision instead of a mirror of it.
|
|
9029
|
-
*
|
|
9030
|
-
* ## Why two routes
|
|
9031
|
-
*
|
|
9032
|
-
* On this host line a plugin's settings are not a "page in Settings": the
|
|
9033
|
-
* Plugins page owns them, and each bundle's page (`plugins.bundle.config` /
|
|
9034
|
-
* `plugins.row.config`) IS the configuration surface — Settings keeps only the
|
|
9035
|
-
* read-only built-in inventory. So "open the plugin settings" means "open the
|
|
9036
|
-
* Plugins page on this bundle", and the supported way to ask for that is the
|
|
9037
|
-
* `pluginNavigation` service the Plugins page provides: `openBundle(packageName)`
|
|
9038
|
-
* selects that page without touching the Session.
|
|
9039
|
-
*
|
|
9040
|
-
* `pluginNavigation` is provided by `@deepseek-ai/dsh-client-ui-plugin-manager`,
|
|
9041
|
-
* NOT by the shell. A profile can be composed without it, and plugin load order
|
|
9042
|
-
* is not something a contributor may assume — so the probe happens at CALL time
|
|
9043
|
-
* rather than at registration time. Probing once in `apply` would capture
|
|
9044
|
-
* `undefined` on a page whose Plugins bundle simply applied after ours, and the
|
|
9045
|
-
* button would be dead for the rest of the session with nothing to re-evaluate it.
|
|
9046
|
-
*
|
|
9047
|
-
* The fallback reaches the SAME page through the lower-level `layout` service.
|
|
9048
|
-
* `selectPanel('plugins')` is the shipped main key of the Plugins page (the
|
|
9049
|
-
* plugin-manager bundle registers `sidebar.panellist` id / `main` key
|
|
9050
|
-
* `'plugins'`), so a host without `pluginNavigation` still lands the user where
|
|
9051
|
-
* the settings are, one click from the row. It is a fallback and not the primary
|
|
9052
|
-
* route deliberately: `'plugins'` is another package's private key, while
|
|
9053
|
-
* `openBundle` is that package's published seam. `selectPanel` THROWS when the
|
|
9054
|
-
* key is not registered (Plugins page disabled, trimmed profile), so the call is
|
|
9055
|
-
* guarded rather than trusted.
|
|
9056
|
-
*
|
|
9057
|
-
* ## Why `false` rather than an exception
|
|
9058
|
-
*
|
|
9059
|
-
* The caller is a popover button. There is no supported host line lacking both
|
|
9060
|
-
* routes, but "cannot" must not become a thrown error inside a click handler on
|
|
9061
|
-
* a user's machine; it becomes a `false` the caller reports, and a button that
|
|
9062
|
-
* stays honest about it.
|
|
9063
|
-
*/
|
|
9064
|
-
/**
|
|
9065
|
-
* The package name the Plugins page knows this plugin by.
|
|
9066
|
-
*
|
|
9067
|
-
* `openBundle` matches the inventory's `pkg.name` by EXACT equality, and that
|
|
9068
|
-
* name is the profile's package name — the npm package name — not the Loader
|
|
9069
|
-
* entry id (which on a live Desktop profile can be `include:dsh-connect-trae`).
|
|
9070
|
-
* This is the same reason the card registers under every spelling the host has
|
|
9071
|
-
* been observed to dispatch: the comparison is exact and the deployment picks
|
|
9072
|
-
* the spelling. The inventory side of that comparison is the package name in
|
|
9073
|
-
* every composition observed, so that is what `openBundle` is asked for.
|
|
9074
|
-
*
|
|
9075
|
-
* Deliberately ONE name, not a list: `openBundle` selects a target and an
|
|
9076
|
-
* unknown target renders the LIST, so a second call with a spelling the
|
|
9077
|
-
* inventory does not hold would undo the first one's navigation.
|
|
9078
|
-
*/
|
|
9079
|
-
const TRAE_PACKAGE_NAME = "dsh-connect-trae";
|
|
9080
|
-
/** The shipped `main` / `sidebar.panellist` key of the Plugins page. */
|
|
9081
|
-
const PLUGINS_PANEL_ID = "plugins";
|
|
9082
|
-
/**
|
|
9083
|
-
* Open this plugin's settings page.
|
|
9084
|
-
*
|
|
9085
|
-
* @param get - the entry's soft service probe (`ctx.get`), which returns
|
|
9086
|
-
* `undefined` for an absent service instead of throwing. Called HERE, on every
|
|
9087
|
-
* invocation, so a service that appears after `apply` is still found.
|
|
9088
|
-
* @returns whether a route was taken. `false` means the caller's control should
|
|
9089
|
-
* report that this host has no way there — see the module note.
|
|
9090
|
-
*/
|
|
9091
|
-
function openPluginSettingsPage(get) {
|
|
9092
|
-
const navigation = get("pluginNavigation");
|
|
9093
|
-
if (navigation !== void 0 && typeof navigation.openBundle === "function") try {
|
|
9094
|
-
navigation.openBundle(TRAE_PACKAGE_NAME);
|
|
9095
|
-
return true;
|
|
9096
|
-
} catch {}
|
|
9097
|
-
const layout = get("layout");
|
|
9098
|
-
if (layout !== void 0 && typeof layout.selectPanel === "function") try {
|
|
9099
|
-
layout.selectPanel(PLUGINS_PANEL_ID);
|
|
9100
|
-
return true;
|
|
9101
|
-
} catch {}
|
|
9102
|
-
return false;
|
|
9103
|
-
}
|
|
9104
|
-
//#endregion
|
|
9105
9298
|
//#region src/client/index.tsx
|
|
9106
9299
|
/** Stable browser-plugin name. */
|
|
9107
9300
|
const name = "dsh-connect-trae-client";
|
|
@@ -9208,11 +9401,11 @@ window.__ModuleLoader__.load({
|
|
|
9208
9401
|
/**
|
|
9209
9402
|
* The composer panel's route to this plugin's own settings page.
|
|
9210
9403
|
*
|
|
9211
|
-
*
|
|
9212
|
-
*
|
|
9213
|
-
*
|
|
9214
|
-
*
|
|
9215
|
-
*
|
|
9404
|
+
* Deliberately a CLOSURE over `softGet` rather than a resolved service: the
|
|
9405
|
+
* seam it uses (the settings shell's `sidebar.settings` store seat) belongs
|
|
9406
|
+
* to another bundle whose apply order is not ours to assume, so the probe
|
|
9407
|
+
* happens on every press and a shell that appears later is still found. See
|
|
9408
|
+
* `settings-route.ts` for the finding that established which seam this is.
|
|
9216
9409
|
*/
|
|
9217
9410
|
const onOpenSettings = () => openPluginSettingsPage(softGet);
|
|
9218
9411
|
const forms = softGet("configForms");
|
|
@@ -9284,19 +9477,90 @@ window.__ModuleLoader__.load({
|
|
|
9284
9477
|
forms?.describe().subscribe?.(rebind);
|
|
9285
9478
|
return scope;
|
|
9286
9479
|
})();
|
|
9287
|
-
|
|
9288
|
-
|
|
9289
|
-
|
|
9290
|
-
|
|
9291
|
-
|
|
9292
|
-
|
|
9293
|
-
|
|
9294
|
-
|
|
9295
|
-
|
|
9296
|
-
|
|
9480
|
+
/**
|
|
9481
|
+
* The configuration card, now drawn as its own `settings.section` page.
|
|
9482
|
+
*
|
|
9483
|
+
* WHY THE SEAT MOVED. It used to register into the Plugins page's
|
|
9484
|
+
* `plugins.bundle.config` / `plugins.row.config` slots, which put the whole
|
|
9485
|
+
* configuration inside the plugin's market detail page, below its README.
|
|
9486
|
+
* That page's job is to describe and install a package; a page that both
|
|
9487
|
+
* advertises and configures makes the user scroll past marketing to reach a
|
|
9488
|
+
* checkbox. `settings.section` is the shell's seat for "this plugin owns a
|
|
9489
|
+
* settings page", addressed by the same id the nav row uses.
|
|
9490
|
+
*
|
|
9491
|
+
* WHY `id` AND `order: 30`. The id is this plugin's package name, and it is
|
|
9492
|
+
* the SAME string the nav glyph below registers under, because the shell
|
|
9493
|
+
* renders `settings.section.icon` filtered by `{ only: section.id }` — an
|
|
9494
|
+
* icon whose id does not match never appears and the shell's default glyph
|
|
9495
|
+
* silently stays. `order: 30` places this page after the shipped sections
|
|
9496
|
+
* (account -10, general 0, models 10, plugins 15, agent-presets 20) and
|
|
9497
|
+
* alongside the other third-party pages, without colliding with a built-in.
|
|
9498
|
+
*
|
|
9499
|
+
* WHY NO CAPABILITY PROBE. `slots.inject()` already IS the probe: it runs
|
|
9500
|
+
* the callback now when the slot is declared and subscribes otherwise,
|
|
9501
|
+
* doing nothing at all on a host that never declares `settings.section`.
|
|
9502
|
+
* Wrapping it in a `typeof` test on some accessor is how a page ends up
|
|
9503
|
+
* never registering anywhere, silently — the failure mode this plugin's
|
|
9504
|
+
* sibling shipped twice. Let the host answer.
|
|
9505
|
+
*
|
|
9506
|
+
* The page and its glyph are registered from ONE `inject` callback and both
|
|
9507
|
+
* disposers are returned as a pair: the glyph is only meaningful while the
|
|
9508
|
+
* page's id exists, so the pairing is structural rather than a convention,
|
|
9509
|
+
* and Cordis disposes both with this fiber.
|
|
9510
|
+
*/
|
|
9511
|
+
const traeSettingsId = TRAE_SETTINGS_ID;
|
|
9512
|
+
const registerSettingsPage = () => {
|
|
9513
|
+
registerSlotEntry(ctx, "settings.section#dsh-connect-trae", "settings.section", () => {
|
|
9514
|
+
const disposers = [];
|
|
9515
|
+
try {
|
|
9516
|
+
disposers.push(ctx.slots.register({
|
|
9517
|
+
name: "settings.section",
|
|
9518
|
+
id: traeSettingsId,
|
|
9519
|
+
order: 30,
|
|
9520
|
+
label: () => t("nav.panel"),
|
|
9521
|
+
locale: namespace,
|
|
9522
|
+
inject: () => ({
|
|
9523
|
+
t,
|
|
9524
|
+
settingsScope,
|
|
9525
|
+
surface: "section"
|
|
9526
|
+
})
|
|
9527
|
+
}, TraeUsageCard));
|
|
9528
|
+
/**
|
|
9529
|
+
* The nav glyph, addressed by the SAME id as the page.
|
|
9530
|
+
*
|
|
9531
|
+
* The host renders this cell with `{ size, active }` inside its own
|
|
9532
|
+
* button and label, so only the picture belongs here. Same shape as
|
|
9533
|
+
* the sidebar row's glyph (a brand-mark `<img>`), because it is the
|
|
9534
|
+
* same identity in a second place.
|
|
9535
|
+
*
|
|
9536
|
+
* Its own `inject`: `settings.section.icon` is a SEPARATE slot that a
|
|
9537
|
+
* host line may not declare, and the page must still appear when only
|
|
9538
|
+
* `settings.section` exists. A missing glyph is the shell's default
|
|
9539
|
+
* gear — a degraded look, not a missing page.
|
|
9540
|
+
*/
|
|
9541
|
+
try {
|
|
9542
|
+
disposers.push(ctx.slots.inject("settings.section.icon", () => ctx.slots.register({
|
|
9543
|
+
name: "settings.section.icon",
|
|
9544
|
+
id: traeSettingsId,
|
|
9545
|
+
locale: namespace
|
|
9546
|
+
}, SettingsSectionIcon)));
|
|
9547
|
+
} catch (error) {
|
|
9548
|
+
console.error("[dsh-connect-trae] settings nav icon failed to register (page unaffected):", error);
|
|
9549
|
+
}
|
|
9550
|
+
} catch (error) {
|
|
9551
|
+
for (const dispose of disposers) try {
|
|
9552
|
+
dispose();
|
|
9553
|
+
} catch {}
|
|
9554
|
+
throw error;
|
|
9555
|
+
}
|
|
9556
|
+
return () => {
|
|
9557
|
+
for (const dispose of disposers) try {
|
|
9558
|
+
dispose();
|
|
9559
|
+
} catch {}
|
|
9560
|
+
};
|
|
9561
|
+
}, "settings section page");
|
|
9297
9562
|
};
|
|
9298
|
-
|
|
9299
|
-
registerCard("plugins.row.config", "dsh-connect-trae#dsh-connect-trae");
|
|
9563
|
+
registerSettingsPage();
|
|
9300
9564
|
/**
|
|
9301
9565
|
* Credit readout in the composer row (`conversation.input.right`, a
|
|
9302
9566
|
* session-scope list slot rendered immediately before the model picker).
|
|
@@ -9382,6 +9646,35 @@ window.__ModuleLoader__.load({
|
|
|
9382
9646
|
});
|
|
9383
9647
|
}
|
|
9384
9648
|
/**
|
|
9649
|
+
* The nav glyph for the `settings.section` page.
|
|
9650
|
+
*
|
|
9651
|
+
* A separate component from {@link TraePanelIcon} even though it draws the
|
|
9652
|
+
* same picture, because the two slots pass DIFFERENT owner props and a shared
|
|
9653
|
+
* component would have to paper over that: `sidebar.panellist` sends
|
|
9654
|
+
* `{ size, active }` on a row the shell paints, `settings.section.icon` sends
|
|
9655
|
+
* `{ size, active }` inside the shell's own nav button, and the second one
|
|
9656
|
+
* also has to survive `size` being absent (the shell always sends it, but a
|
|
9657
|
+
* host line that renames the prop must degrade to the default rather than
|
|
9658
|
+
* divide by `undefined`).
|
|
9659
|
+
*
|
|
9660
|
+
* `active` is applied as a slight opacity step instead of a colour: the
|
|
9661
|
+
* brand PNG carries its own colours, and recolouring someone else's mark to
|
|
9662
|
+
* show selection would misrepresent the product. The shell already paints
|
|
9663
|
+
* the selected row.
|
|
9664
|
+
*/
|
|
9665
|
+
function SettingsSectionIcon(props) {
|
|
9666
|
+
const edge = typeof props.size === "number" && props.size > 0 ? props.size : 16;
|
|
9667
|
+
return (0, react.createElement)("img", {
|
|
9668
|
+
className: "dsm-trae-section-icon",
|
|
9669
|
+
src: TRAE_PLUGIN_ICON,
|
|
9670
|
+
alt: "",
|
|
9671
|
+
width: edge,
|
|
9672
|
+
height: edge,
|
|
9673
|
+
style: { opacity: props.active === false ? .75 : 1 },
|
|
9674
|
+
"aria-hidden": "true"
|
|
9675
|
+
});
|
|
9676
|
+
}
|
|
9677
|
+
/**
|
|
9385
9678
|
* Back to the conversation, for the page's header.
|
|
9386
9679
|
*
|
|
9387
9680
|
* Soft-probed: `layout` is a host service this entry deliberately does not
|
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.9.1",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|