qwenproxy-cli 1.6.9 → 1.6.11

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.
@@ -2185,6 +2185,26 @@ function clearAccountCooldown(accountId) {
2185
2185
  }
2186
2186
  }
2187
2187
  }
2188
+ function getAccountCooldownInfo(accountId) {
2189
+ const entry = cooldowns.get(accountId);
2190
+ if (!entry) return null;
2191
+ const remaining = entry.until - Date.now();
2192
+ if (remaining <= 0) {
2193
+ cooldowns.delete(accountId);
2194
+ if (accountId !== "global") {
2195
+ try {
2196
+ updateAccountCooldown(accountId, 0, null);
2197
+ } catch (err) {
2198
+ console.error(
2199
+ `\u274C [AccountManager] Failed to clear expired cooldown in DB:`,
2200
+ err.message
2201
+ );
2202
+ }
2203
+ }
2204
+ return null;
2205
+ }
2206
+ return { onCooldown: true, remainingMs: remaining, reason: entry.reason };
2207
+ }
2188
2208
  function markAccountHeadersReady(accountId) {
2189
2209
  if (!accountId || accountId === "global") return;
2190
2210
  headersReadyAccounts.add(accountId);
@@ -3519,7 +3539,19 @@ async function isPageLoggedIn(page, timeoutMs = SESSION_PROBE_NAVIGATION_TIMEOUT
3519
3539
  if (localStorage.getItem("qwen_token_logged_out_marker")) {
3520
3540
  return false;
3521
3541
  }
3522
- const res = await fetch("/api/v1/auths/", { method: "GET" });
3542
+ const lsToken = (localStorage.getItem("token") || "").trim();
3543
+ if (lsToken && !/(?:^|;\s*)token=/.test(document.cookie || "")) {
3544
+ try {
3545
+ document.cookie = `token=${encodeURIComponent(lsToken)}; path=/; domain=.qwen.ai; max-age=31536000`;
3546
+ } catch {
3547
+ }
3548
+ }
3549
+ const authHeaders = lsToken ? { Authorization: `Bearer ${lsToken}` } : {};
3550
+ const res = await fetch("/api/v1/auths/", {
3551
+ method: "GET",
3552
+ headers: authHeaders,
3553
+ credentials: "include"
3554
+ });
3523
3555
  if (res.status !== 200) return false;
3524
3556
  const json = await res.json().catch(() => null);
3525
3557
  if (!json) return false;
@@ -3537,6 +3569,7 @@ async function isPageLoggedIn(page, timeoutMs = SESSION_PROBE_NAVIGATION_TIMEOUT
3537
3569
  try {
3538
3570
  const settingsRes = await fetch("/api/v2/users/user/settings", {
3539
3571
  method: "GET",
3572
+ headers: authHeaders,
3540
3573
  credentials: "include",
3541
3574
  signal: AbortSignal.timeout(3e3)
3542
3575
  });
@@ -4324,14 +4357,15 @@ async function getBasicHeaders(accountId) {
4324
4357
  `\u26A0\uFE0F [Playwright] Header refresh failed for ${accountId}; retaining the previous valid cache: ${getErrorMessage(error)}`
4325
4358
  );
4326
4359
  }
4327
- } else if (hadUsableHeaders && headersAge > HEADER_CACHE_TTL * HEADER_REFRESH_THRESHOLD && !cache.refreshInProgress) {
4328
- cache.refreshInProgress = true;
4360
+ } else if (hadUsableHeaders && headersAge > HEADER_CACHE_TTL * HEADER_REFRESH_THRESHOLD && !cache.refreshInProgress && // Header capture types into the chat input and sends a probe message on
4361
+ // the account's page. Doing that while the account is serving a stream
4362
+ // would mutate the live conversation (and can navigate the SPA), so the
4363
+ // proactive refresh waits until the account has no active lease.
4364
+ !hasActiveAccountLease(accountId)) {
4329
4365
  refreshHeadersInternal(accountId).catch((error) => {
4330
4366
  console.warn(
4331
4367
  `\u26A0\uFE0F [Playwright] Background header refresh failed for ${accountId}: ${getErrorMessage(error)}`
4332
4368
  );
4333
- }).finally(() => {
4334
- cache.refreshInProgress = false;
4335
4369
  });
4336
4370
  }
4337
4371
  if (!hasRequiredQwenHeaders(cache.headers)) {
@@ -4469,8 +4503,12 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
4469
4503
  if (accountPages.has(account.id)) {
4470
4504
  return;
4471
4505
  }
4472
- await evictIdlePlaywrightContextsToLimit().catch(() => {
4473
- });
4506
+ if (config.playwright.maxActiveContexts > 0) {
4507
+ await evictIdlePlaywrightContextsToLimit(
4508
+ Math.max(1, config.playwright.maxActiveContexts - 1)
4509
+ ).catch(() => {
4510
+ });
4511
+ }
4474
4512
  const profilePath = getAccountProfilePath(account.id);
4475
4513
  const fingerprint = getFingerprintProfile(account.id);
4476
4514
  const { engine, channel } = resolveBrowserEngine(browserType);
@@ -4695,8 +4733,15 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
4695
4733
  const hasAuthCookie = cookies.some(
4696
4734
  (c) => c.name.toLowerCase().includes("token") || c.name.toLowerCase().includes("session")
4697
4735
  );
4736
+ let initialLoginAttempted = false;
4698
4737
  if (!hasAuthCookie && account.email && account.password) {
4699
- await loginToQwen(account.id, account.email, account.password);
4738
+ initialLoginAttempted = true;
4739
+ const loggedIn = await loginToQwen(account.id, account.email, account.password);
4740
+ if (!loggedIn) {
4741
+ throw new Error(
4742
+ `Authentication failed for account ${maskEmail(account.email)}`
4743
+ );
4744
+ }
4700
4745
  }
4701
4746
  try {
4702
4747
  await acctPage.goto(qwenUrl("/"), {
@@ -4706,13 +4751,21 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
4706
4751
  await sleep2(1500);
4707
4752
  const currentUrl = acctPage.url();
4708
4753
  const isAuthUrl = currentUrl.includes("/auth") || currentUrl.includes("/login");
4709
- if (isAuthUrl && account.email && account.password) {
4754
+ if (isAuthUrl && !initialLoginAttempted && account.email && account.password) {
4710
4755
  console.log(
4711
4756
  `[Playwright] Initial login needed for ${maskEmail(account.email)}, authenticating...`
4712
4757
  );
4713
- await loginToQwen(account.id, account.email, account.password);
4758
+ const loggedIn = await loginToQwen(account.id, account.email, account.password);
4759
+ if (!loggedIn) {
4760
+ throw new Error(
4761
+ `Authentication failed for account ${maskEmail(account.email)}`
4762
+ );
4763
+ }
4714
4764
  }
4715
4765
  } catch (err) {
4766
+ if (err?.message?.includes("Authentication failed for account")) {
4767
+ throw err;
4768
+ }
4716
4769
  console.warn(
4717
4770
  `\u26A0\uFE0F [Playwright] Initial navigation check for ${maskEmail(account.email)}: ${err.message}`
4718
4771
  );
@@ -4724,7 +4777,7 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
4724
4777
  if (!acctPage.isClosed()) {
4725
4778
  try {
4726
4779
  const currentUrl = new URL(acctPage.url());
4727
- if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname !== "/") {
4780
+ if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname.includes("/auth") || currentUrl.pathname.includes("/login") || currentUrl.pathname.includes("_____tmd_____")) {
4728
4781
  await acctPage.goto(qwenUrl("/"), {
4729
4782
  waitUntil: "domcontentloaded",
4730
4783
  timeout: config.timeouts.navigation
@@ -4742,6 +4795,8 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
4742
4795
  } finally {
4743
4796
  release();
4744
4797
  }
4798
+ await evictIdlePlaywrightContextsToLimit().catch(() => {
4799
+ });
4745
4800
  })();
4746
4801
  try {
4747
4802
  await initPromise;
@@ -5261,10 +5316,30 @@ async function loginToQwen(accountId, email, password) {
5261
5316
  const uiResult = await loginViaUi(page, email, password);
5262
5317
  if (uiResult.success) {
5263
5318
  cookieCaches.delete(accountId);
5264
- await saveStorageState(page.context(), accountId);
5265
5319
  try {
5266
- const liveCookies = await page.context().cookies();
5267
- const tokenCookie = liveCookies.find((c) => c.name === "token");
5320
+ let liveCookies = await page.context().cookies();
5321
+ let tokenCookie = liveCookies.find((c) => c.name === "token");
5322
+ if (!tokenCookie) {
5323
+ const lsTok = uiResult.token || await page.evaluate(() => (localStorage.getItem("token") || "").trim()).catch(() => "");
5324
+ if (lsTok) {
5325
+ await page.context().addCookies([
5326
+ {
5327
+ name: "token",
5328
+ value: lsTok,
5329
+ domain: ".qwen.ai",
5330
+ path: "/",
5331
+ expires: Math.floor(Date.now() / 1e3) + 31536e3,
5332
+ httpOnly: false,
5333
+ secure: true,
5334
+ sameSite: "Lax"
5335
+ }
5336
+ ]).catch(() => {
5337
+ });
5338
+ liveCookies = await page.context().cookies();
5339
+ tokenCookie = liveCookies.find((c) => c.name === "token");
5340
+ }
5341
+ }
5342
+ await saveStorageState(page.context(), accountId);
5268
5343
  if (tokenCookie) {
5269
5344
  const cookieStr = liveCookies.map((c) => `${c.name}=${c.value}`).join("; ");
5270
5345
  const cache = getHeaderCache(accountId);
@@ -5273,7 +5348,7 @@ async function loginToQwen(accountId, email, password) {
5273
5348
  markAccountHeadersReady(accountId);
5274
5349
  const { saveAuthSession: saveAuthSession2 } = await Promise.resolve().then(() => (init_database(), database_exports));
5275
5350
  const exp = parseJwtExpiry(tokenCookie.value);
5276
- let rTok = uiResult.refreshToken;
5351
+ let rTok = uiResult.refreshToken || liveCookies.find((c) => c.name === "refresh_token")?.value;
5277
5352
  if (!rTok) {
5278
5353
  try {
5279
5354
  rTok = await page.evaluate(() => localStorage.getItem("refresh_token") || "") || void 0;
@@ -5354,8 +5429,9 @@ async function loginViaApi(page, email, password) {
5354
5429
  }
5355
5430
  const signinUrl = "https://auth.qwen.ai/api/v2/auths/signin";
5356
5431
  const requestId = crypto3.randomUUID();
5432
+ const webVersion = getQwenWebVersion() || "0.3.26";
5357
5433
  const evalRes = await page.evaluate(
5358
- async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2, tz }) => {
5434
+ async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2, tz, version }) => {
5359
5435
  try {
5360
5436
  const response = await fetch(signinUrl2, {
5361
5437
  method: "POST",
@@ -5365,7 +5441,7 @@ async function loginViaApi(page, email, password) {
5365
5441
  accept: "application/json, text/plain, */*",
5366
5442
  "content-type": "application/json",
5367
5443
  source: "web",
5368
- version: getQwenWebVersion() || "0.3.26",
5444
+ version,
5369
5445
  "x-request-origin": "https://chat.qwen.ai",
5370
5446
  timezone: tz,
5371
5447
  "x-request-id": requestId2
@@ -5378,7 +5454,14 @@ async function loginViaApi(page, email, password) {
5378
5454
  return { ok: false, error: e.message };
5379
5455
  }
5380
5456
  },
5381
- { email, password: hashedPassword, signinUrl, requestId, tz: formatAsciiTimezone() }
5457
+ {
5458
+ email,
5459
+ password: hashedPassword,
5460
+ signinUrl,
5461
+ requestId,
5462
+ tz: formatAsciiTimezone(),
5463
+ version: webVersion
5464
+ }
5382
5465
  ).catch(() => null);
5383
5466
  let data = evalRes?.data;
5384
5467
  let signinSuccess = Boolean(
@@ -5387,7 +5470,7 @@ async function loginViaApi(page, email, password) {
5387
5470
  if (!signinSuccess && !data?.data?.code) {
5388
5471
  const fallbackUrl = qwenUrl("/api/v2/auths/signin");
5389
5472
  const fallbackRes = await page.evaluate(
5390
- async ({ email: email2, password: password2, fallbackUrl: fallbackUrl2, requestId: requestId2, tz }) => {
5473
+ async ({ email: email2, password: password2, fallbackUrl: fallbackUrl2, requestId: requestId2, tz, version }) => {
5391
5474
  try {
5392
5475
  const response = await fetch(fallbackUrl2, {
5393
5476
  method: "POST",
@@ -5397,7 +5480,7 @@ async function loginViaApi(page, email, password) {
5397
5480
  accept: "application/json, text/plain, */*",
5398
5481
  "content-type": "application/json",
5399
5482
  source: "web",
5400
- version: getQwenWebVersion() || "0.3.26",
5483
+ version,
5401
5484
  timezone: tz,
5402
5485
  "x-request-id": requestId2
5403
5486
  },
@@ -5409,7 +5492,14 @@ async function loginViaApi(page, email, password) {
5409
5492
  return { ok: false, error: e.message };
5410
5493
  }
5411
5494
  },
5412
- { email, password: hashedPassword, fallbackUrl, requestId, tz: formatAsciiTimezone() }
5495
+ {
5496
+ email,
5497
+ password: hashedPassword,
5498
+ fallbackUrl,
5499
+ requestId,
5500
+ tz: formatAsciiTimezone(),
5501
+ version: webVersion
5502
+ }
5413
5503
  ).catch(() => null);
5414
5504
  if (fallbackRes?.data) {
5415
5505
  data = fallbackRes.data;
@@ -5634,28 +5724,34 @@ async function loginViaUi(page, email, password) {
5634
5724
  await sleep2(500);
5635
5725
  const submitSelector = 'button.qwenchat-auth-pc-submit-button, button[type="submit"], button:has-text("Log in"), button:has-text("Sign in"), button:has-text("Fazer login"), button:has-text("Entrar"), button:has-text("Iniciar sesi\xF3n"), button:has-text("\u767B\u5F55")';
5636
5726
  const submitButton = page.locator(submitSelector).first();
5637
- await page.evaluate((sel) => {
5638
- try {
5639
- const btn = document.querySelector(sel);
5640
- if (btn) {
5641
- btn.removeAttribute("disabled");
5642
- btn.classList.remove("ant-btn-disabled");
5643
- btn.click();
5644
- }
5645
- } catch {
5646
- }
5647
- }, submitSelector).catch(() => {
5648
- });
5727
+ let submitted = false;
5649
5728
  try {
5650
5729
  if (await submitButton.isVisible({ timeout: 2e3 }).catch(() => false)) {
5651
- await submitButton.click({ force: true, timeout: 2e3 }).catch(() => {
5730
+ await submitButton.click({ timeout: 2e3 }).catch(() => {
5652
5731
  });
5732
+ submitted = true;
5653
5733
  }
5654
5734
  } catch {
5655
5735
  }
5656
- const passInput = page.locator(passwordSelector).first();
5657
- await passInput.press("Enter", { timeout: 2e3 }).catch(() => {
5658
- });
5736
+ if (!submitted) {
5737
+ await page.evaluate(() => {
5738
+ try {
5739
+ const btn = document.querySelector(
5740
+ 'button.qwenchat-auth-pc-submit-button, button[type="submit"]'
5741
+ );
5742
+ if (btn) {
5743
+ btn.removeAttribute("disabled");
5744
+ btn.classList.remove("ant-btn-disabled");
5745
+ btn.click();
5746
+ }
5747
+ } catch {
5748
+ }
5749
+ }).catch(() => {
5750
+ });
5751
+ const passInput = page.locator(passwordSelector).first();
5752
+ await passInput.press("Enter", { timeout: 2e3 }).catch(() => {
5753
+ });
5754
+ }
5659
5755
  const postSubmitDeadline = Date.now() + 15e3;
5660
5756
  while (Date.now() < postSubmitDeadline) {
5661
5757
  if (page.isClosed()) break;
@@ -5673,9 +5769,12 @@ async function loginViaUi(page, email, password) {
5673
5769
  ".ant-form-item-explain-error",
5674
5770
  ".ant-message-error",
5675
5771
  ".ant-message-notice",
5772
+ ".ant-message-custom-content",
5773
+ ".ant-notification-notice",
5676
5774
  '[role="alert"]',
5677
5775
  ".qwenchat-auth-error",
5678
- ".auth-error-message"
5776
+ ".auth-error-message",
5777
+ ".qwenchat-toast"
5679
5778
  ].join(", ");
5680
5779
  const errorEl = page.locator(errorSelector).first();
5681
5780
  if (await errorEl.isVisible().catch(() => false)) {
@@ -5689,7 +5788,15 @@ async function loginViaUi(page, email, password) {
5689
5788
  };
5690
5789
  }
5691
5790
  }
5692
- const codeSelector = 'input[placeholder*="code" i], input[placeholder*="c\xF3digo" i], input[name*="code" i]';
5791
+ const codeSelector = [
5792
+ 'input[placeholder*="code" i]',
5793
+ 'input[placeholder*="c\xF3digo" i]',
5794
+ 'input[name*="code" i]',
5795
+ ".qwenchat-verification-code-input",
5796
+ ".qwenchat-verification-code-input-cell",
5797
+ '[aria-label*="Verification code" i]',
5798
+ '[aria-label*="c\xF3digo de verifica\xE7\xE3o" i]'
5799
+ ].join(", ");
5693
5800
  const codeInput = page.locator(codeSelector).first();
5694
5801
  if (await codeInput.isVisible().catch(() => false)) {
5695
5802
  return {
@@ -5699,17 +5806,45 @@ async function loginViaUi(page, email, password) {
5699
5806
  }
5700
5807
  try {
5701
5808
  const liveCookies = await page.context().cookies();
5702
- if (liveCookies.some((c) => c.name === "token")) {
5703
- return { success: true };
5809
+ const rTokCookie = liveCookies.find((c) => c.name === "refresh_token")?.value;
5810
+ const tokCookie = liveCookies.find((c) => c.name === "token")?.value;
5811
+ if (tokCookie) {
5812
+ return { success: true, token: tokCookie, refreshToken: rTokCookie };
5813
+ }
5814
+ const currentUrl = page.url();
5815
+ if (!currentUrl.includes("/auth") && !currentUrl.includes("/login")) {
5816
+ const lsTok = await page.evaluate(() => {
5817
+ const t = (localStorage.getItem("token") || "").trim();
5818
+ if (t) {
5819
+ try {
5820
+ document.cookie = `token=${encodeURIComponent(t)}; path=/; domain=.qwen.ai; max-age=31536000`;
5821
+ } catch {
5822
+ }
5823
+ }
5824
+ return t;
5825
+ }).catch(() => "");
5826
+ if (lsTok) {
5827
+ await page.context().addCookies([
5828
+ {
5829
+ name: "token",
5830
+ value: lsTok,
5831
+ domain: ".qwen.ai",
5832
+ path: "/",
5833
+ expires: Math.floor(Date.now() / 1e3) + 31536e3,
5834
+ httpOnly: false,
5835
+ secure: true,
5836
+ sameSite: "Lax"
5837
+ }
5838
+ ]).catch(() => {
5839
+ });
5840
+ return { success: true, token: lsTok, refreshToken: rTokCookie };
5841
+ }
5842
+ if (await isPageLoggedIn(page, 3e3)) {
5843
+ return { success: true, refreshToken: rTokCookie };
5844
+ }
5704
5845
  }
5705
5846
  } catch {
5706
5847
  }
5707
- const currentUrl = page.url();
5708
- if (!currentUrl.includes("/auth") && !currentUrl.includes("/login")) {
5709
- if (await isPageLoggedIn(page, 3e3)) {
5710
- return { success: true };
5711
- }
5712
- }
5713
5848
  await sleep2(1e3);
5714
5849
  }
5715
5850
  if (await isPageLoggedIn(page, 3e3)) {
@@ -5738,10 +5873,18 @@ async function refreshAccountToken(accountId) {
5738
5873
  } catch {
5739
5874
  }
5740
5875
  }
5876
+ if (!refreshToken) {
5877
+ try {
5878
+ const liveCookies = await page.context().cookies();
5879
+ refreshToken = liveCookies.find((c) => c.name === "refresh_token")?.value;
5880
+ } catch {
5881
+ }
5882
+ }
5741
5883
  if (refreshToken) {
5742
5884
  try {
5885
+ const webVersion = getQwenWebVersion() || "0.3.26";
5743
5886
  const refreshResult = await page.evaluate(
5744
- async ({ rTok, tz }) => {
5887
+ async ({ rTok, tz, version }) => {
5745
5888
  try {
5746
5889
  const response = await fetch("https://auth.qwen.ai/api/v2/auths/refresh", {
5747
5890
  method: "GET",
@@ -5749,7 +5892,7 @@ async function refreshAccountToken(accountId) {
5749
5892
  headers: {
5750
5893
  accept: "application/json, text/plain, */*",
5751
5894
  source: "web",
5752
- version: getQwenWebVersion() || "0.3.26",
5895
+ version,
5753
5896
  "x-request-origin": "https://chat.qwen.ai",
5754
5897
  timezone: tz,
5755
5898
  authorization: `Bearer ${rTok}`
@@ -5761,7 +5904,7 @@ async function refreshAccountToken(accountId) {
5761
5904
  return { ok: false, error: e.message };
5762
5905
  }
5763
5906
  },
5764
- { rTok: refreshToken, tz: formatAsciiTimezone() }
5907
+ { rTok: refreshToken, tz: formatAsciiTimezone(), version: webVersion }
5765
5908
  );
5766
5909
  const json = refreshResult?.data;
5767
5910
  if (json && json.success === true && json.data) {
@@ -6422,19 +6565,26 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
6422
6565
  await executeReauth();
6423
6566
  } else {
6424
6567
  try {
6425
- await page.goto(qwenUrl("/"), {
6426
- waitUntil: "domcontentloaded",
6427
- timeout: Math.min(
6428
- config.timeouts.navigation,
6429
- boundedTimeoutMs,
6430
- SESSION_PROBE_NAVIGATION_TIMEOUT_MS
6431
- )
6432
- });
6433
- await sleep2(2e3);
6434
- const url = page.url();
6568
+ const currentUrl = typeof page.url === "function" ? page.url() : "";
6569
+ const needsNavigation = !currentUrl.startsWith(qwenOrigin()) || currentUrl.includes("/auth") || currentUrl.includes("/login") || currentUrl.includes("_____tmd_____");
6570
+ if (needsNavigation) {
6571
+ await page.goto(qwenUrl("/"), {
6572
+ waitUntil: "domcontentloaded",
6573
+ timeout: Math.min(
6574
+ config.timeouts.navigation,
6575
+ boundedTimeoutMs,
6576
+ SESSION_PROBE_NAVIGATION_TIMEOUT_MS
6577
+ )
6578
+ });
6579
+ await sleep2(2e3);
6580
+ }
6581
+ const url = typeof page.url === "function" ? page.url() : "";
6435
6582
  const isAuthUrl = url.includes("auth") || url.includes("login");
6436
6583
  if (isAuthUrl) {
6437
- await executeReauth();
6584
+ const refreshed = await refreshAccountToken(accountId);
6585
+ if (!refreshed.success) {
6586
+ await executeReauth();
6587
+ }
6438
6588
  }
6439
6589
  } catch (navErr) {
6440
6590
  console.warn(
@@ -6491,7 +6641,7 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
6491
6641
  if (capturedPage && !capturedPage.isClosed()) {
6492
6642
  try {
6493
6643
  const currentUrl = new URL(capturedPage.url());
6494
- if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname !== "/") {
6644
+ if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname.includes("/auth") || currentUrl.pathname.includes("/login") || currentUrl.pathname.includes("_____tmd_____")) {
6495
6645
  await capturedPage.goto(qwenUrl("/"), {
6496
6646
  waitUntil: "domcontentloaded",
6497
6647
  timeout: Math.min(config.timeouts.navigation, boundedTimeoutMs)
@@ -6807,7 +6957,11 @@ function priorityOrderForEviction(accountIds) {
6807
6957
  const ranked = getAccountsByPriority(accountIds.map((id) => ({ id })));
6808
6958
  const rank = /* @__PURE__ */ new Map();
6809
6959
  for (const [index, account] of ranked.entries()) {
6810
- rank.set(account.id, index);
6960
+ if (getAccountCooldownInfo(account.id)) {
6961
+ rank.set(account.id, Number.MAX_SAFE_INTEGER);
6962
+ } else {
6963
+ rank.set(account.id, index);
6964
+ }
6811
6965
  }
6812
6966
  return orderContextsForEviction(
6813
6967
  accountIds,
@@ -6844,14 +6998,15 @@ async function closeIdlePlaywrightAccounts(idleMs) {
6844
6998
  }
6845
6999
  return closed;
6846
7000
  }
6847
- async function evictIdlePlaywrightContextsToLimit() {
6848
- const max = config.playwright.maxActiveContexts;
6849
- if (max <= 0) return 0;
7001
+ async function evictIdlePlaywrightContextsToLimit(targetLimit) {
7002
+ const configuredMax = config.playwright.maxActiveContexts;
7003
+ if (configuredMax <= 0) return 0;
7004
+ const max = targetLimit !== void 0 ? Math.max(0, targetLimit) : configuredMax;
6850
7005
  if (accountPages.size <= max) return 0;
6851
7006
  const candidates = priorityOrderForEviction(
6852
7007
  Array.from(accountPages.keys()).filter((accountId) => {
6853
7008
  if (isAccountServingStream(accountId)) return false;
6854
- if (isAccountRecentlyActive(accountId, 3e4)) return false;
7009
+ if (hasActiveAccountLease(accountId)) return false;
6855
7010
  const mutex = accountMutexes.get(accountId);
6856
7011
  return mutex?.isIdle();
6857
7012
  })
@@ -6864,6 +7019,7 @@ async function evictIdlePlaywrightContextsToLimit() {
6864
7019
  for (const candidate of candidates) {
6865
7020
  if (accountPages.size <= max) break;
6866
7021
  if (isAccountServingStream(candidate.accountId)) continue;
7022
+ if (hasActiveAccountLease(candidate.accountId)) continue;
6867
7023
  const mutex = accountMutexes.get(candidate.accountId);
6868
7024
  if (!mutex?.isIdle()) continue;
6869
7025
  await closePlaywrightForAccount(candidate.accountId).catch((error) => {
@@ -7702,6 +7858,7 @@ var init_model_registry = __esm({
7702
7858
  var captcha_coordinator_exports = {};
7703
7859
  __export(captcha_coordinator_exports, {
7704
7860
  isCaptchaRecoveryActive: () => isCaptchaRecoveryActive,
7861
+ isCaptchaRecoveryInBackoff: () => isCaptchaRecoveryInBackoff,
7705
7862
  recoverBaxiaCaptcha: () => recoverBaxiaCaptcha,
7706
7863
  solveChallengeOnPage: () => solveChallengeOnPage
7707
7864
  });
@@ -7740,6 +7897,11 @@ async function solveChallengeOnPage(page, challengeUrl, waitForMs = config.captc
7740
7897
  }
7741
7898
  }
7742
7899
  }
7900
+ function isCaptchaRecoveryInBackoff(accountId) {
7901
+ if (!accountId) return false;
7902
+ const lastFailure = lastFailedRecoveryAt.get(accountId) ?? 0;
7903
+ return Date.now() - lastFailure < FAILED_RECOVERY_BACKOFF_MS;
7904
+ }
7743
7905
  async function recoverBaxiaCaptcha(accountId, label, options = {}) {
7744
7906
  if (!config.captcha.enabled || !accountId) return false;
7745
7907
  const solver = "baxia";
@@ -7757,17 +7919,34 @@ async function recoverBaxiaCaptcha(accountId, label, options = {}) {
7757
7919
  const challengeUrl = options.challengeBody ? extractBaxiaChallengeUrl(options.challengeBody, config.qwen.baseUrl) : null;
7758
7920
  activeCaptchaRecoveries.add(accountId);
7759
7921
  try {
7760
- const { abortActiveBrowserStreamsForAccount: abortActiveBrowserStreamsForAccount2 } = await Promise.resolve().then(() => (init_qwen(), qwen_exports));
7761
- abortActiveBrowserStreamsForAccount2(
7762
- accountId,
7763
- "Account stream interrupted by captcha challenge recovery"
7764
- );
7765
- } catch {
7766
- }
7767
- try {
7922
+ const { hasActiveBrowserStreamsForAccount: hasActiveBrowserStreamsForAccount2 } = await Promise.resolve().then(() => (init_qwen(), qwen_exports));
7923
+ const hasConcurrentStream = hasActiveBrowserStreamsForAccount2(accountId);
7768
7924
  const solved = await withAccountPage(
7769
7925
  accountId,
7770
- (page) => solveChallengeOnPage(page, challengeUrl),
7926
+ async (page) => {
7927
+ if (hasConcurrentStream) {
7928
+ const solverOptions = {
7929
+ maxAttempts: config.captcha.maxAttempts,
7930
+ retryDelayMs: config.captcha.retryDelayMs,
7931
+ settleMs: config.captcha.settleMs
7932
+ };
7933
+ if (await solveBaxiaCaptcha(page, { ...solverOptions, waitForMs: 0 })) {
7934
+ return true;
7935
+ }
7936
+ const ctx = typeof page.context === "function" ? page.context() : null;
7937
+ if (ctx && typeof ctx.newPage === "function") {
7938
+ const tempPage = await ctx.newPage();
7939
+ try {
7940
+ return await solveChallengeOnPage(tempPage, challengeUrl);
7941
+ } finally {
7942
+ await tempPage.close({ runBeforeUnload: false }).catch(() => {
7943
+ });
7944
+ }
7945
+ }
7946
+ return false;
7947
+ }
7948
+ return await solveChallengeOnPage(page, challengeUrl);
7949
+ },
7771
7950
  solverOperationTimeoutMs,
7772
7951
  solverOperationTimeoutMs,
7773
7952
  false
@@ -8566,6 +8745,7 @@ __export(qwen_exports, {
8566
8745
  getLogicalThreadState: () => getLogicalThreadState,
8567
8746
  getQwenErrorCode: () => getQwenErrorCode,
8568
8747
  getSessionParent: () => getSessionParent,
8748
+ hasActiveBrowserStreamsForAccount: () => hasActiveBrowserStreamsForAccount,
8569
8749
  invalidateLogicalThreadParent: () => invalidateLogicalThreadParent,
8570
8750
  isChatSessionActive: () => isChatSessionActive,
8571
8751
  isRetryableFetchErrorMessage: () => isRetryableFetchErrorMessage,
@@ -8587,6 +8767,14 @@ __export(qwen_exports, {
8587
8767
  });
8588
8768
  import crypto5 from "crypto";
8589
8769
  import { v4 as uuidv43 } from "uuid";
8770
+ function hasActiveBrowserStreamsForAccount(accountId) {
8771
+ for (const state of browserStreamStates.values()) {
8772
+ if (state.accountId === accountId && !state.done) {
8773
+ return true;
8774
+ }
8775
+ }
8776
+ return false;
8777
+ }
8590
8778
  function abortActiveBrowserStreamsForAccount(accountId, reason = "Account browser stream interrupted (page context unloaded)") {
8591
8779
  let count = 0;
8592
8780
  for (const [requestId, state] of browserStreamStates) {
@@ -9364,7 +9552,7 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
9364
9552
  const onFrameNavigated = (frame) => {
9365
9553
  if (!settled && typeof page.mainFrame === "function" && frame === page.mainFrame()) {
9366
9554
  const url2 = typeof frame.url === "function" ? frame.url() : String(frame.url || "");
9367
- if (url2.startsWith(qwenOrigin()) && !url2.includes("/auth") && !url2.includes("_____tmd_____") && !url2.includes("/punish")) {
9555
+ if (url2.startsWith(qwenOrigin()) && !url2.includes("/auth") && !url2.includes("/login") && !url2.includes("_____tmd_____") && !url2.includes("/punish")) {
9368
9556
  return;
9369
9557
  }
9370
9558
  handleBrowserStreamEvent(requestId, {
@@ -9385,10 +9573,12 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
9385
9573
  };
9386
9574
  page.on("framenavigated", onFrameNavigated);
9387
9575
  page.on("close", onPageClosed);
9576
+ page.on("crash", onPageClosed);
9388
9577
  pageCleanup = () => {
9389
9578
  try {
9390
9579
  page.off?.("framenavigated", onFrameNavigated);
9391
9580
  page.off?.("close", onPageClosed);
9581
+ page.off?.("crash", onPageClosed);
9392
9582
  } catch {
9393
9583
  }
9394
9584
  };
@@ -11176,6 +11366,7 @@ async function deleteChatsForConfiguredAccounts(keepBrowserOpen = false) {
11176
11366
  const account = accounts[i];
11177
11367
  const currentIdx = i + 1;
11178
11368
  const totalCount = accounts.length;
11369
+ const wasInitialized = isPlaywrightInitialized(account.id);
11179
11370
  try {
11180
11371
  const ok = await deleteChatsForAccount(account, currentIdx, totalCount);
11181
11372
  if (ok) succeeded++;
@@ -11185,7 +11376,7 @@ async function deleteChatsForConfiguredAccounts(keepBrowserOpen = false) {
11185
11376
  error instanceof Error ? error.message : String(error)
11186
11377
  );
11187
11378
  } finally {
11188
- if (!keepBrowserOpen) {
11379
+ if ((!keepBrowserOpen || !wasInitialized) && !isAuthMockEnabled()) {
11189
11380
  const { closePlaywrightForAccount: closePlaywrightForAccount2 } = await Promise.resolve().then(() => (init_playwright(), playwright_exports));
11190
11381
  await closePlaywrightForAccount2(account.id).catch(() => {
11191
11382
  });