qwenproxy-cli 1.6.0 → 1.6.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/dist/clean-cache.js +47 -4
- package/dist/delete-chats.js +55 -4
- package/dist/index.js +108 -24
- package/dist/login.js +49 -4
- package/dist/reset-cooldowns.js +47 -4
- package/dist/sync-clients.js +2 -2
- package/package.json +1 -1
- package/src/clean-cache.ts +3 -0
- package/src/core/config.ts +2 -2
- package/src/core/process-guard.ts +77 -0
- package/src/core/version.ts +1 -1
- package/src/delete-chats.ts +3 -0
- package/src/index.ts +3 -29
- package/src/login.ts +2 -0
- package/src/routes/chat/retry-policy.ts +30 -0
- package/src/routes/chat/streaming.ts +41 -1
- package/src/services/chat-cleanup.ts +5 -0
- package/src/services/playwright.ts +6 -2
package/dist/clean-cache.js
CHANGED
|
@@ -1016,11 +1016,11 @@ var init_config = __esm({
|
|
|
1016
1016
|
// nothing in this window is dead and should fail fast, retryable).
|
|
1017
1017
|
QWEN_FIRST_CHUNK_TIMEOUT: z.string().default("60000"),
|
|
1018
1018
|
TOTAL_REQUEST_TIMEOUT: z.string().default("600000"),
|
|
1019
|
-
// Mid-stream silence window for thinking models:
|
|
1019
|
+
// Mid-stream silence window for thinking models: 5 min with ZERO upstream
|
|
1020
1020
|
// bytes is a dead stream (WAF swallow / dropped connection) — fail fast and
|
|
1021
1021
|
// let the retry policy rotate accounts. Flowing reasoning chunks RESET this
|
|
1022
1022
|
// timer, so legitimate slow thinking is never cut; only total silence is.
|
|
1023
|
-
REASONING_MODEL_TIMEOUT: z.string().default("
|
|
1023
|
+
REASONING_MODEL_TIMEOUT: z.string().default("300000"),
|
|
1024
1024
|
CACHE_TTL: z.string().default("3600"),
|
|
1025
1025
|
RESPONSE_TTL: z.string().default("1800"),
|
|
1026
1026
|
CACHE_COMPRESSION_ENABLED: z.string().default("true"),
|
|
@@ -2848,15 +2848,18 @@ function loadStorageState(accountId) {
|
|
|
2848
2848
|
return void 0;
|
|
2849
2849
|
}
|
|
2850
2850
|
}
|
|
2851
|
-
async function saveStorageState(context, accountId, timeoutMs =
|
|
2851
|
+
async function saveStorageState(context, accountId, timeoutMs = 1e4) {
|
|
2852
2852
|
try {
|
|
2853
2853
|
const stateFile = getStorageStatePath(accountId);
|
|
2854
2854
|
const dir = path4.dirname(stateFile);
|
|
2855
2855
|
if (!fs7.existsSync(dir)) {
|
|
2856
2856
|
fs7.mkdirSync(dir, { recursive: true });
|
|
2857
2857
|
}
|
|
2858
|
+
const rawPromise = context.storageState({ path: stateFile }).catch(() => {
|
|
2859
|
+
return void 0;
|
|
2860
|
+
});
|
|
2858
2861
|
await withTimeout(
|
|
2859
|
-
|
|
2862
|
+
rawPromise,
|
|
2860
2863
|
timeoutMs,
|
|
2861
2864
|
`storageState timed out after ${timeoutMs}ms`
|
|
2862
2865
|
);
|
|
@@ -5336,6 +5339,44 @@ var init_playwright = __esm({
|
|
|
5336
5339
|
}
|
|
5337
5340
|
});
|
|
5338
5341
|
|
|
5342
|
+
// src/core/process-guard.ts
|
|
5343
|
+
function isPlaywrightAlreadyClosedMessage(message) {
|
|
5344
|
+
if (!message) return false;
|
|
5345
|
+
return message.includes("Target page, context or browser has been closed") || message.includes("Browser has been closed") || message.includes("Target closed") || message.includes("Target crashed") || message.includes("Page crashed") || message.includes("Assertion error") || message.includes("Cannot find parent object") || message.includes("Connection closed") || message.includes("session closed") || message.includes("Session closed") || message.includes("Network.setCacheDisabled") || message.includes("Protocol error") || message.includes("storageState timed out");
|
|
5346
|
+
}
|
|
5347
|
+
function isBenignTeardownError(error) {
|
|
5348
|
+
if (!error) return false;
|
|
5349
|
+
const message = error instanceof Error ? error.message : typeof error === "object" && "message" in error ? String(error.message) : String(error);
|
|
5350
|
+
return isPlaywrightAlreadyClosedMessage(message);
|
|
5351
|
+
}
|
|
5352
|
+
function installProcessTeardownGuards(label = "Process") {
|
|
5353
|
+
if (guardsInstalled) return;
|
|
5354
|
+
guardsInstalled = true;
|
|
5355
|
+
process.on("uncaughtException", (error) => {
|
|
5356
|
+
if (isBenignTeardownError(error)) {
|
|
5357
|
+
const msg = error instanceof Error ? error.message : typeof error === "object" && error !== null && "message" in error ? String(error.message) : String(error);
|
|
5358
|
+
console.warn(`\u26A0\uFE0F [${label}] Handled benign driver teardown exception: ${msg}`);
|
|
5359
|
+
return;
|
|
5360
|
+
}
|
|
5361
|
+
console.error(`\u274C [${label}] Uncaught Exception:`, error);
|
|
5362
|
+
});
|
|
5363
|
+
process.on("unhandledRejection", (reason) => {
|
|
5364
|
+
if (isBenignTeardownError(reason)) {
|
|
5365
|
+
const msg = reason instanceof Error ? reason.message : typeof reason === "object" && reason !== null && "message" in reason ? String(reason.message) : String(reason);
|
|
5366
|
+
console.warn(`\u26A0\uFE0F [${label}] Handled benign driver teardown rejection: ${msg}`);
|
|
5367
|
+
return;
|
|
5368
|
+
}
|
|
5369
|
+
console.error(`\u274C [${label}] Unhandled Rejection:`, reason);
|
|
5370
|
+
});
|
|
5371
|
+
}
|
|
5372
|
+
var guardsInstalled;
|
|
5373
|
+
var init_process_guard = __esm({
|
|
5374
|
+
"src/core/process-guard.ts"() {
|
|
5375
|
+
"use strict";
|
|
5376
|
+
guardsInstalled = false;
|
|
5377
|
+
}
|
|
5378
|
+
});
|
|
5379
|
+
|
|
5339
5380
|
// src/clean-cache.ts
|
|
5340
5381
|
var clean_cache_exports = {};
|
|
5341
5382
|
__export(clean_cache_exports, {
|
|
@@ -5503,6 +5544,8 @@ var isDirectRun;
|
|
|
5503
5544
|
var init_clean_cache = __esm({
|
|
5504
5545
|
"src/clean-cache.ts"() {
|
|
5505
5546
|
init_playwright();
|
|
5547
|
+
init_process_guard();
|
|
5548
|
+
installProcessTeardownGuards("CleanCache");
|
|
5506
5549
|
isDirectRun = process.argv[1] && (process.argv[1].endsWith("clean-cache.ts") || process.argv[1].endsWith("clean-cache.js"));
|
|
5507
5550
|
if (isDirectRun) {
|
|
5508
5551
|
main().catch((err) => {
|
package/dist/delete-chats.js
CHANGED
|
@@ -13,6 +13,44 @@ var __export = (target, all) => {
|
|
|
13
13
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
// src/core/process-guard.ts
|
|
17
|
+
function isPlaywrightAlreadyClosedMessage(message) {
|
|
18
|
+
if (!message) return false;
|
|
19
|
+
return message.includes("Target page, context or browser has been closed") || message.includes("Browser has been closed") || message.includes("Target closed") || message.includes("Target crashed") || message.includes("Page crashed") || message.includes("Assertion error") || message.includes("Cannot find parent object") || message.includes("Connection closed") || message.includes("session closed") || message.includes("Session closed") || message.includes("Network.setCacheDisabled") || message.includes("Protocol error") || message.includes("storageState timed out");
|
|
20
|
+
}
|
|
21
|
+
function isBenignTeardownError(error) {
|
|
22
|
+
if (!error) return false;
|
|
23
|
+
const message = error instanceof Error ? error.message : typeof error === "object" && "message" in error ? String(error.message) : String(error);
|
|
24
|
+
return isPlaywrightAlreadyClosedMessage(message);
|
|
25
|
+
}
|
|
26
|
+
function installProcessTeardownGuards(label = "Process") {
|
|
27
|
+
if (guardsInstalled) return;
|
|
28
|
+
guardsInstalled = true;
|
|
29
|
+
process.on("uncaughtException", (error) => {
|
|
30
|
+
if (isBenignTeardownError(error)) {
|
|
31
|
+
const msg = error instanceof Error ? error.message : typeof error === "object" && error !== null && "message" in error ? String(error.message) : String(error);
|
|
32
|
+
console.warn(`\u26A0\uFE0F [${label}] Handled benign driver teardown exception: ${msg}`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
console.error(`\u274C [${label}] Uncaught Exception:`, error);
|
|
36
|
+
});
|
|
37
|
+
process.on("unhandledRejection", (reason) => {
|
|
38
|
+
if (isBenignTeardownError(reason)) {
|
|
39
|
+
const msg = reason instanceof Error ? reason.message : typeof reason === "object" && reason !== null && "message" in reason ? String(reason.message) : String(reason);
|
|
40
|
+
console.warn(`\u26A0\uFE0F [${label}] Handled benign driver teardown rejection: ${msg}`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
console.error(`\u274C [${label}] Unhandled Rejection:`, reason);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
var guardsInstalled;
|
|
47
|
+
var init_process_guard = __esm({
|
|
48
|
+
"src/core/process-guard.ts"() {
|
|
49
|
+
"use strict";
|
|
50
|
+
guardsInstalled = false;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
16
54
|
// src/core/paths.ts
|
|
17
55
|
import fs from "node:fs";
|
|
18
56
|
import path from "node:path";
|
|
@@ -1071,11 +1109,11 @@ var init_config = __esm({
|
|
|
1071
1109
|
// nothing in this window is dead and should fail fast, retryable).
|
|
1072
1110
|
QWEN_FIRST_CHUNK_TIMEOUT: z.string().default("60000"),
|
|
1073
1111
|
TOTAL_REQUEST_TIMEOUT: z.string().default("600000"),
|
|
1074
|
-
// Mid-stream silence window for thinking models:
|
|
1112
|
+
// Mid-stream silence window for thinking models: 5 min with ZERO upstream
|
|
1075
1113
|
// bytes is a dead stream (WAF swallow / dropped connection) — fail fast and
|
|
1076
1114
|
// let the retry policy rotate accounts. Flowing reasoning chunks RESET this
|
|
1077
1115
|
// timer, so legitimate slow thinking is never cut; only total silence is.
|
|
1078
|
-
REASONING_MODEL_TIMEOUT: z.string().default("
|
|
1116
|
+
REASONING_MODEL_TIMEOUT: z.string().default("300000"),
|
|
1079
1117
|
CACHE_TTL: z.string().default("3600"),
|
|
1080
1118
|
RESPONSE_TTL: z.string().default("1800"),
|
|
1081
1119
|
CACHE_COMPRESSION_ENABLED: z.string().default("true"),
|
|
@@ -3105,6 +3143,8 @@ var init_clean_cache = __esm({
|
|
|
3105
3143
|
"src/clean-cache.ts"() {
|
|
3106
3144
|
"use strict";
|
|
3107
3145
|
init_playwright();
|
|
3146
|
+
init_process_guard();
|
|
3147
|
+
installProcessTeardownGuards("CleanCache");
|
|
3108
3148
|
isDirectRun = process.argv[1] && (process.argv[1].endsWith("clean-cache.ts") || process.argv[1].endsWith("clean-cache.js"));
|
|
3109
3149
|
if (isDirectRun) {
|
|
3110
3150
|
main().catch((err) => {
|
|
@@ -3329,15 +3369,18 @@ function loadStorageState(accountId) {
|
|
|
3329
3369
|
return void 0;
|
|
3330
3370
|
}
|
|
3331
3371
|
}
|
|
3332
|
-
async function saveStorageState(context, accountId, timeoutMs =
|
|
3372
|
+
async function saveStorageState(context, accountId, timeoutMs = 1e4) {
|
|
3333
3373
|
try {
|
|
3334
3374
|
const stateFile = getStorageStatePath(accountId);
|
|
3335
3375
|
const dir = path5.dirname(stateFile);
|
|
3336
3376
|
if (!fs8.existsSync(dir)) {
|
|
3337
3377
|
fs8.mkdirSync(dir, { recursive: true });
|
|
3338
3378
|
}
|
|
3379
|
+
const rawPromise = context.storageState({ path: stateFile }).catch(() => {
|
|
3380
|
+
return void 0;
|
|
3381
|
+
});
|
|
3339
3382
|
await withTimeout(
|
|
3340
|
-
|
|
3383
|
+
rawPromise,
|
|
3341
3384
|
timeoutMs,
|
|
3342
3385
|
`storageState timed out after ${timeoutMs}ms`
|
|
3343
3386
|
);
|
|
@@ -10454,6 +10497,7 @@ var init_qwen = __esm({
|
|
|
10454
10497
|
});
|
|
10455
10498
|
|
|
10456
10499
|
// src/delete-chats.ts
|
|
10500
|
+
init_process_guard();
|
|
10457
10501
|
import "dotenv/config";
|
|
10458
10502
|
|
|
10459
10503
|
// src/services/chat-cleanup.ts
|
|
@@ -10517,6 +10561,12 @@ async function deleteChatsForConfiguredAccounts(keepBrowserOpen = false) {
|
|
|
10517
10561
|
`\u274C [DeleteChats] [${currentIdx}/${totalCount}] Falha ao apagar conversas de ${maskEmail(account.email)}:`,
|
|
10518
10562
|
error instanceof Error ? error.message : String(error)
|
|
10519
10563
|
);
|
|
10564
|
+
} finally {
|
|
10565
|
+
if (!keepBrowserOpen) {
|
|
10566
|
+
const { closePlaywrightForAccount: closePlaywrightForAccount2 } = await Promise.resolve().then(() => (init_playwright(), playwright_exports));
|
|
10567
|
+
await closePlaywrightForAccount2(account.id).catch(() => {
|
|
10568
|
+
});
|
|
10569
|
+
}
|
|
10520
10570
|
}
|
|
10521
10571
|
}
|
|
10522
10572
|
} finally {
|
|
@@ -10537,6 +10587,7 @@ async function deleteChatsForConfiguredAccounts(keepBrowserOpen = false) {
|
|
|
10537
10587
|
}
|
|
10538
10588
|
|
|
10539
10589
|
// src/delete-chats.ts
|
|
10590
|
+
installProcessTeardownGuards("DeleteChats");
|
|
10540
10591
|
async function run() {
|
|
10541
10592
|
console.log("\u{1F5D1}\uFE0F [DeleteChats] Using Playwright sessions");
|
|
10542
10593
|
const result = await deleteChatsForConfiguredAccounts();
|
package/dist/index.js
CHANGED
|
@@ -150,6 +150,44 @@ var init_paths = __esm({
|
|
|
150
150
|
}
|
|
151
151
|
});
|
|
152
152
|
|
|
153
|
+
// src/core/process-guard.ts
|
|
154
|
+
function isPlaywrightAlreadyClosedMessage(message) {
|
|
155
|
+
if (!message) return false;
|
|
156
|
+
return message.includes("Target page, context or browser has been closed") || message.includes("Browser has been closed") || message.includes("Target closed") || message.includes("Target crashed") || message.includes("Page crashed") || message.includes("Assertion error") || message.includes("Cannot find parent object") || message.includes("Connection closed") || message.includes("session closed") || message.includes("Session closed") || message.includes("Network.setCacheDisabled") || message.includes("Protocol error") || message.includes("storageState timed out");
|
|
157
|
+
}
|
|
158
|
+
function isBenignTeardownError(error) {
|
|
159
|
+
if (!error) return false;
|
|
160
|
+
const message = error instanceof Error ? error.message : typeof error === "object" && "message" in error ? String(error.message) : String(error);
|
|
161
|
+
return isPlaywrightAlreadyClosedMessage(message);
|
|
162
|
+
}
|
|
163
|
+
function installProcessTeardownGuards(label = "Process") {
|
|
164
|
+
if (guardsInstalled) return;
|
|
165
|
+
guardsInstalled = true;
|
|
166
|
+
process.on("uncaughtException", (error) => {
|
|
167
|
+
if (isBenignTeardownError(error)) {
|
|
168
|
+
const msg = error instanceof Error ? error.message : typeof error === "object" && error !== null && "message" in error ? String(error.message) : String(error);
|
|
169
|
+
console.warn(`\u26A0\uFE0F [${label}] Handled benign driver teardown exception: ${msg}`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
console.error(`\u274C [${label}] Uncaught Exception:`, error);
|
|
173
|
+
});
|
|
174
|
+
process.on("unhandledRejection", (reason) => {
|
|
175
|
+
if (isBenignTeardownError(reason)) {
|
|
176
|
+
const msg = reason instanceof Error ? reason.message : typeof reason === "object" && reason !== null && "message" in reason ? String(reason.message) : String(reason);
|
|
177
|
+
console.warn(`\u26A0\uFE0F [${label}] Handled benign driver teardown rejection: ${msg}`);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
console.error(`\u274C [${label}] Unhandled Rejection:`, reason);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
var guardsInstalled;
|
|
184
|
+
var init_process_guard = __esm({
|
|
185
|
+
"src/core/process-guard.ts"() {
|
|
186
|
+
"use strict";
|
|
187
|
+
guardsInstalled = false;
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
153
191
|
// src/core/config.ts
|
|
154
192
|
import fs2 from "node:fs";
|
|
155
193
|
import dotenv from "dotenv";
|
|
@@ -289,11 +327,11 @@ var init_config = __esm({
|
|
|
289
327
|
// nothing in this window is dead and should fail fast, retryable).
|
|
290
328
|
QWEN_FIRST_CHUNK_TIMEOUT: z.string().default("60000"),
|
|
291
329
|
TOTAL_REQUEST_TIMEOUT: z.string().default("600000"),
|
|
292
|
-
// Mid-stream silence window for thinking models:
|
|
330
|
+
// Mid-stream silence window for thinking models: 5 min with ZERO upstream
|
|
293
331
|
// bytes is a dead stream (WAF swallow / dropped connection) — fail fast and
|
|
294
332
|
// let the retry policy rotate accounts. Flowing reasoning chunks RESET this
|
|
295
333
|
// timer, so legitimate slow thinking is never cut; only total silence is.
|
|
296
|
-
REASONING_MODEL_TIMEOUT: z.string().default("
|
|
334
|
+
REASONING_MODEL_TIMEOUT: z.string().default("300000"),
|
|
297
335
|
CACHE_TTL: z.string().default("3600"),
|
|
298
336
|
RESPONSE_TTL: z.string().default("1800"),
|
|
299
337
|
CACHE_COMPRESSION_ENABLED: z.string().default("true"),
|
|
@@ -4260,6 +4298,8 @@ var init_clean_cache = __esm({
|
|
|
4260
4298
|
"src/clean-cache.ts"() {
|
|
4261
4299
|
"use strict";
|
|
4262
4300
|
init_playwright();
|
|
4301
|
+
init_process_guard();
|
|
4302
|
+
installProcessTeardownGuards("CleanCache");
|
|
4263
4303
|
isDirectRun = process.argv[1] && (process.argv[1].endsWith("clean-cache.ts") || process.argv[1].endsWith("clean-cache.js"));
|
|
4264
4304
|
if (isDirectRun) {
|
|
4265
4305
|
main().catch((err) => {
|
|
@@ -4484,15 +4524,18 @@ function loadStorageState(accountId) {
|
|
|
4484
4524
|
return void 0;
|
|
4485
4525
|
}
|
|
4486
4526
|
}
|
|
4487
|
-
async function saveStorageState(context, accountId, timeoutMs =
|
|
4527
|
+
async function saveStorageState(context, accountId, timeoutMs = 1e4) {
|
|
4488
4528
|
try {
|
|
4489
4529
|
const stateFile = getStorageStatePath(accountId);
|
|
4490
4530
|
const dir = path6.dirname(stateFile);
|
|
4491
4531
|
if (!fs9.existsSync(dir)) {
|
|
4492
4532
|
fs9.mkdirSync(dir, { recursive: true });
|
|
4493
4533
|
}
|
|
4534
|
+
const rawPromise = context.storageState({ path: stateFile }).catch(() => {
|
|
4535
|
+
return void 0;
|
|
4536
|
+
});
|
|
4494
4537
|
await withTimeout(
|
|
4495
|
-
|
|
4538
|
+
rawPromise,
|
|
4496
4539
|
timeoutMs,
|
|
4497
4540
|
`storageState timed out after ${timeoutMs}ms`
|
|
4498
4541
|
);
|
|
@@ -9415,6 +9458,11 @@ function isCorruptedChatHistoryError(err) {
|
|
|
9415
9458
|
const message = errMessage(err).toLowerCase();
|
|
9416
9459
|
return message.includes("first message must not") || message.includes("first message must be") || message.includes("must not assistant message") || message.includes("must not be assistant");
|
|
9417
9460
|
}
|
|
9461
|
+
function isInternalServerError(err) {
|
|
9462
|
+
const code = errCode(err).toLowerCase();
|
|
9463
|
+
const message = errMessage(err).toLowerCase();
|
|
9464
|
+
return code === "internal_error" || code === "internal_server_error" || code === "server_error" || code === "server_busy" || code === "busy" || message.includes("internal_error") || message.includes("ocorreu um erro inesperado") || message.includes("unexpected error") || message.includes("internal server error");
|
|
9465
|
+
}
|
|
9418
9466
|
function makeRetryAction(reason, overrides = {}) {
|
|
9419
9467
|
return {
|
|
9420
9468
|
retryable: true,
|
|
@@ -9521,6 +9569,15 @@ function classifyRetryAction(err, options) {
|
|
|
9521
9569
|
accountCooldownReason: quota.accountCooldownReason
|
|
9522
9570
|
});
|
|
9523
9571
|
}
|
|
9572
|
+
if (isInternalServerError(err) && !(err instanceof QwenUpstreamError)) {
|
|
9573
|
+
const typed = err;
|
|
9574
|
+
return makeRetryAction("upstream_internal_error", {
|
|
9575
|
+
switchAccount: typed.switchAccount !== false,
|
|
9576
|
+
forceNewChat: true,
|
|
9577
|
+
retryWithFullPrompt: true,
|
|
9578
|
+
retryAfterMs: typed.retryAfterMs ?? Math.min(baseDelayMs * 2, 3e3)
|
|
9579
|
+
});
|
|
9580
|
+
}
|
|
9524
9581
|
if (isNetworkLikeError(err) || err instanceof QwenUpstreamUnavailableError || err instanceof QwenUpstreamError || isAbortError(err)) {
|
|
9525
9582
|
const typed = err;
|
|
9526
9583
|
return makeRetryAction(
|
|
@@ -16063,6 +16120,12 @@ async function deleteChatsForConfiguredAccounts(keepBrowserOpen = false) {
|
|
|
16063
16120
|
`\u274C [DeleteChats] [${currentIdx}/${totalCount}] Falha ao apagar conversas de ${maskEmail(account.email)}:`,
|
|
16064
16121
|
error instanceof Error ? error.message : String(error)
|
|
16065
16122
|
);
|
|
16123
|
+
} finally {
|
|
16124
|
+
if (!keepBrowserOpen) {
|
|
16125
|
+
const { closePlaywrightForAccount: closePlaywrightForAccount2 } = await Promise.resolve().then(() => (init_playwright(), playwright_exports));
|
|
16126
|
+
await closePlaywrightForAccount2(account.id).catch(() => {
|
|
16127
|
+
});
|
|
16128
|
+
}
|
|
16066
16129
|
}
|
|
16067
16130
|
}
|
|
16068
16131
|
} finally {
|
|
@@ -20853,6 +20916,7 @@ async function processStreamingResponse(params) {
|
|
|
20853
20916
|
let activeReader = null;
|
|
20854
20917
|
let invalidInputSameAccountRetries = 0;
|
|
20855
20918
|
let chatInProgressSameAccountRetries = 0;
|
|
20919
|
+
let antiBotSameAccountRetries = 0;
|
|
20856
20920
|
const totalConfiguredAccounts = loadAccounts().length;
|
|
20857
20921
|
let quotaRotationsLeft = Math.min(Math.max(1, totalConfiguredAccounts - 1), 6);
|
|
20858
20922
|
let streamCompletedOk = false;
|
|
@@ -21260,7 +21324,16 @@ async function processStreamingResponse(params) {
|
|
|
21260
21324
|
);
|
|
21261
21325
|
return false;
|
|
21262
21326
|
}
|
|
21263
|
-
|
|
21327
|
+
let switchAccount = policy.switchAccount && !retryInvalidInputOnSameAccount;
|
|
21328
|
+
if (policy.reason === "anti_bot") {
|
|
21329
|
+
if (antiBotSameAccountRetries === 0) {
|
|
21330
|
+
antiBotSameAccountRetries++;
|
|
21331
|
+
switchAccount = false;
|
|
21332
|
+
} else {
|
|
21333
|
+
switchAccount = true;
|
|
21334
|
+
markAccountRateLimited(currentAccountId, 6e4, "AntiBotWafChallenge");
|
|
21335
|
+
}
|
|
21336
|
+
}
|
|
21264
21337
|
if (switchAccount && (policy.accountCooldownMs ?? 0) > 0) {
|
|
21265
21338
|
markAccountRateLimited(
|
|
21266
21339
|
currentAccountId,
|
|
@@ -21272,6 +21345,33 @@ async function processStreamingResponse(params) {
|
|
|
21272
21345
|
Promise.resolve().then(() => (init_playwright(), playwright_exports)).then(({ refreshAccountToken: refreshAccountToken2 }) => refreshAccountToken2(currentAccountId)).catch(() => {
|
|
21273
21346
|
});
|
|
21274
21347
|
}
|
|
21348
|
+
if (currentUiSessionId && targetResponseId) {
|
|
21349
|
+
const streamData = getStream(completionId);
|
|
21350
|
+
const stopHeaders = streamData?.headers;
|
|
21351
|
+
if (stopHeaders?.cookie && stopHeaders["user-agent"]) {
|
|
21352
|
+
void requestQwenTextInBrowser(
|
|
21353
|
+
currentAccountId,
|
|
21354
|
+
"POST",
|
|
21355
|
+
`/api/v2/chat/completions/stop?chat_id=${encodeURIComponent(currentUiSessionId)}`,
|
|
21356
|
+
buildQwenRequestHeaders({
|
|
21357
|
+
cookie: stopHeaders.cookie,
|
|
21358
|
+
userAgent: stopHeaders["user-agent"],
|
|
21359
|
+
bxUa: stopHeaders["bx-ua"],
|
|
21360
|
+
bxUmidtoken: stopHeaders["bx-umidtoken"],
|
|
21361
|
+
bxV: stopHeaders["bx-v"],
|
|
21362
|
+
chatSessionId: currentUiSessionId
|
|
21363
|
+
}),
|
|
21364
|
+
JSON.stringify({
|
|
21365
|
+
chat_id: currentUiSessionId,
|
|
21366
|
+
response_id: targetResponseId
|
|
21367
|
+
}),
|
|
21368
|
+
{
|
|
21369
|
+
referrer: qwenUrl(`/c/${encodeURIComponent(currentUiSessionId)}`),
|
|
21370
|
+
noMutexRecovery: true
|
|
21371
|
+
}
|
|
21372
|
+
).catch(() => void 0);
|
|
21373
|
+
}
|
|
21374
|
+
}
|
|
21275
21375
|
retryContext.releaseAccountLease?.();
|
|
21276
21376
|
retryContext.releaseAccountLease = null;
|
|
21277
21377
|
await reader.cancel().catch(() => void 0);
|
|
@@ -26285,7 +26385,7 @@ function getAppVersion() {
|
|
|
26285
26385
|
}
|
|
26286
26386
|
} catch {
|
|
26287
26387
|
}
|
|
26288
|
-
cachedVersion = "v1.6.
|
|
26388
|
+
cachedVersion = "v1.6.1";
|
|
26289
26389
|
return cachedVersion;
|
|
26290
26390
|
}
|
|
26291
26391
|
var cachedVersion;
|
|
@@ -34808,6 +34908,7 @@ var init_app = __esm({
|
|
|
34808
34908
|
|
|
34809
34909
|
// src/index.ts
|
|
34810
34910
|
init_paths();
|
|
34911
|
+
init_process_guard();
|
|
34811
34912
|
init_server();
|
|
34812
34913
|
import dotenv2 from "dotenv";
|
|
34813
34914
|
import fs26 from "node:fs";
|
|
@@ -34823,24 +34924,7 @@ if (fs26.existsSync(envPath)) {
|
|
|
34823
34924
|
} else {
|
|
34824
34925
|
dotenv2.config({ quiet: true });
|
|
34825
34926
|
}
|
|
34826
|
-
|
|
34827
|
-
const { isPlaywrightAlreadyClosedError: isPlaywrightAlreadyClosedError2 } = await Promise.resolve().then(() => (init_playwright(), playwright_exports));
|
|
34828
|
-
if (isPlaywrightAlreadyClosedError2(error)) {
|
|
34829
|
-
const msg = error instanceof Error ? error.message : typeof error === "object" && error !== null && "message" in error ? String(error.message) : String(error);
|
|
34830
|
-
console.warn(`\u26A0\uFE0F [Playwright] Handled benign driver teardown exception: ${msg}`);
|
|
34831
|
-
return;
|
|
34832
|
-
}
|
|
34833
|
-
console.error("\u274C [Process] Uncaught Exception:", error);
|
|
34834
|
-
});
|
|
34835
|
-
process.on("unhandledRejection", async (reason) => {
|
|
34836
|
-
const { isPlaywrightAlreadyClosedError: isPlaywrightAlreadyClosedError2 } = await Promise.resolve().then(() => (init_playwright(), playwright_exports));
|
|
34837
|
-
if (isPlaywrightAlreadyClosedError2(reason)) {
|
|
34838
|
-
const msg = reason instanceof Error ? reason.message : typeof reason === "object" && reason !== null && "message" in reason ? String(reason.message) : String(reason);
|
|
34839
|
-
console.warn(`\u26A0\uFE0F [Playwright] Handled benign driver teardown rejection: ${msg}`);
|
|
34840
|
-
return;
|
|
34841
|
-
}
|
|
34842
|
-
console.error("\u274C [Process] Unhandled Rejection:", reason);
|
|
34843
|
-
});
|
|
34927
|
+
installProcessTeardownGuards("Playwright");
|
|
34844
34928
|
var isTui = process.argv.includes("--tui") || process.env.QWEN_TUI === "true";
|
|
34845
34929
|
if (isTui) {
|
|
34846
34930
|
const { TuiApp: TuiApp2 } = await Promise.resolve().then(() => (init_app(), app_exports));
|
package/dist/login.js
CHANGED
|
@@ -1102,6 +1102,44 @@ var init_logger = __esm({
|
|
|
1102
1102
|
}
|
|
1103
1103
|
});
|
|
1104
1104
|
|
|
1105
|
+
// src/core/process-guard.ts
|
|
1106
|
+
function isPlaywrightAlreadyClosedMessage(message) {
|
|
1107
|
+
if (!message) return false;
|
|
1108
|
+
return message.includes("Target page, context or browser has been closed") || message.includes("Browser has been closed") || message.includes("Target closed") || message.includes("Target crashed") || message.includes("Page crashed") || message.includes("Assertion error") || message.includes("Cannot find parent object") || message.includes("Connection closed") || message.includes("session closed") || message.includes("Session closed") || message.includes("Network.setCacheDisabled") || message.includes("Protocol error") || message.includes("storageState timed out");
|
|
1109
|
+
}
|
|
1110
|
+
function isBenignTeardownError(error) {
|
|
1111
|
+
if (!error) return false;
|
|
1112
|
+
const message = error instanceof Error ? error.message : typeof error === "object" && "message" in error ? String(error.message) : String(error);
|
|
1113
|
+
return isPlaywrightAlreadyClosedMessage(message);
|
|
1114
|
+
}
|
|
1115
|
+
function installProcessTeardownGuards(label = "Process") {
|
|
1116
|
+
if (guardsInstalled) return;
|
|
1117
|
+
guardsInstalled = true;
|
|
1118
|
+
process.on("uncaughtException", (error) => {
|
|
1119
|
+
if (isBenignTeardownError(error)) {
|
|
1120
|
+
const msg = error instanceof Error ? error.message : typeof error === "object" && error !== null && "message" in error ? String(error.message) : String(error);
|
|
1121
|
+
console.warn(`\u26A0\uFE0F [${label}] Handled benign driver teardown exception: ${msg}`);
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
console.error(`\u274C [${label}] Uncaught Exception:`, error);
|
|
1125
|
+
});
|
|
1126
|
+
process.on("unhandledRejection", (reason) => {
|
|
1127
|
+
if (isBenignTeardownError(reason)) {
|
|
1128
|
+
const msg = reason instanceof Error ? reason.message : typeof reason === "object" && reason !== null && "message" in reason ? String(reason.message) : String(reason);
|
|
1129
|
+
console.warn(`\u26A0\uFE0F [${label}] Handled benign driver teardown rejection: ${msg}`);
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
console.error(`\u274C [${label}] Unhandled Rejection:`, reason);
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
var guardsInstalled;
|
|
1136
|
+
var init_process_guard = __esm({
|
|
1137
|
+
"src/core/process-guard.ts"() {
|
|
1138
|
+
"use strict";
|
|
1139
|
+
guardsInstalled = false;
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1105
1143
|
// src/core/config.ts
|
|
1106
1144
|
var config_exports = {};
|
|
1107
1145
|
__export(config_exports, {
|
|
@@ -1251,11 +1289,11 @@ var init_config = __esm({
|
|
|
1251
1289
|
// nothing in this window is dead and should fail fast, retryable).
|
|
1252
1290
|
QWEN_FIRST_CHUNK_TIMEOUT: z.string().default("60000"),
|
|
1253
1291
|
TOTAL_REQUEST_TIMEOUT: z.string().default("600000"),
|
|
1254
|
-
// Mid-stream silence window for thinking models:
|
|
1292
|
+
// Mid-stream silence window for thinking models: 5 min with ZERO upstream
|
|
1255
1293
|
// bytes is a dead stream (WAF swallow / dropped connection) — fail fast and
|
|
1256
1294
|
// let the retry policy rotate accounts. Flowing reasoning chunks RESET this
|
|
1257
1295
|
// timer, so legitimate slow thinking is never cut; only total silence is.
|
|
1258
|
-
REASONING_MODEL_TIMEOUT: z.string().default("
|
|
1296
|
+
REASONING_MODEL_TIMEOUT: z.string().default("300000"),
|
|
1259
1297
|
CACHE_TTL: z.string().default("3600"),
|
|
1260
1298
|
RESPONSE_TTL: z.string().default("1800"),
|
|
1261
1299
|
CACHE_COMPRESSION_ENABLED: z.string().default("true"),
|
|
@@ -3127,6 +3165,8 @@ var init_clean_cache = __esm({
|
|
|
3127
3165
|
"src/clean-cache.ts"() {
|
|
3128
3166
|
"use strict";
|
|
3129
3167
|
init_playwright();
|
|
3168
|
+
init_process_guard();
|
|
3169
|
+
installProcessTeardownGuards("CleanCache");
|
|
3130
3170
|
isDirectRun = process.argv[1] && (process.argv[1].endsWith("clean-cache.ts") || process.argv[1].endsWith("clean-cache.js"));
|
|
3131
3171
|
if (isDirectRun) {
|
|
3132
3172
|
main().catch((err) => {
|
|
@@ -3351,15 +3391,18 @@ function loadStorageState(accountId) {
|
|
|
3351
3391
|
return void 0;
|
|
3352
3392
|
}
|
|
3353
3393
|
}
|
|
3354
|
-
async function saveStorageState(context, accountId, timeoutMs =
|
|
3394
|
+
async function saveStorageState(context, accountId, timeoutMs = 1e4) {
|
|
3355
3395
|
try {
|
|
3356
3396
|
const stateFile = getStorageStatePath(accountId);
|
|
3357
3397
|
const dir = path5.dirname(stateFile);
|
|
3358
3398
|
if (!fs8.existsSync(dir)) {
|
|
3359
3399
|
fs8.mkdirSync(dir, { recursive: true });
|
|
3360
3400
|
}
|
|
3401
|
+
const rawPromise = context.storageState({ path: stateFile }).catch(() => {
|
|
3402
|
+
return void 0;
|
|
3403
|
+
});
|
|
3361
3404
|
await withTimeout(
|
|
3362
|
-
|
|
3405
|
+
rawPromise,
|
|
3363
3406
|
timeoutMs,
|
|
3364
3407
|
`storageState timed out after ${timeoutMs}ms`
|
|
3365
3408
|
);
|
|
@@ -6698,10 +6741,12 @@ var init_playwright = __esm({
|
|
|
6698
6741
|
// src/login.ts
|
|
6699
6742
|
init_accounts();
|
|
6700
6743
|
init_logger();
|
|
6744
|
+
init_process_guard();
|
|
6701
6745
|
import crypto4 from "crypto";
|
|
6702
6746
|
import * as readline from "readline";
|
|
6703
6747
|
import * as dotenv2 from "dotenv";
|
|
6704
6748
|
dotenv2.config();
|
|
6749
|
+
installProcessTeardownGuards("Login");
|
|
6705
6750
|
var rl = readline.createInterface({
|
|
6706
6751
|
input: process.stdin,
|
|
6707
6752
|
output: process.stdout
|
package/dist/reset-cooldowns.js
CHANGED
|
@@ -1353,11 +1353,11 @@ var init_config = __esm({
|
|
|
1353
1353
|
// nothing in this window is dead and should fail fast, retryable).
|
|
1354
1354
|
QWEN_FIRST_CHUNK_TIMEOUT: z.string().default("60000"),
|
|
1355
1355
|
TOTAL_REQUEST_TIMEOUT: z.string().default("600000"),
|
|
1356
|
-
// Mid-stream silence window for thinking models:
|
|
1356
|
+
// Mid-stream silence window for thinking models: 5 min with ZERO upstream
|
|
1357
1357
|
// bytes is a dead stream (WAF swallow / dropped connection) — fail fast and
|
|
1358
1358
|
// let the retry policy rotate accounts. Flowing reasoning chunks RESET this
|
|
1359
1359
|
// timer, so legitimate slow thinking is never cut; only total silence is.
|
|
1360
|
-
REASONING_MODEL_TIMEOUT: z.string().default("
|
|
1360
|
+
REASONING_MODEL_TIMEOUT: z.string().default("300000"),
|
|
1361
1361
|
CACHE_TTL: z.string().default("3600"),
|
|
1362
1362
|
RESPONSE_TTL: z.string().default("1800"),
|
|
1363
1363
|
CACHE_COMPRESSION_ENABLED: z.string().default("true"),
|
|
@@ -2730,6 +2730,44 @@ var init_jwt = __esm({
|
|
|
2730
2730
|
}
|
|
2731
2731
|
});
|
|
2732
2732
|
|
|
2733
|
+
// src/core/process-guard.ts
|
|
2734
|
+
function isPlaywrightAlreadyClosedMessage(message) {
|
|
2735
|
+
if (!message) return false;
|
|
2736
|
+
return message.includes("Target page, context or browser has been closed") || message.includes("Browser has been closed") || message.includes("Target closed") || message.includes("Target crashed") || message.includes("Page crashed") || message.includes("Assertion error") || message.includes("Cannot find parent object") || message.includes("Connection closed") || message.includes("session closed") || message.includes("Session closed") || message.includes("Network.setCacheDisabled") || message.includes("Protocol error") || message.includes("storageState timed out");
|
|
2737
|
+
}
|
|
2738
|
+
function isBenignTeardownError(error) {
|
|
2739
|
+
if (!error) return false;
|
|
2740
|
+
const message = error instanceof Error ? error.message : typeof error === "object" && "message" in error ? String(error.message) : String(error);
|
|
2741
|
+
return isPlaywrightAlreadyClosedMessage(message);
|
|
2742
|
+
}
|
|
2743
|
+
function installProcessTeardownGuards(label = "Process") {
|
|
2744
|
+
if (guardsInstalled) return;
|
|
2745
|
+
guardsInstalled = true;
|
|
2746
|
+
process.on("uncaughtException", (error) => {
|
|
2747
|
+
if (isBenignTeardownError(error)) {
|
|
2748
|
+
const msg = error instanceof Error ? error.message : typeof error === "object" && error !== null && "message" in error ? String(error.message) : String(error);
|
|
2749
|
+
console.warn(`\u26A0\uFE0F [${label}] Handled benign driver teardown exception: ${msg}`);
|
|
2750
|
+
return;
|
|
2751
|
+
}
|
|
2752
|
+
console.error(`\u274C [${label}] Uncaught Exception:`, error);
|
|
2753
|
+
});
|
|
2754
|
+
process.on("unhandledRejection", (reason) => {
|
|
2755
|
+
if (isBenignTeardownError(reason)) {
|
|
2756
|
+
const msg = reason instanceof Error ? reason.message : typeof reason === "object" && reason !== null && "message" in reason ? String(reason.message) : String(reason);
|
|
2757
|
+
console.warn(`\u26A0\uFE0F [${label}] Handled benign driver teardown rejection: ${msg}`);
|
|
2758
|
+
return;
|
|
2759
|
+
}
|
|
2760
|
+
console.error(`\u274C [${label}] Unhandled Rejection:`, reason);
|
|
2761
|
+
});
|
|
2762
|
+
}
|
|
2763
|
+
var guardsInstalled;
|
|
2764
|
+
var init_process_guard = __esm({
|
|
2765
|
+
"src/core/process-guard.ts"() {
|
|
2766
|
+
"use strict";
|
|
2767
|
+
guardsInstalled = false;
|
|
2768
|
+
}
|
|
2769
|
+
});
|
|
2770
|
+
|
|
2733
2771
|
// src/clean-cache.ts
|
|
2734
2772
|
var clean_cache_exports = {};
|
|
2735
2773
|
__export(clean_cache_exports, {
|
|
@@ -2898,6 +2936,8 @@ var init_clean_cache = __esm({
|
|
|
2898
2936
|
"src/clean-cache.ts"() {
|
|
2899
2937
|
"use strict";
|
|
2900
2938
|
init_playwright();
|
|
2939
|
+
init_process_guard();
|
|
2940
|
+
installProcessTeardownGuards("CleanCache");
|
|
2901
2941
|
isDirectRun = process.argv[1] && (process.argv[1].endsWith("clean-cache.ts") || process.argv[1].endsWith("clean-cache.js"));
|
|
2902
2942
|
if (isDirectRun) {
|
|
2903
2943
|
main().catch((err) => {
|
|
@@ -3071,15 +3111,18 @@ function loadStorageState(accountId) {
|
|
|
3071
3111
|
return void 0;
|
|
3072
3112
|
}
|
|
3073
3113
|
}
|
|
3074
|
-
async function saveStorageState(context, accountId, timeoutMs =
|
|
3114
|
+
async function saveStorageState(context, accountId, timeoutMs = 1e4) {
|
|
3075
3115
|
try {
|
|
3076
3116
|
const stateFile = getStorageStatePath(accountId);
|
|
3077
3117
|
const dir = path5.dirname(stateFile);
|
|
3078
3118
|
if (!fs8.existsSync(dir)) {
|
|
3079
3119
|
fs8.mkdirSync(dir, { recursive: true });
|
|
3080
3120
|
}
|
|
3121
|
+
const rawPromise = context.storageState({ path: stateFile }).catch(() => {
|
|
3122
|
+
return void 0;
|
|
3123
|
+
});
|
|
3081
3124
|
await withTimeout(
|
|
3082
|
-
|
|
3125
|
+
rawPromise,
|
|
3083
3126
|
timeoutMs,
|
|
3084
3127
|
`storageState timed out after ${timeoutMs}ms`
|
|
3085
3128
|
);
|
package/dist/sync-clients.js
CHANGED
|
@@ -183,11 +183,11 @@ var envSchema = z.object({
|
|
|
183
183
|
// nothing in this window is dead and should fail fast, retryable).
|
|
184
184
|
QWEN_FIRST_CHUNK_TIMEOUT: z.string().default("60000"),
|
|
185
185
|
TOTAL_REQUEST_TIMEOUT: z.string().default("600000"),
|
|
186
|
-
// Mid-stream silence window for thinking models:
|
|
186
|
+
// Mid-stream silence window for thinking models: 5 min with ZERO upstream
|
|
187
187
|
// bytes is a dead stream (WAF swallow / dropped connection) — fail fast and
|
|
188
188
|
// let the retry policy rotate accounts. Flowing reasoning chunks RESET this
|
|
189
189
|
// timer, so legitimate slow thinking is never cut; only total silence is.
|
|
190
|
-
REASONING_MODEL_TIMEOUT: z.string().default("
|
|
190
|
+
REASONING_MODEL_TIMEOUT: z.string().default("300000"),
|
|
191
191
|
CACHE_TTL: z.string().default("3600"),
|
|
192
192
|
RESPONSE_TTL: z.string().default("1800"),
|
|
193
193
|
CACHE_COMPRESSION_ENABLED: z.string().default("true"),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qwenproxy-cli",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "High-performance OpenAI & Anthropic compatible API gateway for Qwen with multi-account rotation, interactive TUI, and resilient tool calling.",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"bin": {
|
package/src/clean-cache.ts
CHANGED
|
@@ -4,6 +4,9 @@ import path from "node:path";
|
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import { chromium } from "patchright";
|
|
6
6
|
import { pruneAllPlaywrightProfiles, cleanupOrphanProfiles } from "./services/playwright.ts";
|
|
7
|
+
import { installProcessTeardownGuards } from "./core/process-guard.ts";
|
|
8
|
+
|
|
9
|
+
installProcessTeardownGuards("CleanCache");
|
|
7
10
|
|
|
8
11
|
export function formatBytes(bytes: number): string {
|
|
9
12
|
if (bytes < 1024) return `${bytes} B`;
|
package/src/core/config.ts
CHANGED
|
@@ -102,11 +102,11 @@ const envSchema = z
|
|
|
102
102
|
// nothing in this window is dead and should fail fast, retryable).
|
|
103
103
|
QWEN_FIRST_CHUNK_TIMEOUT: z.string().default("60000"),
|
|
104
104
|
TOTAL_REQUEST_TIMEOUT: z.string().default("600000"),
|
|
105
|
-
// Mid-stream silence window for thinking models:
|
|
105
|
+
// Mid-stream silence window for thinking models: 5 min with ZERO upstream
|
|
106
106
|
// bytes is a dead stream (WAF swallow / dropped connection) — fail fast and
|
|
107
107
|
// let the retry policy rotate accounts. Flowing reasoning chunks RESET this
|
|
108
108
|
// timer, so legitimate slow thinking is never cut; only total silence is.
|
|
109
|
-
REASONING_MODEL_TIMEOUT: z.string().default("
|
|
109
|
+
REASONING_MODEL_TIMEOUT: z.string().default("300000"),
|
|
110
110
|
CACHE_TTL: z.string().default("3600"),
|
|
111
111
|
RESPONSE_TTL: z.string().default("1800"),
|
|
112
112
|
CACHE_COMPRESSION_ENABLED: z.string().default("true"),
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process teardown error guard.
|
|
3
|
+
*
|
|
4
|
+
* Catches unhandled rejections and uncaught exceptions that arise from
|
|
5
|
+
* benign browser/CDP connection closures (e.g. ProtocolError: session closed,
|
|
6
|
+
* Network.setCacheDisabled, target crashed during shutdown) and prevents
|
|
7
|
+
* them from terminating the Node.js process.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export function isPlaywrightAlreadyClosedMessage(message: string): boolean {
|
|
11
|
+
if (!message) return false;
|
|
12
|
+
return (
|
|
13
|
+
message.includes("Target page, context or browser has been closed") ||
|
|
14
|
+
message.includes("Browser has been closed") ||
|
|
15
|
+
message.includes("Target closed") ||
|
|
16
|
+
message.includes("Target crashed") ||
|
|
17
|
+
message.includes("Page crashed") ||
|
|
18
|
+
message.includes("Assertion error") ||
|
|
19
|
+
message.includes("Cannot find parent object") ||
|
|
20
|
+
message.includes("Connection closed") ||
|
|
21
|
+
message.includes("session closed") ||
|
|
22
|
+
message.includes("Session closed") ||
|
|
23
|
+
message.includes("Network.setCacheDisabled") ||
|
|
24
|
+
message.includes("Protocol error") ||
|
|
25
|
+
message.includes("storageState timed out")
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function isBenignTeardownError(error: unknown): boolean {
|
|
30
|
+
if (!error) return false;
|
|
31
|
+
const message =
|
|
32
|
+
error instanceof Error
|
|
33
|
+
? error.message
|
|
34
|
+
: typeof error === "object" && "message" in error
|
|
35
|
+
? String((error as any).message)
|
|
36
|
+
: String(error);
|
|
37
|
+
return isPlaywrightAlreadyClosedMessage(message);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let guardsInstalled = false;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Installs process-wide handlers for uncaughtException and unhandledRejection
|
|
44
|
+
* to guard against asynchronous Playwright/CDP driver teardown crashes.
|
|
45
|
+
*/
|
|
46
|
+
export function installProcessTeardownGuards(label = "Process"): void {
|
|
47
|
+
if (guardsInstalled) return;
|
|
48
|
+
guardsInstalled = true;
|
|
49
|
+
|
|
50
|
+
process.on("uncaughtException", (error: unknown) => {
|
|
51
|
+
if (isBenignTeardownError(error)) {
|
|
52
|
+
const msg =
|
|
53
|
+
error instanceof Error
|
|
54
|
+
? error.message
|
|
55
|
+
: typeof error === "object" && error !== null && "message" in error
|
|
56
|
+
? String((error as any).message)
|
|
57
|
+
: String(error);
|
|
58
|
+
console.warn(`⚠️ [${label}] Handled benign driver teardown exception: ${msg}`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
console.error(`❌ [${label}] Uncaught Exception:`, error);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
process.on("unhandledRejection", (reason: unknown) => {
|
|
65
|
+
if (isBenignTeardownError(reason)) {
|
|
66
|
+
const msg =
|
|
67
|
+
reason instanceof Error
|
|
68
|
+
? reason.message
|
|
69
|
+
: typeof reason === "object" && reason !== null && "message" in reason
|
|
70
|
+
? String((reason as any).message)
|
|
71
|
+
: String(reason);
|
|
72
|
+
console.warn(`⚠️ [${label}] Handled benign driver teardown rejection: ${msg}`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
console.error(`❌ [${label}] Unhandled Rejection:`, reason);
|
|
76
|
+
});
|
|
77
|
+
}
|
package/src/core/version.ts
CHANGED
package/src/delete-chats.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import "dotenv/config";
|
|
2
|
+
import { installProcessTeardownGuards } from "./core/process-guard.ts";
|
|
2
3
|
import { deleteChatsForConfiguredAccounts } from "./services/chat-cleanup.ts";
|
|
3
4
|
|
|
5
|
+
installProcessTeardownGuards("DeleteChats");
|
|
6
|
+
|
|
4
7
|
async function run(): Promise<void> {
|
|
5
8
|
console.log("🗑️ [DeleteChats] Using Playwright sessions");
|
|
6
9
|
|
package/src/index.ts
CHANGED
|
@@ -16,36 +16,10 @@ if (fs.existsSync(envPath)) {
|
|
|
16
16
|
} else {
|
|
17
17
|
dotenv.config({ quiet: true })
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
process.on('uncaughtException', async (error: unknown) => {
|
|
21
|
-
const { isPlaywrightAlreadyClosedError } = await import('./services/playwright.ts');
|
|
22
|
-
if (isPlaywrightAlreadyClosedError(error)) {
|
|
23
|
-
const msg =
|
|
24
|
-
error instanceof Error
|
|
25
|
-
? error.message
|
|
26
|
-
: typeof error === 'object' && error !== null && 'message' in error
|
|
27
|
-
? String((error as any).message)
|
|
28
|
-
: String(error);
|
|
29
|
-
console.warn(`⚠️ [Playwright] Handled benign driver teardown exception: ${msg}`);
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
console.error('❌ [Process] Uncaught Exception:', error);
|
|
33
|
-
});
|
|
19
|
+
import { installProcessTeardownGuards } from './core/process-guard.ts'
|
|
34
20
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (isPlaywrightAlreadyClosedError(reason)) {
|
|
38
|
-
const msg =
|
|
39
|
-
reason instanceof Error
|
|
40
|
-
? reason.message
|
|
41
|
-
: typeof reason === 'object' && reason !== null && 'message' in reason
|
|
42
|
-
? String((reason as any).message)
|
|
43
|
-
: String(reason);
|
|
44
|
-
console.warn(`⚠️ [Playwright] Handled benign driver teardown rejection: ${msg}`);
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
console.error('❌ [Process] Unhandled Rejection:', reason);
|
|
48
|
-
});
|
|
21
|
+
// Prevent benign asynchronous driver/browser teardown exceptions from crashing the server
|
|
22
|
+
installProcessTeardownGuards("Playwright");
|
|
49
23
|
import { startServer } from './api/server.js'
|
|
50
24
|
const isTui = process.argv.includes('--tui') || process.env.QWEN_TUI === 'true'
|
|
51
25
|
|
package/src/login.ts
CHANGED
|
@@ -9,8 +9,10 @@ import {
|
|
|
9
9
|
import { maskEmail } from "./core/logger.ts";
|
|
10
10
|
import * as readline from "readline";
|
|
11
11
|
import * as dotenv from "dotenv";
|
|
12
|
+
import { installProcessTeardownGuards } from "./core/process-guard.ts";
|
|
12
13
|
|
|
13
14
|
dotenv.config();
|
|
15
|
+
installProcessTeardownGuards("Login");
|
|
14
16
|
|
|
15
17
|
const rl = readline.createInterface({
|
|
16
18
|
input: process.stdin,
|
|
@@ -404,6 +404,26 @@ export function isCorruptedChatHistoryError(err: unknown): boolean {
|
|
|
404
404
|
);
|
|
405
405
|
}
|
|
406
406
|
|
|
407
|
+
/**
|
|
408
|
+
* Qwen upstream internal or server-busy errors.
|
|
409
|
+
* Transient server-side failures requiring a fresh chat and account switch.
|
|
410
|
+
*/
|
|
411
|
+
export function isInternalServerError(err: unknown): boolean {
|
|
412
|
+
const code = errCode(err).toLowerCase();
|
|
413
|
+
const message = errMessage(err).toLowerCase();
|
|
414
|
+
return (
|
|
415
|
+
code === "internal_error" ||
|
|
416
|
+
code === "internal_server_error" ||
|
|
417
|
+
code === "server_error" ||
|
|
418
|
+
code === "server_busy" ||
|
|
419
|
+
code === "busy" ||
|
|
420
|
+
message.includes("internal_error") ||
|
|
421
|
+
message.includes("ocorreu um erro inesperado") ||
|
|
422
|
+
message.includes("unexpected error") ||
|
|
423
|
+
message.includes("internal server error")
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
|
|
407
427
|
/**
|
|
408
428
|
* Build a RetryAction with sane defaults so each classification branch only
|
|
409
429
|
* spells out the fields it actually changes. Defaults: retryable, no account
|
|
@@ -590,6 +610,16 @@ export function classifyRetryAction(
|
|
|
590
610
|
});
|
|
591
611
|
}
|
|
592
612
|
|
|
613
|
+
if (isInternalServerError(err) && !(err instanceof QwenUpstreamError)) {
|
|
614
|
+
const typed = err as RetryableStreamError;
|
|
615
|
+
return makeRetryAction("upstream_internal_error", {
|
|
616
|
+
switchAccount: typed.switchAccount !== false,
|
|
617
|
+
forceNewChat: true,
|
|
618
|
+
retryWithFullPrompt: true,
|
|
619
|
+
retryAfterMs: typed.retryAfterMs ?? Math.min(baseDelayMs * 2, 3000),
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
|
|
593
623
|
if (
|
|
594
624
|
isNetworkLikeError(err) ||
|
|
595
625
|
err instanceof QwenUpstreamUnavailableError ||
|
|
@@ -906,6 +906,7 @@ export async function processStreamingResponse(
|
|
|
906
906
|
let activeReader: ReadableStreamDefaultReader<Uint8Array> | null = null;
|
|
907
907
|
let invalidInputSameAccountRetries = 0;
|
|
908
908
|
let chatInProgressSameAccountRetries = 0;
|
|
909
|
+
let antiBotSameAccountRetries = 0;
|
|
909
910
|
const totalConfiguredAccounts = loadAccounts().length;
|
|
910
911
|
let quotaRotationsLeft = Math.min(Math.max(1, totalConfiguredAccounts - 1), 6);
|
|
911
912
|
// Set when the terminal [DONE] reached the client. The `| recovered`
|
|
@@ -1456,9 +1457,19 @@ export async function processStreamingResponse(
|
|
|
1456
1457
|
);
|
|
1457
1458
|
return false;
|
|
1458
1459
|
}
|
|
1459
|
-
|
|
1460
|
+
let switchAccount =
|
|
1460
1461
|
policy.switchAccount && !retryInvalidInputOnSameAccount;
|
|
1461
1462
|
|
|
1463
|
+
if (policy.reason === "anti_bot") {
|
|
1464
|
+
if (antiBotSameAccountRetries === 0) {
|
|
1465
|
+
antiBotSameAccountRetries++;
|
|
1466
|
+
switchAccount = false;
|
|
1467
|
+
} else {
|
|
1468
|
+
switchAccount = true;
|
|
1469
|
+
markAccountRateLimited(currentAccountId, 60_000, "AntiBotWafChallenge");
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1462
1473
|
if (
|
|
1463
1474
|
switchAccount &&
|
|
1464
1475
|
(policy.accountCooldownMs ?? 0) > 0
|
|
@@ -1476,6 +1487,35 @@ export async function processStreamingResponse(
|
|
|
1476
1487
|
.catch(() => {});
|
|
1477
1488
|
}
|
|
1478
1489
|
|
|
1490
|
+
// Preventative stop: signal Qwen to stop generating on the backend before switching
|
|
1491
|
+
if (currentUiSessionId && targetResponseId) {
|
|
1492
|
+
const streamData = getStream(completionId);
|
|
1493
|
+
const stopHeaders = streamData?.headers;
|
|
1494
|
+
if (stopHeaders?.cookie && stopHeaders["user-agent"]) {
|
|
1495
|
+
void requestQwenTextInBrowser(
|
|
1496
|
+
currentAccountId,
|
|
1497
|
+
"POST",
|
|
1498
|
+
`/api/v2/chat/completions/stop?chat_id=${encodeURIComponent(currentUiSessionId)}`,
|
|
1499
|
+
buildQwenRequestHeaders({
|
|
1500
|
+
cookie: stopHeaders.cookie,
|
|
1501
|
+
userAgent: stopHeaders["user-agent"],
|
|
1502
|
+
bxUa: stopHeaders["bx-ua"],
|
|
1503
|
+
bxUmidtoken: stopHeaders["bx-umidtoken"],
|
|
1504
|
+
bxV: stopHeaders["bx-v"],
|
|
1505
|
+
chatSessionId: currentUiSessionId,
|
|
1506
|
+
}),
|
|
1507
|
+
JSON.stringify({
|
|
1508
|
+
chat_id: currentUiSessionId,
|
|
1509
|
+
response_id: targetResponseId,
|
|
1510
|
+
}),
|
|
1511
|
+
{
|
|
1512
|
+
referrer: qwenUrl(`/c/${encodeURIComponent(currentUiSessionId)}`),
|
|
1513
|
+
noMutexRecovery: true,
|
|
1514
|
+
},
|
|
1515
|
+
).catch(() => undefined);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1479
1519
|
retryContext.releaseAccountLease?.();
|
|
1480
1520
|
retryContext.releaseAccountLease = null;
|
|
1481
1521
|
await reader.cancel().catch(() => undefined);
|
|
@@ -108,6 +108,11 @@ export async function deleteChatsForConfiguredAccounts(keepBrowserOpen = false):
|
|
|
108
108
|
`❌ [DeleteChats] [${currentIdx}/${totalCount}] Falha ao apagar conversas de ${maskEmail(account.email)}:`,
|
|
109
109
|
error instanceof Error ? error.message : String(error),
|
|
110
110
|
);
|
|
111
|
+
} finally {
|
|
112
|
+
if (!keepBrowserOpen) {
|
|
113
|
+
const { closePlaywrightForAccount } = await import("./playwright.ts");
|
|
114
|
+
await closePlaywrightForAccount(account.id).catch(() => {});
|
|
115
|
+
}
|
|
111
116
|
}
|
|
112
117
|
}
|
|
113
118
|
} finally {
|
|
@@ -287,7 +287,7 @@ export function loadStorageState(accountId: string): string | undefined {
|
|
|
287
287
|
export async function saveStorageState(
|
|
288
288
|
context: BrowserContext,
|
|
289
289
|
accountId: string,
|
|
290
|
-
timeoutMs =
|
|
290
|
+
timeoutMs = 10_000,
|
|
291
291
|
): Promise<void> {
|
|
292
292
|
try {
|
|
293
293
|
const stateFile = getStorageStatePath(accountId);
|
|
@@ -295,8 +295,12 @@ export async function saveStorageState(
|
|
|
295
295
|
if (!fs.existsSync(dir)) {
|
|
296
296
|
fs.mkdirSync(dir, { recursive: true });
|
|
297
297
|
}
|
|
298
|
+
const rawPromise = context.storageState({ path: stateFile }).catch(() => {
|
|
299
|
+
// Swallowed on context teardown to avoid unhandled rejection if withTimeout already fired
|
|
300
|
+
return undefined as any;
|
|
301
|
+
});
|
|
298
302
|
await withTimeout(
|
|
299
|
-
|
|
303
|
+
rawPromise,
|
|
300
304
|
timeoutMs,
|
|
301
305
|
`storageState timed out after ${timeoutMs}ms`,
|
|
302
306
|
);
|