dsh-connect-trae 3.0.0 → 3.0.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 +18 -0
- package/README.md +1 -0
- package/lib/client.js +83 -0
- package/lib/index.d.ts +110 -5
- package/lib/index.js +338 -171
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.0.1 (2026-10-10)
|
|
4
|
+
|
|
5
|
+
### 修复
|
|
6
|
+
|
|
7
|
+
- **跟随 Trae 当前登录的账号(issue #29)**。在 Trae 桌面端**退出换登录**后,插件此前一直绑定**换号前**的账号继续计费,而同族插件 `dsh-connect-workbuddy` 会自动跟随 App 的新登录;报告人需要每次手动打开账号下拉切一次。根因是两处:`TraeCredentialStore.preferred()` 只取 `credentials[0]`(**不认哪个来源是 App 的 live 登录**),`current()` 在 `accountId` 有值时**严格绑定、find 不命中也不回退**(刻意为之,见 `src/auth.ts` 的注释——避免悄悄给用户换一个计费账号)。
|
|
8
|
+
|
|
9
|
+
分两条路径修,**都不改变未勾选用户的任何行为**:
|
|
10
|
+
|
|
11
|
+
1. **`preferred()` 改为 live 登录优先**(`source === 'desktop'` 的凭据先于插件自己的刷新副本)。只改**排序**、不改 `readAll()` 的取值去重——同一账号仍保留更长寿的副本,否则会退回「有效 token 在隔壁文件却读成过期」的旧问题(`src/auth.ts:371-384` 的注释)。这修好「从未显式选择」的默认路径,与 workbuddy 对齐。
|
|
12
|
+
2. **显式绑定的跟随**:新增只读方法 `liveLogin()` 报告「App 当前登录 ≠ 绑定账号」的**不一致**(纯读,绝不静默换绑);卡片在账号下拉旁给出**提示行 + 一键切换**(复用与下拉相同的 `switchAccount` 写入,保持单一写入路径);新增配置字段 `followAppLogin`(volatile,默认**关闭**)让宿主在每次配置生效时自动重绑——**默认关**是因为自动换绑意味着换人计费,必须由用户主动勾选,未勾选时提示行就是手动通道。
|
|
13
|
+
|
|
14
|
+
配套:usage 文档新增 `appLogin` 字段(仅在不一致时携带,健康文档不加字段、扫描失败也不拖垮整个文档),双语文案 `row.appLoginMismatch` / `row.appLoginFollow` / `row.followAppLogin`,README「多账号切换」补充该行为说明。
|
|
15
|
+
|
|
16
|
+
### 测试
|
|
17
|
+
|
|
18
|
+
- 新增 5 个:`preferred()` 优先 App 登录(旧 own 副本仍列出但不默认选中)、`liveLogin()` 三种不一致/一致状态、usage 文档 `appLogin` 的携带/省略/扫描失败降级;集成测试的 `MemorySettings` 补上 `revision` 冲突门(建模真实 `SettingsConflictError`)与新 volatile 字段 `followAppLogin`。
|
|
19
|
+
- `pnpm run check`(typecheck + 全量测试 + build)通过。
|
|
20
|
+
|
|
3
21
|
## 3.0.0 (2026-10-07)
|
|
4
22
|
|
|
5
23
|
> **积分从「侧边栏常驻一行」改为「输入框工具栏的按供应商读数 + 账号表格浮层」。**
|
package/README.md
CHANGED
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
- **目录与勾选按区域隔离** —— 国内版与国际版各一套模型目录、勾选、图片开关与上下文预算,两个供应商各自读各自的槽位。
|
|
42
42
|
- **识图能力跟随 Trae 自身的标注** —— Trae 把某个模型标为 `multimodal` 时,插件直接按多模态对外声明,无需你在卡片上逐一手勾(issue #16)。判据是「**你手动勾选的 ∪ Trae 标注多模态的**」:Trae 明确标 `false` 或未标注的模型保持纯文本,你可以照旧手动为它们打开图片输入。卡片上被自动开启的那一行显示为已勾选且只读,并说明原因。详见下方「图片输入(识图)」。
|
|
43
43
|
- **多账号切换** —— 支持重新读取 Token 列表并选择账号;Token 不写入 DSH 设置。**你的选择会被记住并长期生效**:插件不会因为某个账号额度用尽、或检测到另一个可用账号就自作主张替你切换(换账号意味着换人计费)。所以当绑定的账号额度耗尽时,卡片会**明确告诉你**,并把本机其他还有额度的账号连同其可用积分数列出来,由你一键切换——而不是让面板一直空着、也不自动把你切走。「刷新账号与 Token」每次都会回报结果(找到几个账号、当前仍绑定谁),不会再「点了没反应」。下拉里显示的是产品名(`Trae` / `TRAE SOLO` / 国际版),便于分辨凭据来自哪个安装。
|
|
44
|
+
- **跟随 Trae 当前登录的账号** —— 你在 Trae 桌面端**退出并换登录**后,卡片会检测到「插件绑定的账号」与「Trae 现在登录的账号」不一致,并给出一行提示与**一键切换**(复用账号下拉的同一次写入,不引入第二条切换路径)。想让插件**自动跟随**,可打开设置卡片里的「跟随 Trae 当前登录的账号」开关:此后每次配置生效都会把 `accounts.<region>` 重绑到 App 当前登录的账号。**该开关默认关闭**——已有配置保持原来的严格绑定,自动换绑意味着换人计费,必须由你主动勾选;未勾选时上面那行提示就是你的手动通道。(issue #29)
|
|
44
45
|
- **只读用量与模型管理** —— 国内版查看 Work 积分与通用积分,国际版查看订阅/试用状态;刷新/启用 Trae 模型;只读查询不消耗额度。
|
|
45
46
|
- **一键测试启用模型** —— 给**已启用**的每个模型发一条最小消息(一句 `ping`、不带工具),逐个告诉你能不能真的应答:**可用** / **需要更高档位**(Trae `1005`,你的套餐不覆盖)/ **上游不提供**(`4001`/`4011`,SOLO 通道没有这个模型)/ **被限流** / **登录凭据被拒** / **本次未测出**。最后一项是一等公民:超时、断连、代理、5xx **一律不算「不可用」**——只有上游明确的拒绝才算负面结论。**这是插件里唯一消耗额度的操作**(签到改变的是账号状态,不消耗),只在你点击时运行,结果只留在当前会话里不写入设置。
|
|
46
47
|
- **每日签到领取** —— 国内版卡片上一键领取当日签到积分(按钮显示每日奖励,该账号已领取则显示「今日已领取」并禁用);这是插件里**唯一会改变账号状态**的操作,由你主动点击触发。Trae 的签到按「**每台设备每天一次**」限制:切换账号后如果本机今天的名额已经用掉(无论是哪个账号用的),当前账号在本机当天无法再签,卡片会说明原因而不是谎报「已领取」。国际版没有签到活动,因此不显示该按钮。
|
package/lib/client.js
CHANGED
|
@@ -103,6 +103,20 @@ window.__ModuleLoader__.load({
|
|
|
103
103
|
return typeof accounts === "object" && accounts !== null ? accounts : {};
|
|
104
104
|
}
|
|
105
105
|
/**
|
|
106
|
+
* Whether the host-side "follow the Trae app's sign-in" switch is on (issue
|
|
107
|
+
* #29). Absent reads as OFF, so an existing configuration keeps the strict
|
|
108
|
+
* bind it shipped with and the switch is an opt-in — the same opt-out
|
|
109
|
+
* semantics as {@link regionEnabledOf}, for the same reason: a config written
|
|
110
|
+
* before this field existed cannot ask for behavior it never declared.
|
|
111
|
+
*
|
|
112
|
+
* Reads the whole settings section (or the bare field); deep-unwraps first
|
|
113
|
+
* because on DSH 0.1.7 a volatile field arrives as a `{get(): T}` live
|
|
114
|
+
* reference, whose `typeof === 'object'` would otherwise pass a naive check.
|
|
115
|
+
*/
|
|
116
|
+
function followAppLoginEnabled(configured) {
|
|
117
|
+
return unwrapVolatileDeep(configured)?.followAppLogin === true;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
106
120
|
* Build the next `regions` settings value for the card's save. The write
|
|
107
121
|
* targets ONLY the signed-in account's region slot; every other region's slot
|
|
108
122
|
* is carried over untouched, so switching accounts never clobbers the other
|
|
@@ -822,6 +836,36 @@ window.__ModuleLoader__.load({
|
|
|
822
836
|
}
|
|
823
837
|
};
|
|
824
838
|
/**
|
|
839
|
+
* Follow the Trae app's current sign-in from the hint row (issue #29).
|
|
840
|
+
*
|
|
841
|
+
* One click, and it goes through the SAME `switchAccount` write the account
|
|
842
|
+
* dropdown uses — so the card has exactly one writer for `accounts`, the Host
|
|
843
|
+
* sees the change through `loader/volatile-update` like every other card
|
|
844
|
+
* write, and there is no second settings path to keep in sync. The app's
|
|
845
|
+
* sign-in is `status.appLogin`, which the Host only reports when it differs
|
|
846
|
+
* from the bound account, so a click here always moves the binding to what
|
|
847
|
+
* the app is actually logged into.
|
|
848
|
+
*/
|
|
849
|
+
const followAppLogin = async () => {
|
|
850
|
+
if (status.status !== "signed-in" || status.appLogin === void 0) return;
|
|
851
|
+
await switchAccount(status.appLogin.id);
|
|
852
|
+
};
|
|
853
|
+
/**
|
|
854
|
+
* Toggle the follow-the-app switch (`followAppLogin`, issue #29): the Host's
|
|
855
|
+
* automatic rebind follows this field, so the card only commits it. OFF by
|
|
856
|
+
* default in the schema; an existing config keeps the strict bind it shipped
|
|
857
|
+
* with until the user ticks this.
|
|
858
|
+
*/
|
|
859
|
+
const toggleFollowAppLogin = async (enabled) => {
|
|
860
|
+
if (settingsScope === void 0 || settingsScope.getSnapshot().writable !== true) return;
|
|
861
|
+
setWriteError(void 0);
|
|
862
|
+
try {
|
|
863
|
+
await writeSettingsField(settingsScope, "followAppLogin", enabled, (readBack) => followAppLoginEnabled(readBack) === enabled);
|
|
864
|
+
} catch (error) {
|
|
865
|
+
if (mounted.current) setWriteError(error instanceof Error ? error.message : t("row.requestFailed"));
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
/**
|
|
825
869
|
* Claim today's check-in reward for the active tab's region.
|
|
826
870
|
*
|
|
827
871
|
* The Host route owns every guard that matters — POST only, loopback origin
|
|
@@ -1229,6 +1273,21 @@ window.__ModuleLoader__.load({
|
|
|
1229
1273
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.showPointsInMainUi") })]
|
|
1230
1274
|
})
|
|
1231
1275
|
}),
|
|
1276
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1277
|
+
className: "dsm-trae-mainui-switch",
|
|
1278
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1279
|
+
className: "dsm-trae-mainui-switch-label",
|
|
1280
|
+
title: t("row.followAppLoginHint"),
|
|
1281
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1282
|
+
type: "checkbox",
|
|
1283
|
+
checked: followAppLoginEnabled(settingsValue),
|
|
1284
|
+
disabled: settingsScope?.getSnapshot().writable !== true,
|
|
1285
|
+
onChange: (event) => {
|
|
1286
|
+
toggleFollowAppLogin(event.currentTarget.checked);
|
|
1287
|
+
}
|
|
1288
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.followAppLogin") })]
|
|
1289
|
+
})
|
|
1290
|
+
}),
|
|
1232
1291
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1233
1292
|
className: "dsm-trae-usage-account",
|
|
1234
1293
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1301,6 +1360,22 @@ window.__ModuleLoader__.load({
|
|
|
1301
1360
|
children: t("row.creditsAlternativeSwitch")
|
|
1302
1361
|
})]
|
|
1303
1362
|
}) : null,
|
|
1363
|
+
status.status === "signed-in" && status.appLogin !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1364
|
+
className: "dsm-trae-usage-alternative",
|
|
1365
|
+
role: "status",
|
|
1366
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1367
|
+
className: "dsm-trae-usage-alternative-text",
|
|
1368
|
+
children: t("row.appLoginMismatch", { account: status.appLogin.accountName })
|
|
1369
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1370
|
+
type: "button",
|
|
1371
|
+
className: "dsm-trae-usage-switch-button",
|
|
1372
|
+
disabled: switchingAccount || settingsScope?.getSnapshot()?.writable !== true,
|
|
1373
|
+
onClick: () => {
|
|
1374
|
+
followAppLogin();
|
|
1375
|
+
},
|
|
1376
|
+
children: t("row.appLoginFollow")
|
|
1377
|
+
})]
|
|
1378
|
+
}) : null,
|
|
1304
1379
|
rescanNote === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1305
1380
|
className: "dsm-trae-usage-note",
|
|
1306
1381
|
role: "status",
|
|
@@ -2285,6 +2360,10 @@ window.__ModuleLoader__.load({
|
|
|
2285
2360
|
"row.creditsExhausted": "The bound account ({account}) has run out of credit",
|
|
2286
2361
|
"row.creditsAlternative": "Another account on this machine still has credit: {accounts}",
|
|
2287
2362
|
"row.creditsAlternativeSwitch": "Switch to it",
|
|
2363
|
+
"row.appLoginMismatch": "Trae is signed in to {account}, but this plugin is still bound to the previous account",
|
|
2364
|
+
"row.appLoginFollow": "Switch to it",
|
|
2365
|
+
"row.followAppLogin": "Follow the account Trae is signed in to",
|
|
2366
|
+
"row.followAppLoginHint": "When you switch accounts in the Trae desktop app, rebind to the new sign-in automatically instead of staying on the previous one. Off by default: an existing binding is kept until you tick this.",
|
|
2288
2367
|
"row.modelsTestEmpty": "Enable at least one model first.",
|
|
2289
2368
|
"row.modelsTestError": "Test failed: {message}",
|
|
2290
2369
|
"row.modelsTestDone": "{available} of {total} answered.",
|
|
@@ -2401,6 +2480,10 @@ window.__ModuleLoader__.load({
|
|
|
2401
2480
|
"row.creditsExhausted": "当前绑定账号({account})额度已用尽",
|
|
2402
2481
|
"row.creditsAlternative": "本机还有可用账号:{accounts}",
|
|
2403
2482
|
"row.creditsAlternativeSwitch": "切换到它",
|
|
2483
|
+
"row.appLoginMismatch": "Trae 当前登录的是 {account},但本插件仍绑定在上一个账号",
|
|
2484
|
+
"row.appLoginFollow": "切换过去",
|
|
2485
|
+
"row.followAppLogin": "跟随 Trae 当前登录的账号",
|
|
2486
|
+
"row.followAppLoginHint": "在 Trae 桌面端切换登录后,自动换绑到新登录的账号,而不是停留在旧账号。默认关闭:已有绑定保持不变,直到你勾选它。",
|
|
2404
2487
|
"row.modelsTestEmpty": "请先启用至少一个模型。",
|
|
2405
2488
|
"row.modelsTestError": "测试失败:{message}",
|
|
2406
2489
|
"row.modelsTestDone": "{total} 个里有 {available} 个正常应答。",
|
package/lib/index.d.ts
CHANGED
|
@@ -475,12 +475,35 @@ declare class TraeCredentialStore {
|
|
|
475
475
|
selectAccount(accountId: string | undefined): void;
|
|
476
476
|
candidates(): TraeStorageCandidate[];
|
|
477
477
|
/**
|
|
478
|
-
* Deterministic default when no account is explicitly selected: the
|
|
479
|
-
*
|
|
480
|
-
*
|
|
481
|
-
*
|
|
478
|
+
* Deterministic default when no account is explicitly selected: the account
|
|
479
|
+
* the Trae desktop app is signed in to RIGHT NOW, else the first discovered
|
|
480
|
+
* one. This is NOT credit-seeking — it never reorders accounts to find one
|
|
481
|
+
* with general credits. The plugin bills exactly the account the user
|
|
482
|
+
* selected, the app's current sign-in when nothing has been selected, and
|
|
483
|
+
* only then the first account.
|
|
484
|
+
*
|
|
485
|
+
* The app's live sign-in outranks the plugin's own refreshed copies (issue
|
|
486
|
+
* #29): after the user switches accounts in Trae, the desktop storage file
|
|
487
|
+
* holds the NEW login while the plugin's copy of the old one still resolves,
|
|
488
|
+
* so a bare `credentials[0]` kept defaulting to a sign-in the app has
|
|
489
|
+
* abandoned — and `dsh-connect-workbuddy`, whose `preferred()` ranks the live
|
|
490
|
+
* file first, followed the app instead. Ordering only: the VALUE behind an
|
|
491
|
+
* account id is still whichever copy expires later (`readAll`), so a refresh
|
|
492
|
+
* the plugin performed is never discarded by this preference.
|
|
482
493
|
*/
|
|
483
494
|
private preferred;
|
|
495
|
+
/**
|
|
496
|
+
* The account the Trae app is signed in to right now, when that differs from
|
|
497
|
+
* the account this store is bound to (issue #29).
|
|
498
|
+
*
|
|
499
|
+
* This is a READ, not a rebind: `current()` keeps resolving the bound
|
|
500
|
+
* account verbatim so billing never moves without a decision — the store
|
|
501
|
+
* only reports the mismatch, and the card (hint row) or the host (follow
|
|
502
|
+
* switch) turns it into a settings write the user can see and undo. Absent
|
|
503
|
+
* when nothing is live, when the live account IS the bound one, or when the
|
|
504
|
+
* binding itself already fell through to the live account.
|
|
505
|
+
*/
|
|
506
|
+
liveLogin(): Promise<TraeAccountChoice | undefined>;
|
|
484
507
|
accounts(): Promise<TraeAccountChoice[]>;
|
|
485
508
|
current(): Promise<TraeCredential | undefined>;
|
|
486
509
|
/**
|
|
@@ -1689,6 +1712,24 @@ interface TraeWebAccount {
|
|
|
1689
1712
|
tokenExpiresAtMs: number;
|
|
1690
1713
|
selected: boolean;
|
|
1691
1714
|
}
|
|
1715
|
+
/**
|
|
1716
|
+
* The account the Trae desktop app is signed in to right now, when it differs
|
|
1717
|
+
* from the one this plugin is bound to (issue #29).
|
|
1718
|
+
*
|
|
1719
|
+
* Reported on the usage document so the card can offer one-click follow without
|
|
1720
|
+
* an extra round trip; also written back on a POST to the accounts refresh
|
|
1721
|
+
* route when the client confirms it saw the hint. Absent whenever the two
|
|
1722
|
+
* agree or the app is not readable — a healthy document carries no extra
|
|
1723
|
+
* fields and the plugin never bills from this value directly: the FOLLOW is a
|
|
1724
|
+
* settings write of `accounts.<region>`, made by the card or by the host's
|
|
1725
|
+
* follow switch, never a silent rebind here.
|
|
1726
|
+
*/
|
|
1727
|
+
interface TraeWebAppLogin {
|
|
1728
|
+
id: string;
|
|
1729
|
+
accountName: string;
|
|
1730
|
+
edition: 'cn' | 'sg' | 'solo' | 'solo-sg';
|
|
1731
|
+
region: TraeRegion;
|
|
1732
|
+
}
|
|
1692
1733
|
interface TraeWebCreditAlternative {
|
|
1693
1734
|
id: string;
|
|
1694
1735
|
accountName: string;
|
|
@@ -1729,6 +1770,33 @@ declare function unwrapVolatile<T>(value: T): T;
|
|
|
1729
1770
|
* value is never touched.
|
|
1730
1771
|
*/
|
|
1731
1772
|
declare function unwrapVolatileDeep<T>(value: T): T;
|
|
1773
|
+
/**
|
|
1774
|
+
* Read the per-region account selections out of a settings value.
|
|
1775
|
+
*
|
|
1776
|
+
* SHARED because `accounts` now has TWO writers: the card, and the composer
|
|
1777
|
+
* panel's account table (2.14.0). Two writers of one field must use one merge
|
|
1778
|
+
* rule, or they drift — and the rule is not obvious.
|
|
1779
|
+
*
|
|
1780
|
+
* Deep-unwraps first: on DSH 0.1.7 a volatile field arrives as a `{get(): T}`
|
|
1781
|
+
* live reference, which passes the `typeof === 'object'` check and would be
|
|
1782
|
+
* returned as if it were the map. Every lookup on it is then `undefined`, and
|
|
1783
|
+
* the merge (`{ ...configured, [region]: id }`) would spread a reference into
|
|
1784
|
+
* `{get: <function>}` — dropping the other region's selection and leaking a
|
|
1785
|
+
* function into the settings document.
|
|
1786
|
+
*/
|
|
1787
|
+
declare function configuredAccountsOf(configured: unknown): Record<string, string>;
|
|
1788
|
+
/**
|
|
1789
|
+
* Whether the host-side "follow the Trae app's sign-in" switch is on (issue
|
|
1790
|
+
* #29). Absent reads as OFF, so an existing configuration keeps the strict
|
|
1791
|
+
* bind it shipped with and the switch is an opt-in — the same opt-out
|
|
1792
|
+
* semantics as {@link regionEnabledOf}, for the same reason: a config written
|
|
1793
|
+
* before this field existed cannot ask for behavior it never declared.
|
|
1794
|
+
*
|
|
1795
|
+
* Reads the whole settings section (or the bare field); deep-unwraps first
|
|
1796
|
+
* because on DSH 0.1.7 a volatile field arrives as a `{get(): T}` live
|
|
1797
|
+
* reference, whose `typeof === 'object'` would otherwise pass a naive check.
|
|
1798
|
+
*/
|
|
1799
|
+
declare function followAppLoginEnabled(configured: unknown): boolean;
|
|
1732
1800
|
/**
|
|
1733
1801
|
* Build the next `regions` settings value for the card's save. The write
|
|
1734
1802
|
* targets ONLY the signed-in account's region slot; every other region's slot
|
|
@@ -1819,6 +1887,15 @@ type TraeWebUsage = {
|
|
|
1819
1887
|
* other case so a healthy account never pays for the extra read.
|
|
1820
1888
|
*/
|
|
1821
1889
|
alternatives?: readonly TraeWebCreditAlternative[];
|
|
1890
|
+
/**
|
|
1891
|
+
* Present only when the Trae app's CURRENT sign-in differs from the
|
|
1892
|
+
* account this plugin is bound to (issue #29): the card then offers a
|
|
1893
|
+
* one-click follow instead of making the user hunt the account dropdown
|
|
1894
|
+
* after every app-side sign-out. Absent in every other case — a healthy
|
|
1895
|
+
* document costs no extra read beyond the accounts scan the document
|
|
1896
|
+
* already runs.
|
|
1897
|
+
*/
|
|
1898
|
+
appLogin?: TraeWebAppLogin;
|
|
1822
1899
|
/**
|
|
1823
1900
|
* Daily check-in state (CN only). Absent on the international region,
|
|
1824
1901
|
* whose check-in surface does not exist — probed 2026-09-24: the
|
|
@@ -1853,6 +1930,14 @@ interface TraeUsageRouteOptions {
|
|
|
1853
1930
|
* exhausted-account hint is simply not offered.
|
|
1854
1931
|
*/
|
|
1855
1932
|
creditsOfAccount?(region: TraeRegion, accountId: string): Promise<TraeWebCredits | undefined>;
|
|
1933
|
+
/**
|
|
1934
|
+
* The account the Trae app is signed in to right now, when it differs from
|
|
1935
|
+
* the bound one (issue #29). Optional: absent means the card's follow hint
|
|
1936
|
+
* simply is not offered (a test harness or a host without the store's scan).
|
|
1937
|
+
* Read-only — the HOST never rebinds from this value; the follow is a
|
|
1938
|
+
* settings write, made by the card or by the host's follow switch.
|
|
1939
|
+
*/
|
|
1940
|
+
liveLogin?(region: TraeRegion): Promise<TraeWebAppLogin | undefined>;
|
|
1856
1941
|
/** The requested region's last-refreshed raw directory (one entry per upstream model). */
|
|
1857
1942
|
displayModels(region: TraeRegion): readonly TraeModelInfo[];
|
|
1858
1943
|
/** The user's model selection in the requested region, stored as model id (= Trae name). */
|
|
@@ -2030,6 +2115,26 @@ interface Config {
|
|
|
2030
2115
|
* settings form can both read and write it.
|
|
2031
2116
|
*/
|
|
2032
2117
|
showPointsInMainUi?: boolean;
|
|
2118
|
+
/**
|
|
2119
|
+
* Follow the Trae desktop app's CURRENT sign-in instead of staying bound to
|
|
2120
|
+
* the account the user (or the default) picked earlier (issue #29).
|
|
2121
|
+
*
|
|
2122
|
+
* When the app switches accounts, the old binding kept billing the previous
|
|
2123
|
+
* account forever while `dsh-connect-workbuddy` followed the app
|
|
2124
|
+
* automatically — every Trae sign-out then needed a manual switch here. With
|
|
2125
|
+
* this switch ON, each settings re-apply rebinds `accounts.<region>` to
|
|
2126
|
+
* whatever the app is signed in to right now (the live login outranks the
|
|
2127
|
+
* plugin's own refreshed copies, see `TraeCredentialStore.preferred`).
|
|
2128
|
+
*
|
|
2129
|
+
* OFF by default: an existing configuration keeps the strict bind it
|
|
2130
|
+
* shipped with, because silently switching billing accounts is exactly what
|
|
2131
|
+
* `current()`'s no-fallback rule exists to prevent — the user opts in. When
|
|
2132
|
+
* OFF, the card still shows a one-click hint (its own settings write), so
|
|
2133
|
+
* the report in #29 is solved on both paths without any behavior change
|
|
2134
|
+
* nobody asked for. Declared volatile so the card's switch can read and
|
|
2135
|
+
* write it on the 0.1.7 settings form.
|
|
2136
|
+
*/
|
|
2137
|
+
followAppLogin?: boolean;
|
|
2033
2138
|
/**
|
|
2034
2139
|
* Per-region model state, keyed `cn` | `ai`. The CN and international apps
|
|
2035
2140
|
* expose different rosters, so each keeps its own directory and selection
|
|
@@ -2088,4 +2193,4 @@ declare function asVolatile<S>(schema: S): S;
|
|
|
2088
2193
|
declare const Config: z<Config>;
|
|
2089
2194
|
declare function apply(ctx: Context, config: Config): void;
|
|
2090
2195
|
//#endregion
|
|
2091
|
-
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_MODELS_TEST_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROBE_TIMEOUT_MS, 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_STATE_DB_FILENAME, 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 TraeModelProbeVerdict, type TraeModelsTestResponse, type TraeObservedModelConfig, type TraeProbeResult, type TraeProbeVerdict, 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, classifyProbeError, classifyProbeResult, classifyTraeRawChatFailure, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, describeIdShape, describeNameShape, discoveredCatalog, fallbackModelsFor, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, maskUserPath, mergeTraeModelSources, name, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeBody, probeModelsSequentially, probeOneModel, 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, traeStateDatabaseCandidates, traeStorageCandidates, traeWebUsage, traeWindowsAppNames, unwrapVolatile, unwrapVolatileDeep, withTraeRegion };
|
|
2196
|
+
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_MODELS_TEST_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROBE_TIMEOUT_MS, 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_STATE_DB_FILENAME, 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 TraeModelProbeVerdict, type TraeModelsTestResponse, type TraeObservedModelConfig, type TraeProbeResult, type TraeProbeVerdict, 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 TraeWebAppLogin, type TraeWebCheckin, type TraeWebCheckinClaim, type TraeWebCredits, type TraeWebUsage, type TraeWireModel, UnconfiguredTraeUpstreamClient, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, asVolatile, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyProbeError, classifyProbeResult, classifyTraeRawChatFailure, configuredAccountsOf, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, describeIdShape, describeNameShape, discoveredCatalog, fallbackModelsFor, followAppLoginEnabled, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, maskUserPath, mergeTraeModelSources, name, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeBody, probeModelsSequentially, probeOneModel, 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, traeStateDatabaseCandidates, traeStorageCandidates, traeWebUsage, traeWindowsAppNames, unwrapVolatile, unwrapVolatileDeep, withTraeRegion };
|
package/lib/index.js
CHANGED
|
@@ -452,6 +452,202 @@ function createTraeAdapter(options) {
|
|
|
452
452
|
};
|
|
453
453
|
}
|
|
454
454
|
//#endregion
|
|
455
|
+
//#region src/status-paths.ts
|
|
456
|
+
/** Plugin-owned usage endpoint consumed by its browser half. */
|
|
457
|
+
const TRAE_USAGE_PATH = "/plugins/dsh-connect-trae/usage";
|
|
458
|
+
/** Plugin-owned live model refresh endpoint. */
|
|
459
|
+
const TRAE_MODELS_REFRESH_PATH = "/plugins/dsh-connect-trae/models/refresh";
|
|
460
|
+
/**
|
|
461
|
+
* Plugin-owned availability probe: POST the model ids to test, get one verdict
|
|
462
|
+
* per model. Unlike every read-only sibling this route SPENDS quota — a real
|
|
463
|
+
* (minimal) chat call per model — so it is user-initiated only, and the card
|
|
464
|
+
* labels the button as consuming a small amount.
|
|
465
|
+
*/
|
|
466
|
+
const TRAE_MODELS_TEST_PATH = "/plugins/dsh-connect-trae/models/test";
|
|
467
|
+
/** Plugin-owned local account rescan endpoint. */
|
|
468
|
+
const TRAE_ACCOUNTS_REFRESH_PATH = "/plugins/dsh-connect-trae/accounts/refresh";
|
|
469
|
+
/**
|
|
470
|
+
* General balance of EVERY account in a region, for the composer panel's table.
|
|
471
|
+
*
|
|
472
|
+
* Separate from the usage route on purpose: the usage document backs the
|
|
473
|
+
* 5-minute readout, and multiplying its upstream reads by the account count
|
|
474
|
+
* would charge a healthy account for something it never looks at — the same
|
|
475
|
+
* principle that keeps `creditAlternatives` firing only when a balance hits
|
|
476
|
+
* zero. This one is paid when the panel is actually opened, which is the only
|
|
477
|
+
* place the table is shown.
|
|
478
|
+
*/
|
|
479
|
+
const TRAE_ACCOUNT_CREDITS_PATH = "/plugins/dsh-connect-trae/account-credits";
|
|
480
|
+
/**
|
|
481
|
+
* Plugin-owned daily check-in claim endpoint. POST, loopback-only, and the only
|
|
482
|
+
* route in this plugin that changes upstream account state.
|
|
483
|
+
*/
|
|
484
|
+
const TRAE_CHECKIN_PATH = "/plugins/dsh-connect-trae/checkin";
|
|
485
|
+
/** Query parameter naming the region a card request addresses. */
|
|
486
|
+
const TRAE_REGION_PARAM = "region";
|
|
487
|
+
/** Every region, in card tab order. */
|
|
488
|
+
const TRAE_REGIONS = ["cn", "ai"];
|
|
489
|
+
/**
|
|
490
|
+
* Address one region's status route. The two regions are separate provider
|
|
491
|
+
* stacks; every card request carries the region whose tab the user is on.
|
|
492
|
+
*/
|
|
493
|
+
function withTraeRegion(path, region) {
|
|
494
|
+
return `${path}?${TRAE_REGION_PARAM}=${region}`;
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Read the region parameter off a status-route URL. Absent means the domestic
|
|
498
|
+
* tab (`cn`); a present-but-unknown value returns undefined so the route can
|
|
499
|
+
* answer 400 instead of guessing.
|
|
500
|
+
*/
|
|
501
|
+
function regionOfTraeStatusUrl(url) {
|
|
502
|
+
const at = url.indexOf("?");
|
|
503
|
+
const value = at === -1 ? null : new URLSearchParams(url.slice(at + 1)).get(TRAE_REGION_PARAM);
|
|
504
|
+
if (value === null || value === "") return "cn";
|
|
505
|
+
return TRAE_REGIONS.includes(value) ? value : void 0;
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Peel ONE `{get(): T}` live reference, the shape DSH 0.1.7 delivers a
|
|
509
|
+
* volatile-marked settings field as (see {@link unwrapVolatileDeep}).
|
|
510
|
+
*/
|
|
511
|
+
function unwrapVolatile(value) {
|
|
512
|
+
if (value !== null && typeof value === "object" && typeof value.get === "function") return value.get();
|
|
513
|
+
return value;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Deep copy of a value with every `{get(): T}` live reference replaced by the
|
|
517
|
+
* value it resolves to.
|
|
518
|
+
*
|
|
519
|
+
* DSH 0.1.7 hands volatile-marked fields back as live references, and a live
|
|
520
|
+
* reference is still `typeof === 'object'` — so it passes a naive object check
|
|
521
|
+
* and every lookup on it is `undefined`. Worse, spreading one (`{ ...ref }`)
|
|
522
|
+
* does NOT read the field: it produces `{ get: <function> }`. Any merge that
|
|
523
|
+
* spreads a resolved field to preserve its siblings — the card's "write one
|
|
524
|
+
* region, keep the other" write, or the account map's spread — would otherwise
|
|
525
|
+
* DROP every sibling and leak a function into the document.
|
|
526
|
+
*
|
|
527
|
+
* Both halves therefore unwrap before touching a resolved field. This lives in
|
|
528
|
+
* the node-free host↔client bridge because the browser half needs it too.
|
|
529
|
+
*
|
|
530
|
+
* Non-reference values are recursed into so a nested volatile field is caught
|
|
531
|
+
* as well; arrays and objects are rebuilt rather than mutated, so the caller's
|
|
532
|
+
* value is never touched.
|
|
533
|
+
*/
|
|
534
|
+
function unwrapVolatileDeep(value) {
|
|
535
|
+
if (value === null || typeof value !== "object") return value;
|
|
536
|
+
if (typeof value.get === "function") return unwrapVolatileDeep(value.get());
|
|
537
|
+
if (Array.isArray(value)) return value.map((entry) => unwrapVolatileDeep(entry));
|
|
538
|
+
const source = value;
|
|
539
|
+
const out = {};
|
|
540
|
+
for (const key of Object.keys(source)) out[key] = unwrapVolatileDeep(source[key]);
|
|
541
|
+
return out;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Read the per-region account selections out of a settings value.
|
|
545
|
+
*
|
|
546
|
+
* SHARED because `accounts` now has TWO writers: the card, and the composer
|
|
547
|
+
* panel's account table (2.14.0). Two writers of one field must use one merge
|
|
548
|
+
* rule, or they drift — and the rule is not obvious.
|
|
549
|
+
*
|
|
550
|
+
* Deep-unwraps first: on DSH 0.1.7 a volatile field arrives as a `{get(): T}`
|
|
551
|
+
* live reference, which passes the `typeof === 'object'` check and would be
|
|
552
|
+
* returned as if it were the map. Every lookup on it is then `undefined`, and
|
|
553
|
+
* the merge (`{ ...configured, [region]: id }`) would spread a reference into
|
|
554
|
+
* `{get: <function>}` — dropping the other region's selection and leaking a
|
|
555
|
+
* function into the settings document.
|
|
556
|
+
*/
|
|
557
|
+
function configuredAccountsOf(configured) {
|
|
558
|
+
const accounts = unwrapVolatileDeep(configured)?.accounts;
|
|
559
|
+
return typeof accounts === "object" && accounts !== null ? accounts : {};
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Whether the host-side "follow the Trae app's sign-in" switch is on (issue
|
|
563
|
+
* #29). Absent reads as OFF, so an existing configuration keeps the strict
|
|
564
|
+
* bind it shipped with and the switch is an opt-in — the same opt-out
|
|
565
|
+
* semantics as {@link regionEnabledOf}, for the same reason: a config written
|
|
566
|
+
* before this field existed cannot ask for behavior it never declared.
|
|
567
|
+
*
|
|
568
|
+
* Reads the whole settings section (or the bare field); deep-unwraps first
|
|
569
|
+
* because on DSH 0.1.7 a volatile field arrives as a `{get(): T}` live
|
|
570
|
+
* reference, whose `typeof === 'object'` would otherwise pass a naive check.
|
|
571
|
+
*/
|
|
572
|
+
function followAppLoginEnabled(configured) {
|
|
573
|
+
return unwrapVolatileDeep(configured)?.followAppLogin === true;
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Build the next `regions` settings value for the card's save. The write
|
|
577
|
+
* targets ONLY the signed-in account's region slot; every other region's slot
|
|
578
|
+
* is carried over untouched, so switching accounts never clobbers the other
|
|
579
|
+
* region's picks. Tolerates any stored shape (absent, non-object) by starting
|
|
580
|
+
* from an empty document.
|
|
581
|
+
*/
|
|
582
|
+
function nextRegionSlots(regions, region, slot) {
|
|
583
|
+
const unwrapped = unwrapVolatileDeep(regions);
|
|
584
|
+
return {
|
|
585
|
+
...typeof unwrapped === "object" && unwrapped !== null && !Array.isArray(unwrapped) ? unwrapped : {},
|
|
586
|
+
[region]: slot
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Narrow a settings value to the `regions` map. Accepts EITHER the whole
|
|
591
|
+
* settings section (`{ regions: {...}, ... }`) or the `regions` map itself, and
|
|
592
|
+
* unwraps the former. This tolerance is deliberate: passing the whole section
|
|
593
|
+
* where the map was expected was a real shipped bug — the lookup then read
|
|
594
|
+
* `section['cn']` (absent), so the card's checkbox reported `true` forever and
|
|
595
|
+
* clicking it appeared to do nothing even though the write succeeded.
|
|
596
|
+
*
|
|
597
|
+
* Unwraps live references first: on DSH 0.1.7 the `regions` field arrives as a
|
|
598
|
+
* `{get(): T}` reference, which passes the `typeof === 'object'` check below
|
|
599
|
+
* and would otherwise be returned as if it were the map — making every region
|
|
600
|
+
* read back as absent (and the enabled flag read as "on" forever).
|
|
601
|
+
*/
|
|
602
|
+
function regionsMapOf(value) {
|
|
603
|
+
const unwrapped = unwrapVolatileDeep(value);
|
|
604
|
+
if (typeof unwrapped !== "object" || unwrapped === null || Array.isArray(unwrapped)) return {};
|
|
605
|
+
const record = unwrapped;
|
|
606
|
+
const nested = record["regions"];
|
|
607
|
+
if (typeof nested === "object" && nested !== null && !Array.isArray(nested)) return nested;
|
|
608
|
+
return record;
|
|
609
|
+
}
|
|
610
|
+
/** One region's stored slot as a plain object; any other shape reads as empty. */
|
|
611
|
+
function regionSlotOf(value, region) {
|
|
612
|
+
const slot = unwrapVolatile(regionsMapOf(value)[region]);
|
|
613
|
+
return typeof slot === "object" && slot !== null && !Array.isArray(slot) ? slot : {};
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Whether one region's provider is switched on. Opt-out semantics: only an
|
|
617
|
+
* explicit `false` disables it, so a config written before this switch existed
|
|
618
|
+
* (and the pre-region-split flat fields, which never carry `enabled`) keep both
|
|
619
|
+
* providers running exactly as before. The Host reads the same rule through
|
|
620
|
+
* `regionStateOf`, so card and Host can never disagree about a region's state.
|
|
621
|
+
*
|
|
622
|
+
* `value` may be the whole settings section or the `regions` map (see
|
|
623
|
+
* {@link regionsMapOf}).
|
|
624
|
+
*/
|
|
625
|
+
function regionEnabledOf(value, region) {
|
|
626
|
+
return regionSlotOf(value, region)["enabled"] !== false;
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Build the next `regions` settings value for a provider on/off toggle. ONLY
|
|
630
|
+
* the target region's `enabled` flag changes: every other field of that slot
|
|
631
|
+
* (its directory, selection, image opt-ins, context budgets) and every other
|
|
632
|
+
* region's slot are carried over verbatim, so switching a provider off never
|
|
633
|
+
* discards the user's model picks and switching it back on restores them.
|
|
634
|
+
*
|
|
635
|
+
* This is deliberately separate from {@link nextRegionSlots}: that helper
|
|
636
|
+
* writes a whole slot from a signed-in tab's draft, while this one must work
|
|
637
|
+
* for a region that is signed OUT — which is precisely the region a user wants
|
|
638
|
+
* to switch off (no international install, no international account).
|
|
639
|
+
*
|
|
640
|
+
* `value` may be the whole settings section or the `regions` map; the RETURN
|
|
641
|
+
* value is always the `regions` map, i.e. exactly what `settingsScope.set(
|
|
642
|
+
* 'regions', ...)` needs.
|
|
643
|
+
*/
|
|
644
|
+
function nextRegionEnabled(value, region, enabled) {
|
|
645
|
+
return nextRegionSlots(regionsMapOf(value), region, {
|
|
646
|
+
...regionSlotOf(value, region),
|
|
647
|
+
enabled
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
//#endregion
|
|
455
651
|
//#region src/decrypt.ts
|
|
456
652
|
const TRAE_AUTH_STORAGE_KEY = "iCubeAuthInfo://icube.cloudide";
|
|
457
653
|
const SALT_A = Uint8Array.from([
|
|
@@ -1171,13 +1367,52 @@ var TraeCredentialStore = class {
|
|
|
1171
1367
|
return this.edition === "auto" ? all.filter((candidate) => candidate.source === "desktop" || candidate.edition === cliEdition) : all.filter((candidate) => candidate.edition === this.edition && (candidate.source === "desktop" || candidate.edition === cliEdition));
|
|
1172
1368
|
}
|
|
1173
1369
|
/**
|
|
1174
|
-
* Deterministic default when no account is explicitly selected: the
|
|
1175
|
-
*
|
|
1176
|
-
*
|
|
1177
|
-
*
|
|
1370
|
+
* Deterministic default when no account is explicitly selected: the account
|
|
1371
|
+
* the Trae desktop app is signed in to RIGHT NOW, else the first discovered
|
|
1372
|
+
* one. This is NOT credit-seeking — it never reorders accounts to find one
|
|
1373
|
+
* with general credits. The plugin bills exactly the account the user
|
|
1374
|
+
* selected, the app's current sign-in when nothing has been selected, and
|
|
1375
|
+
* only then the first account.
|
|
1376
|
+
*
|
|
1377
|
+
* The app's live sign-in outranks the plugin's own refreshed copies (issue
|
|
1378
|
+
* #29): after the user switches accounts in Trae, the desktop storage file
|
|
1379
|
+
* holds the NEW login while the plugin's copy of the old one still resolves,
|
|
1380
|
+
* so a bare `credentials[0]` kept defaulting to a sign-in the app has
|
|
1381
|
+
* abandoned — and `dsh-connect-workbuddy`, whose `preferred()` ranks the live
|
|
1382
|
+
* file first, followed the app instead. Ordering only: the VALUE behind an
|
|
1383
|
+
* account id is still whichever copy expires later (`readAll`), so a refresh
|
|
1384
|
+
* the plugin performed is never discarded by this preference.
|
|
1178
1385
|
*/
|
|
1179
1386
|
preferred(credentials) {
|
|
1180
|
-
return credentials[0];
|
|
1387
|
+
return credentials.find((credential) => credential.source === "desktop") ?? credentials[0];
|
|
1388
|
+
}
|
|
1389
|
+
/**
|
|
1390
|
+
* The account the Trae app is signed in to right now, when that differs from
|
|
1391
|
+
* the account this store is bound to (issue #29).
|
|
1392
|
+
*
|
|
1393
|
+
* This is a READ, not a rebind: `current()` keeps resolving the bound
|
|
1394
|
+
* account verbatim so billing never moves without a decision — the store
|
|
1395
|
+
* only reports the mismatch, and the card (hint row) or the host (follow
|
|
1396
|
+
* switch) turns it into a settings write the user can see and undo. Absent
|
|
1397
|
+
* when nothing is live, when the live account IS the bound one, or when the
|
|
1398
|
+
* binding itself already fell through to the live account.
|
|
1399
|
+
*/
|
|
1400
|
+
async liveLogin() {
|
|
1401
|
+
if (this.accountId === void 0 || this.accountId === "") return void 0;
|
|
1402
|
+
const credentials = await this.readAll();
|
|
1403
|
+
const live = this.preferred(credentials);
|
|
1404
|
+
if (live === void 0) return void 0;
|
|
1405
|
+
const liveId = traeAccountId(live);
|
|
1406
|
+
if (liveId === this.accountId) return void 0;
|
|
1407
|
+
return {
|
|
1408
|
+
id: liveId,
|
|
1409
|
+
accountName: live.accountName ?? (live.userId || `${live.edition} account`),
|
|
1410
|
+
edition: live.edition,
|
|
1411
|
+
region: regionOfCredential(live),
|
|
1412
|
+
source: live.source,
|
|
1413
|
+
tokenExpiresAtMs: live.expiresAtMs,
|
|
1414
|
+
selected: false
|
|
1415
|
+
};
|
|
1181
1416
|
}
|
|
1182
1417
|
async accounts() {
|
|
1183
1418
|
const credentials = await this.readAll();
|
|
@@ -3927,170 +4162,6 @@ var TraeUsageClient = class {
|
|
|
3927
4162
|
}
|
|
3928
4163
|
};
|
|
3929
4164
|
//#endregion
|
|
3930
|
-
//#region src/status-paths.ts
|
|
3931
|
-
/** Plugin-owned usage endpoint consumed by its browser half. */
|
|
3932
|
-
const TRAE_USAGE_PATH = "/plugins/dsh-connect-trae/usage";
|
|
3933
|
-
/** Plugin-owned live model refresh endpoint. */
|
|
3934
|
-
const TRAE_MODELS_REFRESH_PATH = "/plugins/dsh-connect-trae/models/refresh";
|
|
3935
|
-
/**
|
|
3936
|
-
* Plugin-owned availability probe: POST the model ids to test, get one verdict
|
|
3937
|
-
* per model. Unlike every read-only sibling this route SPENDS quota — a real
|
|
3938
|
-
* (minimal) chat call per model — so it is user-initiated only, and the card
|
|
3939
|
-
* labels the button as consuming a small amount.
|
|
3940
|
-
*/
|
|
3941
|
-
const TRAE_MODELS_TEST_PATH = "/plugins/dsh-connect-trae/models/test";
|
|
3942
|
-
/** Plugin-owned local account rescan endpoint. */
|
|
3943
|
-
const TRAE_ACCOUNTS_REFRESH_PATH = "/plugins/dsh-connect-trae/accounts/refresh";
|
|
3944
|
-
/**
|
|
3945
|
-
* General balance of EVERY account in a region, for the composer panel's table.
|
|
3946
|
-
*
|
|
3947
|
-
* Separate from the usage route on purpose: the usage document backs the
|
|
3948
|
-
* 5-minute readout, and multiplying its upstream reads by the account count
|
|
3949
|
-
* would charge a healthy account for something it never looks at — the same
|
|
3950
|
-
* principle that keeps `creditAlternatives` firing only when a balance hits
|
|
3951
|
-
* zero. This one is paid when the panel is actually opened, which is the only
|
|
3952
|
-
* place the table is shown.
|
|
3953
|
-
*/
|
|
3954
|
-
const TRAE_ACCOUNT_CREDITS_PATH = "/plugins/dsh-connect-trae/account-credits";
|
|
3955
|
-
/**
|
|
3956
|
-
* Plugin-owned daily check-in claim endpoint. POST, loopback-only, and the only
|
|
3957
|
-
* route in this plugin that changes upstream account state.
|
|
3958
|
-
*/
|
|
3959
|
-
const TRAE_CHECKIN_PATH = "/plugins/dsh-connect-trae/checkin";
|
|
3960
|
-
/** Query parameter naming the region a card request addresses. */
|
|
3961
|
-
const TRAE_REGION_PARAM = "region";
|
|
3962
|
-
/** Every region, in card tab order. */
|
|
3963
|
-
const TRAE_REGIONS = ["cn", "ai"];
|
|
3964
|
-
/**
|
|
3965
|
-
* Address one region's status route. The two regions are separate provider
|
|
3966
|
-
* stacks; every card request carries the region whose tab the user is on.
|
|
3967
|
-
*/
|
|
3968
|
-
function withTraeRegion(path, region) {
|
|
3969
|
-
return `${path}?${TRAE_REGION_PARAM}=${region}`;
|
|
3970
|
-
}
|
|
3971
|
-
/**
|
|
3972
|
-
* Read the region parameter off a status-route URL. Absent means the domestic
|
|
3973
|
-
* tab (`cn`); a present-but-unknown value returns undefined so the route can
|
|
3974
|
-
* answer 400 instead of guessing.
|
|
3975
|
-
*/
|
|
3976
|
-
function regionOfTraeStatusUrl(url) {
|
|
3977
|
-
const at = url.indexOf("?");
|
|
3978
|
-
const value = at === -1 ? null : new URLSearchParams(url.slice(at + 1)).get(TRAE_REGION_PARAM);
|
|
3979
|
-
if (value === null || value === "") return "cn";
|
|
3980
|
-
return TRAE_REGIONS.includes(value) ? value : void 0;
|
|
3981
|
-
}
|
|
3982
|
-
/**
|
|
3983
|
-
* Peel ONE `{get(): T}` live reference, the shape DSH 0.1.7 delivers a
|
|
3984
|
-
* volatile-marked settings field as (see {@link unwrapVolatileDeep}).
|
|
3985
|
-
*/
|
|
3986
|
-
function unwrapVolatile(value) {
|
|
3987
|
-
if (value !== null && typeof value === "object" && typeof value.get === "function") return value.get();
|
|
3988
|
-
return value;
|
|
3989
|
-
}
|
|
3990
|
-
/**
|
|
3991
|
-
* Deep copy of a value with every `{get(): T}` live reference replaced by the
|
|
3992
|
-
* value it resolves to.
|
|
3993
|
-
*
|
|
3994
|
-
* DSH 0.1.7 hands volatile-marked fields back as live references, and a live
|
|
3995
|
-
* reference is still `typeof === 'object'` — so it passes a naive object check
|
|
3996
|
-
* and every lookup on it is `undefined`. Worse, spreading one (`{ ...ref }`)
|
|
3997
|
-
* does NOT read the field: it produces `{ get: <function> }`. Any merge that
|
|
3998
|
-
* spreads a resolved field to preserve its siblings — the card's "write one
|
|
3999
|
-
* region, keep the other" write, or the account map's spread — would otherwise
|
|
4000
|
-
* DROP every sibling and leak a function into the document.
|
|
4001
|
-
*
|
|
4002
|
-
* Both halves therefore unwrap before touching a resolved field. This lives in
|
|
4003
|
-
* the node-free host↔client bridge because the browser half needs it too.
|
|
4004
|
-
*
|
|
4005
|
-
* Non-reference values are recursed into so a nested volatile field is caught
|
|
4006
|
-
* as well; arrays and objects are rebuilt rather than mutated, so the caller's
|
|
4007
|
-
* value is never touched.
|
|
4008
|
-
*/
|
|
4009
|
-
function unwrapVolatileDeep(value) {
|
|
4010
|
-
if (value === null || typeof value !== "object") return value;
|
|
4011
|
-
if (typeof value.get === "function") return unwrapVolatileDeep(value.get());
|
|
4012
|
-
if (Array.isArray(value)) return value.map((entry) => unwrapVolatileDeep(entry));
|
|
4013
|
-
const source = value;
|
|
4014
|
-
const out = {};
|
|
4015
|
-
for (const key of Object.keys(source)) out[key] = unwrapVolatileDeep(source[key]);
|
|
4016
|
-
return out;
|
|
4017
|
-
}
|
|
4018
|
-
/**
|
|
4019
|
-
* Build the next `regions` settings value for the card's save. The write
|
|
4020
|
-
* targets ONLY the signed-in account's region slot; every other region's slot
|
|
4021
|
-
* is carried over untouched, so switching accounts never clobbers the other
|
|
4022
|
-
* region's picks. Tolerates any stored shape (absent, non-object) by starting
|
|
4023
|
-
* from an empty document.
|
|
4024
|
-
*/
|
|
4025
|
-
function nextRegionSlots(regions, region, slot) {
|
|
4026
|
-
const unwrapped = unwrapVolatileDeep(regions);
|
|
4027
|
-
return {
|
|
4028
|
-
...typeof unwrapped === "object" && unwrapped !== null && !Array.isArray(unwrapped) ? unwrapped : {},
|
|
4029
|
-
[region]: slot
|
|
4030
|
-
};
|
|
4031
|
-
}
|
|
4032
|
-
/**
|
|
4033
|
-
* Narrow a settings value to the `regions` map. Accepts EITHER the whole
|
|
4034
|
-
* settings section (`{ regions: {...}, ... }`) or the `regions` map itself, and
|
|
4035
|
-
* unwraps the former. This tolerance is deliberate: passing the whole section
|
|
4036
|
-
* where the map was expected was a real shipped bug — the lookup then read
|
|
4037
|
-
* `section['cn']` (absent), so the card's checkbox reported `true` forever and
|
|
4038
|
-
* clicking it appeared to do nothing even though the write succeeded.
|
|
4039
|
-
*
|
|
4040
|
-
* Unwraps live references first: on DSH 0.1.7 the `regions` field arrives as a
|
|
4041
|
-
* `{get(): T}` reference, which passes the `typeof === 'object'` check below
|
|
4042
|
-
* and would otherwise be returned as if it were the map — making every region
|
|
4043
|
-
* read back as absent (and the enabled flag read as "on" forever).
|
|
4044
|
-
*/
|
|
4045
|
-
function regionsMapOf(value) {
|
|
4046
|
-
const unwrapped = unwrapVolatileDeep(value);
|
|
4047
|
-
if (typeof unwrapped !== "object" || unwrapped === null || Array.isArray(unwrapped)) return {};
|
|
4048
|
-
const record = unwrapped;
|
|
4049
|
-
const nested = record["regions"];
|
|
4050
|
-
if (typeof nested === "object" && nested !== null && !Array.isArray(nested)) return nested;
|
|
4051
|
-
return record;
|
|
4052
|
-
}
|
|
4053
|
-
/** One region's stored slot as a plain object; any other shape reads as empty. */
|
|
4054
|
-
function regionSlotOf(value, region) {
|
|
4055
|
-
const slot = unwrapVolatile(regionsMapOf(value)[region]);
|
|
4056
|
-
return typeof slot === "object" && slot !== null && !Array.isArray(slot) ? slot : {};
|
|
4057
|
-
}
|
|
4058
|
-
/**
|
|
4059
|
-
* Whether one region's provider is switched on. Opt-out semantics: only an
|
|
4060
|
-
* explicit `false` disables it, so a config written before this switch existed
|
|
4061
|
-
* (and the pre-region-split flat fields, which never carry `enabled`) keep both
|
|
4062
|
-
* providers running exactly as before. The Host reads the same rule through
|
|
4063
|
-
* `regionStateOf`, so card and Host can never disagree about a region's state.
|
|
4064
|
-
*
|
|
4065
|
-
* `value` may be the whole settings section or the `regions` map (see
|
|
4066
|
-
* {@link regionsMapOf}).
|
|
4067
|
-
*/
|
|
4068
|
-
function regionEnabledOf(value, region) {
|
|
4069
|
-
return regionSlotOf(value, region)["enabled"] !== false;
|
|
4070
|
-
}
|
|
4071
|
-
/**
|
|
4072
|
-
* Build the next `regions` settings value for a provider on/off toggle. ONLY
|
|
4073
|
-
* the target region's `enabled` flag changes: every other field of that slot
|
|
4074
|
-
* (its directory, selection, image opt-ins, context budgets) and every other
|
|
4075
|
-
* region's slot are carried over verbatim, so switching a provider off never
|
|
4076
|
-
* discards the user's model picks and switching it back on restores them.
|
|
4077
|
-
*
|
|
4078
|
-
* This is deliberately separate from {@link nextRegionSlots}: that helper
|
|
4079
|
-
* writes a whole slot from a signed-in tab's draft, while this one must work
|
|
4080
|
-
* for a region that is signed OUT — which is precisely the region a user wants
|
|
4081
|
-
* to switch off (no international install, no international account).
|
|
4082
|
-
*
|
|
4083
|
-
* `value` may be the whole settings section or the `regions` map; the RETURN
|
|
4084
|
-
* value is always the `regions` map, i.e. exactly what `settingsScope.set(
|
|
4085
|
-
* 'regions', ...)` needs.
|
|
4086
|
-
*/
|
|
4087
|
-
function nextRegionEnabled(value, region, enabled) {
|
|
4088
|
-
return nextRegionSlots(regionsMapOf(value), region, {
|
|
4089
|
-
...regionSlotOf(value, region),
|
|
4090
|
-
enabled
|
|
4091
|
-
});
|
|
4092
|
-
}
|
|
4093
|
-
//#endregion
|
|
4094
4165
|
//#region src/web-status.ts
|
|
4095
4166
|
/**
|
|
4096
4167
|
* The upstream's business code for "该设备今日已参与签到" — this DEVICE already
|
|
@@ -4233,6 +4304,10 @@ async function traeWebUsage(deps, region) {
|
|
|
4233
4304
|
message: safeMessage(error)
|
|
4234
4305
|
};
|
|
4235
4306
|
}
|
|
4307
|
+
let appLogin;
|
|
4308
|
+
try {
|
|
4309
|
+
appLogin = await deps.liveLogin?.(region);
|
|
4310
|
+
} catch {}
|
|
4236
4311
|
const account = {
|
|
4237
4312
|
accountId: accounts.find((item) => item.selected)?.id ?? "",
|
|
4238
4313
|
accountName: credential.accountName ?? credential.userId,
|
|
@@ -4240,6 +4315,7 @@ async function traeWebUsage(deps, region) {
|
|
|
4240
4315
|
region,
|
|
4241
4316
|
enabled,
|
|
4242
4317
|
accounts,
|
|
4318
|
+
...appLogin === void 0 ? {} : { appLogin },
|
|
4243
4319
|
models: deps.displayModels(region).map((model) => ({
|
|
4244
4320
|
...model,
|
|
4245
4321
|
...model.input === void 0 ? {} : { input: [...model.input] }
|
|
@@ -4837,6 +4913,7 @@ const Config = z.object({
|
|
|
4837
4913
|
]).default("auto").description("Trae edition hint")),
|
|
4838
4914
|
accountId: z.string().description("Deprecated: pre-split account selector, attributed to its own region"),
|
|
4839
4915
|
showPointsInMainUi: asVolatile(z.boolean().default(false).description("在输入框工具栏显示积分读数「Trae CN · <数值>」(仅当选中的模型属于本插件时出现,可点击查看账号与积分明细)")),
|
|
4916
|
+
followAppLogin: asVolatile(z.boolean().default(false).description("跟随 Trae 桌面端当前登录账号换绑(打开后每次配置生效时重绑到 App 现在登录的账号)")),
|
|
4840
4917
|
accounts: asVolatile(accountSelectionConfig.description("Per-region account selections, keyed cn | ai")),
|
|
4841
4918
|
regions: asVolatile(z.dict(regionStateConfig).default({}).description("Per-region model directory and selection, keyed cn | ai")),
|
|
4842
4919
|
lastCatalog: z.array(modelConfig).description("Deprecated: pre-region-split CN model directory"),
|
|
@@ -5092,6 +5169,49 @@ function apply(ctx, config) {
|
|
|
5092
5169
|
rawDiagnostic: () => rawDiagnostic()
|
|
5093
5170
|
};
|
|
5094
5171
|
}
|
|
5172
|
+
/**
|
|
5173
|
+
* The app's current sign-in when it differs from the bound account (issue
|
|
5174
|
+
* #29). Pure read: the store resolves the bound account verbatim, so billing
|
|
5175
|
+
* never moves from here — the card's hint and the follow switch below are
|
|
5176
|
+
* the only writers.
|
|
5177
|
+
*/
|
|
5178
|
+
const liveLoginOf = async (region) => {
|
|
5179
|
+
try {
|
|
5180
|
+
const live = await stacks[region].store.liveLogin();
|
|
5181
|
+
if (live === void 0 || live.region !== region) return void 0;
|
|
5182
|
+
return {
|
|
5183
|
+
id: live.id,
|
|
5184
|
+
accountName: live.accountName,
|
|
5185
|
+
edition: live.edition,
|
|
5186
|
+
region: live.region
|
|
5187
|
+
};
|
|
5188
|
+
} catch {
|
|
5189
|
+
return;
|
|
5190
|
+
}
|
|
5191
|
+
};
|
|
5192
|
+
/**
|
|
5193
|
+
* Persist `accounts.<region>` = the app's live sign-in and rebind the store
|
|
5194
|
+
* (issue #29). The ONE Host-side path that moves the binding, driven by the
|
|
5195
|
+
* follow switch in {@link maybeFollowAppLogin}; the card's one-click hint
|
|
5196
|
+
* goes through its own settings write instead (one writer per path). Returns
|
|
5197
|
+
* the applied login so the caller can report what moved.
|
|
5198
|
+
*
|
|
5199
|
+
* The write goes through `settings.update` (the volatile write gate), not a
|
|
5200
|
+
* direct config mutation: `apply()` receives a snapshot, and the settings
|
|
5201
|
+
* event (`loader/volatile-update`) is what re-runs `applySelection` with the
|
|
5202
|
+
* updated document — the same channel every card write already uses.
|
|
5203
|
+
*/
|
|
5204
|
+
const followAppLogin = async (region) => {
|
|
5205
|
+
const live = await liveLoginOf(region);
|
|
5206
|
+
if (live === void 0 || writeSettings === void 0) return void 0;
|
|
5207
|
+
const configured = configuredAccountsOf(current());
|
|
5208
|
+
if (configured[region] === live.id) return live;
|
|
5209
|
+
await writeSettings({ accounts: {
|
|
5210
|
+
...configured,
|
|
5211
|
+
[region]: live.id
|
|
5212
|
+
} });
|
|
5213
|
+
return live;
|
|
5214
|
+
};
|
|
5095
5215
|
ctx.inject(["webServer"], (webCtx) => registerTraeUsageRoute(webCtx, {
|
|
5096
5216
|
store: (region) => stacks[region].store,
|
|
5097
5217
|
client: (region) => stacks[region].usageClient,
|
|
@@ -5101,7 +5221,8 @@ function apply(ctx, config) {
|
|
|
5101
5221
|
discoverModels: (region, signal) => stacks[region].discoverModels(signal),
|
|
5102
5222
|
probeModels: (region, ids) => stacks[region].probeModels(ids),
|
|
5103
5223
|
rawDiagnostic: (region) => stacks[region].rawDiagnostic(),
|
|
5104
|
-
creditsOfAccount: (region, accountId) => stacks[region].creditsOfAccount(accountId)
|
|
5224
|
+
creditsOfAccount: (region, accountId) => stacks[region].creditsOfAccount(accountId),
|
|
5225
|
+
liveLogin: (region) => liveLoginOf(region)
|
|
5105
5226
|
}));
|
|
5106
5227
|
/**
|
|
5107
5228
|
* Live registration handles per region, filled once the shim is listening.
|
|
@@ -5157,13 +5278,59 @@ function apply(ctx, config) {
|
|
|
5157
5278
|
}
|
|
5158
5279
|
} catch {}
|
|
5159
5280
|
})();
|
|
5281
|
+
let writeSettings;
|
|
5160
5282
|
ctx.inject(["settings"], (settingsCtx) => {
|
|
5161
5283
|
const settings = settingsCtx.settings;
|
|
5162
5284
|
ctx.effect(() => settings.configure({ auto: true }, ctx.fiber));
|
|
5285
|
+
writeSettings = async (patch) => {
|
|
5286
|
+
const revision = () => settings.describe().find((entry) => entry.ns === settingsNs)?.revision;
|
|
5287
|
+
const conflict = (error) => typeof error === "object" && error !== null && error.code === "SETTINGS_CONFLICT";
|
|
5288
|
+
try {
|
|
5289
|
+
await settings.update(settingsNs, patch, revision());
|
|
5290
|
+
} catch (error) {
|
|
5291
|
+
if (!conflict(error)) throw error;
|
|
5292
|
+
await settings.update(settingsNs, patch, revision());
|
|
5293
|
+
}
|
|
5294
|
+
};
|
|
5295
|
+
ctx.effect(() => () => {
|
|
5296
|
+
writeSettings = void 0;
|
|
5297
|
+
});
|
|
5163
5298
|
});
|
|
5164
5299
|
ctx.on("loader/volatile-update", () => {
|
|
5165
5300
|
applySelection(current());
|
|
5301
|
+
maybeFollowAppLogin();
|
|
5166
5302
|
});
|
|
5303
|
+
/**
|
|
5304
|
+
* Follow the app's sign-in on every settings change when the switch is ON
|
|
5305
|
+
* (issue #29). Runs AFTER `applySelection` so this pass binds the previous
|
|
5306
|
+
* document, reads the app's live login against it, and — if they differ —
|
|
5307
|
+
* writes `accounts.<region>` once; that write emits `volatile-update` again,
|
|
5308
|
+
* which re-runs `applySelection` on the NEW document. The loop terminates on
|
|
5309
|
+
* its own the moment `liveLogin()` reports no mismatch (the second pass binds
|
|
5310
|
+
* the live account, so `preferred` and `current` agree). The guard below
|
|
5311
|
+
* additionally forbids two writes in flight, so a stuck mismatch can never
|
|
5312
|
+
* turn into a write storm: the next settings change retries from scratch.
|
|
5313
|
+
*
|
|
5314
|
+
* The switch reads FALSE for an existing config (see {@link Config}), so a
|
|
5315
|
+
* user who never opted in never gets a rebind — the card's one-click hint is
|
|
5316
|
+
* their path instead.
|
|
5317
|
+
*/
|
|
5318
|
+
let followInFlight = false;
|
|
5319
|
+
const maybeFollowAppLogin = async () => {
|
|
5320
|
+
if (followInFlight || !followAppLoginEnabled(current())) return;
|
|
5321
|
+
followInFlight = true;
|
|
5322
|
+
try {
|
|
5323
|
+
for (const region of REGION_KEYS) {
|
|
5324
|
+
if (!regionEnabled(current(), region)) continue;
|
|
5325
|
+
await followAppLogin(region);
|
|
5326
|
+
}
|
|
5327
|
+
} catch (error) {
|
|
5328
|
+
ctx.logger.warn("dsh-connect-trae: follow-app-login rebind skipped", error);
|
|
5329
|
+
} finally {
|
|
5330
|
+
followInFlight = false;
|
|
5331
|
+
}
|
|
5332
|
+
};
|
|
5333
|
+
maybeFollowAppLogin();
|
|
5167
5334
|
let stopped = false;
|
|
5168
5335
|
ctx.effect(() => () => {
|
|
5169
5336
|
stopped = true;
|
|
@@ -5245,4 +5412,4 @@ function apply(ctx, config) {
|
|
|
5245
5412
|
});
|
|
5246
5413
|
}
|
|
5247
5414
|
//#endregion
|
|
5248
|
-
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_MODELS_TEST_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROBE_TIMEOUT_MS, 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_STATE_DB_FILENAME, 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, classifyProbeError, classifyProbeResult, classifyTraeRawChatFailure, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, describeIdShape, describeNameShape, discoveredCatalog, fallbackModelsFor, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, maskUserPath, mergeTraeModelSources, name, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeBody, probeModelsSequentially, probeOneModel, 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, traeStateDatabaseCandidates, traeStorageCandidates, traeWebUsage, traeWindowsAppNames, unwrapVolatile, unwrapVolatileDeep, withTraeRegion };
|
|
5415
|
+
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_MODELS_TEST_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROBE_TIMEOUT_MS, 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_STATE_DB_FILENAME, 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, classifyProbeError, classifyProbeResult, classifyTraeRawChatFailure, configuredAccountsOf, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, describeIdShape, describeNameShape, discoveredCatalog, fallbackModelsFor, followAppLoginEnabled, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, maskUserPath, mergeTraeModelSources, name, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeBody, probeModelsSequentially, probeOneModel, 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, traeStateDatabaseCandidates, traeStorageCandidates, traeWebUsage, traeWindowsAppNames, 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": "把本机登录的 Trae 模型接入 DeepSeek Harness:国内版与国际版双供应商并行,提供用量/积分概览与每日签到领取。",
|
|
5
|
-
"version": "3.0.
|
|
5
|
+
"version": "3.0.1",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|