dsh-connect-trae 3.10.0 → 3.10.2
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 +59 -0
- package/lib/client.js +95 -86
- package/lib/index.d.ts +51 -1
- package/lib/index.js +116 -13
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,64 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.10.2 (2026-10-19)
|
|
4
|
+
|
|
5
|
+
> 设置页的账号列表**先出、积分后补**。打开卡片原本要等**一整份**用量文档组装完才画第一笔:那份文档的
|
|
6
|
+
> 尾巴上挂着「逐账号读上游余额」和「逐账号读签到状态」,每个账号一次网络往返,单次超时 30 秒、没有重试,
|
|
7
|
+
> 上游一慢就层层等待——这正是兄弟插件 dsh-connect-workbuddy 的 issue #77(实测 30–60 秒才看到账号)。
|
|
8
|
+
> Trae 这边同一份文档里 `poolMembersOf` 甚至被调了 **4 次**(`members`、`effectiveMemberAccountIds`、
|
|
9
|
+
> `memberNames`、`otherAccounts` 各一次),签到那条路还要再触发一次,所以同样的机器上比 workbuddy 更慢。
|
|
10
|
+
> 本版把首屏与「测量」拆开:首屏只用本机已存的东西,测量结果随后补上。
|
|
11
|
+
|
|
12
|
+
### 修复
|
|
13
|
+
|
|
14
|
+
- **账号列表不再等上游余额**。`GET /usage?region=…&detail=fast` 返回一份只用本机状态的「首屏文档」:
|
|
15
|
+
`store.current()` 读已存凭据(不做网络刷新)、账号行来自新增的 `localMembers`(就是请求路径本来就在用的
|
|
16
|
+
`localPoolMembers`,零上游调用)、签到状态来自本进程已测过的缓存。**实测 0 次上游请求**,本机 1.3 秒的
|
|
17
|
+
完整文档变成毫秒级首屏。
|
|
18
|
+
- **卡片分两笔画**:先请求 `detail=fast` 上屏,再请求完整文档补齐积分/签到/模型探测结果。首屏文档**永不
|
|
19
|
+
覆盖**已经在屏的完整文档——否则每次轮询都会把积分闪回「—」。
|
|
20
|
+
- **首屏之前不再谎称「未登录」**。旧代码在 `statusByRegion` 里预置了一份 `signed-out` 占位文档并直接渲染,
|
|
21
|
+
于是整段等待时间里已登录的用户看到的是「去登录」引导;现在状态表初始为空,未拿到任何文档时显示新增的
|
|
22
|
+
「正在加载账号…」,真正的 `signed-out` 文档到了才显示登录引导。
|
|
23
|
+
- **完整文档故意保持不变**:`detail` 缺省、或值不是精确的 `fast`(比如大小写写错、拼成 `fastest`)时一律
|
|
24
|
+
退回完整文档——宁可慢一会儿,也不能给出一份更空的卡片。
|
|
25
|
+
- Host 侧配套:`TraePoolDeps` 新增可选 `localMembers` / `cachedCheckinState`,`src/index.ts` 里分别接到
|
|
26
|
+
`localPoolMembers` 与新增的签到状态缓存(`CHECKIN_CACHE_MS = 10 * 60_000`)。老版本 Host 没有这两个
|
|
27
|
+
dep 时,首屏文档**省略** pool 段而不是回头去付那次昂贵读取。
|
|
28
|
+
|
|
29
|
+
### 测试
|
|
30
|
+
|
|
31
|
+
- 新增 `tests/issue77-fast-first-paint.spec.ts`(11 例):首屏文档的上游调用数为 0、用的是 `current()`
|
|
32
|
+
而不是 `resolve()`、缺 dep 时省略 pool 段、`detail` 缺省/拼错一律退回完整文档。
|
|
33
|
+
- 新增 `tests/issue77-card-first-paint.spec.tsx`(3 例):完整文档还在路上时账号行已经上屏、未拿到任何
|
|
34
|
+
文档时显示「正在加载账号」而不是「未登录」引导、迟到的首屏文档不会覆盖已在屏的完整文档。
|
|
35
|
+
- 全仓 **902** 例通过,0 skipped。
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
## 3.10.1 (2026-10-19)
|
|
39
|
+
|
|
40
|
+
> 「一键签到所有账号」下线。Trae 限制**每台设备每天只能签到一次**:本机任一账号签过之后,这个按钮
|
|
41
|
+
> 对整批账号在当天剩余时间里只能是灰的——一个永远点不动的控件,还得在下方专门写一句话解释它为什么灰。
|
|
42
|
+
> 单账号签到仍在名册表里,那才是能真正执行的入口。
|
|
43
|
+
|
|
44
|
+
### 变更
|
|
45
|
+
|
|
46
|
+
- **移除账号池动作行里的「一键签到所有账号」按钮**,动作行现在只剩「一键测试所有账号」。连带移除
|
|
47
|
+
批量签到的请求逻辑(`runCheckin`)、`busy` 的 `checkin` 态、签到进行中的状态行,以及只服务于这个
|
|
48
|
+
按钮的 8 个文案键(中英各一份:`poolCheckinAll` / `poolCheckingIn` / `poolLogCheckinStart` /
|
|
49
|
+
`poolLogCheckinClaimed` / `poolLogCheckinAlready` / `poolLogCheckinFailed` / `poolLogCheckinDone` /
|
|
50
|
+
`poolActionNoteCheckinDevice`),并删掉卡片里已不可达的 `checkin-unsupported` 409 分支。
|
|
51
|
+
- **单账号签到不受影响**:名册表(「Trae 账号」表格)的签到列仍按账号逐个签到,签到列的状态文字与
|
|
52
|
+
本机设备限制提示照旧。
|
|
53
|
+
- Host 侧 `?action=checkin` 路由与 `checkinSupported` 字段保留不删:浏览器里可能还有旧的 client
|
|
54
|
+
bundle 在调它,删掉会让它们直接 503。新卡片不再调用该 action。
|
|
55
|
+
|
|
56
|
+
### 测试
|
|
57
|
+
|
|
58
|
+
- `card-pool` 的批量签到用例改为断言按钮缺席:不再渲染 `poolCheckinAll`、不再出现「本机今日的签到
|
|
59
|
+
已用掉」提示行;同时保留「成员为空」「面板无免费模型」两条原因断言,确保这次删除没有顺带删掉测试
|
|
60
|
+
按钮的说明。全仓 **888** 例通过,0 skipped。
|
|
61
|
+
|
|
3
62
|
## 3.10.0 (2026-10-18)
|
|
4
63
|
|
|
5
64
|
> 本版把「账号在本机到底存了几份、删一个账号到底删掉了什么」讲清楚并做对:本机每个 Trae 登录都会
|
package/lib/client.js
CHANGED
|
@@ -136,6 +136,22 @@ window.__ModuleLoader__.load({
|
|
|
136
136
|
function withTraeTransferAction(path, panel, action) {
|
|
137
137
|
return `${path}?${TRAE_REGION_PARAM}=${panel}&action=${action}`;
|
|
138
138
|
}
|
|
139
|
+
/** Query parameter asking for a thinner first-paint document (issue #77). */
|
|
140
|
+
const TRAE_DETAIL_PARAM = "detail";
|
|
141
|
+
/** The only recognised `detail` value: local state only, no upstream read. */
|
|
142
|
+
const TRAE_DETAIL_FAST = "fast";
|
|
143
|
+
/**
|
|
144
|
+
* Address one panel's usage route asking for the FAST document, e.g.
|
|
145
|
+
* `/plugins/dsh-connect-trae/usage?region=cn&detail=fast`.
|
|
146
|
+
*
|
|
147
|
+
* Kept beside {@link withTraeRegion} for the same reason as
|
|
148
|
+
* {@link withTraeTransferAction}: the region is already a parameter, so a caller
|
|
149
|
+
* appending `&detail=` by hand is one typo away from a request that silently
|
|
150
|
+
* asks for the wrong document.
|
|
151
|
+
*/
|
|
152
|
+
function withTraeRegionFastDetail(path, panel) {
|
|
153
|
+
return `${withTraeRegion(path, panel)}&${TRAE_DETAIL_PARAM}=${TRAE_DETAIL_FAST}`;
|
|
154
|
+
}
|
|
139
155
|
/**
|
|
140
156
|
* Peel ONE `{get(): T}` live reference, the shape DSH 0.1.7 delivers a
|
|
141
157
|
* volatile-marked settings field as (see {@link unwrapVolatileDeep}).
|
|
@@ -1269,7 +1285,6 @@ window.__ModuleLoader__.load({
|
|
|
1269
1285
|
if (status === 409) switch (body?.reason) {
|
|
1270
1286
|
case "no-members": return t("row.poolErrNoMembers");
|
|
1271
1287
|
case "no-live-members": return t("row.poolErrNoLiveMembers");
|
|
1272
|
-
case "checkin-unsupported": return t("row.poolErrCheckinUnsupported");
|
|
1273
1288
|
case "target-model-stale": return t("row.poolErrTargetStale");
|
|
1274
1289
|
case "no-free-model": return t("row.poolErrNoFreeModel");
|
|
1275
1290
|
default: return t("row.poolErrConflict");
|
|
@@ -1497,43 +1512,6 @@ window.__ModuleLoader__.load({
|
|
|
1497
1512
|
if (mountRef.current) setBusy(void 0);
|
|
1498
1513
|
}
|
|
1499
1514
|
};
|
|
1500
|
-
const runCheckin = async () => {
|
|
1501
|
-
if (busy !== void 0) return;
|
|
1502
|
-
const members = [...selected];
|
|
1503
|
-
if (members.length === 0) {
|
|
1504
|
-
pushLog("warn", t("row.poolLogNoMembers"));
|
|
1505
|
-
return;
|
|
1506
|
-
}
|
|
1507
|
-
setBusy("checkin");
|
|
1508
|
-
setNow(Date.now());
|
|
1509
|
-
pushLog("info", t("row.poolLogCheckinStart", { count: members.length }));
|
|
1510
|
-
try {
|
|
1511
|
-
const response = await fetch(withTraePoolAction(panel, "checkin"), { method: "POST" });
|
|
1512
|
-
const body = await response.json();
|
|
1513
|
-
if (!response.ok) throw new Error(renderRouteError(t, response.status, body));
|
|
1514
|
-
for (const row of body?.rows ?? []) switch (row.status) {
|
|
1515
|
-
case "claimed":
|
|
1516
|
-
pushLog("ok", t("row.poolLogCheckinClaimed", { accountName: row.accountName }));
|
|
1517
|
-
break;
|
|
1518
|
-
case "already":
|
|
1519
|
-
case "device-already":
|
|
1520
|
-
pushLog("info", t("row.poolLogCheckinAlready", { accountName: row.accountName }));
|
|
1521
|
-
break;
|
|
1522
|
-
case "failed":
|
|
1523
|
-
case "disabled": pushLog("error", t("row.poolLogCheckinFailed", {
|
|
1524
|
-
accountName: row.accountName,
|
|
1525
|
-
message: row.message ?? ""
|
|
1526
|
-
}));
|
|
1527
|
-
}
|
|
1528
|
-
pushLog(members.length > 0 && body?.rows ? "ok" : "info", t("row.poolLogCheckinDone"));
|
|
1529
|
-
onRefresh?.();
|
|
1530
|
-
setNow(Date.now());
|
|
1531
|
-
} catch (error) {
|
|
1532
|
-
pushLog("error", t("row.poolLogBatchFailed", { message: error instanceof Error ? error.message : String(error) }));
|
|
1533
|
-
} finally {
|
|
1534
|
-
if (mountRef.current) setBusy(void 0);
|
|
1535
|
-
}
|
|
1536
|
-
};
|
|
1537
1515
|
const runTest = async () => {
|
|
1538
1516
|
if (busy !== void 0) return;
|
|
1539
1517
|
const members = [...selected];
|
|
@@ -1763,7 +1741,6 @@ window.__ModuleLoader__.load({
|
|
|
1763
1741
|
*/
|
|
1764
1742
|
const actionNotes = [];
|
|
1765
1743
|
if (selected.size === 0) actionNotes.push(t("row.poolActionNoteNoMembers"));
|
|
1766
|
-
if (deviceClaimed) actionNotes.push(t("row.poolActionNoteCheckinDevice"));
|
|
1767
1744
|
if (pool.targetModelSource === "none") actionNotes.push(t("row.poolActionNoteTestNone"));
|
|
1768
1745
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1769
1746
|
className: "dsm-trae-pool",
|
|
@@ -1798,16 +1775,6 @@ window.__ModuleLoader__.load({
|
|
|
1798
1775
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1799
1776
|
className: "dsm-trae-pool-actions",
|
|
1800
1777
|
children: [
|
|
1801
|
-
pool.checkinSupported === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1802
|
-
type: "button",
|
|
1803
|
-
className: "dsm-btn dsm-btn-outline dsm-trae-pool-action-checkin",
|
|
1804
|
-
disabled: busy !== void 0 || !writable || selected.size === 0 || deviceClaimed,
|
|
1805
|
-
title: deviceClaimed ? t("row.checkinDeviceSpent") : void 0,
|
|
1806
|
-
onClick: () => {
|
|
1807
|
-
runCheckin();
|
|
1808
|
-
},
|
|
1809
|
-
children: busy === "checkin" ? t("row.poolCheckingIn") : t("row.poolCheckinAll")
|
|
1810
|
-
}) : null,
|
|
1811
1778
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1812
1779
|
type: "button",
|
|
1813
1780
|
className: "dsm-btn dsm-btn-outline dsm-trae-pool-action-test",
|
|
@@ -1818,7 +1785,7 @@ window.__ModuleLoader__.load({
|
|
|
1818
1785
|
},
|
|
1819
1786
|
children: busy === "test" ? t("row.poolTesting") : t("row.poolTestAll")
|
|
1820
1787
|
}),
|
|
1821
|
-
busy === "
|
|
1788
|
+
busy === "test" || testing !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1822
1789
|
className: "dsm-trae-pool-busy",
|
|
1823
1790
|
role: "status",
|
|
1824
1791
|
children: t("row.poolBusyHint")
|
|
@@ -3086,21 +3053,18 @@ window.__ModuleLoader__.load({
|
|
|
3086
3053
|
const [open, setOpen] = (0, react.useState)(view === "page");
|
|
3087
3054
|
/** The region whose tab is on screen; each tab is its own provider stack. */
|
|
3088
3055
|
const [activeRegion, setActiveRegion] = (0, react.useState)("cn");
|
|
3089
|
-
/**
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
accounts: []
|
|
3102
|
-
}
|
|
3103
|
-
});
|
|
3056
|
+
/**
|
|
3057
|
+
* Last-known usage per region, so tab dots survive tab switches.
|
|
3058
|
+
*
|
|
3059
|
+
* A region is ABSENT until something has been read for it (issue #77). It used
|
|
3060
|
+
* to be seeded with a signed-out document, which meant the whole wait for the
|
|
3061
|
+
* first paint rendered as "not signed in — sign in again / scan for accounts"
|
|
3062
|
+
* to a user who was perfectly signed in. The difference between "nothing known
|
|
3063
|
+
* yet" and "known to be signed out" is the difference between a loading line
|
|
3064
|
+
* and a wrong instruction, so the map starts empty and the render below says
|
|
3065
|
+
* which one it is.
|
|
3066
|
+
*/
|
|
3067
|
+
const [statusByRegion, setStatusByRegion] = (0, react.useState)({});
|
|
3104
3068
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
3105
3069
|
/**
|
|
3106
3070
|
* Availability verdicts from the one-click test, per region, keyed by model
|
|
@@ -3373,10 +3337,54 @@ window.__ModuleLoader__.load({
|
|
|
3373
3337
|
return;
|
|
3374
3338
|
}
|
|
3375
3339
|
}, [t]);
|
|
3340
|
+
/**
|
|
3341
|
+
* Paint the fast document (issue #77).
|
|
3342
|
+
*
|
|
3343
|
+
* The full document above cannot answer before the Host has read every member's
|
|
3344
|
+
* balance — one upstream round-trip per account, and this panel's pool assembly
|
|
3345
|
+
* happens to walk the membership more than once. That is the wait the issue
|
|
3346
|
+
* reports: tens of seconds in which the card used to show nothing at all.
|
|
3347
|
+
*
|
|
3348
|
+
* The fast document is built from local state only (no upstream read at all), so
|
|
3349
|
+
* it answers in milliseconds and carries everything needed to DRAW: the bound
|
|
3350
|
+
* account, its expiry, the model list and the pool rows. The live figures it
|
|
3351
|
+
* omits (credits, check-in column) are exactly the values that need the round
|
|
3352
|
+
* trip, and the card already renders their absence as `—` / unknown.
|
|
3353
|
+
*
|
|
3354
|
+
* Deliberately separate from {@link refreshUsage} rather than a parameter of it:
|
|
3355
|
+
* a first paint never writes an error state (a slow upstream is not the user's
|
|
3356
|
+
* problem until the full document says so), and it must never downgrade a region
|
|
3357
|
+
* that has already been painted in full.
|
|
3358
|
+
*/
|
|
3359
|
+
const refreshUsageFast = (0, react.useCallback)(async (region, signal) => {
|
|
3360
|
+
try {
|
|
3361
|
+
const response = await fetch(withTraeRegionFastDetail(TRAE_USAGE_PATH, region), {
|
|
3362
|
+
headers: { accept: "application/json" },
|
|
3363
|
+
credentials: "same-origin",
|
|
3364
|
+
...signal === void 0 ? {} : { signal }
|
|
3365
|
+
});
|
|
3366
|
+
if (!response.ok) return;
|
|
3367
|
+
const usage = await response.json().catch(() => void 0);
|
|
3368
|
+
if (usage === void 0) return;
|
|
3369
|
+
if (!mounted.current || signal?.aborted === true) return;
|
|
3370
|
+
setStatusByRegion((prev) => {
|
|
3371
|
+
const previous = prev[region];
|
|
3372
|
+
if (previous !== void 0 && previous.status === "signed-in") return prev;
|
|
3373
|
+
return {
|
|
3374
|
+
...prev,
|
|
3375
|
+
[region]: usage
|
|
3376
|
+
};
|
|
3377
|
+
});
|
|
3378
|
+
} catch {}
|
|
3379
|
+
}, []);
|
|
3376
3380
|
(0, react.useEffect)(() => {
|
|
3377
3381
|
if (!open || !activeRegionOn) return;
|
|
3378
3382
|
const controller = new AbortController();
|
|
3379
|
-
|
|
3383
|
+
(async () => {
|
|
3384
|
+
await refreshUsageFast(activeRegion, controller.signal);
|
|
3385
|
+
if (controller.signal.aborted) return;
|
|
3386
|
+
refreshUsage(activeRegion, controller.signal);
|
|
3387
|
+
})();
|
|
3380
3388
|
return () => {
|
|
3381
3389
|
controller.abort();
|
|
3382
3390
|
};
|
|
@@ -3384,9 +3392,19 @@ window.__ModuleLoader__.load({
|
|
|
3384
3392
|
open,
|
|
3385
3393
|
activeRegion,
|
|
3386
3394
|
activeRegionOn,
|
|
3387
|
-
refreshUsage
|
|
3395
|
+
refreshUsage,
|
|
3396
|
+
refreshUsageFast
|
|
3388
3397
|
]);
|
|
3389
|
-
|
|
3398
|
+
/**
|
|
3399
|
+
* The document actually on screen for this region, and whether one has arrived.
|
|
3400
|
+
*
|
|
3401
|
+
* `loaded === undefined` is the only state that may render the loading line: a
|
|
3402
|
+
* signed-out document is a real answer (the user must sign in), never a stand-in
|
|
3403
|
+
* for "still fetching".
|
|
3404
|
+
*/
|
|
3405
|
+
const loaded = statusByRegion[activeRegion];
|
|
3406
|
+
const loading = loaded === void 0;
|
|
3407
|
+
const status = loaded ?? {
|
|
3390
3408
|
status: "signed-out",
|
|
3391
3409
|
accounts: []
|
|
3392
3410
|
};
|
|
@@ -4072,7 +4090,7 @@ window.__ModuleLoader__.load({
|
|
|
4072
4090
|
children: view.label
|
|
4073
4091
|
});
|
|
4074
4092
|
};
|
|
4075
|
-
const label = status.status === "signed-in" ? t("row.signedIn", { accountName: status.accountName }) : status.status === "error" ? t("row.requestFailed") : t("row.signedOut");
|
|
4093
|
+
const label = loading ? t("row.accountsLoading") : status.status === "signed-in" ? t("row.signedIn", { accountName: status.accountName }) : status.status === "error" ? t("row.requestFailed") : t("row.signedOut");
|
|
4076
4094
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
4077
4095
|
className: `dsm-plugin-card${open ? " dsm-plugin-card-open" : ""}`,
|
|
4078
4096
|
children: [
|
|
@@ -4707,7 +4725,12 @@ window.__ModuleLoader__.load({
|
|
|
4707
4725
|
]
|
|
4708
4726
|
})
|
|
4709
4727
|
] }) : null,
|
|
4710
|
-
status.status === "signed-out" ? /* @__PURE__ */ (0, react_jsx_runtime.
|
|
4728
|
+
status.status === "signed-out" && loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4729
|
+
className: "dsm-trae-usage-text",
|
|
4730
|
+
role: "status",
|
|
4731
|
+
children: t("row.accountsLoading")
|
|
4732
|
+
}) }) : null,
|
|
4733
|
+
status.status === "signed-out" && !loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4711
4734
|
className: "dsm-trae-usage-text",
|
|
4712
4735
|
children: status.message ?? t("row.signedOutHint")
|
|
4713
4736
|
}), status.searched && status.searched.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
@@ -5521,6 +5544,7 @@ window.__ModuleLoader__.load({
|
|
|
5521
5544
|
"row.transferNoToken": "no token, will be skipped",
|
|
5522
5545
|
"row.accountsRescan": "Detect accounts again",
|
|
5523
5546
|
"row.accountsScanning": "Detecting…",
|
|
5547
|
+
"row.accountsLoading": "Loading your accounts…",
|
|
5524
5548
|
"row.accountsNone": "This region reports no usable account.",
|
|
5525
5549
|
"row.accountCurrentBadge": "in use",
|
|
5526
5550
|
"row.accountSwitch": "Switch",
|
|
@@ -5615,7 +5639,6 @@ window.__ModuleLoader__.load({
|
|
|
5615
5639
|
"row.poolCurrentBadge": "serving",
|
|
5616
5640
|
"row.poolMember": "in pool",
|
|
5617
5641
|
"row.poolNotMember": "not in pool",
|
|
5618
|
-
"row.poolCheckinAll": "Check in all accounts",
|
|
5619
5642
|
"row.poolBusyHint": "Working through accounts one at a time…",
|
|
5620
5643
|
"row.poolTestAll": "Test all accounts",
|
|
5621
5644
|
"row.poolTesting": "Testing…",
|
|
@@ -5658,7 +5681,6 @@ window.__ModuleLoader__.load({
|
|
|
5658
5681
|
"row.poolTargetStaleClear": "Back to automatic",
|
|
5659
5682
|
"row.poolTargetNone": "No free model available for this panel",
|
|
5660
5683
|
"row.poolTargetNoneHint": "The pool can only pick a free model; none resolves right now, so tests are disabled.",
|
|
5661
|
-
"row.poolActionNoteCheckinDevice": "This machine already used today's check-in (Trae allows one per device per day) — check-in is unavailable until tomorrow.",
|
|
5662
5684
|
"row.poolActionNoteTestNone": "No free model resolves in this panel — pick a target model before testing.",
|
|
5663
5685
|
"row.poolActionNoteNoMembers": "Tick the accounts you want in the pool and save first.",
|
|
5664
5686
|
"row.poolTargetAutoOption": "Automatic (free model)",
|
|
@@ -5683,12 +5705,7 @@ window.__ModuleLoader__.load({
|
|
|
5683
5705
|
"row.poolLogTitle": "Activity",
|
|
5684
5706
|
"row.poolLogClear": "Clear",
|
|
5685
5707
|
"row.poolLogEmpty": "No pool activity yet.",
|
|
5686
|
-
"row.poolLogCheckinStart": "Check-in started for {count} account(s).",
|
|
5687
5708
|
"row.poolLogTestStart": "Test started for {count} account(s) against {model}.",
|
|
5688
|
-
"row.poolLogCheckinClaimed": "{accountName} checked in.",
|
|
5689
|
-
"row.poolLogCheckinAlready": "{accountName} already checked in today.",
|
|
5690
|
-
"row.poolLogCheckinFailed": "{accountName} check-in failed: {message}",
|
|
5691
|
-
"row.poolLogCheckinDone": "Check-in finished.",
|
|
5692
5709
|
"row.poolLogTestRow": "{accountName}: {outcome}.",
|
|
5693
5710
|
"row.poolLogTestRowMalformed": "{accountName}: malformed result.",
|
|
5694
5711
|
"row.poolLogTestDone": "Test finished.",
|
|
@@ -5741,7 +5758,6 @@ window.__ModuleLoader__.load({
|
|
|
5741
5758
|
"row.poolErrNoFreeModel": "no free model is available for this panel",
|
|
5742
5759
|
"row.poolErrConflict": "the pool refused the request",
|
|
5743
5760
|
"row.poolErrGeneric": "pool request failed (HTTP {status})",
|
|
5744
|
-
"row.poolCheckingIn": "Checking in…",
|
|
5745
5761
|
"row.poolSettingsHint": "The switch and the target model are preferences — save them. Test, check in and delete act at once.",
|
|
5746
5762
|
"row.poolLogSaved": "Pool settings saved.",
|
|
5747
5763
|
"row.poolLogSaveFailed": "Could not save the pool settings.",
|
|
@@ -5850,6 +5866,7 @@ window.__ModuleLoader__.load({
|
|
|
5850
5866
|
"row.transferNoToken": "无 Token,将被跳过",
|
|
5851
5867
|
"row.accountsRescan": "重新检测账号",
|
|
5852
5868
|
"row.accountsScanning": "正在检测…",
|
|
5869
|
+
"row.accountsLoading": "正在加载账号…",
|
|
5853
5870
|
"row.accountsNone": "该区域没有可列出的账号。",
|
|
5854
5871
|
"row.accountCurrentBadge": "当前使用",
|
|
5855
5872
|
"row.accountSwitch": "切换",
|
|
@@ -5944,7 +5961,6 @@ window.__ModuleLoader__.load({
|
|
|
5944
5961
|
"row.poolCurrentBadge": "派单中",
|
|
5945
5962
|
"row.poolMember": "已在池中",
|
|
5946
5963
|
"row.poolNotMember": "未入池",
|
|
5947
|
-
"row.poolCheckinAll": "一键签到所有账号",
|
|
5948
5964
|
"row.poolBusyHint": "逐个处理账号中…",
|
|
5949
5965
|
"row.poolTestAll": "一键测试所有账号",
|
|
5950
5966
|
"row.poolTesting": "测速中…",
|
|
@@ -5987,7 +6003,6 @@ window.__ModuleLoader__.load({
|
|
|
5987
6003
|
"row.poolTargetStaleClear": "改回自动",
|
|
5988
6004
|
"row.poolTargetNone": "本面板暂无可用免费模型",
|
|
5989
6005
|
"row.poolTargetNoneHint": "账号池只会挑免费模型;当前无法解析出可用模型,测速暂被禁用。",
|
|
5990
|
-
"row.poolActionNoteCheckinDevice": "本机今日的签到已用掉(Trae 按每台设备每天一次限制),明天才能再签。",
|
|
5991
6006
|
"row.poolActionNoteTestNone": "本面板没有解析出免费模型——先指定一个目标模型再测速。",
|
|
5992
6007
|
"row.poolActionNoteNoMembers": "先勾选要入池的账号并保存。",
|
|
5993
6008
|
"row.poolTargetAutoOption": "自动(免费模型)",
|
|
@@ -6012,12 +6027,7 @@ window.__ModuleLoader__.load({
|
|
|
6012
6027
|
"row.poolLogTitle": "活动记录",
|
|
6013
6028
|
"row.poolLogClear": "清空",
|
|
6014
6029
|
"row.poolLogEmpty": "暂无账号池活动。",
|
|
6015
|
-
"row.poolLogCheckinStart": "开始为 {count} 个账号签到。",
|
|
6016
6030
|
"row.poolLogTestStart": "开始为 {count} 个账号测速({model})。",
|
|
6017
|
-
"row.poolLogCheckinClaimed": "{accountName} 已签到。",
|
|
6018
|
-
"row.poolLogCheckinAlready": "{accountName} 今日已签到。",
|
|
6019
|
-
"row.poolLogCheckinFailed": "{accountName} 签到失败:{message}",
|
|
6020
|
-
"row.poolLogCheckinDone": "签到完成。",
|
|
6021
6031
|
"row.poolLogTestRow": "{accountName}:{outcome}。",
|
|
6022
6032
|
"row.poolLogTestRowMalformed": "{accountName}:结果异常。",
|
|
6023
6033
|
"row.poolLogTestDone": "测速完成。",
|
|
@@ -6070,7 +6080,6 @@ window.__ModuleLoader__.load({
|
|
|
6070
6080
|
"row.poolErrNoFreeModel": "本面板没有可用的免费模型",
|
|
6071
6081
|
"row.poolErrConflict": "账号池拒绝了该请求",
|
|
6072
6082
|
"row.poolErrGeneric": "账号池请求失败(HTTP {status})",
|
|
6073
|
-
"row.poolCheckingIn": "签到中…",
|
|
6074
6083
|
"row.poolSettingsHint": "开关与目标模型是偏好,改完要保存;行内的测速、签到与删除立即生效。",
|
|
6075
6084
|
"row.poolLogSaved": "账号池设置已保存。",
|
|
6076
6085
|
"row.poolLogSaveFailed": "无法保存账号池设置。",
|
package/lib/index.d.ts
CHANGED
|
@@ -3459,6 +3459,31 @@ interface TraePoolDeps {
|
|
|
3459
3459
|
* Only the status document's own assembly and an explicit batch call it.
|
|
3460
3460
|
*/
|
|
3461
3461
|
members(panel: TraePanel): Promise<readonly TraePoolMember[]>;
|
|
3462
|
+
/**
|
|
3463
|
+
* One panel's pool membership from LOCAL state only — the first-paint source
|
|
3464
|
+
* (issue #77).
|
|
3465
|
+
*
|
|
3466
|
+
* Cheap by contract in the strongest sense: no upstream read at all, so the
|
|
3467
|
+
* account table can be painted before any billing endpoint answers. A member
|
|
3468
|
+
* whose credit total has never been measured carries no `credits`, which the
|
|
3469
|
+
* card already renders as `—` rather than as a wrong figure; the follow-up
|
|
3470
|
+
* full document fills the real numbers in.
|
|
3471
|
+
*
|
|
3472
|
+
* A Host built without it degrades to omitting the pool block from a
|
|
3473
|
+
* first-paint document rather than paying {@link members} there — the card
|
|
3474
|
+
* then shows its single-account view for one refresh cycle.
|
|
3475
|
+
*/
|
|
3476
|
+
localMembers?(panel: TraePanel): Promise<readonly TraePoolMember[]>;
|
|
3477
|
+
/**
|
|
3478
|
+
* The panel's saved member names, from a local scan only.
|
|
3479
|
+
*
|
|
3480
|
+
* The first-paint counterpart of {@link memberNames}. The full document builds
|
|
3481
|
+
* these by walking {@link members} (which reads every balance), which is a
|
|
3482
|
+
* second full membership pass for a map of names; the fast document must not
|
|
3483
|
+
* pay that, and it does not need to: the saved-list rows it renders are the
|
|
3484
|
+
* ones {@link localMembers} already names.
|
|
3485
|
+
*/
|
|
3486
|
+
localMemberNames?(panel: TraePanel): Promise<Record<string, string>>;
|
|
3462
3487
|
/**
|
|
3463
3488
|
* The checked ids that still resolve to a local sign-in, from a local scan only.
|
|
3464
3489
|
* The batch route's empty-pool guard runs this on every request, so it must not
|
|
@@ -3482,6 +3507,16 @@ interface TraePoolDeps {
|
|
|
3482
3507
|
* TODAY because this machine already spent its claim on another account.
|
|
3483
3508
|
*/
|
|
3484
3509
|
checkinState?(panel: TraePanel): Promise<Record<string, TraePoolCheckinState>>;
|
|
3510
|
+
/**
|
|
3511
|
+
* Today's check-in state from the cells already measured this process — the
|
|
3512
|
+
* first-paint counterpart (issue #77) of {@link checkinState}.
|
|
3513
|
+
*
|
|
3514
|
+
* Synchronous by contract: it reads the Host's measurement cache and nothing
|
|
3515
|
+
* else, so it must never start a request. A Host without it degrades to an
|
|
3516
|
+
* unknown check-in column in the first paint, which the card already renders
|
|
3517
|
+
* as unknown; the full document fills the column in.
|
|
3518
|
+
*/
|
|
3519
|
+
cachedCheckinState?(panel: TraePanel): Readonly<Record<string, TraePoolCheckinState>>;
|
|
3485
3520
|
/** Run the check-in batch. Persists nothing to settings; drops its own caches. */
|
|
3486
3521
|
checkin?(panel: TraePanel): Promise<readonly TraePoolCheckinRow[]>;
|
|
3487
3522
|
/** Run the model-availability batch and persist its measurements. */
|
|
@@ -3500,6 +3535,19 @@ interface TraePoolCheckinState {
|
|
|
3500
3535
|
/** Whether this account is offered the reward at all; false renders `disabled`. */
|
|
3501
3536
|
enabled?: boolean;
|
|
3502
3537
|
}
|
|
3538
|
+
/**
|
|
3539
|
+
* Which usage document a request asked for (issue #77).
|
|
3540
|
+
*
|
|
3541
|
+
* `full` is the document every earlier caller has always received: live credit
|
|
3542
|
+
* figures, fresh check-in state, the ranked pool. `fast` is the same document
|
|
3543
|
+
* built from LOCAL state only — no upstream read at all — so the card can paint
|
|
3544
|
+
* the account table immediately and then upgrade it with the full document.
|
|
3545
|
+
*
|
|
3546
|
+
* The distinction lives in ONE optional query parameter (`detail`) so an old
|
|
3547
|
+
* caller, a hand-written curl and a browser refresh all keep working: an absent
|
|
3548
|
+
* or unrecognised value is `full`.
|
|
3549
|
+
*/
|
|
3550
|
+
type TraeStatusDetail = 'full' | 'fast';
|
|
3503
3551
|
/**
|
|
3504
3552
|
* Assemble one region's card document. `panel` is the tab the card is on; the
|
|
3505
3553
|
* panel-scoped store already answers with only that region's accounts, so the
|
|
@@ -3507,7 +3555,9 @@ interface TraePoolCheckinState {
|
|
|
3507
3555
|
* Sign-in state is read-only; credit is a live billing answer whose failure
|
|
3508
3556
|
* degrades to `creditsError` rather than failing the whole document.
|
|
3509
3557
|
*/
|
|
3510
|
-
declare function traeWebUsage(deps: TraeUsageRouteOptions, panel: TraePanel
|
|
3558
|
+
declare function traeWebUsage(deps: TraeUsageRouteOptions, panel: TraePanel, options?: {
|
|
3559
|
+
detail?: TraeStatusDetail;
|
|
3560
|
+
}): Promise<TraeWebUsage>;
|
|
3511
3561
|
/** Mount the GET usage route on an optional webServer context. */
|
|
3512
3562
|
declare function registerTraeUsageRoute(ctx: Context$1, deps: TraeUsageRouteOptions): void;
|
|
3513
3563
|
//#endregion
|
package/lib/index.js
CHANGED
|
@@ -1089,6 +1089,23 @@ function regionOfTraeStatusUrl(url) {
|
|
|
1089
1089
|
if (value === null || value === "") return "cn";
|
|
1090
1090
|
return TRAE_PANELS.includes(value) ? value : void 0;
|
|
1091
1091
|
}
|
|
1092
|
+
/** Query parameter asking for a thinner first-paint document (issue #77). */
|
|
1093
|
+
const TRAE_DETAIL_PARAM = "detail";
|
|
1094
|
+
/** The only recognised `detail` value: local state only, no upstream read. */
|
|
1095
|
+
const TRAE_DETAIL_FAST = "fast";
|
|
1096
|
+
/**
|
|
1097
|
+
* Whether a request URL asks for the fast document.
|
|
1098
|
+
*
|
|
1099
|
+
* Only the exact value counts — a typo (`fastest`, `FAST`) degrades to the FULL
|
|
1100
|
+
* document, because a thinner panel is a worse failure than a slower one: the
|
|
1101
|
+
* caller that made the mistake is asking about an optimisation, so the safe
|
|
1102
|
+
* answer is the truthful one.
|
|
1103
|
+
*/
|
|
1104
|
+
function fastDetailOf(url) {
|
|
1105
|
+
const at = url.indexOf("?");
|
|
1106
|
+
if (at === -1) return false;
|
|
1107
|
+
return new URLSearchParams(url.slice(at + 1)).get(TRAE_DETAIL_PARAM) === TRAE_DETAIL_FAST;
|
|
1108
|
+
}
|
|
1092
1109
|
/**
|
|
1093
1110
|
* Peel ONE `{get(): T}` live reference, the shape DSH 0.1.7 delivers a
|
|
1094
1111
|
* volatile-marked settings field as (see {@link unwrapVolatileDeep}).
|
|
@@ -6856,7 +6873,8 @@ function toCheckin(status) {
|
|
|
6856
6873
|
* Sign-in state is read-only; credit is a live billing answer whose failure
|
|
6857
6874
|
* degrades to `creditsError` rather than failing the whole document.
|
|
6858
6875
|
*/
|
|
6859
|
-
async function traeWebUsage(deps, panel) {
|
|
6876
|
+
async function traeWebUsage(deps, panel, options = {}) {
|
|
6877
|
+
const fast = options.detail === "fast";
|
|
6860
6878
|
const store = deps.store(panel);
|
|
6861
6879
|
const enabled = deps.regionEnabled(panel);
|
|
6862
6880
|
/**
|
|
@@ -6887,7 +6905,8 @@ async function traeWebUsage(deps, panel) {
|
|
|
6887
6905
|
}
|
|
6888
6906
|
let credential;
|
|
6889
6907
|
try {
|
|
6890
|
-
credential = await store.resolve();
|
|
6908
|
+
credential = fast ? await store.current() : await store.resolve();
|
|
6909
|
+
if (credential === void 0) credential = await store.resolve();
|
|
6891
6910
|
} catch (error) {
|
|
6892
6911
|
return {
|
|
6893
6912
|
status: "signed-out",
|
|
@@ -6916,7 +6935,16 @@ async function traeWebUsage(deps, panel) {
|
|
|
6916
6935
|
enabledModelIds: [...deps.enabledModelIds(panel)],
|
|
6917
6936
|
...deps.rawDiagnostic === void 0 ? {} : { rawChat: deps.rawDiagnostic(panel) }
|
|
6918
6937
|
};
|
|
6919
|
-
|
|
6938
|
+
if (fast) {
|
|
6939
|
+
const poolSection = await poolSectionOf(deps, panel, true);
|
|
6940
|
+
return {
|
|
6941
|
+
status: "signed-in",
|
|
6942
|
+
...account,
|
|
6943
|
+
...poolSection,
|
|
6944
|
+
...panel === "enterprise" ? { creditsUnavailable: "enterprise" } : {}
|
|
6945
|
+
};
|
|
6946
|
+
}
|
|
6947
|
+
const poolSection = await poolSectionOf(deps, panel, false);
|
|
6920
6948
|
const client = deps.client(panel);
|
|
6921
6949
|
if (regionOfPanel(panel) === "ai") try {
|
|
6922
6950
|
const payStatus = await client.payStatus();
|
|
@@ -7009,10 +7037,10 @@ async function creditAlternatives(deps, panel, accounts) {
|
|
|
7009
7037
|
* BOUND account, which `traeWebUsage` reports as `status: error` before the pool
|
|
7010
7038
|
* is even assembled.
|
|
7011
7039
|
*/
|
|
7012
|
-
async function poolSectionOf(deps, panel) {
|
|
7040
|
+
async function poolSectionOf(deps, panel, fast = false) {
|
|
7013
7041
|
if (deps.pool === void 0) return {};
|
|
7014
7042
|
try {
|
|
7015
|
-
const pool = await traeWebPool(deps, panel);
|
|
7043
|
+
const pool = await traeWebPool(deps, panel, fast);
|
|
7016
7044
|
return pool === void 0 ? {} : { pool };
|
|
7017
7045
|
} catch {
|
|
7018
7046
|
return {};
|
|
@@ -7030,20 +7058,35 @@ async function poolSectionOf(deps, panel) {
|
|
|
7030
7058
|
* Every per-row read is failure-tolerant on purpose: one account whose balance
|
|
7031
7059
|
* read throws must not remove the other members from the table.
|
|
7032
7060
|
*/
|
|
7033
|
-
async function traeWebPool(deps, panel) {
|
|
7061
|
+
async function traeWebPool(deps, panel, fast = false) {
|
|
7034
7062
|
const pool = deps.pool;
|
|
7035
7063
|
if (pool === void 0) return void 0;
|
|
7036
7064
|
const preferences = pool.preferences(panel);
|
|
7037
|
-
const members = await pool.members(panel);
|
|
7065
|
+
const members = fast ? await pool.localMembers?.(panel) : await pool.members(panel);
|
|
7066
|
+
if (members === void 0) return void 0;
|
|
7038
7067
|
const nowMs = Date.now();
|
|
7039
7068
|
const ranked = rankPool(members, nowMs);
|
|
7040
7069
|
const target = resolveTargetModel(pool.catalog?.(panel) ?? [], preferences.targetModelId);
|
|
7041
7070
|
const byId = new Map(members.map((member) => [member.account.id, member]));
|
|
7042
7071
|
const excludedById = new Map(ranked.flatMap((row) => row.excludedBy === void 0 ? [] : [[row.account.id, row.excludedBy]]));
|
|
7043
7072
|
const rankedIds = new Set(ranked.map((row) => row.account.id));
|
|
7044
|
-
|
|
7045
|
-
|
|
7046
|
-
|
|
7073
|
+
/**
|
|
7074
|
+
* ISSUE #77: every dep below is shaped so the first paint can avoid the
|
|
7075
|
+
* upstream. `otherAccounts`, `memberNames` and `checkinState` are all derived
|
|
7076
|
+
* from a FULL membership pass on this Host (one balance read per account), and
|
|
7077
|
+
* three of them in a row is most of the reported wait. The fast document
|
|
7078
|
+
* answers the same questions from the roster it already has:
|
|
7079
|
+
*
|
|
7080
|
+
* - `otherAccounts`: the local roster minus the members already ranked. The
|
|
7081
|
+
* saved-but-unreachable ghosts are added below from `preferences` anyway, so
|
|
7082
|
+
* nothing a row is built from goes missing.
|
|
7083
|
+
* - `memberNames`: only consulted for ghosts, whose rows the fast document
|
|
7084
|
+
* carries from `localMemberNames` when the Host offers it.
|
|
7085
|
+
* - `checkinState`: the measurement cache, read synchronously.
|
|
7086
|
+
*/
|
|
7087
|
+
const others = fast ? members.filter((member) => !rankedIds.has(member.account.id)).map((member) => member.account) : (await pool.otherAccounts?.(panel) ?? []).filter((account) => !rankedIds.has(account.id));
|
|
7088
|
+
const checkinState = fast ? { ...pool.cachedCheckinState?.(panel) ?? {} } : await pool.checkinState?.(panel).catch(() => ({})) ?? {};
|
|
7089
|
+
const memberNames = fast ? await pool.localMemberNames?.(panel).catch(() => ({})) ?? {} : await pool.memberNames?.(panel).catch(() => ({})) ?? {};
|
|
7047
7090
|
const currentAccountId = await pool.currentAccountId?.(panel).catch(() => void 0);
|
|
7048
7091
|
/**
|
|
7049
7092
|
* A row per listed account, members first.
|
|
@@ -7239,8 +7282,9 @@ function registerTraeUsageRoute(ctx, deps) {
|
|
|
7239
7282
|
}
|
|
7240
7283
|
const panel = requestRegion(req, res);
|
|
7241
7284
|
if (panel === void 0) return;
|
|
7285
|
+
const detail = fastDetailOf(req.url ?? "") ? "fast" : "full";
|
|
7242
7286
|
try {
|
|
7243
|
-
json(res, 200, await traeWebUsage(deps, panel));
|
|
7287
|
+
json(res, 200, await traeWebUsage(deps, panel, { detail }));
|
|
7244
7288
|
} catch (error) {
|
|
7245
7289
|
json(res, 500, { error: safeMessage(error) });
|
|
7246
7290
|
}
|
|
@@ -8543,6 +8587,15 @@ const Config = z.object({
|
|
|
8543
8587
|
/** Every region, in card tab order. */
|
|
8544
8588
|
const REGION_KEYS = ["cn", "ai"];
|
|
8545
8589
|
/**
|
|
8590
|
+
* How long a measured check-in state stays usable (issue #77).
|
|
8591
|
+
*
|
|
8592
|
+
* The upstream rewards ONE check-in per device per day, so a figure measured
|
|
8593
|
+
* minutes ago cannot have changed; re-reading it is a pure upstream round-trip.
|
|
8594
|
+
* Ten minutes matches the sibling plugin's cache and is comfortably shorter than
|
|
8595
|
+
* a day, so a genuine second check-in still shows up on the next poll after it.
|
|
8596
|
+
*/
|
|
8597
|
+
const CHECKIN_CACHE_MS = 6e5;
|
|
8598
|
+
/**
|
|
8546
8599
|
* What ONE failed real request means for the account that served it (issue #44).
|
|
8547
8600
|
*
|
|
8548
8601
|
* The shim only reports account-scoped kinds here, so this table never has to
|
|
@@ -8992,6 +9045,24 @@ function apply(ctx, config) {
|
|
|
8992
9045
|
enterprise: /* @__PURE__ */ new Map(),
|
|
8993
9046
|
ai: /* @__PURE__ */ new Map()
|
|
8994
9047
|
};
|
|
9048
|
+
/**
|
|
9049
|
+
* Today's check-in state per account, measured by the LAST full read.
|
|
9050
|
+
*
|
|
9051
|
+
* Issue #77: the check-in column used to be an upstream call per account with
|
|
9052
|
+
* no memory at all, so every open of the card paid for every member twice (the
|
|
9053
|
+
* pool's own membership pass plus one `checkinStatus()` each). The first-paint
|
|
9054
|
+
* document may not wait for any of that, and re-measuring what was measured
|
|
9055
|
+
* minutes ago buys nothing: a check-in happens at most once per device per day.
|
|
9056
|
+
*
|
|
9057
|
+
* Entries older than {@link CHECKIN_CACHE_MS} are treated as absent rather than
|
|
9058
|
+
* as stale truth, so a document never claims "already checked in" off yesterday's
|
|
9059
|
+
* measurement. A cold cache means "not read", which the card renders as unknown.
|
|
9060
|
+
*/
|
|
9061
|
+
const checkinStateCache = {
|
|
9062
|
+
cn: {},
|
|
9063
|
+
enterprise: {},
|
|
9064
|
+
ai: {}
|
|
9065
|
+
};
|
|
8995
9066
|
const poolCreditsOfAccount = async (panel, accountId) => {
|
|
8996
9067
|
const credits = await stacks[panel].creditsOfAccount(accountId);
|
|
8997
9068
|
if (credits === void 0) return void 0;
|
|
@@ -9067,6 +9138,24 @@ function apply(ctx, config) {
|
|
|
9067
9138
|
}));
|
|
9068
9139
|
};
|
|
9069
9140
|
/**
|
|
9141
|
+
* Today's check-in state from the cells already measured — the first-paint read
|
|
9142
|
+
* (issue #77).
|
|
9143
|
+
*
|
|
9144
|
+
* Synchronous and local by contract: it never starts a request, so the card's
|
|
9145
|
+
* first paint cannot be held by the upstream. Only FRESH cells are returned; an
|
|
9146
|
+
* expired entry is dropped rather than reported, because "checked in" from four
|
|
9147
|
+
* hours ago is a claim about a day that may already be over.
|
|
9148
|
+
*/
|
|
9149
|
+
function cachedCheckinStateOf(panel) {
|
|
9150
|
+
const nowMs = Date.now();
|
|
9151
|
+
const state = {};
|
|
9152
|
+
for (const [accountId, entry] of Object.entries(checkinStateCache[panel])) {
|
|
9153
|
+
if (nowMs - entry.atMs >= CHECKIN_CACHE_MS) continue;
|
|
9154
|
+
state[accountId] = entry.state;
|
|
9155
|
+
}
|
|
9156
|
+
return state;
|
|
9157
|
+
}
|
|
9158
|
+
/**
|
|
9070
9159
|
* Pool members from LOCAL state only: the store's logins, the saved probes and
|
|
9071
9160
|
* the cached credit totals. No upstream call, so it is safe on the request
|
|
9072
9161
|
* path (see {@link poolCreditCache} for why the cache exists).
|
|
@@ -9291,7 +9380,8 @@ function apply(ctx, config) {
|
|
|
9291
9380
|
checkinSupported: (stored) => regionOfPanel(stored) === "cn",
|
|
9292
9381
|
checkin: async (stored) => checkinAllAccounts(await poolTargetsOf(stored), probeDeps()),
|
|
9293
9382
|
checkinState: async (stored) => {
|
|
9294
|
-
const state =
|
|
9383
|
+
const state = cachedCheckinStateOf(stored);
|
|
9384
|
+
const nowMs = Date.now();
|
|
9295
9385
|
for (const target of await poolTargetsOf(stored)) {
|
|
9296
9386
|
const credential = await stacks[stored].store.resolveCredentialOf(target.accountId);
|
|
9297
9387
|
if (credential === void 0) continue;
|
|
@@ -9301,15 +9391,21 @@ function apply(ctx, config) {
|
|
|
9301
9391
|
});
|
|
9302
9392
|
try {
|
|
9303
9393
|
const status = await client.checkinStatus();
|
|
9304
|
-
|
|
9394
|
+
const measured = {
|
|
9305
9395
|
checkedIn: status.checkedIn,
|
|
9306
9396
|
deviceCheckedIn: status.didCheckedIn,
|
|
9307
9397
|
enabled: status.enabled
|
|
9308
9398
|
};
|
|
9399
|
+
state[target.accountId] = measured;
|
|
9400
|
+
checkinStateCache[stored][target.accountId] = {
|
|
9401
|
+
state: measured,
|
|
9402
|
+
atMs: nowMs
|
|
9403
|
+
};
|
|
9309
9404
|
} catch {}
|
|
9310
9405
|
}
|
|
9311
9406
|
return state;
|
|
9312
9407
|
},
|
|
9408
|
+
cachedCheckinState: (stored) => cachedCheckinStateOf(stored),
|
|
9313
9409
|
test: async (stored, modelId, onRow) => {
|
|
9314
9410
|
await warmWireMap(stored);
|
|
9315
9411
|
const probeInputTokens = probeInputTokensOf(stored);
|
|
@@ -9409,6 +9505,12 @@ function apply(ctx, config) {
|
|
|
9409
9505
|
pool: {
|
|
9410
9506
|
preferences: (panel) => poolPreferencesOf(panel),
|
|
9411
9507
|
members: (panel) => poolMembersOf(panel),
|
|
9508
|
+
localMembers: (panel) => localPoolMembers(panel),
|
|
9509
|
+
localMemberNames: async (panel) => {
|
|
9510
|
+
const names = {};
|
|
9511
|
+
for (const member of await localPoolMembers(panel)) names[member.account.id] = member.account.accountName;
|
|
9512
|
+
return names;
|
|
9513
|
+
},
|
|
9412
9514
|
effectiveMemberAccountIds: async (panel) => {
|
|
9413
9515
|
return effectiveMembersOf(poolPreferencesOf(panel).memberAccountIds ?? [], new Set((await poolMembersOf(panel)).map((member) => member.account.id)));
|
|
9414
9516
|
},
|
|
@@ -9423,6 +9525,7 @@ function apply(ctx, config) {
|
|
|
9423
9525
|
checkinSupported: (panel) => regionOfPanel(panel) === "cn",
|
|
9424
9526
|
checkin: (panel) => poolDepsOf(panel).checkin?.(panel) ?? Promise.resolve([]),
|
|
9425
9527
|
checkinState: (panel) => poolDepsOf(panel).checkinState?.(panel) ?? Promise.resolve({}),
|
|
9528
|
+
cachedCheckinState: (panel) => cachedCheckinStateOf(panel),
|
|
9426
9529
|
test: (panel, modelId, onRow) => poolDepsOf(panel).test?.(panel, modelId, onRow) ?? Promise.resolve([]),
|
|
9427
9530
|
testOne: (panel, accountId) => poolDepsOf(panel).testOne?.(panel, accountId) ?? Promise.resolve(void 0),
|
|
9428
9531
|
removeOne: (panel, accountId) => poolDepsOf(panel).removeOne?.(panel, accountId) ?? Promise.resolve(false)
|
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.10.
|
|
5
|
+
"version": "3.10.2",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|