qwenproxy-cli 1.6.10 → 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
@@ -5447,14 +5447,15 @@ async function getBasicHeaders(accountId) {
5447
5447
  `\u26A0\uFE0F [Playwright] Header refresh failed for ${accountId}; retaining the previous valid cache: ${getErrorMessage(error)}`
5448
5448
  );
5449
5449
  }
5450
- } else if (hadUsableHeaders && headersAge > HEADER_CACHE_TTL * HEADER_REFRESH_THRESHOLD && !cache3.refreshInProgress) {
5451
- 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)) {
5452
5455
  refreshHeadersInternal(accountId).catch((error) => {
5453
5456
  console.warn(
5454
5457
  `\u26A0\uFE0F [Playwright] Background header refresh failed for ${accountId}: ${getErrorMessage(error)}`
5455
5458
  );
5456
- }).finally(() => {
5457
- cache3.refreshInProgress = false;
5458
5459
  });
5459
5460
  }
5460
5461
  if (!hasRequiredQwenHeaders(cache3.headers)) {
@@ -5592,8 +5593,12 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
5592
5593
  if (accountPages.has(account.id)) {
5593
5594
  return;
5594
5595
  }
5595
- await evictIdlePlaywrightContextsToLimit().catch(() => {
5596
- });
5596
+ if (config.playwright.maxActiveContexts > 0) {
5597
+ await evictIdlePlaywrightContextsToLimit(
5598
+ Math.max(1, config.playwright.maxActiveContexts - 1)
5599
+ ).catch(() => {
5600
+ });
5601
+ }
5597
5602
  const profilePath = getAccountProfilePath(account.id);
5598
5603
  const fingerprint = getFingerprintProfile(account.id);
5599
5604
  const { engine, channel } = resolveBrowserEngine(browserType);
@@ -5862,7 +5867,7 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
5862
5867
  if (!acctPage.isClosed()) {
5863
5868
  try {
5864
5869
  const currentUrl = new URL(acctPage.url());
5865
- if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname !== "/") {
5870
+ if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname.includes("/auth") || currentUrl.pathname.includes("/login") || currentUrl.pathname.includes("_____tmd_____")) {
5866
5871
  await acctPage.goto(qwenUrl("/"), {
5867
5872
  waitUntil: "domcontentloaded",
5868
5873
  timeout: config.timeouts.navigation
@@ -5880,6 +5885,8 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
5880
5885
  } finally {
5881
5886
  release();
5882
5887
  }
5888
+ await evictIdlePlaywrightContextsToLimit().catch(() => {
5889
+ });
5883
5890
  })();
5884
5891
  try {
5885
5892
  await initPromise;
@@ -7648,19 +7655,26 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
7648
7655
  await executeReauth();
7649
7656
  } else {
7650
7657
  try {
7651
- await page.goto(qwenUrl("/"), {
7652
- waitUntil: "domcontentloaded",
7653
- timeout: Math.min(
7654
- config.timeouts.navigation,
7655
- boundedTimeoutMs,
7656
- SESSION_PROBE_NAVIGATION_TIMEOUT_MS
7657
- )
7658
- });
7659
- await sleep2(2e3);
7660
- 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() : "";
7661
7672
  const isAuthUrl = url.includes("auth") || url.includes("login");
7662
7673
  if (isAuthUrl) {
7663
- await executeReauth();
7674
+ const refreshed = await refreshAccountToken(accountId);
7675
+ if (!refreshed.success) {
7676
+ await executeReauth();
7677
+ }
7664
7678
  }
7665
7679
  } catch (navErr) {
7666
7680
  console.warn(
@@ -7717,7 +7731,7 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
7717
7731
  if (capturedPage && !capturedPage.isClosed()) {
7718
7732
  try {
7719
7733
  const currentUrl = new URL(capturedPage.url());
7720
- if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname !== "/") {
7734
+ if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname.includes("/auth") || currentUrl.pathname.includes("/login") || currentUrl.pathname.includes("_____tmd_____")) {
7721
7735
  await capturedPage.goto(qwenUrl("/"), {
7722
7736
  waitUntil: "domcontentloaded",
7723
7737
  timeout: Math.min(config.timeouts.navigation, boundedTimeoutMs)
@@ -8033,7 +8047,11 @@ function priorityOrderForEviction(accountIds) {
8033
8047
  const ranked = getAccountsByPriority(accountIds.map((id) => ({ id })));
8034
8048
  const rank = /* @__PURE__ */ new Map();
8035
8049
  for (const [index, account] of ranked.entries()) {
8036
- 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
+ }
8037
8055
  }
8038
8056
  return orderContextsForEviction(
8039
8057
  accountIds,
@@ -8070,14 +8088,15 @@ async function closeIdlePlaywrightAccounts(idleMs) {
8070
8088
  }
8071
8089
  return closed;
8072
8090
  }
8073
- async function evictIdlePlaywrightContextsToLimit() {
8074
- const max = config.playwright.maxActiveContexts;
8075
- 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;
8076
8095
  if (accountPages.size <= max) return 0;
8077
8096
  const candidates = priorityOrderForEviction(
8078
8097
  Array.from(accountPages.keys()).filter((accountId) => {
8079
8098
  if (isAccountServingStream(accountId)) return false;
8080
- if (isAccountRecentlyActive(accountId, 3e4)) return false;
8099
+ if (hasActiveAccountLease(accountId)) return false;
8081
8100
  const mutex = accountMutexes.get(accountId);
8082
8101
  return mutex?.isIdle();
8083
8102
  })
@@ -8090,6 +8109,7 @@ async function evictIdlePlaywrightContextsToLimit() {
8090
8109
  for (const candidate of candidates) {
8091
8110
  if (accountPages.size <= max) break;
8092
8111
  if (isAccountServingStream(candidate.accountId)) continue;
8112
+ if (hasActiveAccountLease(candidate.accountId)) continue;
8093
8113
  const mutex = accountMutexes.get(candidate.accountId);
8094
8114
  if (!mutex?.isIdle()) continue;
8095
8115
  await closePlaywrightForAccount(candidate.accountId).catch((error) => {
@@ -8520,12 +8540,13 @@ var init_watchdog = __esm({
8520
8540
  await new Promise((resolve) => setTimeout(resolve, 100));
8521
8541
  this.emit("recovery:ram:freed");
8522
8542
  try {
8523
- 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);
8524
8545
  const closed = await closeIdlePlaywrightAccounts2(
8525
8546
  config.sessionKeeper.idleMs
8526
8547
  );
8527
- if (closed > 0) {
8528
- this.emit("recovery:ram:contexts-closed", closed);
8548
+ if (evicted + closed > 0) {
8549
+ this.emit("recovery:ram:contexts-closed", evicted + closed);
8529
8550
  }
8530
8551
  } catch {
8531
8552
  }
@@ -9208,6 +9229,7 @@ var init_model_registry = __esm({
9208
9229
  var captcha_coordinator_exports = {};
9209
9230
  __export(captcha_coordinator_exports, {
9210
9231
  isCaptchaRecoveryActive: () => isCaptchaRecoveryActive,
9232
+ isCaptchaRecoveryInBackoff: () => isCaptchaRecoveryInBackoff,
9211
9233
  recoverBaxiaCaptcha: () => recoverBaxiaCaptcha,
9212
9234
  solveChallengeOnPage: () => solveChallengeOnPage
9213
9235
  });
@@ -9246,6 +9268,11 @@ async function solveChallengeOnPage(page, challengeUrl, waitForMs = config.captc
9246
9268
  }
9247
9269
  }
9248
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
+ }
9249
9276
  async function recoverBaxiaCaptcha(accountId, label, options = {}) {
9250
9277
  if (!config.captcha.enabled || !accountId) return false;
9251
9278
  const solver = "baxia";
@@ -9263,17 +9290,34 @@ async function recoverBaxiaCaptcha(accountId, label, options = {}) {
9263
9290
  const challengeUrl = options.challengeBody ? extractBaxiaChallengeUrl(options.challengeBody, config.qwen.baseUrl) : null;
9264
9291
  activeCaptchaRecoveries.add(accountId);
9265
9292
  try {
9266
- const { abortActiveBrowserStreamsForAccount: abortActiveBrowserStreamsForAccount2 } = await Promise.resolve().then(() => (init_qwen(), qwen_exports));
9267
- abortActiveBrowserStreamsForAccount2(
9268
- accountId,
9269
- "Account stream interrupted by captcha challenge recovery"
9270
- );
9271
- } catch {
9272
- }
9273
- try {
9293
+ const { hasActiveBrowserStreamsForAccount: hasActiveBrowserStreamsForAccount2 } = await Promise.resolve().then(() => (init_qwen(), qwen_exports));
9294
+ const hasConcurrentStream = hasActiveBrowserStreamsForAccount2(accountId);
9274
9295
  const solved = await withAccountPage(
9275
9296
  accountId,
9276
- (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
+ },
9277
9321
  solverOperationTimeoutMs,
9278
9322
  solverOperationTimeoutMs,
9279
9323
  false
@@ -11872,6 +11916,7 @@ __export(qwen_exports, {
11872
11916
  getLogicalThreadState: () => getLogicalThreadState,
11873
11917
  getQwenErrorCode: () => getQwenErrorCode,
11874
11918
  getSessionParent: () => getSessionParent,
11919
+ hasActiveBrowserStreamsForAccount: () => hasActiveBrowserStreamsForAccount,
11875
11920
  invalidateLogicalThreadParent: () => invalidateLogicalThreadParent,
11876
11921
  isChatSessionActive: () => isChatSessionActive,
11877
11922
  isRetryableFetchErrorMessage: () => isRetryableFetchErrorMessage,
@@ -11893,6 +11938,14 @@ __export(qwen_exports, {
11893
11938
  });
11894
11939
  import crypto7 from "crypto";
11895
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
+ }
11896
11949
  function abortActiveBrowserStreamsForAccount(accountId, reason = "Account browser stream interrupted (page context unloaded)") {
11897
11950
  let count = 0;
11898
11951
  for (const [requestId, state] of browserStreamStates) {
@@ -12670,7 +12723,7 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
12670
12723
  const onFrameNavigated = (frame) => {
12671
12724
  if (!settled && typeof page.mainFrame === "function" && frame === page.mainFrame()) {
12672
12725
  const url2 = typeof frame.url === "function" ? frame.url() : String(frame.url || "");
12673
- 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")) {
12674
12727
  return;
12675
12728
  }
12676
12729
  handleBrowserStreamEvent(requestId, {
@@ -12691,10 +12744,12 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
12691
12744
  };
12692
12745
  page.on("framenavigated", onFrameNavigated);
12693
12746
  page.on("close", onPageClosed);
12747
+ page.on("crash", onPageClosed);
12694
12748
  pageCleanup = () => {
12695
12749
  try {
12696
12750
  page.off?.("framenavigated", onFrameNavigated);
12697
12751
  page.off?.("close", onPageClosed);
12752
+ page.off?.("crash", onPageClosed);
12698
12753
  } catch {
12699
12754
  }
12700
12755
  };
@@ -16843,7 +16898,16 @@ async function deleteChatsForAccountId(accountId) {
16843
16898
  if (!credentials) {
16844
16899
  throw new Error(`Credenciais da conta ${account.email} n\xE3o encontradas.`);
16845
16900
  }
16846
- 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
+ }
16847
16911
  }
16848
16912
  async function deleteChatsForConfiguredAccounts(keepBrowserOpen = false) {
16849
16913
  const accounts = loadAccounts();
@@ -16858,6 +16922,7 @@ async function deleteChatsForConfiguredAccounts(keepBrowserOpen = false) {
16858
16922
  const account = accounts[i];
16859
16923
  const currentIdx = i + 1;
16860
16924
  const totalCount = accounts.length;
16925
+ const wasInitialized = isPlaywrightInitialized(account.id);
16861
16926
  try {
16862
16927
  const ok = await deleteChatsForAccount(account, currentIdx, totalCount);
16863
16928
  if (ok) succeeded++;
@@ -16867,7 +16932,7 @@ async function deleteChatsForConfiguredAccounts(keepBrowserOpen = false) {
16867
16932
  error instanceof Error ? error.message : String(error)
16868
16933
  );
16869
16934
  } finally {
16870
- if (!keepBrowserOpen) {
16935
+ if ((!keepBrowserOpen || !wasInitialized) && !isAuthMockEnabled()) {
16871
16936
  const { closePlaywrightForAccount: closePlaywrightForAccount2 } = await Promise.resolve().then(() => (init_playwright(), playwright_exports));
16872
16937
  await closePlaywrightForAccount2(account.id).catch(() => {
16873
16938
  });
@@ -18154,6 +18219,9 @@ ${loopReminder}` : truncation.prompt;
18154
18219
  if (policy.reason === "corrupted_chat_history") {
18155
18220
  invalidateLogicalThreadParent(params.sessionId);
18156
18221
  }
18222
+ if (policy.reason === "anti_bot" && isCaptchaRecoveryInBackoff(currentAccountId)) {
18223
+ return { success: false, error: err };
18224
+ }
18157
18225
  const retryInvalidInputOnSameAccount = shouldRetryInvalidInputOnSameAccount(
18158
18226
  policy.reason,
18159
18227
  invalidInputSameAccountRetried
@@ -18342,6 +18410,7 @@ var init_account = __esm({
18342
18410
  init_account_manager();
18343
18411
  init_account_priority();
18344
18412
  init_waf_isolation();
18413
+ init_captcha_coordinator();
18345
18414
  init_accounts();
18346
18415
  init_config();
18347
18416
  init_errors();
@@ -21965,8 +22034,8 @@ async function processStreamingResponse(params) {
21965
22034
  );
21966
22035
  };
21967
22036
  const emitAnswerText = async (textChunk) => {
21968
- if (textChunk) emittedModelOutput = true;
21969
22037
  if (!toolParser) {
22038
+ if (textChunk) emittedModelOutput = true;
21970
22039
  finalContent += textChunk;
21971
22040
  writeDeltaEvent({ content: textChunk });
21972
22041
  return;
@@ -22342,6 +22411,23 @@ async function processStreamingResponse(params) {
22342
22411
  if (fastMatch) {
22343
22412
  const unescaped = fastMatch[1].replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
22344
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
+ }
22345
22431
  const result = getIncrementalDelta(
22346
22432
  lastRawContent,
22347
22433
  unescaped,
@@ -22476,7 +22562,6 @@ async function processStreamingResponse(params) {
22476
22562
  if (foundStr && vStr !== "") {
22477
22563
  if (vStr === "FINISHED") continue;
22478
22564
  if (isThinkingChunk) {
22479
- emittedModelOutput = true;
22480
22565
  reasoningBuffer += vStr;
22481
22566
  writeDeltaEvent({ reasoning_content: vStr });
22482
22567
  } else {
@@ -27211,7 +27296,7 @@ function getAppVersion() {
27211
27296
  }
27212
27297
  } catch {
27213
27298
  }
27214
- cachedVersion = "v1.6.10";
27299
+ cachedVersion = "v1.6.11";
27215
27300
  return cachedVersion;
27216
27301
  }
27217
27302
  var cachedVersion;
@@ -27617,10 +27702,11 @@ async function startServer(options) {
27617
27702
  } else if (remainingAccounts.length > 0) {
27618
27703
  void (async () => {
27619
27704
  const { ensureAccountInPriority: ensureAccountInPriority2 } = await Promise.resolve().then(() => (init_account_priority(), account_priority_exports));
27620
- const targetWarmTotal = Math.min(
27705
+ const maxWarmCap = config.playwright.maxActiveContexts > 0 ? Math.min(
27621
27706
  config.playwright.startupWarmAccounts,
27622
- totalAccounts
27623
- );
27707
+ config.playwright.maxActiveContexts
27708
+ ) : config.playwright.startupWarmAccounts;
27709
+ const targetWarmTotal = Math.min(maxWarmCap, totalAccounts);
27624
27710
  let currentReadyCount = readyAccountIds.size;
27625
27711
  if (currentReadyCount < targetWarmTotal && remainingAccounts.length > 0) {
27626
27712
  for (const reserveAccount of remainingAccounts) {
package/dist/login.js CHANGED
@@ -4310,14 +4310,15 @@ async function getBasicHeaders(accountId) {
4310
4310
  `\u26A0\uFE0F [Playwright] Header refresh failed for ${accountId}; retaining the previous valid cache: ${getErrorMessage(error)}`
4311
4311
  );
4312
4312
  }
4313
- } else if (hadUsableHeaders && headersAge > HEADER_CACHE_TTL * HEADER_REFRESH_THRESHOLD && !cache.refreshInProgress) {
4314
- cache.refreshInProgress = true;
4313
+ } 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
4314
+ // the account's page. Doing that while the account is serving a stream
4315
+ // would mutate the live conversation (and can navigate the SPA), so the
4316
+ // proactive refresh waits until the account has no active lease.
4317
+ !hasActiveAccountLease(accountId)) {
4315
4318
  refreshHeadersInternal(accountId).catch((error) => {
4316
4319
  console.warn(
4317
4320
  `\u26A0\uFE0F [Playwright] Background header refresh failed for ${accountId}: ${getErrorMessage(error)}`
4318
4321
  );
4319
- }).finally(() => {
4320
- cache.refreshInProgress = false;
4321
4322
  });
4322
4323
  }
4323
4324
  if (!hasRequiredQwenHeaders(cache.headers)) {
@@ -4455,8 +4456,12 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
4455
4456
  if (accountPages.has(account.id)) {
4456
4457
  return;
4457
4458
  }
4458
- await evictIdlePlaywrightContextsToLimit().catch(() => {
4459
- });
4459
+ if (config.playwright.maxActiveContexts > 0) {
4460
+ await evictIdlePlaywrightContextsToLimit(
4461
+ Math.max(1, config.playwright.maxActiveContexts - 1)
4462
+ ).catch(() => {
4463
+ });
4464
+ }
4460
4465
  const profilePath = getAccountProfilePath(account.id);
4461
4466
  const fingerprint = getFingerprintProfile(account.id);
4462
4467
  const { engine, channel } = resolveBrowserEngine(browserType);
@@ -4725,7 +4730,7 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
4725
4730
  if (!acctPage.isClosed()) {
4726
4731
  try {
4727
4732
  const currentUrl = new URL(acctPage.url());
4728
- if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname !== "/") {
4733
+ if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname.includes("/auth") || currentUrl.pathname.includes("/login") || currentUrl.pathname.includes("_____tmd_____")) {
4729
4734
  await acctPage.goto(qwenUrl("/"), {
4730
4735
  waitUntil: "domcontentloaded",
4731
4736
  timeout: config.timeouts.navigation
@@ -4743,6 +4748,8 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
4743
4748
  } finally {
4744
4749
  release();
4745
4750
  }
4751
+ await evictIdlePlaywrightContextsToLimit().catch(() => {
4752
+ });
4746
4753
  })();
4747
4754
  try {
4748
4755
  await initPromise;
@@ -6511,19 +6518,26 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
6511
6518
  await executeReauth();
6512
6519
  } else {
6513
6520
  try {
6514
- await page.goto(qwenUrl("/"), {
6515
- waitUntil: "domcontentloaded",
6516
- timeout: Math.min(
6517
- config.timeouts.navigation,
6518
- boundedTimeoutMs,
6519
- SESSION_PROBE_NAVIGATION_TIMEOUT_MS
6520
- )
6521
- });
6522
- await sleep2(2e3);
6523
- const url = page.url();
6521
+ const currentUrl = typeof page.url === "function" ? page.url() : "";
6522
+ const needsNavigation = !currentUrl.startsWith(qwenOrigin()) || currentUrl.includes("/auth") || currentUrl.includes("/login") || currentUrl.includes("_____tmd_____");
6523
+ if (needsNavigation) {
6524
+ await page.goto(qwenUrl("/"), {
6525
+ waitUntil: "domcontentloaded",
6526
+ timeout: Math.min(
6527
+ config.timeouts.navigation,
6528
+ boundedTimeoutMs,
6529
+ SESSION_PROBE_NAVIGATION_TIMEOUT_MS
6530
+ )
6531
+ });
6532
+ await sleep2(2e3);
6533
+ }
6534
+ const url = typeof page.url === "function" ? page.url() : "";
6524
6535
  const isAuthUrl = url.includes("auth") || url.includes("login");
6525
6536
  if (isAuthUrl) {
6526
- await executeReauth();
6537
+ const refreshed = await refreshAccountToken(accountId);
6538
+ if (!refreshed.success) {
6539
+ await executeReauth();
6540
+ }
6527
6541
  }
6528
6542
  } catch (navErr) {
6529
6543
  console.warn(
@@ -6580,7 +6594,7 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
6580
6594
  if (capturedPage && !capturedPage.isClosed()) {
6581
6595
  try {
6582
6596
  const currentUrl = new URL(capturedPage.url());
6583
- if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname !== "/") {
6597
+ if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname.includes("/auth") || currentUrl.pathname.includes("/login") || currentUrl.pathname.includes("_____tmd_____")) {
6584
6598
  await capturedPage.goto(qwenUrl("/"), {
6585
6599
  waitUntil: "domcontentloaded",
6586
6600
  timeout: Math.min(config.timeouts.navigation, boundedTimeoutMs)
@@ -6896,7 +6910,11 @@ function priorityOrderForEviction(accountIds) {
6896
6910
  const ranked = getAccountsByPriority(accountIds.map((id) => ({ id })));
6897
6911
  const rank = /* @__PURE__ */ new Map();
6898
6912
  for (const [index, account] of ranked.entries()) {
6899
- rank.set(account.id, index);
6913
+ if (getAccountCooldownInfo(account.id)) {
6914
+ rank.set(account.id, Number.MAX_SAFE_INTEGER);
6915
+ } else {
6916
+ rank.set(account.id, index);
6917
+ }
6900
6918
  }
6901
6919
  return orderContextsForEviction(
6902
6920
  accountIds,
@@ -6933,14 +6951,15 @@ async function closeIdlePlaywrightAccounts(idleMs) {
6933
6951
  }
6934
6952
  return closed;
6935
6953
  }
6936
- async function evictIdlePlaywrightContextsToLimit() {
6937
- const max = config.playwright.maxActiveContexts;
6938
- if (max <= 0) return 0;
6954
+ async function evictIdlePlaywrightContextsToLimit(targetLimit) {
6955
+ const configuredMax = config.playwright.maxActiveContexts;
6956
+ if (configuredMax <= 0) return 0;
6957
+ const max = targetLimit !== void 0 ? Math.max(0, targetLimit) : configuredMax;
6939
6958
  if (accountPages.size <= max) return 0;
6940
6959
  const candidates = priorityOrderForEviction(
6941
6960
  Array.from(accountPages.keys()).filter((accountId) => {
6942
6961
  if (isAccountServingStream(accountId)) return false;
6943
- if (isAccountRecentlyActive(accountId, 3e4)) return false;
6962
+ if (hasActiveAccountLease(accountId)) return false;
6944
6963
  const mutex = accountMutexes.get(accountId);
6945
6964
  return mutex?.isIdle();
6946
6965
  })
@@ -6953,6 +6972,7 @@ async function evictIdlePlaywrightContextsToLimit() {
6953
6972
  for (const candidate of candidates) {
6954
6973
  if (accountPages.size <= max) break;
6955
6974
  if (isAccountServingStream(candidate.accountId)) continue;
6975
+ if (hasActiveAccountLease(candidate.accountId)) continue;
6956
6976
  const mutex = accountMutexes.get(candidate.accountId);
6957
6977
  if (!mutex?.isIdle()) continue;
6958
6978
  await closePlaywrightForAccount(candidate.accountId).catch((error) => {
@@ -1236,6 +1236,26 @@ function clearAllAccountCooldowns() {
1236
1236
  cooldowns.clear();
1237
1237
  return count;
1238
1238
  }
1239
+ function getAccountCooldownInfo(accountId) {
1240
+ const entry = cooldowns.get(accountId);
1241
+ if (!entry) return null;
1242
+ const remaining = entry.until - Date.now();
1243
+ if (remaining <= 0) {
1244
+ cooldowns.delete(accountId);
1245
+ if (accountId !== "global") {
1246
+ try {
1247
+ updateAccountCooldown(accountId, 0, null);
1248
+ } catch (err) {
1249
+ console.error(
1250
+ `\u274C [AccountManager] Failed to clear expired cooldown in DB:`,
1251
+ err.message
1252
+ );
1253
+ }
1254
+ }
1255
+ return null;
1256
+ }
1257
+ return { onCooldown: true, remainingMs: remaining, reason: entry.reason };
1258
+ }
1239
1259
  function markAccountHeadersReady(accountId) {
1240
1260
  if (!accountId || accountId === "global") return;
1241
1261
  headersReadyAccounts.add(accountId);
@@ -3889,8 +3909,12 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
3889
3909
  if (accountPages.has(account.id)) {
3890
3910
  return;
3891
3911
  }
3892
- await evictIdlePlaywrightContextsToLimit().catch(() => {
3893
- });
3912
+ if (config.playwright.maxActiveContexts > 0) {
3913
+ await evictIdlePlaywrightContextsToLimit(
3914
+ Math.max(1, config.playwright.maxActiveContexts - 1)
3915
+ ).catch(() => {
3916
+ });
3917
+ }
3894
3918
  const profilePath = getAccountProfilePath(account.id);
3895
3919
  const fingerprint = getFingerprintProfile(account.id);
3896
3920
  const { engine, channel } = resolveBrowserEngine(browserType);
@@ -4159,7 +4183,7 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
4159
4183
  if (!acctPage.isClosed()) {
4160
4184
  try {
4161
4185
  const currentUrl = new URL(acctPage.url());
4162
- if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname !== "/") {
4186
+ if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname.includes("/auth") || currentUrl.pathname.includes("/login") || currentUrl.pathname.includes("_____tmd_____")) {
4163
4187
  await acctPage.goto(qwenUrl("/"), {
4164
4188
  waitUntil: "domcontentloaded",
4165
4189
  timeout: config.timeouts.navigation
@@ -4177,6 +4201,8 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
4177
4201
  } finally {
4178
4202
  release();
4179
4203
  }
4204
+ await evictIdlePlaywrightContextsToLimit().catch(() => {
4205
+ });
4180
4206
  })();
4181
4207
  try {
4182
4208
  await initPromise;
@@ -5679,7 +5705,11 @@ function priorityOrderForEviction(accountIds) {
5679
5705
  const ranked = getAccountsByPriority(accountIds.map((id) => ({ id })));
5680
5706
  const rank = /* @__PURE__ */ new Map();
5681
5707
  for (const [index, account] of ranked.entries()) {
5682
- rank.set(account.id, index);
5708
+ if (getAccountCooldownInfo(account.id)) {
5709
+ rank.set(account.id, Number.MAX_SAFE_INTEGER);
5710
+ } else {
5711
+ rank.set(account.id, index);
5712
+ }
5683
5713
  }
5684
5714
  return orderContextsForEviction(
5685
5715
  accountIds,
@@ -5687,14 +5717,15 @@ function priorityOrderForEviction(accountIds) {
5687
5717
  (id) => lastAccountActivity.get(id) ?? 0
5688
5718
  );
5689
5719
  }
5690
- async function evictIdlePlaywrightContextsToLimit() {
5691
- const max = config.playwright.maxActiveContexts;
5692
- if (max <= 0) return 0;
5720
+ async function evictIdlePlaywrightContextsToLimit(targetLimit) {
5721
+ const configuredMax = config.playwright.maxActiveContexts;
5722
+ if (configuredMax <= 0) return 0;
5723
+ const max = targetLimit !== void 0 ? Math.max(0, targetLimit) : configuredMax;
5693
5724
  if (accountPages.size <= max) return 0;
5694
5725
  const candidates = priorityOrderForEviction(
5695
5726
  Array.from(accountPages.keys()).filter((accountId) => {
5696
5727
  if (isAccountServingStream(accountId)) return false;
5697
- if (isAccountRecentlyActive(accountId, 3e4)) return false;
5728
+ if (hasActiveAccountLease(accountId)) return false;
5698
5729
  const mutex = accountMutexes.get(accountId);
5699
5730
  return mutex?.isIdle();
5700
5731
  })
@@ -5707,6 +5738,7 @@ async function evictIdlePlaywrightContextsToLimit() {
5707
5738
  for (const candidate of candidates) {
5708
5739
  if (accountPages.size <= max) break;
5709
5740
  if (isAccountServingStream(candidate.accountId)) continue;
5741
+ if (hasActiveAccountLease(candidate.accountId)) continue;
5710
5742
  const mutex = accountMutexes.get(candidate.accountId);
5711
5743
  if (!mutex?.isIdle()) continue;
5712
5744
  await closePlaywrightForAccount(candidate.accountId).catch((error) => {
@@ -5809,10 +5841,6 @@ async function closePlaywrightForAccount(accountId) {
5809
5841
  release();
5810
5842
  }
5811
5843
  }
5812
- function isAccountRecentlyActive(accountId, maxIdleMs = 3e5) {
5813
- const last = lastAccountActivity.get(accountId) ?? 0;
5814
- return last > 0 && Date.now() - last < maxIdleMs;
5815
- }
5816
5844
  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;
5817
5845
  var init_playwright = __esm({
5818
5846
  "src/services/playwright.ts"() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qwenproxy-cli",
3
- "version": "1.6.10",
3
+ "version": "1.6.11",
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": {
@@ -58,7 +58,7 @@
58
58
  "bugs": {
59
59
  "url": "https://github.com/johngbl/qwenproxy/issues"
60
60
  },
61
- "author": "johngbl <147551701+johngbl@users.noreply.github.com>",
61
+ "author": "johngbl",
62
62
  "license": "ISC",
63
63
  "dependencies": {
64
64
  "@hono/node-server": "^2.1.1",
package/src/api/server.ts CHANGED
@@ -883,10 +883,14 @@ export async function startServer(options?: {
883
883
  void (async () => {
884
884
  const { ensureAccountInPriority } = await import("../core/account-priority.ts");
885
885
 
886
- const targetWarmTotal = Math.min(
887
- config.playwright.startupWarmAccounts,
888
- totalAccounts,
889
- );
886
+ const maxWarmCap =
887
+ config.playwright.maxActiveContexts > 0
888
+ ? Math.min(
889
+ config.playwright.startupWarmAccounts,
890
+ config.playwright.maxActiveContexts,
891
+ )
892
+ : config.playwright.startupWarmAccounts;
893
+ const targetWarmTotal = Math.min(maxWarmCap, totalAccounts);
890
894
  let currentReadyCount = readyAccountIds.size;
891
895
 
892
896
  if (currentReadyCount < targetWarmTotal && remainingAccounts.length > 0) {