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.
package/dist/index.js CHANGED
@@ -4629,7 +4629,19 @@ async function isPageLoggedIn(page, timeoutMs = SESSION_PROBE_NAVIGATION_TIMEOUT
4629
4629
  if (localStorage.getItem("qwen_token_logged_out_marker")) {
4630
4630
  return false;
4631
4631
  }
4632
- const res = await fetch("/api/v1/auths/", { method: "GET" });
4632
+ const lsToken = (localStorage.getItem("token") || "").trim();
4633
+ if (lsToken && !/(?:^|;\s*)token=/.test(document.cookie || "")) {
4634
+ try {
4635
+ document.cookie = `token=${encodeURIComponent(lsToken)}; path=/; domain=.qwen.ai; max-age=31536000`;
4636
+ } catch {
4637
+ }
4638
+ }
4639
+ const authHeaders = lsToken ? { Authorization: `Bearer ${lsToken}` } : {};
4640
+ const res = await fetch("/api/v1/auths/", {
4641
+ method: "GET",
4642
+ headers: authHeaders,
4643
+ credentials: "include"
4644
+ });
4633
4645
  if (res.status !== 200) return false;
4634
4646
  const json = await res.json().catch(() => null);
4635
4647
  if (!json) return false;
@@ -4647,6 +4659,7 @@ async function isPageLoggedIn(page, timeoutMs = SESSION_PROBE_NAVIGATION_TIMEOUT
4647
4659
  try {
4648
4660
  const settingsRes = await fetch("/api/v2/users/user/settings", {
4649
4661
  method: "GET",
4662
+ headers: authHeaders,
4650
4663
  credentials: "include",
4651
4664
  signal: AbortSignal.timeout(3e3)
4652
4665
  });
@@ -5434,14 +5447,15 @@ async function getBasicHeaders(accountId) {
5434
5447
  `\u26A0\uFE0F [Playwright] Header refresh failed for ${accountId}; retaining the previous valid cache: ${getErrorMessage(error)}`
5435
5448
  );
5436
5449
  }
5437
- } else if (hadUsableHeaders && headersAge > HEADER_CACHE_TTL * HEADER_REFRESH_THRESHOLD && !cache3.refreshInProgress) {
5438
- cache3.refreshInProgress = true;
5450
+ } else if (hadUsableHeaders && headersAge > HEADER_CACHE_TTL * HEADER_REFRESH_THRESHOLD && !cache3.refreshInProgress && // Header capture types into the chat input and sends a probe message on
5451
+ // the account's page. Doing that while the account is serving a stream
5452
+ // would mutate the live conversation (and can navigate the SPA), so the
5453
+ // proactive refresh waits until the account has no active lease.
5454
+ !hasActiveAccountLease(accountId)) {
5439
5455
  refreshHeadersInternal(accountId).catch((error) => {
5440
5456
  console.warn(
5441
5457
  `\u26A0\uFE0F [Playwright] Background header refresh failed for ${accountId}: ${getErrorMessage(error)}`
5442
5458
  );
5443
- }).finally(() => {
5444
- cache3.refreshInProgress = false;
5445
5459
  });
5446
5460
  }
5447
5461
  if (!hasRequiredQwenHeaders(cache3.headers)) {
@@ -5579,8 +5593,12 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
5579
5593
  if (accountPages.has(account.id)) {
5580
5594
  return;
5581
5595
  }
5582
- await evictIdlePlaywrightContextsToLimit().catch(() => {
5583
- });
5596
+ if (config.playwright.maxActiveContexts > 0) {
5597
+ await evictIdlePlaywrightContextsToLimit(
5598
+ Math.max(1, config.playwright.maxActiveContexts - 1)
5599
+ ).catch(() => {
5600
+ });
5601
+ }
5584
5602
  const profilePath = getAccountProfilePath(account.id);
5585
5603
  const fingerprint = getFingerprintProfile(account.id);
5586
5604
  const { engine, channel } = resolveBrowserEngine(browserType);
@@ -5805,8 +5823,15 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
5805
5823
  const hasAuthCookie = cookies.some(
5806
5824
  (c) => c.name.toLowerCase().includes("token") || c.name.toLowerCase().includes("session")
5807
5825
  );
5826
+ let initialLoginAttempted = false;
5808
5827
  if (!hasAuthCookie && account.email && account.password) {
5809
- await loginToQwen(account.id, account.email, account.password);
5828
+ initialLoginAttempted = true;
5829
+ const loggedIn = await loginToQwen(account.id, account.email, account.password);
5830
+ if (!loggedIn) {
5831
+ throw new Error(
5832
+ `Authentication failed for account ${maskEmail(account.email)}`
5833
+ );
5834
+ }
5810
5835
  }
5811
5836
  try {
5812
5837
  await acctPage.goto(qwenUrl("/"), {
@@ -5816,13 +5841,21 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
5816
5841
  await sleep2(1500);
5817
5842
  const currentUrl = acctPage.url();
5818
5843
  const isAuthUrl = currentUrl.includes("/auth") || currentUrl.includes("/login");
5819
- if (isAuthUrl && account.email && account.password) {
5844
+ if (isAuthUrl && !initialLoginAttempted && account.email && account.password) {
5820
5845
  console.log(
5821
5846
  `[Playwright] Initial login needed for ${maskEmail(account.email)}, authenticating...`
5822
5847
  );
5823
- await loginToQwen(account.id, account.email, account.password);
5848
+ const loggedIn = await loginToQwen(account.id, account.email, account.password);
5849
+ if (!loggedIn) {
5850
+ throw new Error(
5851
+ `Authentication failed for account ${maskEmail(account.email)}`
5852
+ );
5853
+ }
5824
5854
  }
5825
5855
  } catch (err) {
5856
+ if (err?.message?.includes("Authentication failed for account")) {
5857
+ throw err;
5858
+ }
5826
5859
  console.warn(
5827
5860
  `\u26A0\uFE0F [Playwright] Initial navigation check for ${maskEmail(account.email)}: ${err.message}`
5828
5861
  );
@@ -5834,7 +5867,7 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
5834
5867
  if (!acctPage.isClosed()) {
5835
5868
  try {
5836
5869
  const currentUrl = new URL(acctPage.url());
5837
- if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname !== "/") {
5870
+ if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname.includes("/auth") || currentUrl.pathname.includes("/login") || currentUrl.pathname.includes("_____tmd_____")) {
5838
5871
  await acctPage.goto(qwenUrl("/"), {
5839
5872
  waitUntil: "domcontentloaded",
5840
5873
  timeout: config.timeouts.navigation
@@ -5852,6 +5885,8 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
5852
5885
  } finally {
5853
5886
  release();
5854
5887
  }
5888
+ await evictIdlePlaywrightContextsToLimit().catch(() => {
5889
+ });
5855
5890
  })();
5856
5891
  try {
5857
5892
  await initPromise;
@@ -6371,10 +6406,30 @@ async function loginToQwen(accountId, email, password) {
6371
6406
  const uiResult = await loginViaUi(page, email, password);
6372
6407
  if (uiResult.success) {
6373
6408
  cookieCaches.delete(accountId);
6374
- await saveStorageState(page.context(), accountId);
6375
6409
  try {
6376
- const liveCookies = await page.context().cookies();
6377
- const tokenCookie = liveCookies.find((c) => c.name === "token");
6410
+ let liveCookies = await page.context().cookies();
6411
+ let tokenCookie = liveCookies.find((c) => c.name === "token");
6412
+ if (!tokenCookie) {
6413
+ const lsTok = uiResult.token || await page.evaluate(() => (localStorage.getItem("token") || "").trim()).catch(() => "");
6414
+ if (lsTok) {
6415
+ await page.context().addCookies([
6416
+ {
6417
+ name: "token",
6418
+ value: lsTok,
6419
+ domain: ".qwen.ai",
6420
+ path: "/",
6421
+ expires: Math.floor(Date.now() / 1e3) + 31536e3,
6422
+ httpOnly: false,
6423
+ secure: true,
6424
+ sameSite: "Lax"
6425
+ }
6426
+ ]).catch(() => {
6427
+ });
6428
+ liveCookies = await page.context().cookies();
6429
+ tokenCookie = liveCookies.find((c) => c.name === "token");
6430
+ }
6431
+ }
6432
+ await saveStorageState(page.context(), accountId);
6378
6433
  if (tokenCookie) {
6379
6434
  const cookieStr = liveCookies.map((c) => `${c.name}=${c.value}`).join("; ");
6380
6435
  const cache3 = getHeaderCache(accountId);
@@ -6383,7 +6438,7 @@ async function loginToQwen(accountId, email, password) {
6383
6438
  markAccountHeadersReady(accountId);
6384
6439
  const { saveAuthSession: saveAuthSession2 } = await Promise.resolve().then(() => (init_database(), database_exports));
6385
6440
  const exp = parseJwtExpiry(tokenCookie.value);
6386
- let rTok = uiResult.refreshToken;
6441
+ let rTok = uiResult.refreshToken || liveCookies.find((c) => c.name === "refresh_token")?.value;
6387
6442
  if (!rTok) {
6388
6443
  try {
6389
6444
  rTok = await page.evaluate(() => localStorage.getItem("refresh_token") || "") || void 0;
@@ -6464,8 +6519,9 @@ async function loginViaApi(page, email, password) {
6464
6519
  }
6465
6520
  const signinUrl = "https://auth.qwen.ai/api/v2/auths/signin";
6466
6521
  const requestId = crypto5.randomUUID();
6522
+ const webVersion = getQwenWebVersion() || "0.3.26";
6467
6523
  const evalRes = await page.evaluate(
6468
- async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2, tz }) => {
6524
+ async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2, tz, version }) => {
6469
6525
  try {
6470
6526
  const response = await fetch(signinUrl2, {
6471
6527
  method: "POST",
@@ -6475,7 +6531,7 @@ async function loginViaApi(page, email, password) {
6475
6531
  accept: "application/json, text/plain, */*",
6476
6532
  "content-type": "application/json",
6477
6533
  source: "web",
6478
- version: getQwenWebVersion() || "0.3.26",
6534
+ version,
6479
6535
  "x-request-origin": "https://chat.qwen.ai",
6480
6536
  timezone: tz,
6481
6537
  "x-request-id": requestId2
@@ -6488,7 +6544,14 @@ async function loginViaApi(page, email, password) {
6488
6544
  return { ok: false, error: e.message };
6489
6545
  }
6490
6546
  },
6491
- { email, password: hashedPassword, signinUrl, requestId, tz: formatAsciiTimezone() }
6547
+ {
6548
+ email,
6549
+ password: hashedPassword,
6550
+ signinUrl,
6551
+ requestId,
6552
+ tz: formatAsciiTimezone(),
6553
+ version: webVersion
6554
+ }
6492
6555
  ).catch(() => null);
6493
6556
  let data = evalRes?.data;
6494
6557
  let signinSuccess = Boolean(
@@ -6497,7 +6560,7 @@ async function loginViaApi(page, email, password) {
6497
6560
  if (!signinSuccess && !data?.data?.code) {
6498
6561
  const fallbackUrl = qwenUrl("/api/v2/auths/signin");
6499
6562
  const fallbackRes = await page.evaluate(
6500
- async ({ email: email2, password: password2, fallbackUrl: fallbackUrl2, requestId: requestId2, tz }) => {
6563
+ async ({ email: email2, password: password2, fallbackUrl: fallbackUrl2, requestId: requestId2, tz, version }) => {
6501
6564
  try {
6502
6565
  const response = await fetch(fallbackUrl2, {
6503
6566
  method: "POST",
@@ -6507,7 +6570,7 @@ async function loginViaApi(page, email, password) {
6507
6570
  accept: "application/json, text/plain, */*",
6508
6571
  "content-type": "application/json",
6509
6572
  source: "web",
6510
- version: getQwenWebVersion() || "0.3.26",
6573
+ version,
6511
6574
  timezone: tz,
6512
6575
  "x-request-id": requestId2
6513
6576
  },
@@ -6519,7 +6582,14 @@ async function loginViaApi(page, email, password) {
6519
6582
  return { ok: false, error: e.message };
6520
6583
  }
6521
6584
  },
6522
- { email, password: hashedPassword, fallbackUrl, requestId, tz: formatAsciiTimezone() }
6585
+ {
6586
+ email,
6587
+ password: hashedPassword,
6588
+ fallbackUrl,
6589
+ requestId,
6590
+ tz: formatAsciiTimezone(),
6591
+ version: webVersion
6592
+ }
6523
6593
  ).catch(() => null);
6524
6594
  if (fallbackRes?.data) {
6525
6595
  data = fallbackRes.data;
@@ -6744,28 +6814,34 @@ async function loginViaUi(page, email, password) {
6744
6814
  await sleep2(500);
6745
6815
  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")';
6746
6816
  const submitButton = page.locator(submitSelector).first();
6747
- await page.evaluate((sel) => {
6748
- try {
6749
- const btn = document.querySelector(sel);
6750
- if (btn) {
6751
- btn.removeAttribute("disabled");
6752
- btn.classList.remove("ant-btn-disabled");
6753
- btn.click();
6754
- }
6755
- } catch {
6756
- }
6757
- }, submitSelector).catch(() => {
6758
- });
6817
+ let submitted = false;
6759
6818
  try {
6760
6819
  if (await submitButton.isVisible({ timeout: 2e3 }).catch(() => false)) {
6761
- await submitButton.click({ force: true, timeout: 2e3 }).catch(() => {
6820
+ await submitButton.click({ timeout: 2e3 }).catch(() => {
6762
6821
  });
6822
+ submitted = true;
6763
6823
  }
6764
6824
  } catch {
6765
6825
  }
6766
- const passInput = page.locator(passwordSelector).first();
6767
- await passInput.press("Enter", { timeout: 2e3 }).catch(() => {
6768
- });
6826
+ if (!submitted) {
6827
+ await page.evaluate(() => {
6828
+ try {
6829
+ const btn = document.querySelector(
6830
+ 'button.qwenchat-auth-pc-submit-button, button[type="submit"]'
6831
+ );
6832
+ if (btn) {
6833
+ btn.removeAttribute("disabled");
6834
+ btn.classList.remove("ant-btn-disabled");
6835
+ btn.click();
6836
+ }
6837
+ } catch {
6838
+ }
6839
+ }).catch(() => {
6840
+ });
6841
+ const passInput = page.locator(passwordSelector).first();
6842
+ await passInput.press("Enter", { timeout: 2e3 }).catch(() => {
6843
+ });
6844
+ }
6769
6845
  const postSubmitDeadline = Date.now() + 15e3;
6770
6846
  while (Date.now() < postSubmitDeadline) {
6771
6847
  if (page.isClosed()) break;
@@ -6783,9 +6859,12 @@ async function loginViaUi(page, email, password) {
6783
6859
  ".ant-form-item-explain-error",
6784
6860
  ".ant-message-error",
6785
6861
  ".ant-message-notice",
6862
+ ".ant-message-custom-content",
6863
+ ".ant-notification-notice",
6786
6864
  '[role="alert"]',
6787
6865
  ".qwenchat-auth-error",
6788
- ".auth-error-message"
6866
+ ".auth-error-message",
6867
+ ".qwenchat-toast"
6789
6868
  ].join(", ");
6790
6869
  const errorEl = page.locator(errorSelector).first();
6791
6870
  if (await errorEl.isVisible().catch(() => false)) {
@@ -6799,7 +6878,15 @@ async function loginViaUi(page, email, password) {
6799
6878
  };
6800
6879
  }
6801
6880
  }
6802
- const codeSelector = 'input[placeholder*="code" i], input[placeholder*="c\xF3digo" i], input[name*="code" i]';
6881
+ const codeSelector = [
6882
+ 'input[placeholder*="code" i]',
6883
+ 'input[placeholder*="c\xF3digo" i]',
6884
+ 'input[name*="code" i]',
6885
+ ".qwenchat-verification-code-input",
6886
+ ".qwenchat-verification-code-input-cell",
6887
+ '[aria-label*="Verification code" i]',
6888
+ '[aria-label*="c\xF3digo de verifica\xE7\xE3o" i]'
6889
+ ].join(", ");
6803
6890
  const codeInput = page.locator(codeSelector).first();
6804
6891
  if (await codeInput.isVisible().catch(() => false)) {
6805
6892
  return {
@@ -6809,17 +6896,45 @@ async function loginViaUi(page, email, password) {
6809
6896
  }
6810
6897
  try {
6811
6898
  const liveCookies = await page.context().cookies();
6812
- if (liveCookies.some((c) => c.name === "token")) {
6813
- return { success: true };
6899
+ const rTokCookie = liveCookies.find((c) => c.name === "refresh_token")?.value;
6900
+ const tokCookie = liveCookies.find((c) => c.name === "token")?.value;
6901
+ if (tokCookie) {
6902
+ return { success: true, token: tokCookie, refreshToken: rTokCookie };
6903
+ }
6904
+ const currentUrl = page.url();
6905
+ if (!currentUrl.includes("/auth") && !currentUrl.includes("/login")) {
6906
+ const lsTok = await page.evaluate(() => {
6907
+ const t = (localStorage.getItem("token") || "").trim();
6908
+ if (t) {
6909
+ try {
6910
+ document.cookie = `token=${encodeURIComponent(t)}; path=/; domain=.qwen.ai; max-age=31536000`;
6911
+ } catch {
6912
+ }
6913
+ }
6914
+ return t;
6915
+ }).catch(() => "");
6916
+ if (lsTok) {
6917
+ await page.context().addCookies([
6918
+ {
6919
+ name: "token",
6920
+ value: lsTok,
6921
+ domain: ".qwen.ai",
6922
+ path: "/",
6923
+ expires: Math.floor(Date.now() / 1e3) + 31536e3,
6924
+ httpOnly: false,
6925
+ secure: true,
6926
+ sameSite: "Lax"
6927
+ }
6928
+ ]).catch(() => {
6929
+ });
6930
+ return { success: true, token: lsTok, refreshToken: rTokCookie };
6931
+ }
6932
+ if (await isPageLoggedIn(page, 3e3)) {
6933
+ return { success: true, refreshToken: rTokCookie };
6934
+ }
6814
6935
  }
6815
6936
  } catch {
6816
6937
  }
6817
- const currentUrl = page.url();
6818
- if (!currentUrl.includes("/auth") && !currentUrl.includes("/login")) {
6819
- if (await isPageLoggedIn(page, 3e3)) {
6820
- return { success: true };
6821
- }
6822
- }
6823
6938
  await sleep2(1e3);
6824
6939
  }
6825
6940
  if (await isPageLoggedIn(page, 3e3)) {
@@ -6848,10 +6963,18 @@ async function refreshAccountToken(accountId) {
6848
6963
  } catch {
6849
6964
  }
6850
6965
  }
6966
+ if (!refreshToken) {
6967
+ try {
6968
+ const liveCookies = await page.context().cookies();
6969
+ refreshToken = liveCookies.find((c) => c.name === "refresh_token")?.value;
6970
+ } catch {
6971
+ }
6972
+ }
6851
6973
  if (refreshToken) {
6852
6974
  try {
6975
+ const webVersion = getQwenWebVersion() || "0.3.26";
6853
6976
  const refreshResult = await page.evaluate(
6854
- async ({ rTok, tz }) => {
6977
+ async ({ rTok, tz, version }) => {
6855
6978
  try {
6856
6979
  const response = await fetch("https://auth.qwen.ai/api/v2/auths/refresh", {
6857
6980
  method: "GET",
@@ -6859,7 +6982,7 @@ async function refreshAccountToken(accountId) {
6859
6982
  headers: {
6860
6983
  accept: "application/json, text/plain, */*",
6861
6984
  source: "web",
6862
- version: getQwenWebVersion() || "0.3.26",
6985
+ version,
6863
6986
  "x-request-origin": "https://chat.qwen.ai",
6864
6987
  timezone: tz,
6865
6988
  authorization: `Bearer ${rTok}`
@@ -6871,7 +6994,7 @@ async function refreshAccountToken(accountId) {
6871
6994
  return { ok: false, error: e.message };
6872
6995
  }
6873
6996
  },
6874
- { rTok: refreshToken, tz: formatAsciiTimezone() }
6997
+ { rTok: refreshToken, tz: formatAsciiTimezone(), version: webVersion }
6875
6998
  );
6876
6999
  const json = refreshResult?.data;
6877
7000
  if (json && json.success === true && json.data) {
@@ -7532,19 +7655,26 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
7532
7655
  await executeReauth();
7533
7656
  } else {
7534
7657
  try {
7535
- await page.goto(qwenUrl("/"), {
7536
- waitUntil: "domcontentloaded",
7537
- timeout: Math.min(
7538
- config.timeouts.navigation,
7539
- boundedTimeoutMs,
7540
- SESSION_PROBE_NAVIGATION_TIMEOUT_MS
7541
- )
7542
- });
7543
- await sleep2(2e3);
7544
- const url = page.url();
7658
+ const currentUrl = typeof page.url === "function" ? page.url() : "";
7659
+ const needsNavigation = !currentUrl.startsWith(qwenOrigin()) || currentUrl.includes("/auth") || currentUrl.includes("/login") || currentUrl.includes("_____tmd_____");
7660
+ if (needsNavigation) {
7661
+ await page.goto(qwenUrl("/"), {
7662
+ waitUntil: "domcontentloaded",
7663
+ timeout: Math.min(
7664
+ config.timeouts.navigation,
7665
+ boundedTimeoutMs,
7666
+ SESSION_PROBE_NAVIGATION_TIMEOUT_MS
7667
+ )
7668
+ });
7669
+ await sleep2(2e3);
7670
+ }
7671
+ const url = typeof page.url === "function" ? page.url() : "";
7545
7672
  const isAuthUrl = url.includes("auth") || url.includes("login");
7546
7673
  if (isAuthUrl) {
7547
- await executeReauth();
7674
+ const refreshed = await refreshAccountToken(accountId);
7675
+ if (!refreshed.success) {
7676
+ await executeReauth();
7677
+ }
7548
7678
  }
7549
7679
  } catch (navErr) {
7550
7680
  console.warn(
@@ -7601,7 +7731,7 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
7601
7731
  if (capturedPage && !capturedPage.isClosed()) {
7602
7732
  try {
7603
7733
  const currentUrl = new URL(capturedPage.url());
7604
- if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname !== "/") {
7734
+ if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname.includes("/auth") || currentUrl.pathname.includes("/login") || currentUrl.pathname.includes("_____tmd_____")) {
7605
7735
  await capturedPage.goto(qwenUrl("/"), {
7606
7736
  waitUntil: "domcontentloaded",
7607
7737
  timeout: Math.min(config.timeouts.navigation, boundedTimeoutMs)
@@ -7917,7 +8047,11 @@ function priorityOrderForEviction(accountIds) {
7917
8047
  const ranked = getAccountsByPriority(accountIds.map((id) => ({ id })));
7918
8048
  const rank = /* @__PURE__ */ new Map();
7919
8049
  for (const [index, account] of ranked.entries()) {
7920
- rank.set(account.id, index);
8050
+ if (getAccountCooldownInfo(account.id)) {
8051
+ rank.set(account.id, Number.MAX_SAFE_INTEGER);
8052
+ } else {
8053
+ rank.set(account.id, index);
8054
+ }
7921
8055
  }
7922
8056
  return orderContextsForEviction(
7923
8057
  accountIds,
@@ -7954,14 +8088,15 @@ async function closeIdlePlaywrightAccounts(idleMs) {
7954
8088
  }
7955
8089
  return closed;
7956
8090
  }
7957
- async function evictIdlePlaywrightContextsToLimit() {
7958
- const max = config.playwright.maxActiveContexts;
7959
- if (max <= 0) return 0;
8091
+ async function evictIdlePlaywrightContextsToLimit(targetLimit) {
8092
+ const configuredMax = config.playwright.maxActiveContexts;
8093
+ if (configuredMax <= 0) return 0;
8094
+ const max = targetLimit !== void 0 ? Math.max(0, targetLimit) : configuredMax;
7960
8095
  if (accountPages.size <= max) return 0;
7961
8096
  const candidates = priorityOrderForEviction(
7962
8097
  Array.from(accountPages.keys()).filter((accountId) => {
7963
8098
  if (isAccountServingStream(accountId)) return false;
7964
- if (isAccountRecentlyActive(accountId, 3e4)) return false;
8099
+ if (hasActiveAccountLease(accountId)) return false;
7965
8100
  const mutex = accountMutexes.get(accountId);
7966
8101
  return mutex?.isIdle();
7967
8102
  })
@@ -7974,6 +8109,7 @@ async function evictIdlePlaywrightContextsToLimit() {
7974
8109
  for (const candidate of candidates) {
7975
8110
  if (accountPages.size <= max) break;
7976
8111
  if (isAccountServingStream(candidate.accountId)) continue;
8112
+ if (hasActiveAccountLease(candidate.accountId)) continue;
7977
8113
  const mutex = accountMutexes.get(candidate.accountId);
7978
8114
  if (!mutex?.isIdle()) continue;
7979
8115
  await closePlaywrightForAccount(candidate.accountId).catch((error) => {
@@ -8404,12 +8540,13 @@ var init_watchdog = __esm({
8404
8540
  await new Promise((resolve) => setTimeout(resolve, 100));
8405
8541
  this.emit("recovery:ram:freed");
8406
8542
  try {
8407
- const { closeIdlePlaywrightAccounts: closeIdlePlaywrightAccounts2 } = await Promise.resolve().then(() => (init_playwright(), playwright_exports));
8543
+ const { closeIdlePlaywrightAccounts: closeIdlePlaywrightAccounts2, evictIdlePlaywrightContextsToLimit: evictIdlePlaywrightContextsToLimit2 } = await Promise.resolve().then(() => (init_playwright(), playwright_exports));
8544
+ const evicted = await evictIdlePlaywrightContextsToLimit2(1);
8408
8545
  const closed = await closeIdlePlaywrightAccounts2(
8409
8546
  config.sessionKeeper.idleMs
8410
8547
  );
8411
- if (closed > 0) {
8412
- this.emit("recovery:ram:contexts-closed", closed);
8548
+ if (evicted + closed > 0) {
8549
+ this.emit("recovery:ram:contexts-closed", evicted + closed);
8413
8550
  }
8414
8551
  } catch {
8415
8552
  }
@@ -9092,6 +9229,7 @@ var init_model_registry = __esm({
9092
9229
  var captcha_coordinator_exports = {};
9093
9230
  __export(captcha_coordinator_exports, {
9094
9231
  isCaptchaRecoveryActive: () => isCaptchaRecoveryActive,
9232
+ isCaptchaRecoveryInBackoff: () => isCaptchaRecoveryInBackoff,
9095
9233
  recoverBaxiaCaptcha: () => recoverBaxiaCaptcha,
9096
9234
  solveChallengeOnPage: () => solveChallengeOnPage
9097
9235
  });
@@ -9130,6 +9268,11 @@ async function solveChallengeOnPage(page, challengeUrl, waitForMs = config.captc
9130
9268
  }
9131
9269
  }
9132
9270
  }
9271
+ function isCaptchaRecoveryInBackoff(accountId) {
9272
+ if (!accountId) return false;
9273
+ const lastFailure = lastFailedRecoveryAt.get(accountId) ?? 0;
9274
+ return Date.now() - lastFailure < FAILED_RECOVERY_BACKOFF_MS;
9275
+ }
9133
9276
  async function recoverBaxiaCaptcha(accountId, label, options = {}) {
9134
9277
  if (!config.captcha.enabled || !accountId) return false;
9135
9278
  const solver = "baxia";
@@ -9147,17 +9290,34 @@ async function recoverBaxiaCaptcha(accountId, label, options = {}) {
9147
9290
  const challengeUrl = options.challengeBody ? extractBaxiaChallengeUrl(options.challengeBody, config.qwen.baseUrl) : null;
9148
9291
  activeCaptchaRecoveries.add(accountId);
9149
9292
  try {
9150
- const { abortActiveBrowserStreamsForAccount: abortActiveBrowserStreamsForAccount2 } = await Promise.resolve().then(() => (init_qwen(), qwen_exports));
9151
- abortActiveBrowserStreamsForAccount2(
9152
- accountId,
9153
- "Account stream interrupted by captcha challenge recovery"
9154
- );
9155
- } catch {
9156
- }
9157
- try {
9293
+ const { hasActiveBrowserStreamsForAccount: hasActiveBrowserStreamsForAccount2 } = await Promise.resolve().then(() => (init_qwen(), qwen_exports));
9294
+ const hasConcurrentStream = hasActiveBrowserStreamsForAccount2(accountId);
9158
9295
  const solved = await withAccountPage(
9159
9296
  accountId,
9160
- (page) => solveChallengeOnPage(page, challengeUrl),
9297
+ async (page) => {
9298
+ if (hasConcurrentStream) {
9299
+ const solverOptions = {
9300
+ maxAttempts: config.captcha.maxAttempts,
9301
+ retryDelayMs: config.captcha.retryDelayMs,
9302
+ settleMs: config.captcha.settleMs
9303
+ };
9304
+ if (await solveBaxiaCaptcha(page, { ...solverOptions, waitForMs: 0 })) {
9305
+ return true;
9306
+ }
9307
+ const ctx = typeof page.context === "function" ? page.context() : null;
9308
+ if (ctx && typeof ctx.newPage === "function") {
9309
+ const tempPage = await ctx.newPage();
9310
+ try {
9311
+ return await solveChallengeOnPage(tempPage, challengeUrl);
9312
+ } finally {
9313
+ await tempPage.close({ runBeforeUnload: false }).catch(() => {
9314
+ });
9315
+ }
9316
+ }
9317
+ return false;
9318
+ }
9319
+ return await solveChallengeOnPage(page, challengeUrl);
9320
+ },
9161
9321
  solverOperationTimeoutMs,
9162
9322
  solverOperationTimeoutMs,
9163
9323
  false
@@ -11756,6 +11916,7 @@ __export(qwen_exports, {
11756
11916
  getLogicalThreadState: () => getLogicalThreadState,
11757
11917
  getQwenErrorCode: () => getQwenErrorCode,
11758
11918
  getSessionParent: () => getSessionParent,
11919
+ hasActiveBrowserStreamsForAccount: () => hasActiveBrowserStreamsForAccount,
11759
11920
  invalidateLogicalThreadParent: () => invalidateLogicalThreadParent,
11760
11921
  isChatSessionActive: () => isChatSessionActive,
11761
11922
  isRetryableFetchErrorMessage: () => isRetryableFetchErrorMessage,
@@ -11777,6 +11938,14 @@ __export(qwen_exports, {
11777
11938
  });
11778
11939
  import crypto7 from "crypto";
11779
11940
  import { v4 as uuidv43 } from "uuid";
11941
+ function hasActiveBrowserStreamsForAccount(accountId) {
11942
+ for (const state of browserStreamStates.values()) {
11943
+ if (state.accountId === accountId && !state.done) {
11944
+ return true;
11945
+ }
11946
+ }
11947
+ return false;
11948
+ }
11780
11949
  function abortActiveBrowserStreamsForAccount(accountId, reason = "Account browser stream interrupted (page context unloaded)") {
11781
11950
  let count = 0;
11782
11951
  for (const [requestId, state] of browserStreamStates) {
@@ -12554,7 +12723,7 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
12554
12723
  const onFrameNavigated = (frame) => {
12555
12724
  if (!settled && typeof page.mainFrame === "function" && frame === page.mainFrame()) {
12556
12725
  const url2 = typeof frame.url === "function" ? frame.url() : String(frame.url || "");
12557
- if (url2.startsWith(qwenOrigin()) && !url2.includes("/auth") && !url2.includes("_____tmd_____") && !url2.includes("/punish")) {
12726
+ if (url2.startsWith(qwenOrigin()) && !url2.includes("/auth") && !url2.includes("/login") && !url2.includes("_____tmd_____") && !url2.includes("/punish")) {
12558
12727
  return;
12559
12728
  }
12560
12729
  handleBrowserStreamEvent(requestId, {
@@ -12575,10 +12744,12 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
12575
12744
  };
12576
12745
  page.on("framenavigated", onFrameNavigated);
12577
12746
  page.on("close", onPageClosed);
12747
+ page.on("crash", onPageClosed);
12578
12748
  pageCleanup = () => {
12579
12749
  try {
12580
12750
  page.off?.("framenavigated", onFrameNavigated);
12581
12751
  page.off?.("close", onPageClosed);
12752
+ page.off?.("crash", onPageClosed);
12582
12753
  } catch {
12583
12754
  }
12584
12755
  };
@@ -16727,7 +16898,16 @@ async function deleteChatsForAccountId(accountId) {
16727
16898
  if (!credentials) {
16728
16899
  throw new Error(`Credenciais da conta ${account.email} n\xE3o encontradas.`);
16729
16900
  }
16730
- return deleteChatsForAccount(credentials);
16901
+ const wasInitialized = isPlaywrightInitialized(account.id);
16902
+ try {
16903
+ return await deleteChatsForAccount(credentials);
16904
+ } finally {
16905
+ if (!wasInitialized && !isAuthMockEnabled()) {
16906
+ const { closePlaywrightForAccount: closePlaywrightForAccount2 } = await Promise.resolve().then(() => (init_playwright(), playwright_exports));
16907
+ await closePlaywrightForAccount2(account.id).catch(() => {
16908
+ });
16909
+ }
16910
+ }
16731
16911
  }
16732
16912
  async function deleteChatsForConfiguredAccounts(keepBrowserOpen = false) {
16733
16913
  const accounts = loadAccounts();
@@ -16742,6 +16922,7 @@ async function deleteChatsForConfiguredAccounts(keepBrowserOpen = false) {
16742
16922
  const account = accounts[i];
16743
16923
  const currentIdx = i + 1;
16744
16924
  const totalCount = accounts.length;
16925
+ const wasInitialized = isPlaywrightInitialized(account.id);
16745
16926
  try {
16746
16927
  const ok = await deleteChatsForAccount(account, currentIdx, totalCount);
16747
16928
  if (ok) succeeded++;
@@ -16751,7 +16932,7 @@ async function deleteChatsForConfiguredAccounts(keepBrowserOpen = false) {
16751
16932
  error instanceof Error ? error.message : String(error)
16752
16933
  );
16753
16934
  } finally {
16754
- if (!keepBrowserOpen) {
16935
+ if ((!keepBrowserOpen || !wasInitialized) && !isAuthMockEnabled()) {
16755
16936
  const { closePlaywrightForAccount: closePlaywrightForAccount2 } = await Promise.resolve().then(() => (init_playwright(), playwright_exports));
16756
16937
  await closePlaywrightForAccount2(account.id).catch(() => {
16757
16938
  });
@@ -18038,6 +18219,9 @@ ${loopReminder}` : truncation.prompt;
18038
18219
  if (policy.reason === "corrupted_chat_history") {
18039
18220
  invalidateLogicalThreadParent(params.sessionId);
18040
18221
  }
18222
+ if (policy.reason === "anti_bot" && isCaptchaRecoveryInBackoff(currentAccountId)) {
18223
+ return { success: false, error: err };
18224
+ }
18041
18225
  const retryInvalidInputOnSameAccount = shouldRetryInvalidInputOnSameAccount(
18042
18226
  policy.reason,
18043
18227
  invalidInputSameAccountRetried
@@ -18226,6 +18410,7 @@ var init_account = __esm({
18226
18410
  init_account_manager();
18227
18411
  init_account_priority();
18228
18412
  init_waf_isolation();
18413
+ init_captcha_coordinator();
18229
18414
  init_accounts();
18230
18415
  init_config();
18231
18416
  init_errors();
@@ -21849,8 +22034,8 @@ async function processStreamingResponse(params) {
21849
22034
  );
21850
22035
  };
21851
22036
  const emitAnswerText = async (textChunk) => {
21852
- if (textChunk) emittedModelOutput = true;
21853
22037
  if (!toolParser) {
22038
+ if (textChunk) emittedModelOutput = true;
21854
22039
  finalContent += textChunk;
21855
22040
  writeDeltaEvent({ content: textChunk });
21856
22041
  return;
@@ -22226,6 +22411,23 @@ async function processStreamingResponse(params) {
22226
22411
  if (fastMatch) {
22227
22412
  const unescaped = fastMatch[1].replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
22228
22413
  if (unescaped) {
22414
+ if (!emittedModelOutput && isOverloadMessage(unescaped)) {
22415
+ const overloadErr = toRetryableStreamError(
22416
+ "server_overloaded",
22417
+ unescaped,
22418
+ {
22419
+ switchAccount: true,
22420
+ forceNewChat: true,
22421
+ reason: "server_busy",
22422
+ accountCooldownMs: OVERLOAD_COOLDOWN_MS,
22423
+ accountCooldownReason: "ServerOverloaded"
22424
+ }
22425
+ );
22426
+ if (await recoverFromStreamError(overloadErr)) {
22427
+ break;
22428
+ }
22429
+ throw overloadErr;
22430
+ }
22229
22431
  const result = getIncrementalDelta(
22230
22432
  lastRawContent,
22231
22433
  unescaped,
@@ -22360,7 +22562,6 @@ async function processStreamingResponse(params) {
22360
22562
  if (foundStr && vStr !== "") {
22361
22563
  if (vStr === "FINISHED") continue;
22362
22564
  if (isThinkingChunk) {
22363
- emittedModelOutput = true;
22364
22565
  reasoningBuffer += vStr;
22365
22566
  writeDeltaEvent({ reasoning_content: vStr });
22366
22567
  } else {
@@ -27095,7 +27296,7 @@ function getAppVersion() {
27095
27296
  }
27096
27297
  } catch {
27097
27298
  }
27098
- cachedVersion = "v1.6.9";
27299
+ cachedVersion = "v1.6.11";
27099
27300
  return cachedVersion;
27100
27301
  }
27101
27302
  var cachedVersion;
@@ -27501,10 +27702,11 @@ async function startServer(options) {
27501
27702
  } else if (remainingAccounts.length > 0) {
27502
27703
  void (async () => {
27503
27704
  const { ensureAccountInPriority: ensureAccountInPriority2 } = await Promise.resolve().then(() => (init_account_priority(), account_priority_exports));
27504
- const targetWarmTotal = Math.min(
27705
+ const maxWarmCap = config.playwright.maxActiveContexts > 0 ? Math.min(
27505
27706
  config.playwright.startupWarmAccounts,
27506
- totalAccounts
27507
- );
27707
+ config.playwright.maxActiveContexts
27708
+ ) : config.playwright.startupWarmAccounts;
27709
+ const targetWarmTotal = Math.min(maxWarmCap, totalAccounts);
27508
27710
  let currentReadyCount = readyAccountIds.size;
27509
27711
  if (currentReadyCount < targetWarmTotal && remainingAccounts.length > 0) {
27510
27712
  for (const reserveAccount of remainingAccounts) {