dsh-deepseek-web-login 0.6.4 → 0.6.6
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 +68 -0
- package/lib/client.js +19 -6
- package/lib/client.js.map +1 -1
- package/lib/index.js +169 -31
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,74 @@
|
|
|
2
2
|
|
|
3
3
|
本项目遵循大致语义化版本;日期为本地时间。
|
|
4
4
|
|
|
5
|
+
## 0.6.6 — 2026-09-29
|
|
6
|
+
|
|
7
|
+
**修「网络一抖,整库账号被标成需要重新登录,点重登还得重敲密码」。**
|
|
8
|
+
|
|
9
|
+
起因是用户申诉:面板上一排账号全写着「校验失败:fetch failed」,点「重登」之后浏览器
|
|
10
|
+
打开的是**登录页**,得重新填手机号 + 验证码。查下来是两件事叠在一起:
|
|
11
|
+
|
|
12
|
+
- **探活失败的两种性质没分开**:`fetch failed`(本次真因在 cause 里:
|
|
13
|
+
`net::ERR_NETWORK_IO_SUSPENDED`,机器休眠、网络挂起)与「token 失效」被写进**同一个字段**,
|
|
14
|
+
而这个字段的语义是"这个号必须重新登录" —— 徽章、账号轮换、重登是否清登录态全都读它。
|
|
15
|
+
一次休眠就把 9 个账号同时标红,凭证其实全是好的。
|
|
16
|
+
- **重登一进门就清登录态**:`!!lastVerifyError ⇒ 先清 profile + 登录分区`。
|
|
17
|
+
清除之后浏览器里什么都没有,于是必然要重新登录一遍 —— 这就是"点了还要我输密码"。
|
|
18
|
+
|
|
19
|
+
改法:
|
|
20
|
+
|
|
21
|
+
1. **写入点就分类**。只有授权类失败(401 / 403 / invalid token / 过期)写 `lastVerifyError`;
|
|
22
|
+
网络类(断网、超时、5xx、网络挂起)写新的 `lastCheckError`,且**不覆盖**已有的授权类结论
|
|
23
|
+
(否则真死号会被网络抖动洗白)。成功时两个标记一起清。
|
|
24
|
+
2. **重登先做一次只读探活**,按结果分三条路(判据是纯函数 `planRelogin`):
|
|
25
|
+
- 探活通过 ⇒ 账号本来就是好的:清掉标记,**不打开浏览器、不清任何东西**,一键结束;
|
|
26
|
+
- 网络类失败 ⇒ 只报网络问题,**不清登录态、不开窗口**(这时候重登必然白敲);
|
|
27
|
+
- 授权类失败 ⇒ 才清登录态 + 手动登录一次(原行为)。
|
|
28
|
+
3. **错误串保留原因链**:`fetch failed ← net::ERR_NETWORK_IO_SUSPENDED`。
|
|
29
|
+
以前只留最外层一句 `fetch failed`,既看不出是休眠还是 DNS,也没法判断该等网络还是该重登。
|
|
30
|
+
4. **面板**:徽章只在授权失效时显示「❌ 需要重新登录」,网络类显示中性的
|
|
31
|
+
「⚠️ 未能校验(网络)」;「校验全部」的汇总分开报"登录态已失效"与"网络没通、未能校验"。
|
|
32
|
+
5. **老数据自愈**:读账号时把历史上写错位置的网络类失败自动迁到 `lastCheckError`,
|
|
33
|
+
老库不必等下一次探活,也不会再被它误导。
|
|
34
|
+
|
|
35
|
+
用例:`check-relogin-integrity`(三条分支 + 不清登录态 + cause 保留)、
|
|
36
|
+
`check-account-add`(两个标记一起清、`carried` 白名单不漏新字段)、
|
|
37
|
+
`check-accounts`(读路径迁移)、`check-bundle`(产物里 `planRelogin` 三态**真求值** +
|
|
38
|
+
`describeError` 真的沿 cause 链)。反向验证 5/5 + 产物层 3/3 如期红。
|
|
39
|
+
|
|
40
|
+
## 0.6.5 — 2026-09-27
|
|
41
|
+
|
|
42
|
+
> 修复 0.6.3 的重试策略**形状**错误:它会让「按本地退避重试」算出 `NaN`,把整轮打成 UNKNOWN 错误。
|
|
43
|
+
|
|
44
|
+
### 修复
|
|
45
|
+
|
|
46
|
+
- **`providerRetryPolicy` 的返回值改成扁平字段**(0.6.3 包了一层 `backoff`)。dsh-llm 的取法是
|
|
47
|
+
`adapter.providerRetryPolicy(p) ?? resolveRetryPolicy(…)` —— 我们一返回对象,右侧那个"会把 `backoff`
|
|
48
|
+
展开成扁平字段"的规范化就**不会执行**,而运行期的消费方读的正是扁平字段
|
|
49
|
+
(`policy.initialDelayMs / maxDelayMs / jitterRatio`)。实测 DSH 会话日志里 `llm/retry` 事件的
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
"policyKey":"[\"normal\",5,[\"EMPTY_RESPONSE\",\"RATE_LIMIT\",…],null,null,null]"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
后三项是 `null` = `undefined` —— 两个后果,第二个是致命的:
|
|
56
|
+
|
|
57
|
+
1. `providerRetryAfterMs > maxDelayMs` 变成 `X > undefined` = **恒 false** ⇒「要等太久就放弃」失效
|
|
58
|
+
(连封禁一天的解除时间也会被照单等下去);
|
|
59
|
+
2. 失败**不带** provider 延迟时(例如 `EMPTY_RESPONSE`)走本地退避 `initialDelayMs * 2**n`,
|
|
60
|
+
`undefined` ⇒ **`NaN`** ⇒ DSH 写会话事件时拒收非有限数 ⇒ 整轮以
|
|
61
|
+
`UNKNOWN: session event "llm/retry" carries non-JSON-serializable data` 结束。
|
|
62
|
+
|
|
63
|
+
实测 2026-09-27 17:33:51:一轮里第一次重试(限流,带 2s)正常成功;第二次重试
|
|
64
|
+
(`EMPTY_RESPONSE`,无 provider 延迟)触发该错误 ⇒ 现象就是"任务突然自己停了"。
|
|
65
|
+
|
|
66
|
+
### 测试
|
|
67
|
+
|
|
68
|
+
- `check-llm-retry.mjs` 补四条**意图级**断言:策略扁平键齐全且**无 `backoff` 嵌套**、
|
|
69
|
+
**回放 DSH 的 `retryPolicyKey` 不许出现 `null`**、**回放 `localDelay` 必须算出有限正数**、
|
|
70
|
+
超长解除时间仍须被判「等太久」而放弃。
|
|
71
|
+
- 反向验证 3 步全部如期红:改回嵌套 → 6 条红;`maxDelayMs` 调小到 10s → 2 条;抽掉 `initialDelayMs` → 4 条。
|
|
72
|
+
|
|
5
73
|
## 0.6.4 — 2026-09-27
|
|
6
74
|
|
|
7
75
|
> 文档版本:把「封号归因」那一节带到 npm 包页面上(纯文档,**无代码改动**)。
|
package/lib/client.js
CHANGED
|
@@ -900,14 +900,19 @@ background:var(--bg2);white-space:pre-wrap;font-size:12px}
|
|
|
900
900
|
*/
|
|
901
901
|
const reloginAccount = (id, title) => {
|
|
902
902
|
(async () => {
|
|
903
|
-
accountsMsg.textContent =
|
|
903
|
+
accountsMsg.textContent = `正在校验「${title}」并准备登录窗口……`;
|
|
904
904
|
try {
|
|
905
905
|
const prep = await api("/login/relogin", {
|
|
906
906
|
method: "POST",
|
|
907
907
|
body: JSON.stringify({ id })
|
|
908
908
|
});
|
|
909
|
+
if (prep?.alreadyValid) {
|
|
910
|
+
accountsMsg.textContent = String(prep.hint ?? "这条账号校验通过,不需要重新登录");
|
|
911
|
+
await loadAccounts();
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
909
914
|
if (prep?.ok === false) {
|
|
910
|
-
accountsMsg.textContent =
|
|
915
|
+
accountsMsg.textContent = String(prep?.error ?? "未知原因");
|
|
911
916
|
return;
|
|
912
917
|
}
|
|
913
918
|
boostUntil = Date.now() + 3e5;
|
|
@@ -938,6 +943,7 @@ background:var(--bg2);white-space:pre-wrap;font-size:12px}
|
|
|
938
943
|
if (item.unverified) title.append(el("span", "dsw-badge off", "❔ 未校验"));
|
|
939
944
|
if (item.limit && Number.isFinite(item.limit.untilMs) && item.limit.untilMs > Date.now()) title.append(el("span", "dsw-badge off", `⏳ 受限至 ${shortTime(item.limit.untilMs)}`));
|
|
940
945
|
if (item.lastVerifyError) title.append(el("span", "dsw-badge err", "❌ 需要重新登录"));
|
|
946
|
+
else if (item.lastCheckError) title.append(el("span", "dsw-badge off", "⚠️ 未能校验(网络)"));
|
|
941
947
|
main.append(title);
|
|
942
948
|
const meta = [];
|
|
943
949
|
if (item.display) meta.push(item.display);
|
|
@@ -946,12 +952,13 @@ background:var(--bg2);white-space:pre-wrap;font-size:12px}
|
|
|
946
952
|
const cookieMeta = Array.isArray(item.cookieMeta) ? item.cookieMeta : [];
|
|
947
953
|
meta.push(`cookie:${describeCookieLife(summarizeCookieLife(cookieMeta))}`);
|
|
948
954
|
main.append(el("div", "dsw-account-meta", meta.join(" · ")));
|
|
949
|
-
if (item.lastVerifyError) main.append(el("div", "dsw-account-fix", `⚠️ ${relTime(item.lastVerifyError.at)}
|
|
955
|
+
if (item.lastVerifyError) main.append(el("div", "dsw-account-fix", `⚠️ ${relTime(item.lastVerifyError.at)}校验失败(登录态已失效):${item.lastVerifyError.message}`));
|
|
956
|
+
else if (item.lastCheckError) main.append(el("div", "dsw-account-fix", `⚠️ ${relTime(item.lastCheckError.at)}未能校验(网络问题,账号未必失效):${item.lastCheckError.message}`));
|
|
950
957
|
row.append(main);
|
|
951
958
|
const actions = el("div", "dsw-account-actions");
|
|
952
|
-
if (item.lastVerifyError) {
|
|
959
|
+
if (item.lastVerifyError || item.lastCheckError) {
|
|
953
960
|
const reloginBtn = el("button", "dsw-btn dsw-preset", "重登");
|
|
954
|
-
reloginBtn.title = "
|
|
961
|
+
reloginBtn.title = "点一下会先做一次只读校验:凭证还能用就立刻恢复(不打开浏览器、不用敲密码);只有确认授权失效(401/过期)时才会清掉登录态、让你重新登录一次。捕获后原地更新这条记录,不新增、也不切换当前账号";
|
|
955
962
|
reloginBtn.addEventListener("click", () => reloginAccount(item.id, item.title || item.id));
|
|
956
963
|
actions.append(reloginBtn);
|
|
957
964
|
}
|
|
@@ -1143,7 +1150,13 @@ background:var(--bg2);white-space:pre-wrap;font-size:12px}
|
|
|
1143
1150
|
return;
|
|
1144
1151
|
}
|
|
1145
1152
|
const failed = Number(result?.failed ?? 0);
|
|
1146
|
-
|
|
1153
|
+
const authFailed = Number(result?.authFailed ?? 0);
|
|
1154
|
+
const transportFailed = Number(result?.transportFailed ?? 0);
|
|
1155
|
+
const summary = [`${Number(result?.passed ?? 0)} 个正常`];
|
|
1156
|
+
if (authFailed > 0) summary.push(`${authFailed} 个登录态已失效(需要重登)`);
|
|
1157
|
+
if (transportFailed > 0) summary.push(`${transportFailed} 个网络没通、未能校验(账号未必失效)`);
|
|
1158
|
+
if (failed === 0) summary.length = 0;
|
|
1159
|
+
accountsMsg.textContent = failed > 0 ? `已校验 ${result?.checked ?? 0} 个账号:${summary.join("、")}。` : `已校验 ${result?.checked ?? 0} 个账号:全部正常。`;
|
|
1147
1160
|
} catch (error) {
|
|
1148
1161
|
accountsMsg.textContent = `刷新失败:${error?.message ?? error}`;
|
|
1149
1162
|
} finally {
|