qwenproxy-cli 1.6.2 → 1.6.4
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 +73 -4
- package/dist/delete-chats.js +100 -9
- package/dist/index.js +66 -20
- package/dist/login.js +54 -8
- package/dist/reset-cooldowns.js +73 -4
- package/package.json +1 -1
- package/src/core/version.ts +1 -1
- package/src/routes/chat/retry-policy.ts +18 -16
- package/src/services/playwright.ts +72 -16
- package/src/services/qwen.ts +1 -1
package/dist/clean-cache.js
CHANGED
|
@@ -14,6 +14,25 @@ var __export = (target, all) => {
|
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
// src/core/paths.ts
|
|
17
|
+
var paths_exports = {};
|
|
18
|
+
__export(paths_exports, {
|
|
19
|
+
ensureDataDirs: () => ensureDataDirs,
|
|
20
|
+
getAccountPriorityPath: () => getAccountPriorityPath,
|
|
21
|
+
getAccountProfilePath: () => getAccountProfilePath,
|
|
22
|
+
getDataDir: () => getDataDir,
|
|
23
|
+
getDbDir: () => getDbDir,
|
|
24
|
+
getDbPath: () => getDbPath,
|
|
25
|
+
getEncryptionKeyPath: () => getEncryptionKeyPath,
|
|
26
|
+
getEnvFilePath: () => getEnvFilePath,
|
|
27
|
+
getLogsDir: () => getLogsDir,
|
|
28
|
+
getOsGlobalDataDir: () => getOsGlobalDataDir,
|
|
29
|
+
getProfilesDir: () => getProfilesDir,
|
|
30
|
+
getServerLogFilePath: () => getServerLogFilePath,
|
|
31
|
+
getSyncStatePath: () => getSyncStatePath,
|
|
32
|
+
getTuiSettingsPath: () => getTuiSettingsPath,
|
|
33
|
+
isRunningUnderNodeTest: () => isRunningUnderNodeTest,
|
|
34
|
+
resolveDataDir: () => resolveDataDir
|
|
35
|
+
});
|
|
17
36
|
import fs from "node:fs";
|
|
18
37
|
import path from "node:path";
|
|
19
38
|
import os from "node:os";
|
|
@@ -78,6 +97,12 @@ function getAccountProfilePath(accountId, customDataDir) {
|
|
|
78
97
|
function getAccountPriorityPath(customDataDir) {
|
|
79
98
|
return path.join(customDataDir || getDataDir(), "account-priority.json");
|
|
80
99
|
}
|
|
100
|
+
function getSyncStatePath(customDataDir) {
|
|
101
|
+
return path.join(customDataDir || getDataDir(), "sync-state.json");
|
|
102
|
+
}
|
|
103
|
+
function getTuiSettingsPath(customDataDir) {
|
|
104
|
+
return path.join(customDataDir || getDataDir(), "tui-settings.json");
|
|
105
|
+
}
|
|
81
106
|
function getEnvFilePath(customDataDir) {
|
|
82
107
|
if (isRunningUnderNodeTest()) {
|
|
83
108
|
return path.join(customDataDir || getDataDir(), ".env");
|
|
@@ -88,6 +113,26 @@ function getEnvFilePath(customDataDir) {
|
|
|
88
113
|
}
|
|
89
114
|
return path.join(customDataDir || getDataDir(), ".env");
|
|
90
115
|
}
|
|
116
|
+
function getLogsDir(customDataDir) {
|
|
117
|
+
return path.join(customDataDir || getDataDir(), "logs");
|
|
118
|
+
}
|
|
119
|
+
function getServerLogFilePath(customDataDir) {
|
|
120
|
+
return path.join(getLogsDir(customDataDir), "server.log");
|
|
121
|
+
}
|
|
122
|
+
function ensureDataDirs(targetDataDir) {
|
|
123
|
+
const root = targetDataDir || getDataDir();
|
|
124
|
+
const dbDir = getDbDir(root);
|
|
125
|
+
const profilesDir = getProfilesDir(root);
|
|
126
|
+
const logsDir = getLogsDir(root);
|
|
127
|
+
try {
|
|
128
|
+
if (!fs.existsSync(root)) fs.mkdirSync(root, { recursive: true });
|
|
129
|
+
if (!fs.existsSync(dbDir)) fs.mkdirSync(dbDir, { recursive: true });
|
|
130
|
+
if (!fs.existsSync(profilesDir)) fs.mkdirSync(profilesDir, { recursive: true });
|
|
131
|
+
if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir, { recursive: true });
|
|
132
|
+
} catch (err) {
|
|
133
|
+
console.error(`[Paths] Error ensuring data directories at ${root}:`, err?.message || String(err));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
91
136
|
var init_paths = __esm({
|
|
92
137
|
"src/core/paths.ts"() {
|
|
93
138
|
"use strict";
|
|
@@ -2796,9 +2841,10 @@ function getAccountMutex(accountId) {
|
|
|
2796
2841
|
async function recoverStuckAccountMutex(accountId, mutex, key) {
|
|
2797
2842
|
if (accountMutexes.get(accountId) !== mutex) return;
|
|
2798
2843
|
const lockState = mutex.state();
|
|
2799
|
-
|
|
2844
|
+
const minLockTimeout = Math.max(PLAYWRIGHT_MUTEX_WAIT_MS, config.timeouts.headers);
|
|
2845
|
+
if (lockState.heldForMs < minLockTimeout || lockState.heldBy.startsWith("init:") || lockState.heldBy.startsWith("headers:") || lockState.heldBy.startsWith("refresh:") || lockState.heldBy.startsWith("profile-reset:") || isAccountServingStream(accountId)) {
|
|
2800
2846
|
logger.warn(
|
|
2801
|
-
`[Playwright] Skipping destructive mutex recovery | account=${accountId} | heldBy=${lockState.heldBy} | heldFor=${lockState.heldForMs}ms | limit=${
|
|
2847
|
+
`[Playwright] Skipping destructive mutex recovery | account=${accountId} | heldBy=${lockState.heldBy} | heldFor=${lockState.heldForMs}ms | limit=${minLockTimeout}ms | servingStream=${isAccountServingStream(accountId)}`
|
|
2802
2848
|
);
|
|
2803
2849
|
return;
|
|
2804
2850
|
}
|
|
@@ -4470,7 +4516,24 @@ async function refreshAccountToken(accountId) {
|
|
|
4470
4516
|
return { success: false, error: "Could not refresh or re-authenticate token" };
|
|
4471
4517
|
}
|
|
4472
4518
|
async function captureQwenHeaders(accountId, pageOverride, timeoutMs = config.timeouts.headers, triggerGraceMs = HEADER_CAPTURE_TRIGGER_GRACE_MS) {
|
|
4473
|
-
|
|
4519
|
+
let page = pageOverride ?? accountPages.get(accountId);
|
|
4520
|
+
if (!page || page.isClosed()) {
|
|
4521
|
+
const { isRunningUnderNodeTest: isRunningUnderNodeTest3 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
4522
|
+
if (!isRunningUnderNodeTest3()) {
|
|
4523
|
+
const { getAccountCredentials: getAccountCredentials2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
|
|
4524
|
+
const creds = getAccountCredentials2(accountId);
|
|
4525
|
+
if (creds) {
|
|
4526
|
+
await initPlaywrightForAccount(
|
|
4527
|
+
creds,
|
|
4528
|
+
config.playwright.headless,
|
|
4529
|
+
config.playwright.browser,
|
|
4530
|
+
{ skipHeaderCapture: true }
|
|
4531
|
+
).catch(() => {
|
|
4532
|
+
});
|
|
4533
|
+
page = accountPages.get(accountId);
|
|
4534
|
+
}
|
|
4535
|
+
}
|
|
4536
|
+
}
|
|
4474
4537
|
if (!page || page.isClosed()) {
|
|
4475
4538
|
throw new Error(`Playwright page unavailable for header capture: ${accountId}`);
|
|
4476
4539
|
}
|
|
@@ -5171,8 +5234,10 @@ async function evictIdlePlaywrightContextsToLimit() {
|
|
|
5171
5234
|
if (accountPages.size <= max) return 0;
|
|
5172
5235
|
const candidates = priorityOrderForEviction(
|
|
5173
5236
|
Array.from(accountPages.keys()).filter((accountId) => {
|
|
5237
|
+
if (isAccountServingStream(accountId)) return false;
|
|
5238
|
+
if (isAccountRecentlyActive(accountId, 3e4)) return false;
|
|
5174
5239
|
const mutex = accountMutexes.get(accountId);
|
|
5175
|
-
return mutex?.isIdle()
|
|
5240
|
+
return mutex?.isIdle();
|
|
5176
5241
|
})
|
|
5177
5242
|
).map((accountId) => ({
|
|
5178
5243
|
accountId,
|
|
@@ -5285,6 +5350,10 @@ async function closePlaywrightForAccount(accountId) {
|
|
|
5285
5350
|
release();
|
|
5286
5351
|
}
|
|
5287
5352
|
}
|
|
5353
|
+
function isAccountRecentlyActive(accountId, maxIdleMs = 3e5) {
|
|
5354
|
+
const last = lastAccountActivity.get(accountId) ?? 0;
|
|
5355
|
+
return last > 0 && Date.now() - last < maxIdleMs;
|
|
5356
|
+
}
|
|
5288
5357
|
var requireLocal, contextInitHooks, ACCOUNT_MUTEX_MAX_HOLD_MS, accountMutexes, accountContexts, accountPages, inFlightAccountInits, headerCaches, HEADER_CACHE_TTL, COOKIE_CACHE_TTL, cookieCaches, lastAccountActivity, lastKeepAliveNavigation, lastProactiveTokenRefresh, profileResetQueue, profileResetChain, closingAllPlaywright, sleep2, HEADER_CAPTURE_SETTLE_MS, PLAYWRIGHT_MUTEX_WAIT_MS, ACCOUNT_PAGE_OPERATION_TIMEOUT_MS, SESSION_PROBE_NAVIGATION_TIMEOUT_MS, HEADER_CAPTURE_TRIGGER_GRACE_MS, FIRST_TRIGGER_GRACE_MS, HEADER_CAPTURE_TRIGGER_ATTEMPTS, CHAT_INPUT_APPEAR_TIMEOUT_MS, CHAT_INPUT_ACTION_TIMEOUT_MS, PROFILE_RESET_TIMEOUT_MS;
|
|
5289
5358
|
var init_playwright = __esm({
|
|
5290
5359
|
"src/services/playwright.ts"() {
|
package/dist/delete-chats.js
CHANGED
|
@@ -52,6 +52,25 @@ var init_process_guard = __esm({
|
|
|
52
52
|
});
|
|
53
53
|
|
|
54
54
|
// src/core/paths.ts
|
|
55
|
+
var paths_exports = {};
|
|
56
|
+
__export(paths_exports, {
|
|
57
|
+
ensureDataDirs: () => ensureDataDirs,
|
|
58
|
+
getAccountPriorityPath: () => getAccountPriorityPath,
|
|
59
|
+
getAccountProfilePath: () => getAccountProfilePath,
|
|
60
|
+
getDataDir: () => getDataDir,
|
|
61
|
+
getDbDir: () => getDbDir,
|
|
62
|
+
getDbPath: () => getDbPath,
|
|
63
|
+
getEncryptionKeyPath: () => getEncryptionKeyPath,
|
|
64
|
+
getEnvFilePath: () => getEnvFilePath,
|
|
65
|
+
getLogsDir: () => getLogsDir,
|
|
66
|
+
getOsGlobalDataDir: () => getOsGlobalDataDir,
|
|
67
|
+
getProfilesDir: () => getProfilesDir,
|
|
68
|
+
getServerLogFilePath: () => getServerLogFilePath,
|
|
69
|
+
getSyncStatePath: () => getSyncStatePath,
|
|
70
|
+
getTuiSettingsPath: () => getTuiSettingsPath,
|
|
71
|
+
isRunningUnderNodeTest: () => isRunningUnderNodeTest,
|
|
72
|
+
resolveDataDir: () => resolveDataDir
|
|
73
|
+
});
|
|
55
74
|
import fs from "node:fs";
|
|
56
75
|
import path from "node:path";
|
|
57
76
|
import os from "node:os";
|
|
@@ -116,6 +135,12 @@ function getAccountProfilePath(accountId, customDataDir) {
|
|
|
116
135
|
function getAccountPriorityPath(customDataDir) {
|
|
117
136
|
return path.join(customDataDir || getDataDir(), "account-priority.json");
|
|
118
137
|
}
|
|
138
|
+
function getSyncStatePath(customDataDir) {
|
|
139
|
+
return path.join(customDataDir || getDataDir(), "sync-state.json");
|
|
140
|
+
}
|
|
141
|
+
function getTuiSettingsPath(customDataDir) {
|
|
142
|
+
return path.join(customDataDir || getDataDir(), "tui-settings.json");
|
|
143
|
+
}
|
|
119
144
|
function getEnvFilePath(customDataDir) {
|
|
120
145
|
if (isRunningUnderNodeTest()) {
|
|
121
146
|
return path.join(customDataDir || getDataDir(), ".env");
|
|
@@ -126,6 +151,26 @@ function getEnvFilePath(customDataDir) {
|
|
|
126
151
|
}
|
|
127
152
|
return path.join(customDataDir || getDataDir(), ".env");
|
|
128
153
|
}
|
|
154
|
+
function getLogsDir(customDataDir) {
|
|
155
|
+
return path.join(customDataDir || getDataDir(), "logs");
|
|
156
|
+
}
|
|
157
|
+
function getServerLogFilePath(customDataDir) {
|
|
158
|
+
return path.join(getLogsDir(customDataDir), "server.log");
|
|
159
|
+
}
|
|
160
|
+
function ensureDataDirs(targetDataDir) {
|
|
161
|
+
const root = targetDataDir || getDataDir();
|
|
162
|
+
const dbDir = getDbDir(root);
|
|
163
|
+
const profilesDir = getProfilesDir(root);
|
|
164
|
+
const logsDir = getLogsDir(root);
|
|
165
|
+
try {
|
|
166
|
+
if (!fs.existsSync(root)) fs.mkdirSync(root, { recursive: true });
|
|
167
|
+
if (!fs.existsSync(dbDir)) fs.mkdirSync(dbDir, { recursive: true });
|
|
168
|
+
if (!fs.existsSync(profilesDir)) fs.mkdirSync(profilesDir, { recursive: true });
|
|
169
|
+
if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir, { recursive: true });
|
|
170
|
+
} catch (err) {
|
|
171
|
+
console.error(`[Paths] Error ensuring data directories at ${root}:`, err?.message || String(err));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
129
174
|
var init_paths = __esm({
|
|
130
175
|
"src/core/paths.ts"() {
|
|
131
176
|
"use strict";
|
|
@@ -3168,6 +3213,7 @@ __export(playwright_exports, {
|
|
|
3168
3213
|
closeIdlePlaywrightAccounts: () => closeIdlePlaywrightAccounts,
|
|
3169
3214
|
closePlaywrightForAccount: () => closePlaywrightForAccount,
|
|
3170
3215
|
evictIdlePlaywrightContextsToLimit: () => evictIdlePlaywrightContextsToLimit,
|
|
3216
|
+
getAccountMutex: () => getAccountMutex,
|
|
3171
3217
|
getActivePlaywrightAccountIds: () => getActivePlaywrightAccountIds,
|
|
3172
3218
|
getBasicHeaders: () => getBasicHeaders,
|
|
3173
3219
|
getCookies: () => getCookies,
|
|
@@ -3317,9 +3363,10 @@ function getAccountMutex(accountId) {
|
|
|
3317
3363
|
async function recoverStuckAccountMutex(accountId, mutex, key) {
|
|
3318
3364
|
if (accountMutexes.get(accountId) !== mutex) return;
|
|
3319
3365
|
const lockState = mutex.state();
|
|
3320
|
-
|
|
3366
|
+
const minLockTimeout = Math.max(PLAYWRIGHT_MUTEX_WAIT_MS, config.timeouts.headers);
|
|
3367
|
+
if (lockState.heldForMs < minLockTimeout || lockState.heldBy.startsWith("init:") || lockState.heldBy.startsWith("headers:") || lockState.heldBy.startsWith("refresh:") || lockState.heldBy.startsWith("profile-reset:") || isAccountServingStream(accountId)) {
|
|
3321
3368
|
logger.warn(
|
|
3322
|
-
`[Playwright] Skipping destructive mutex recovery | account=${accountId} | heldBy=${lockState.heldBy} | heldFor=${lockState.heldForMs}ms | limit=${
|
|
3369
|
+
`[Playwright] Skipping destructive mutex recovery | account=${accountId} | heldBy=${lockState.heldBy} | heldFor=${lockState.heldForMs}ms | limit=${minLockTimeout}ms | servingStream=${isAccountServingStream(accountId)}`
|
|
3323
3370
|
);
|
|
3324
3371
|
return;
|
|
3325
3372
|
}
|
|
@@ -4121,7 +4168,8 @@ async function getBasicHeaders(accountId) {
|
|
|
4121
4168
|
}
|
|
4122
4169
|
const release = await acquireAccountMutex(
|
|
4123
4170
|
accountId,
|
|
4124
|
-
`headers:${accountId.substring(0, 12)}
|
|
4171
|
+
`headers:${accountId.substring(0, 12)}`,
|
|
4172
|
+
Math.max(PLAYWRIGHT_MUTEX_WAIT_MS, config.timeouts.headers + 3e4)
|
|
4125
4173
|
);
|
|
4126
4174
|
try {
|
|
4127
4175
|
touchAccountActivity(accountId);
|
|
@@ -4214,8 +4262,10 @@ async function getBasicHeaders(accountId) {
|
|
|
4214
4262
|
return { cookie: cookie2, userAgent, bxV: bxV2, bxUa: bxUa2, bxUmidtoken: bxUmidtoken2, ...clientHints };
|
|
4215
4263
|
}
|
|
4216
4264
|
}
|
|
4265
|
+
let refreshedInThisCall = false;
|
|
4217
4266
|
if (headersAge > HEADER_CACHE_TTL && !cache.refreshInProgress) {
|
|
4218
4267
|
try {
|
|
4268
|
+
refreshedInThisCall = true;
|
|
4219
4269
|
await refreshHeadersInternal(accountId);
|
|
4220
4270
|
} catch (error) {
|
|
4221
4271
|
if (!hadUsableHeaders) throw error;
|
|
@@ -4284,7 +4334,7 @@ async function getBasicHeaders(accountId) {
|
|
|
4284
4334
|
} catch {
|
|
4285
4335
|
}
|
|
4286
4336
|
}
|
|
4287
|
-
if (!hasRequiredQwenHeaders(cache.headers)) {
|
|
4337
|
+
if (!hasRequiredQwenHeaders(cache.headers) && !refreshedInThisCall) {
|
|
4288
4338
|
console.log(
|
|
4289
4339
|
`\u{1F504} [Playwright] Missing required anti-bot headers for ${accountId}, triggering header interception...`
|
|
4290
4340
|
);
|
|
@@ -5727,7 +5777,24 @@ async function refreshAccountToken(accountId) {
|
|
|
5727
5777
|
return { success: false, error: "Could not refresh or re-authenticate token" };
|
|
5728
5778
|
}
|
|
5729
5779
|
async function captureQwenHeaders(accountId, pageOverride, timeoutMs = config.timeouts.headers, triggerGraceMs = HEADER_CAPTURE_TRIGGER_GRACE_MS) {
|
|
5730
|
-
|
|
5780
|
+
let page = pageOverride ?? accountPages.get(accountId);
|
|
5781
|
+
if (!page || page.isClosed()) {
|
|
5782
|
+
const { isRunningUnderNodeTest: isRunningUnderNodeTest4 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
5783
|
+
if (!isRunningUnderNodeTest4()) {
|
|
5784
|
+
const { getAccountCredentials: getAccountCredentials2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
|
|
5785
|
+
const creds = getAccountCredentials2(accountId);
|
|
5786
|
+
if (creds) {
|
|
5787
|
+
await initPlaywrightForAccount(
|
|
5788
|
+
creds,
|
|
5789
|
+
config.playwright.headless,
|
|
5790
|
+
config.playwright.browser,
|
|
5791
|
+
{ skipHeaderCapture: true }
|
|
5792
|
+
).catch(() => {
|
|
5793
|
+
});
|
|
5794
|
+
page = accountPages.get(accountId);
|
|
5795
|
+
}
|
|
5796
|
+
}
|
|
5797
|
+
}
|
|
5731
5798
|
if (!page || page.isClosed()) {
|
|
5732
5799
|
throw new Error(`Playwright page unavailable for header capture: ${accountId}`);
|
|
5733
5800
|
}
|
|
@@ -6370,16 +6437,38 @@ async function refreshHeaders(accountId, timeoutMs = config.timeouts.headers, fo
|
|
|
6370
6437
|
release();
|
|
6371
6438
|
}
|
|
6372
6439
|
}
|
|
6373
|
-
async function
|
|
6440
|
+
async function ensureAccountPageAvailable(accountId) {
|
|
6374
6441
|
const inFlightInit = inFlightAccountInits.get(accountId);
|
|
6375
6442
|
if (inFlightInit) {
|
|
6376
6443
|
await inFlightInit.catch(() => {
|
|
6377
6444
|
});
|
|
6378
6445
|
}
|
|
6379
|
-
|
|
6446
|
+
let page = accountPages.get(accountId);
|
|
6447
|
+
if (page && !page.isClosed()) {
|
|
6448
|
+
return page;
|
|
6449
|
+
}
|
|
6450
|
+
const { isRunningUnderNodeTest: isRunningUnderNodeTest4 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
6451
|
+
if (isRunningUnderNodeTest4()) {
|
|
6452
|
+
throw new Error(`Playwright page unavailable for account: ${accountId}`);
|
|
6453
|
+
}
|
|
6454
|
+
const { getAccountCredentials: getAccountCredentials2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
|
|
6455
|
+
const creds = getAccountCredentials2(accountId);
|
|
6456
|
+
if (!creds) {
|
|
6457
|
+
throw new Error(`Playwright page unavailable for account: ${accountId}`);
|
|
6458
|
+
}
|
|
6459
|
+
await initPlaywrightForAccount(
|
|
6460
|
+
creds,
|
|
6461
|
+
config.playwright.headless,
|
|
6462
|
+
config.playwright.browser
|
|
6463
|
+
);
|
|
6464
|
+
page = accountPages.get(accountId);
|
|
6380
6465
|
if (!page || page.isClosed()) {
|
|
6381
6466
|
throw new Error(`Playwright page unavailable for account: ${accountId}`);
|
|
6382
6467
|
}
|
|
6468
|
+
return page;
|
|
6469
|
+
}
|
|
6470
|
+
async function withAccountPage(accountId, fn, timeoutMs = ACCOUNT_PAGE_OPERATION_TIMEOUT_MS, mutexTimeoutMs = PLAYWRIGHT_MUTEX_WAIT_MS, recoverOnTimeout = true) {
|
|
6471
|
+
const page = await ensureAccountPageAvailable(accountId);
|
|
6383
6472
|
const release = await acquireAccountMutex(
|
|
6384
6473
|
accountId,
|
|
6385
6474
|
`page:${accountId.substring(0, 12)}`,
|
|
@@ -6680,8 +6769,10 @@ async function evictIdlePlaywrightContextsToLimit() {
|
|
|
6680
6769
|
if (accountPages.size <= max) return 0;
|
|
6681
6770
|
const candidates = priorityOrderForEviction(
|
|
6682
6771
|
Array.from(accountPages.keys()).filter((accountId) => {
|
|
6772
|
+
if (isAccountServingStream(accountId)) return false;
|
|
6773
|
+
if (isAccountRecentlyActive(accountId, 3e4)) return false;
|
|
6683
6774
|
const mutex = accountMutexes.get(accountId);
|
|
6684
|
-
return mutex?.isIdle()
|
|
6775
|
+
return mutex?.isIdle();
|
|
6685
6776
|
})
|
|
6686
6777
|
).map((accountId) => ({
|
|
6687
6778
|
accountId,
|
|
@@ -8771,7 +8862,7 @@ async function withQwenBrowserPage(accountId, fn, targetPath, operationTimeoutMs
|
|
|
8771
8862
|
return fn(page);
|
|
8772
8863
|
},
|
|
8773
8864
|
operationTimeoutMs,
|
|
8774
|
-
Math.
|
|
8865
|
+
Math.max(6e4, operationTimeoutMs),
|
|
8775
8866
|
recoverOnTimeout
|
|
8776
8867
|
);
|
|
8777
8868
|
}
|
package/dist/index.js
CHANGED
|
@@ -4323,6 +4323,7 @@ __export(playwright_exports, {
|
|
|
4323
4323
|
closeIdlePlaywrightAccounts: () => closeIdlePlaywrightAccounts,
|
|
4324
4324
|
closePlaywrightForAccount: () => closePlaywrightForAccount,
|
|
4325
4325
|
evictIdlePlaywrightContextsToLimit: () => evictIdlePlaywrightContextsToLimit,
|
|
4326
|
+
getAccountMutex: () => getAccountMutex,
|
|
4326
4327
|
getActivePlaywrightAccountIds: () => getActivePlaywrightAccountIds,
|
|
4327
4328
|
getBasicHeaders: () => getBasicHeaders,
|
|
4328
4329
|
getCookies: () => getCookies,
|
|
@@ -4472,9 +4473,10 @@ function getAccountMutex(accountId) {
|
|
|
4472
4473
|
async function recoverStuckAccountMutex(accountId, mutex, key) {
|
|
4473
4474
|
if (accountMutexes.get(accountId) !== mutex) return;
|
|
4474
4475
|
const lockState = mutex.state();
|
|
4475
|
-
|
|
4476
|
+
const minLockTimeout = Math.max(PLAYWRIGHT_MUTEX_WAIT_MS, config.timeouts.headers);
|
|
4477
|
+
if (lockState.heldForMs < minLockTimeout || lockState.heldBy.startsWith("init:") || lockState.heldBy.startsWith("headers:") || lockState.heldBy.startsWith("refresh:") || lockState.heldBy.startsWith("profile-reset:") || isAccountServingStream(accountId)) {
|
|
4476
4478
|
logger.warn(
|
|
4477
|
-
`[Playwright] Skipping destructive mutex recovery | account=${accountId} | heldBy=${lockState.heldBy} | heldFor=${lockState.heldForMs}ms | limit=${
|
|
4479
|
+
`[Playwright] Skipping destructive mutex recovery | account=${accountId} | heldBy=${lockState.heldBy} | heldFor=${lockState.heldForMs}ms | limit=${minLockTimeout}ms | servingStream=${isAccountServingStream(accountId)}`
|
|
4478
4480
|
);
|
|
4479
4481
|
return;
|
|
4480
4482
|
}
|
|
@@ -5276,7 +5278,8 @@ async function getBasicHeaders(accountId) {
|
|
|
5276
5278
|
}
|
|
5277
5279
|
const release = await acquireAccountMutex(
|
|
5278
5280
|
accountId,
|
|
5279
|
-
`headers:${accountId.substring(0, 12)}
|
|
5281
|
+
`headers:${accountId.substring(0, 12)}`,
|
|
5282
|
+
Math.max(PLAYWRIGHT_MUTEX_WAIT_MS, config.timeouts.headers + 3e4)
|
|
5280
5283
|
);
|
|
5281
5284
|
try {
|
|
5282
5285
|
touchAccountActivity(accountId);
|
|
@@ -5369,8 +5372,10 @@ async function getBasicHeaders(accountId) {
|
|
|
5369
5372
|
return { cookie: cookie2, userAgent, bxV: bxV2, bxUa: bxUa2, bxUmidtoken: bxUmidtoken2, ...clientHints };
|
|
5370
5373
|
}
|
|
5371
5374
|
}
|
|
5375
|
+
let refreshedInThisCall = false;
|
|
5372
5376
|
if (headersAge > HEADER_CACHE_TTL && !cache3.refreshInProgress) {
|
|
5373
5377
|
try {
|
|
5378
|
+
refreshedInThisCall = true;
|
|
5374
5379
|
await refreshHeadersInternal(accountId);
|
|
5375
5380
|
} catch (error) {
|
|
5376
5381
|
if (!hadUsableHeaders) throw error;
|
|
@@ -5439,7 +5444,7 @@ async function getBasicHeaders(accountId) {
|
|
|
5439
5444
|
} catch {
|
|
5440
5445
|
}
|
|
5441
5446
|
}
|
|
5442
|
-
if (!hasRequiredQwenHeaders(cache3.headers)) {
|
|
5447
|
+
if (!hasRequiredQwenHeaders(cache3.headers) && !refreshedInThisCall) {
|
|
5443
5448
|
console.log(
|
|
5444
5449
|
`\u{1F504} [Playwright] Missing required anti-bot headers for ${accountId}, triggering header interception...`
|
|
5445
5450
|
);
|
|
@@ -6882,7 +6887,24 @@ async function refreshAccountToken(accountId) {
|
|
|
6882
6887
|
return { success: false, error: "Could not refresh or re-authenticate token" };
|
|
6883
6888
|
}
|
|
6884
6889
|
async function captureQwenHeaders(accountId, pageOverride, timeoutMs = config.timeouts.headers, triggerGraceMs = HEADER_CAPTURE_TRIGGER_GRACE_MS) {
|
|
6885
|
-
|
|
6890
|
+
let page = pageOverride ?? accountPages.get(accountId);
|
|
6891
|
+
if (!page || page.isClosed()) {
|
|
6892
|
+
const { isRunningUnderNodeTest: isRunningUnderNodeTest4 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
6893
|
+
if (!isRunningUnderNodeTest4()) {
|
|
6894
|
+
const { getAccountCredentials: getAccountCredentials2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
|
|
6895
|
+
const creds = getAccountCredentials2(accountId);
|
|
6896
|
+
if (creds) {
|
|
6897
|
+
await initPlaywrightForAccount(
|
|
6898
|
+
creds,
|
|
6899
|
+
config.playwright.headless,
|
|
6900
|
+
config.playwright.browser,
|
|
6901
|
+
{ skipHeaderCapture: true }
|
|
6902
|
+
).catch(() => {
|
|
6903
|
+
});
|
|
6904
|
+
page = accountPages.get(accountId);
|
|
6905
|
+
}
|
|
6906
|
+
}
|
|
6907
|
+
}
|
|
6886
6908
|
if (!page || page.isClosed()) {
|
|
6887
6909
|
throw new Error(`Playwright page unavailable for header capture: ${accountId}`);
|
|
6888
6910
|
}
|
|
@@ -7525,16 +7547,38 @@ async function refreshHeaders(accountId, timeoutMs = config.timeouts.headers, fo
|
|
|
7525
7547
|
release();
|
|
7526
7548
|
}
|
|
7527
7549
|
}
|
|
7528
|
-
async function
|
|
7550
|
+
async function ensureAccountPageAvailable(accountId) {
|
|
7529
7551
|
const inFlightInit = inFlightAccountInits.get(accountId);
|
|
7530
7552
|
if (inFlightInit) {
|
|
7531
7553
|
await inFlightInit.catch(() => {
|
|
7532
7554
|
});
|
|
7533
7555
|
}
|
|
7534
|
-
|
|
7556
|
+
let page = accountPages.get(accountId);
|
|
7557
|
+
if (page && !page.isClosed()) {
|
|
7558
|
+
return page;
|
|
7559
|
+
}
|
|
7560
|
+
const { isRunningUnderNodeTest: isRunningUnderNodeTest4 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
7561
|
+
if (isRunningUnderNodeTest4()) {
|
|
7562
|
+
throw new Error(`Playwright page unavailable for account: ${accountId}`);
|
|
7563
|
+
}
|
|
7564
|
+
const { getAccountCredentials: getAccountCredentials2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
|
|
7565
|
+
const creds = getAccountCredentials2(accountId);
|
|
7566
|
+
if (!creds) {
|
|
7567
|
+
throw new Error(`Playwright page unavailable for account: ${accountId}`);
|
|
7568
|
+
}
|
|
7569
|
+
await initPlaywrightForAccount(
|
|
7570
|
+
creds,
|
|
7571
|
+
config.playwright.headless,
|
|
7572
|
+
config.playwright.browser
|
|
7573
|
+
);
|
|
7574
|
+
page = accountPages.get(accountId);
|
|
7535
7575
|
if (!page || page.isClosed()) {
|
|
7536
7576
|
throw new Error(`Playwright page unavailable for account: ${accountId}`);
|
|
7537
7577
|
}
|
|
7578
|
+
return page;
|
|
7579
|
+
}
|
|
7580
|
+
async function withAccountPage(accountId, fn, timeoutMs = ACCOUNT_PAGE_OPERATION_TIMEOUT_MS, mutexTimeoutMs = PLAYWRIGHT_MUTEX_WAIT_MS, recoverOnTimeout = true) {
|
|
7581
|
+
const page = await ensureAccountPageAvailable(accountId);
|
|
7538
7582
|
const release = await acquireAccountMutex(
|
|
7539
7583
|
accountId,
|
|
7540
7584
|
`page:${accountId.substring(0, 12)}`,
|
|
@@ -7835,8 +7879,10 @@ async function evictIdlePlaywrightContextsToLimit() {
|
|
|
7835
7879
|
if (accountPages.size <= max) return 0;
|
|
7836
7880
|
const candidates = priorityOrderForEviction(
|
|
7837
7881
|
Array.from(accountPages.keys()).filter((accountId) => {
|
|
7882
|
+
if (isAccountServingStream(accountId)) return false;
|
|
7883
|
+
if (isAccountRecentlyActive(accountId, 3e4)) return false;
|
|
7838
7884
|
const mutex = accountMutexes.get(accountId);
|
|
7839
|
-
return mutex?.isIdle()
|
|
7885
|
+
return mutex?.isIdle();
|
|
7840
7886
|
})
|
|
7841
7887
|
).map((accountId) => ({
|
|
7842
7888
|
accountId,
|
|
@@ -9784,7 +9830,7 @@ function isModelNotFoundError(err) {
|
|
|
9784
9830
|
function isNetworkLikeError(err) {
|
|
9785
9831
|
if (err instanceof QwenNetworkError) return true;
|
|
9786
9832
|
const message = errMessage(err).toLowerCase();
|
|
9787
|
-
return message === "network error" || message.includes("failed to fetch") || message.includes("fetch failed") || message.includes("network connection was lost") || message.includes("connection reset") || message.includes("connection closed") || message.includes("socket hang up") || message.includes("econnreset") || message.includes("econnrefused") || message.includes("etimedout");
|
|
9833
|
+
return message === "network error" || message.includes("failed to fetch") || message.includes("fetch failed") || message.includes("network connection was lost") || message.includes("connection reset") || message.includes("connection closed") || message.includes("socket hang up") || message.includes("econnreset") || message.includes("econnrefused") || message.includes("etimedout") || message.includes("stream ended before response headers") || message.includes("timed out waiting for response headers");
|
|
9788
9834
|
}
|
|
9789
9835
|
function isCorruptedChatHistoryError(err) {
|
|
9790
9836
|
const message = errMessage(err).toLowerCase();
|
|
@@ -9827,6 +9873,15 @@ function classifyRetryAction(err, options) {
|
|
|
9827
9873
|
if (isTerminalLocalError(err)) {
|
|
9828
9874
|
return makeRetryAction("terminal_local", { retryable: false });
|
|
9829
9875
|
}
|
|
9876
|
+
if (err instanceof PersonalizationSyncError) {
|
|
9877
|
+
return makeRetryAction("personalization_sync_failed", {
|
|
9878
|
+
switchAccount: true,
|
|
9879
|
+
forceNewChat: true,
|
|
9880
|
+
retryAfterMs: baseDelayMs,
|
|
9881
|
+
accountCooldownMs: 0,
|
|
9882
|
+
accountCooldownReason: "PersonalizationFailed"
|
|
9883
|
+
});
|
|
9884
|
+
}
|
|
9830
9885
|
const message = errMessage(err).toLowerCase();
|
|
9831
9886
|
const code = errCode(err).toLowerCase();
|
|
9832
9887
|
if (isAccountInitializationError(err)) {
|
|
@@ -9843,15 +9898,6 @@ function classifyRetryAction(err, options) {
|
|
|
9843
9898
|
retryAfterMs: Math.min(baseDelayMs, 1e3)
|
|
9844
9899
|
});
|
|
9845
9900
|
}
|
|
9846
|
-
if (err instanceof PersonalizationSyncError) {
|
|
9847
|
-
return makeRetryAction("personalization_sync_failed", {
|
|
9848
|
-
switchAccount: true,
|
|
9849
|
-
forceNewChat: true,
|
|
9850
|
-
retryAfterMs: baseDelayMs,
|
|
9851
|
-
accountCooldownMs: 0,
|
|
9852
|
-
accountCooldownReason: "PersonalizationFailed"
|
|
9853
|
-
});
|
|
9854
|
-
}
|
|
9855
9901
|
if (code === "createchatinvalidresponse" || code === "createchatfailed" || code === "unauthorized" || message.includes("401 n\xE3o autorizado") || message.includes("n\xE3o tem permiss\xE3o para acessar") || message.includes("401 unauthorized") || message.includes('"code":"unauthorized"') || message.includes('"code": "unauthorized"') || message.includes("token has expired")) {
|
|
9856
9902
|
return makeRetryAction("account_initialization_failed", {
|
|
9857
9903
|
switchAccount: true,
|
|
@@ -12006,7 +12052,7 @@ async function withQwenBrowserPage(accountId, fn, targetPath, operationTimeoutMs
|
|
|
12006
12052
|
return fn(page);
|
|
12007
12053
|
},
|
|
12008
12054
|
operationTimeoutMs,
|
|
12009
|
-
Math.
|
|
12055
|
+
Math.max(6e4, operationTimeoutMs),
|
|
12010
12056
|
recoverOnTimeout
|
|
12011
12057
|
);
|
|
12012
12058
|
}
|
|
@@ -26782,7 +26828,7 @@ function getAppVersion() {
|
|
|
26782
26828
|
}
|
|
26783
26829
|
} catch {
|
|
26784
26830
|
}
|
|
26785
|
-
cachedVersion = "v1.6.
|
|
26831
|
+
cachedVersion = "v1.6.4";
|
|
26786
26832
|
return cachedVersion;
|
|
26787
26833
|
}
|
|
26788
26834
|
var cachedVersion;
|
package/dist/login.js
CHANGED
|
@@ -3190,6 +3190,7 @@ __export(playwright_exports, {
|
|
|
3190
3190
|
closeIdlePlaywrightAccounts: () => closeIdlePlaywrightAccounts,
|
|
3191
3191
|
closePlaywrightForAccount: () => closePlaywrightForAccount,
|
|
3192
3192
|
evictIdlePlaywrightContextsToLimit: () => evictIdlePlaywrightContextsToLimit,
|
|
3193
|
+
getAccountMutex: () => getAccountMutex,
|
|
3193
3194
|
getActivePlaywrightAccountIds: () => getActivePlaywrightAccountIds,
|
|
3194
3195
|
getBasicHeaders: () => getBasicHeaders,
|
|
3195
3196
|
getCookies: () => getCookies,
|
|
@@ -3339,9 +3340,10 @@ function getAccountMutex(accountId) {
|
|
|
3339
3340
|
async function recoverStuckAccountMutex(accountId, mutex, key) {
|
|
3340
3341
|
if (accountMutexes.get(accountId) !== mutex) return;
|
|
3341
3342
|
const lockState = mutex.state();
|
|
3342
|
-
|
|
3343
|
+
const minLockTimeout = Math.max(PLAYWRIGHT_MUTEX_WAIT_MS, config.timeouts.headers);
|
|
3344
|
+
if (lockState.heldForMs < minLockTimeout || lockState.heldBy.startsWith("init:") || lockState.heldBy.startsWith("headers:") || lockState.heldBy.startsWith("refresh:") || lockState.heldBy.startsWith("profile-reset:") || isAccountServingStream(accountId)) {
|
|
3343
3345
|
logger.warn(
|
|
3344
|
-
`[Playwright] Skipping destructive mutex recovery | account=${accountId} | heldBy=${lockState.heldBy} | heldFor=${lockState.heldForMs}ms | limit=${
|
|
3346
|
+
`[Playwright] Skipping destructive mutex recovery | account=${accountId} | heldBy=${lockState.heldBy} | heldFor=${lockState.heldForMs}ms | limit=${minLockTimeout}ms | servingStream=${isAccountServingStream(accountId)}`
|
|
3345
3347
|
);
|
|
3346
3348
|
return;
|
|
3347
3349
|
}
|
|
@@ -4143,7 +4145,8 @@ async function getBasicHeaders(accountId) {
|
|
|
4143
4145
|
}
|
|
4144
4146
|
const release = await acquireAccountMutex(
|
|
4145
4147
|
accountId,
|
|
4146
|
-
`headers:${accountId.substring(0, 12)}
|
|
4148
|
+
`headers:${accountId.substring(0, 12)}`,
|
|
4149
|
+
Math.max(PLAYWRIGHT_MUTEX_WAIT_MS, config.timeouts.headers + 3e4)
|
|
4147
4150
|
);
|
|
4148
4151
|
try {
|
|
4149
4152
|
touchAccountActivity(accountId);
|
|
@@ -4236,8 +4239,10 @@ async function getBasicHeaders(accountId) {
|
|
|
4236
4239
|
return { cookie: cookie2, userAgent, bxV: bxV2, bxUa: bxUa2, bxUmidtoken: bxUmidtoken2, ...clientHints };
|
|
4237
4240
|
}
|
|
4238
4241
|
}
|
|
4242
|
+
let refreshedInThisCall = false;
|
|
4239
4243
|
if (headersAge > HEADER_CACHE_TTL && !cache.refreshInProgress) {
|
|
4240
4244
|
try {
|
|
4245
|
+
refreshedInThisCall = true;
|
|
4241
4246
|
await refreshHeadersInternal(accountId);
|
|
4242
4247
|
} catch (error) {
|
|
4243
4248
|
if (!hadUsableHeaders) throw error;
|
|
@@ -4306,7 +4311,7 @@ async function getBasicHeaders(accountId) {
|
|
|
4306
4311
|
} catch {
|
|
4307
4312
|
}
|
|
4308
4313
|
}
|
|
4309
|
-
if (!hasRequiredQwenHeaders(cache.headers)) {
|
|
4314
|
+
if (!hasRequiredQwenHeaders(cache.headers) && !refreshedInThisCall) {
|
|
4310
4315
|
console.log(
|
|
4311
4316
|
`\u{1F504} [Playwright] Missing required anti-bot headers for ${accountId}, triggering header interception...`
|
|
4312
4317
|
);
|
|
@@ -5749,7 +5754,24 @@ async function refreshAccountToken(accountId) {
|
|
|
5749
5754
|
return { success: false, error: "Could not refresh or re-authenticate token" };
|
|
5750
5755
|
}
|
|
5751
5756
|
async function captureQwenHeaders(accountId, pageOverride, timeoutMs = config.timeouts.headers, triggerGraceMs = HEADER_CAPTURE_TRIGGER_GRACE_MS) {
|
|
5752
|
-
|
|
5757
|
+
let page = pageOverride ?? accountPages.get(accountId);
|
|
5758
|
+
if (!page || page.isClosed()) {
|
|
5759
|
+
const { isRunningUnderNodeTest: isRunningUnderNodeTest3 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
5760
|
+
if (!isRunningUnderNodeTest3()) {
|
|
5761
|
+
const { getAccountCredentials: getAccountCredentials2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
|
|
5762
|
+
const creds = getAccountCredentials2(accountId);
|
|
5763
|
+
if (creds) {
|
|
5764
|
+
await initPlaywrightForAccount(
|
|
5765
|
+
creds,
|
|
5766
|
+
config.playwright.headless,
|
|
5767
|
+
config.playwright.browser,
|
|
5768
|
+
{ skipHeaderCapture: true }
|
|
5769
|
+
).catch(() => {
|
|
5770
|
+
});
|
|
5771
|
+
page = accountPages.get(accountId);
|
|
5772
|
+
}
|
|
5773
|
+
}
|
|
5774
|
+
}
|
|
5753
5775
|
if (!page || page.isClosed()) {
|
|
5754
5776
|
throw new Error(`Playwright page unavailable for header capture: ${accountId}`);
|
|
5755
5777
|
}
|
|
@@ -6392,16 +6414,38 @@ async function refreshHeaders(accountId, timeoutMs = config.timeouts.headers, fo
|
|
|
6392
6414
|
release();
|
|
6393
6415
|
}
|
|
6394
6416
|
}
|
|
6395
|
-
async function
|
|
6417
|
+
async function ensureAccountPageAvailable(accountId) {
|
|
6396
6418
|
const inFlightInit = inFlightAccountInits.get(accountId);
|
|
6397
6419
|
if (inFlightInit) {
|
|
6398
6420
|
await inFlightInit.catch(() => {
|
|
6399
6421
|
});
|
|
6400
6422
|
}
|
|
6401
|
-
|
|
6423
|
+
let page = accountPages.get(accountId);
|
|
6424
|
+
if (page && !page.isClosed()) {
|
|
6425
|
+
return page;
|
|
6426
|
+
}
|
|
6427
|
+
const { isRunningUnderNodeTest: isRunningUnderNodeTest3 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
6428
|
+
if (isRunningUnderNodeTest3()) {
|
|
6429
|
+
throw new Error(`Playwright page unavailable for account: ${accountId}`);
|
|
6430
|
+
}
|
|
6431
|
+
const { getAccountCredentials: getAccountCredentials2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
|
|
6432
|
+
const creds = getAccountCredentials2(accountId);
|
|
6433
|
+
if (!creds) {
|
|
6434
|
+
throw new Error(`Playwright page unavailable for account: ${accountId}`);
|
|
6435
|
+
}
|
|
6436
|
+
await initPlaywrightForAccount(
|
|
6437
|
+
creds,
|
|
6438
|
+
config.playwright.headless,
|
|
6439
|
+
config.playwright.browser
|
|
6440
|
+
);
|
|
6441
|
+
page = accountPages.get(accountId);
|
|
6402
6442
|
if (!page || page.isClosed()) {
|
|
6403
6443
|
throw new Error(`Playwright page unavailable for account: ${accountId}`);
|
|
6404
6444
|
}
|
|
6445
|
+
return page;
|
|
6446
|
+
}
|
|
6447
|
+
async function withAccountPage(accountId, fn, timeoutMs = ACCOUNT_PAGE_OPERATION_TIMEOUT_MS, mutexTimeoutMs = PLAYWRIGHT_MUTEX_WAIT_MS, recoverOnTimeout = true) {
|
|
6448
|
+
const page = await ensureAccountPageAvailable(accountId);
|
|
6405
6449
|
const release = await acquireAccountMutex(
|
|
6406
6450
|
accountId,
|
|
6407
6451
|
`page:${accountId.substring(0, 12)}`,
|
|
@@ -6702,8 +6746,10 @@ async function evictIdlePlaywrightContextsToLimit() {
|
|
|
6702
6746
|
if (accountPages.size <= max) return 0;
|
|
6703
6747
|
const candidates = priorityOrderForEviction(
|
|
6704
6748
|
Array.from(accountPages.keys()).filter((accountId) => {
|
|
6749
|
+
if (isAccountServingStream(accountId)) return false;
|
|
6750
|
+
if (isAccountRecentlyActive(accountId, 3e4)) return false;
|
|
6705
6751
|
const mutex = accountMutexes.get(accountId);
|
|
6706
|
-
return mutex?.isIdle()
|
|
6752
|
+
return mutex?.isIdle();
|
|
6707
6753
|
})
|
|
6708
6754
|
).map((accountId) => ({
|
|
6709
6755
|
accountId,
|
package/dist/reset-cooldowns.js
CHANGED
|
@@ -14,6 +14,25 @@ var __export = (target, all) => {
|
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
// src/core/paths.ts
|
|
17
|
+
var paths_exports = {};
|
|
18
|
+
__export(paths_exports, {
|
|
19
|
+
ensureDataDirs: () => ensureDataDirs,
|
|
20
|
+
getAccountPriorityPath: () => getAccountPriorityPath,
|
|
21
|
+
getAccountProfilePath: () => getAccountProfilePath,
|
|
22
|
+
getDataDir: () => getDataDir,
|
|
23
|
+
getDbDir: () => getDbDir,
|
|
24
|
+
getDbPath: () => getDbPath,
|
|
25
|
+
getEncryptionKeyPath: () => getEncryptionKeyPath,
|
|
26
|
+
getEnvFilePath: () => getEnvFilePath,
|
|
27
|
+
getLogsDir: () => getLogsDir,
|
|
28
|
+
getOsGlobalDataDir: () => getOsGlobalDataDir,
|
|
29
|
+
getProfilesDir: () => getProfilesDir,
|
|
30
|
+
getServerLogFilePath: () => getServerLogFilePath,
|
|
31
|
+
getSyncStatePath: () => getSyncStatePath,
|
|
32
|
+
getTuiSettingsPath: () => getTuiSettingsPath,
|
|
33
|
+
isRunningUnderNodeTest: () => isRunningUnderNodeTest,
|
|
34
|
+
resolveDataDir: () => resolveDataDir
|
|
35
|
+
});
|
|
17
36
|
import fs from "node:fs";
|
|
18
37
|
import path from "node:path";
|
|
19
38
|
import os from "node:os";
|
|
@@ -78,6 +97,12 @@ function getAccountProfilePath(accountId, customDataDir) {
|
|
|
78
97
|
function getAccountPriorityPath(customDataDir) {
|
|
79
98
|
return path.join(customDataDir || getDataDir(), "account-priority.json");
|
|
80
99
|
}
|
|
100
|
+
function getSyncStatePath(customDataDir) {
|
|
101
|
+
return path.join(customDataDir || getDataDir(), "sync-state.json");
|
|
102
|
+
}
|
|
103
|
+
function getTuiSettingsPath(customDataDir) {
|
|
104
|
+
return path.join(customDataDir || getDataDir(), "tui-settings.json");
|
|
105
|
+
}
|
|
81
106
|
function getEnvFilePath(customDataDir) {
|
|
82
107
|
if (isRunningUnderNodeTest()) {
|
|
83
108
|
return path.join(customDataDir || getDataDir(), ".env");
|
|
@@ -88,6 +113,26 @@ function getEnvFilePath(customDataDir) {
|
|
|
88
113
|
}
|
|
89
114
|
return path.join(customDataDir || getDataDir(), ".env");
|
|
90
115
|
}
|
|
116
|
+
function getLogsDir(customDataDir) {
|
|
117
|
+
return path.join(customDataDir || getDataDir(), "logs");
|
|
118
|
+
}
|
|
119
|
+
function getServerLogFilePath(customDataDir) {
|
|
120
|
+
return path.join(getLogsDir(customDataDir), "server.log");
|
|
121
|
+
}
|
|
122
|
+
function ensureDataDirs(targetDataDir) {
|
|
123
|
+
const root = targetDataDir || getDataDir();
|
|
124
|
+
const dbDir = getDbDir(root);
|
|
125
|
+
const profilesDir = getProfilesDir(root);
|
|
126
|
+
const logsDir = getLogsDir(root);
|
|
127
|
+
try {
|
|
128
|
+
if (!fs.existsSync(root)) fs.mkdirSync(root, { recursive: true });
|
|
129
|
+
if (!fs.existsSync(dbDir)) fs.mkdirSync(dbDir, { recursive: true });
|
|
130
|
+
if (!fs.existsSync(profilesDir)) fs.mkdirSync(profilesDir, { recursive: true });
|
|
131
|
+
if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir, { recursive: true });
|
|
132
|
+
} catch (err) {
|
|
133
|
+
console.error(`[Paths] Error ensuring data directories at ${root}:`, err?.message || String(err));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
91
136
|
var init_paths = __esm({
|
|
92
137
|
"src/core/paths.ts"() {
|
|
93
138
|
"use strict";
|
|
@@ -3059,9 +3104,10 @@ function getAccountMutex(accountId) {
|
|
|
3059
3104
|
async function recoverStuckAccountMutex(accountId, mutex, key) {
|
|
3060
3105
|
if (accountMutexes.get(accountId) !== mutex) return;
|
|
3061
3106
|
const lockState = mutex.state();
|
|
3062
|
-
|
|
3107
|
+
const minLockTimeout = Math.max(PLAYWRIGHT_MUTEX_WAIT_MS, config.timeouts.headers);
|
|
3108
|
+
if (lockState.heldForMs < minLockTimeout || lockState.heldBy.startsWith("init:") || lockState.heldBy.startsWith("headers:") || lockState.heldBy.startsWith("refresh:") || lockState.heldBy.startsWith("profile-reset:") || isAccountServingStream(accountId)) {
|
|
3063
3109
|
logger.warn(
|
|
3064
|
-
`[Playwright] Skipping destructive mutex recovery | account=${accountId} | heldBy=${lockState.heldBy} | heldFor=${lockState.heldForMs}ms | limit=${
|
|
3110
|
+
`[Playwright] Skipping destructive mutex recovery | account=${accountId} | heldBy=${lockState.heldBy} | heldFor=${lockState.heldForMs}ms | limit=${minLockTimeout}ms | servingStream=${isAccountServingStream(accountId)}`
|
|
3065
3111
|
);
|
|
3066
3112
|
return;
|
|
3067
3113
|
}
|
|
@@ -4733,7 +4779,24 @@ async function refreshAccountToken(accountId) {
|
|
|
4733
4779
|
return { success: false, error: "Could not refresh or re-authenticate token" };
|
|
4734
4780
|
}
|
|
4735
4781
|
async function captureQwenHeaders(accountId, pageOverride, timeoutMs = config.timeouts.headers, triggerGraceMs = HEADER_CAPTURE_TRIGGER_GRACE_MS) {
|
|
4736
|
-
|
|
4782
|
+
let page = pageOverride ?? accountPages.get(accountId);
|
|
4783
|
+
if (!page || page.isClosed()) {
|
|
4784
|
+
const { isRunningUnderNodeTest: isRunningUnderNodeTest3 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
4785
|
+
if (!isRunningUnderNodeTest3()) {
|
|
4786
|
+
const { getAccountCredentials: getAccountCredentials2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
|
|
4787
|
+
const creds = getAccountCredentials2(accountId);
|
|
4788
|
+
if (creds) {
|
|
4789
|
+
await initPlaywrightForAccount(
|
|
4790
|
+
creds,
|
|
4791
|
+
config.playwright.headless,
|
|
4792
|
+
config.playwright.browser,
|
|
4793
|
+
{ skipHeaderCapture: true }
|
|
4794
|
+
).catch(() => {
|
|
4795
|
+
});
|
|
4796
|
+
page = accountPages.get(accountId);
|
|
4797
|
+
}
|
|
4798
|
+
}
|
|
4799
|
+
}
|
|
4737
4800
|
if (!page || page.isClosed()) {
|
|
4738
4801
|
throw new Error(`Playwright page unavailable for header capture: ${accountId}`);
|
|
4739
4802
|
}
|
|
@@ -5434,8 +5497,10 @@ async function evictIdlePlaywrightContextsToLimit() {
|
|
|
5434
5497
|
if (accountPages.size <= max) return 0;
|
|
5435
5498
|
const candidates = priorityOrderForEviction(
|
|
5436
5499
|
Array.from(accountPages.keys()).filter((accountId) => {
|
|
5500
|
+
if (isAccountServingStream(accountId)) return false;
|
|
5501
|
+
if (isAccountRecentlyActive(accountId, 3e4)) return false;
|
|
5437
5502
|
const mutex = accountMutexes.get(accountId);
|
|
5438
|
-
return mutex?.isIdle()
|
|
5503
|
+
return mutex?.isIdle();
|
|
5439
5504
|
})
|
|
5440
5505
|
).map((accountId) => ({
|
|
5441
5506
|
accountId,
|
|
@@ -5548,6 +5613,10 @@ async function closePlaywrightForAccount(accountId) {
|
|
|
5548
5613
|
release();
|
|
5549
5614
|
}
|
|
5550
5615
|
}
|
|
5616
|
+
function isAccountRecentlyActive(accountId, maxIdleMs = 3e5) {
|
|
5617
|
+
const last = lastAccountActivity.get(accountId) ?? 0;
|
|
5618
|
+
return last > 0 && Date.now() - last < maxIdleMs;
|
|
5619
|
+
}
|
|
5551
5620
|
var requireLocal, contextInitHooks, ACCOUNT_MUTEX_MAX_HOLD_MS, accountMutexes, accountContexts, accountPages, inFlightAccountInits, headerCaches, HEADER_CACHE_TTL, COOKIE_CACHE_TTL, cookieCaches, lastAccountActivity, lastKeepAliveNavigation, lastProactiveTokenRefresh, profileResetQueue, profileResetChain, closingAllPlaywright, sleep2, HEADER_CAPTURE_SETTLE_MS, PLAYWRIGHT_MUTEX_WAIT_MS, ACCOUNT_PAGE_OPERATION_TIMEOUT_MS, SESSION_PROBE_NAVIGATION_TIMEOUT_MS, HEADER_CAPTURE_TRIGGER_GRACE_MS, FIRST_TRIGGER_GRACE_MS, HEADER_CAPTURE_TRIGGER_ATTEMPTS, CHAT_INPUT_APPEAR_TIMEOUT_MS, CHAT_INPUT_ACTION_TIMEOUT_MS, PROFILE_RESET_TIMEOUT_MS;
|
|
5552
5621
|
var init_playwright = __esm({
|
|
5553
5622
|
"src/services/playwright.ts"() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qwenproxy-cli",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.4",
|
|
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/core/version.ts
CHANGED
|
@@ -390,7 +390,9 @@ export function isNetworkLikeError(err: unknown): boolean {
|
|
|
390
390
|
message.includes("socket hang up") ||
|
|
391
391
|
message.includes("econnreset") ||
|
|
392
392
|
message.includes("econnrefused") ||
|
|
393
|
-
message.includes("etimedout")
|
|
393
|
+
message.includes("etimedout") ||
|
|
394
|
+
message.includes("stream ended before response headers") ||
|
|
395
|
+
message.includes("timed out waiting for response headers")
|
|
394
396
|
);
|
|
395
397
|
}
|
|
396
398
|
|
|
@@ -499,6 +501,21 @@ export function classifyRetryAction(
|
|
|
499
501
|
return makeRetryAction("terminal_local", { retryable: false });
|
|
500
502
|
}
|
|
501
503
|
|
|
504
|
+
// Agent instructions ride ONLY the account-level personalization. An
|
|
505
|
+
// unconfirmed sync means this account cannot serve the request as-is —
|
|
506
|
+
// rotate to another account (each attempt re-syncs on its own account).
|
|
507
|
+
// Do NOT park the account with a 300s cooldown: rotation across accounts
|
|
508
|
+
// is already guarded by triedAccounts, preventing pool collapse.
|
|
509
|
+
if (err instanceof PersonalizationSyncError) {
|
|
510
|
+
return makeRetryAction("personalization_sync_failed", {
|
|
511
|
+
switchAccount: true,
|
|
512
|
+
forceNewChat: true,
|
|
513
|
+
retryAfterMs: baseDelayMs,
|
|
514
|
+
accountCooldownMs: 0,
|
|
515
|
+
accountCooldownReason: "PersonalizationFailed",
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
502
519
|
const message = errMessage(err).toLowerCase();
|
|
503
520
|
const code = errCode(err).toLowerCase();
|
|
504
521
|
if (isAccountInitializationError(err)) {
|
|
@@ -521,21 +538,6 @@ export function classifyRetryAction(
|
|
|
521
538
|
});
|
|
522
539
|
}
|
|
523
540
|
|
|
524
|
-
// Agent instructions ride ONLY the account-level personalization. An
|
|
525
|
-
// unconfirmed sync means this account cannot serve the request as-is —
|
|
526
|
-
// rotate to another account (each attempt re-syncs on its own account).
|
|
527
|
-
// Do NOT park the account with a 300s cooldown: rotation across accounts
|
|
528
|
-
// is already guarded by triedAccounts, preventing pool collapse.
|
|
529
|
-
if (err instanceof PersonalizationSyncError) {
|
|
530
|
-
return makeRetryAction("personalization_sync_failed", {
|
|
531
|
-
switchAccount: true,
|
|
532
|
-
forceNewChat: true,
|
|
533
|
-
retryAfterMs: baseDelayMs,
|
|
534
|
-
accountCooldownMs: 0,
|
|
535
|
-
accountCooldownReason: "PersonalizationFailed",
|
|
536
|
-
});
|
|
537
|
-
}
|
|
538
|
-
|
|
539
541
|
// Upstream 401 / Unauthorized on chat creation or API requests:
|
|
540
542
|
// Access token expired (15m window). Rotate without locking into a 300s cooldown
|
|
541
543
|
// so the account can immediately be renewed via refresh_token.
|
|
@@ -166,7 +166,7 @@ export function buildChromiumLaunchArgs(viewport: {
|
|
|
166
166
|
const ACCOUNT_MUTEX_MAX_HOLD_MS = 180_000;
|
|
167
167
|
const accountMutexes = new Map<string, Mutex>();
|
|
168
168
|
|
|
169
|
-
function getAccountMutex(accountId: string): Mutex {
|
|
169
|
+
export function getAccountMutex(accountId: string): Mutex {
|
|
170
170
|
let mutex = accountMutexes.get(accountId);
|
|
171
171
|
if (!mutex) {
|
|
172
172
|
mutex = new Mutex(`playwright:${accountId.substring(0, 8)}`, ACCOUNT_MUTEX_MAX_HOLD_MS);
|
|
@@ -189,15 +189,20 @@ async function recoverStuckAccountMutex(
|
|
|
189
189
|
const lockState = mutex.state();
|
|
190
190
|
// A lock is only considered stuck if it has been held for at least PLAYWRIGHT_MUTEX_WAIT_MS (60s).
|
|
191
191
|
// A waiter with a short timeout (e.g. 5s) timing out does NOT mean the lock holder is stuck!
|
|
192
|
-
// Furthermore, never nuke an account while it is initializing (init: takes 20-30s)
|
|
193
|
-
//
|
|
192
|
+
// Furthermore, never nuke an account while it is initializing (init: takes 20-30s),
|
|
193
|
+
// capturing/refreshing headers (headers/refresh can take up to HEADERS_TIMEOUT),
|
|
194
|
+
// running page operations, or while it is actively serving a stream to a user.
|
|
195
|
+
const minLockTimeout = Math.max(PLAYWRIGHT_MUTEX_WAIT_MS, config.timeouts.headers);
|
|
194
196
|
if (
|
|
195
|
-
lockState.heldForMs <
|
|
197
|
+
lockState.heldForMs < minLockTimeout ||
|
|
196
198
|
lockState.heldBy.startsWith("init:") ||
|
|
199
|
+
lockState.heldBy.startsWith("headers:") ||
|
|
200
|
+
lockState.heldBy.startsWith("refresh:") ||
|
|
201
|
+
lockState.heldBy.startsWith("profile-reset:") ||
|
|
197
202
|
isAccountServingStream(accountId)
|
|
198
203
|
) {
|
|
199
204
|
logger.warn(
|
|
200
|
-
`[Playwright] Skipping destructive mutex recovery | account=${accountId} | heldBy=${lockState.heldBy} | heldFor=${lockState.heldForMs}ms | limit=${
|
|
205
|
+
`[Playwright] Skipping destructive mutex recovery | account=${accountId} | heldBy=${lockState.heldBy} | heldFor=${lockState.heldForMs}ms | limit=${minLockTimeout}ms | servingStream=${isAccountServingStream(accountId)}`,
|
|
201
206
|
);
|
|
202
207
|
return;
|
|
203
208
|
}
|
|
@@ -1246,6 +1251,7 @@ export async function getBasicHeaders(accountId: string): Promise<{
|
|
|
1246
1251
|
const release = await acquireAccountMutex(
|
|
1247
1252
|
accountId,
|
|
1248
1253
|
`headers:${accountId.substring(0, 12)}`,
|
|
1254
|
+
Math.max(PLAYWRIGHT_MUTEX_WAIT_MS, config.timeouts.headers + 30_000),
|
|
1249
1255
|
);
|
|
1250
1256
|
try {
|
|
1251
1257
|
touchAccountActivity(accountId);
|
|
@@ -1369,8 +1375,10 @@ export async function getBasicHeaders(accountId: string): Promise<{
|
|
|
1369
1375
|
|
|
1370
1376
|
// Refresh headers if stale. A valid cached set remains usable when a
|
|
1371
1377
|
// browser recapture transiently fails; a cold/partial cache must fail.
|
|
1378
|
+
let refreshedInThisCall = false;
|
|
1372
1379
|
if (headersAge > HEADER_CACHE_TTL && !cache.refreshInProgress) {
|
|
1373
1380
|
try {
|
|
1381
|
+
refreshedInThisCall = true;
|
|
1374
1382
|
await refreshHeadersInternal(accountId);
|
|
1375
1383
|
} catch (error) {
|
|
1376
1384
|
if (!hadUsableHeaders) throw error;
|
|
@@ -1443,7 +1451,7 @@ export async function getBasicHeaders(accountId: string): Promise<{
|
|
|
1443
1451
|
} catch {}
|
|
1444
1452
|
}
|
|
1445
1453
|
|
|
1446
|
-
if (!hasRequiredQwenHeaders(cache.headers)) {
|
|
1454
|
+
if (!hasRequiredQwenHeaders(cache.headers) && !refreshedInThisCall) {
|
|
1447
1455
|
console.log(
|
|
1448
1456
|
`🔄 [Playwright] Missing required anti-bot headers for ${accountId}, triggering header interception...`,
|
|
1449
1457
|
);
|
|
@@ -3242,7 +3250,24 @@ export async function captureQwenHeaders(
|
|
|
3242
3250
|
timeoutMs = config.timeouts.headers,
|
|
3243
3251
|
triggerGraceMs = HEADER_CAPTURE_TRIGGER_GRACE_MS,
|
|
3244
3252
|
): Promise<void> {
|
|
3245
|
-
|
|
3253
|
+
let page = pageOverride ?? accountPages.get(accountId);
|
|
3254
|
+
if (!page || page.isClosed()) {
|
|
3255
|
+
const { isRunningUnderNodeTest } = await import("../core/paths.ts");
|
|
3256
|
+
if (!isRunningUnderNodeTest()) {
|
|
3257
|
+
const { getAccountCredentials } = await import("../core/accounts.ts");
|
|
3258
|
+
const creds = getAccountCredentials(accountId);
|
|
3259
|
+
if (creds) {
|
|
3260
|
+
await initPlaywrightForAccount(
|
|
3261
|
+
creds,
|
|
3262
|
+
config.playwright.headless,
|
|
3263
|
+
config.playwright.browser,
|
|
3264
|
+
{ skipHeaderCapture: true },
|
|
3265
|
+
).catch(() => {});
|
|
3266
|
+
page = accountPages.get(accountId);
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
|
|
3246
3271
|
if (!page || page.isClosed()) {
|
|
3247
3272
|
throw new Error(`Playwright page unavailable for header capture: ${accountId}`);
|
|
3248
3273
|
}
|
|
@@ -4100,22 +4125,51 @@ export async function refreshHeaders(
|
|
|
4100
4125
|
* Run work against the account Playwright page under the per-account mutex.
|
|
4101
4126
|
* Used by captcha recovery so it cannot race header capture / login.
|
|
4102
4127
|
*/
|
|
4103
|
-
|
|
4104
|
-
accountId: string,
|
|
4105
|
-
fn: (page: Page) => Promise<T>,
|
|
4106
|
-
timeoutMs = ACCOUNT_PAGE_OPERATION_TIMEOUT_MS,
|
|
4107
|
-
mutexTimeoutMs = PLAYWRIGHT_MUTEX_WAIT_MS,
|
|
4108
|
-
recoverOnTimeout = true,
|
|
4109
|
-
): Promise<T> {
|
|
4128
|
+
async function ensureAccountPageAvailable(accountId: string): Promise<Page> {
|
|
4110
4129
|
const inFlightInit = inFlightAccountInits.get(accountId);
|
|
4111
4130
|
if (inFlightInit) {
|
|
4112
4131
|
await inFlightInit.catch(() => {});
|
|
4113
4132
|
}
|
|
4114
4133
|
|
|
4115
|
-
|
|
4134
|
+
let page = accountPages.get(accountId);
|
|
4135
|
+
if (page && !page.isClosed()) {
|
|
4136
|
+
return page;
|
|
4137
|
+
}
|
|
4138
|
+
|
|
4139
|
+
// Under Node.js test suites, preserve strict mock failure contract
|
|
4140
|
+
const { isRunningUnderNodeTest } = await import("../core/paths.ts");
|
|
4141
|
+
if (isRunningUnderNodeTest()) {
|
|
4142
|
+
throw new Error(`Playwright page unavailable for account: ${accountId}`);
|
|
4143
|
+
}
|
|
4144
|
+
|
|
4145
|
+
// Lazy boot / recovery: if the account was evicted or in standby, initialize it transparently on demand
|
|
4146
|
+
const { getAccountCredentials } = await import("../core/accounts.ts");
|
|
4147
|
+
const creds = getAccountCredentials(accountId);
|
|
4148
|
+
if (!creds) {
|
|
4149
|
+
throw new Error(`Playwright page unavailable for account: ${accountId}`);
|
|
4150
|
+
}
|
|
4151
|
+
|
|
4152
|
+
await initPlaywrightForAccount(
|
|
4153
|
+
creds,
|
|
4154
|
+
config.playwright.headless,
|
|
4155
|
+
config.playwright.browser,
|
|
4156
|
+
);
|
|
4157
|
+
|
|
4158
|
+
page = accountPages.get(accountId);
|
|
4116
4159
|
if (!page || page.isClosed()) {
|
|
4117
4160
|
throw new Error(`Playwright page unavailable for account: ${accountId}`);
|
|
4118
4161
|
}
|
|
4162
|
+
return page;
|
|
4163
|
+
}
|
|
4164
|
+
|
|
4165
|
+
export async function withAccountPage<T>(
|
|
4166
|
+
accountId: string,
|
|
4167
|
+
fn: (page: Page) => Promise<T>,
|
|
4168
|
+
timeoutMs = ACCOUNT_PAGE_OPERATION_TIMEOUT_MS,
|
|
4169
|
+
mutexTimeoutMs = PLAYWRIGHT_MUTEX_WAIT_MS,
|
|
4170
|
+
recoverOnTimeout = true,
|
|
4171
|
+
): Promise<T> {
|
|
4172
|
+
const page = await ensureAccountPageAvailable(accountId);
|
|
4119
4173
|
const release = await acquireAccountMutex(
|
|
4120
4174
|
accountId,
|
|
4121
4175
|
`page:${accountId.substring(0, 12)}`,
|
|
@@ -4542,8 +4596,10 @@ export async function evictIdlePlaywrightContextsToLimit(): Promise<number> {
|
|
|
4542
4596
|
|
|
4543
4597
|
const candidates = priorityOrderForEviction(
|
|
4544
4598
|
Array.from(accountPages.keys()).filter((accountId) => {
|
|
4599
|
+
if (isAccountServingStream(accountId)) return false;
|
|
4600
|
+
if (isAccountRecentlyActive(accountId, 30_000)) return false;
|
|
4545
4601
|
const mutex = accountMutexes.get(accountId);
|
|
4546
|
-
return mutex?.isIdle()
|
|
4602
|
+
return mutex?.isIdle();
|
|
4547
4603
|
}),
|
|
4548
4604
|
).map((accountId) => ({
|
|
4549
4605
|
accountId,
|