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.
@@ -2035,6 +2035,26 @@ function clearAccountCooldown(accountId) {
2035
2035
  }
2036
2036
  }
2037
2037
  }
2038
+ function getAccountCooldownInfo(accountId) {
2039
+ const entry = cooldowns.get(accountId);
2040
+ if (!entry) return null;
2041
+ const remaining = entry.until - Date.now();
2042
+ if (remaining <= 0) {
2043
+ cooldowns.delete(accountId);
2044
+ if (accountId !== "global") {
2045
+ try {
2046
+ updateAccountCooldown(accountId, 0, null);
2047
+ } catch (err) {
2048
+ console.error(
2049
+ `\u274C [AccountManager] Failed to clear expired cooldown in DB:`,
2050
+ err.message
2051
+ );
2052
+ }
2053
+ }
2054
+ return null;
2055
+ }
2056
+ return { onCooldown: true, remainingMs: remaining, reason: entry.reason };
2057
+ }
2038
2058
  function markAccountHeadersReady(accountId) {
2039
2059
  if (!accountId || accountId === "global") return;
2040
2060
  headersReadyAccounts.add(accountId);
@@ -3623,8 +3643,12 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
3623
3643
  if (accountPages.has(account.id)) {
3624
3644
  return;
3625
3645
  }
3626
- await evictIdlePlaywrightContextsToLimit().catch(() => {
3627
- });
3646
+ if (config.playwright.maxActiveContexts > 0) {
3647
+ await evictIdlePlaywrightContextsToLimit(
3648
+ Math.max(1, config.playwright.maxActiveContexts - 1)
3649
+ ).catch(() => {
3650
+ });
3651
+ }
3628
3652
  const profilePath = getAccountProfilePath(account.id);
3629
3653
  const fingerprint = getFingerprintProfile(account.id);
3630
3654
  const { engine, channel } = resolveBrowserEngine(browserType);
@@ -3893,7 +3917,7 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
3893
3917
  if (!acctPage.isClosed()) {
3894
3918
  try {
3895
3919
  const currentUrl = new URL(acctPage.url());
3896
- if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname !== "/") {
3920
+ if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname.includes("/auth") || currentUrl.pathname.includes("/login") || currentUrl.pathname.includes("_____tmd_____")) {
3897
3921
  await acctPage.goto(qwenUrl("/"), {
3898
3922
  waitUntil: "domcontentloaded",
3899
3923
  timeout: config.timeouts.navigation
@@ -3911,6 +3935,8 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
3911
3935
  } finally {
3912
3936
  release();
3913
3937
  }
3938
+ await evictIdlePlaywrightContextsToLimit().catch(() => {
3939
+ });
3914
3940
  })();
3915
3941
  try {
3916
3942
  await initPromise;
@@ -5413,7 +5439,11 @@ function priorityOrderForEviction(accountIds) {
5413
5439
  const ranked = getAccountsByPriority(accountIds.map((id) => ({ id })));
5414
5440
  const rank = /* @__PURE__ */ new Map();
5415
5441
  for (const [index, account] of ranked.entries()) {
5416
- rank.set(account.id, index);
5442
+ if (getAccountCooldownInfo(account.id)) {
5443
+ rank.set(account.id, Number.MAX_SAFE_INTEGER);
5444
+ } else {
5445
+ rank.set(account.id, index);
5446
+ }
5417
5447
  }
5418
5448
  return orderContextsForEviction(
5419
5449
  accountIds,
@@ -5421,14 +5451,15 @@ function priorityOrderForEviction(accountIds) {
5421
5451
  (id) => lastAccountActivity.get(id) ?? 0
5422
5452
  );
5423
5453
  }
5424
- async function evictIdlePlaywrightContextsToLimit() {
5425
- const max = config.playwright.maxActiveContexts;
5426
- if (max <= 0) return 0;
5454
+ async function evictIdlePlaywrightContextsToLimit(targetLimit) {
5455
+ const configuredMax = config.playwright.maxActiveContexts;
5456
+ if (configuredMax <= 0) return 0;
5457
+ const max = targetLimit !== void 0 ? Math.max(0, targetLimit) : configuredMax;
5427
5458
  if (accountPages.size <= max) return 0;
5428
5459
  const candidates = priorityOrderForEviction(
5429
5460
  Array.from(accountPages.keys()).filter((accountId) => {
5430
5461
  if (isAccountServingStream(accountId)) return false;
5431
- if (isAccountRecentlyActive(accountId, 3e4)) return false;
5462
+ if (hasActiveAccountLease(accountId)) return false;
5432
5463
  const mutex = accountMutexes.get(accountId);
5433
5464
  return mutex?.isIdle();
5434
5465
  })
@@ -5441,6 +5472,7 @@ async function evictIdlePlaywrightContextsToLimit() {
5441
5472
  for (const candidate of candidates) {
5442
5473
  if (accountPages.size <= max) break;
5443
5474
  if (isAccountServingStream(candidate.accountId)) continue;
5475
+ if (hasActiveAccountLease(candidate.accountId)) continue;
5444
5476
  const mutex = accountMutexes.get(candidate.accountId);
5445
5477
  if (!mutex?.isIdle()) continue;
5446
5478
  await closePlaywrightForAccount(candidate.accountId).catch((error) => {
@@ -5543,10 +5575,6 @@ async function closePlaywrightForAccount(accountId) {
5543
5575
  release();
5544
5576
  }
5545
5577
  }
5546
- function isAccountRecentlyActive(accountId, maxIdleMs = 3e5) {
5547
- const last = lastAccountActivity.get(accountId) ?? 0;
5548
- return last > 0 && Date.now() - last < maxIdleMs;
5549
- }
5550
5578
  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;
5551
5579
  var init_playwright = __esm({
5552
5580
  "src/services/playwright.ts"() {
@@ -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);
@@ -4337,14 +4357,15 @@ async function getBasicHeaders(accountId) {
4337
4357
  `\u26A0\uFE0F [Playwright] Header refresh failed for ${accountId}; retaining the previous valid cache: ${getErrorMessage(error)}`
4338
4358
  );
4339
4359
  }
4340
- } else if (hadUsableHeaders && headersAge > HEADER_CACHE_TTL * HEADER_REFRESH_THRESHOLD && !cache.refreshInProgress) {
4341
- 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)) {
4342
4365
  refreshHeadersInternal(accountId).catch((error) => {
4343
4366
  console.warn(
4344
4367
  `\u26A0\uFE0F [Playwright] Background header refresh failed for ${accountId}: ${getErrorMessage(error)}`
4345
4368
  );
4346
- }).finally(() => {
4347
- cache.refreshInProgress = false;
4348
4369
  });
4349
4370
  }
4350
4371
  if (!hasRequiredQwenHeaders(cache.headers)) {
@@ -4482,8 +4503,12 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
4482
4503
  if (accountPages.has(account.id)) {
4483
4504
  return;
4484
4505
  }
4485
- await evictIdlePlaywrightContextsToLimit().catch(() => {
4486
- });
4506
+ if (config.playwright.maxActiveContexts > 0) {
4507
+ await evictIdlePlaywrightContextsToLimit(
4508
+ Math.max(1, config.playwright.maxActiveContexts - 1)
4509
+ ).catch(() => {
4510
+ });
4511
+ }
4487
4512
  const profilePath = getAccountProfilePath(account.id);
4488
4513
  const fingerprint = getFingerprintProfile(account.id);
4489
4514
  const { engine, channel } = resolveBrowserEngine(browserType);
@@ -4752,7 +4777,7 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
4752
4777
  if (!acctPage.isClosed()) {
4753
4778
  try {
4754
4779
  const currentUrl = new URL(acctPage.url());
4755
- if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname !== "/") {
4780
+ if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname.includes("/auth") || currentUrl.pathname.includes("/login") || currentUrl.pathname.includes("_____tmd_____")) {
4756
4781
  await acctPage.goto(qwenUrl("/"), {
4757
4782
  waitUntil: "domcontentloaded",
4758
4783
  timeout: config.timeouts.navigation
@@ -4770,6 +4795,8 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
4770
4795
  } finally {
4771
4796
  release();
4772
4797
  }
4798
+ await evictIdlePlaywrightContextsToLimit().catch(() => {
4799
+ });
4773
4800
  })();
4774
4801
  try {
4775
4802
  await initPromise;
@@ -6538,19 +6565,26 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
6538
6565
  await executeReauth();
6539
6566
  } else {
6540
6567
  try {
6541
- await page.goto(qwenUrl("/"), {
6542
- waitUntil: "domcontentloaded",
6543
- timeout: Math.min(
6544
- config.timeouts.navigation,
6545
- boundedTimeoutMs,
6546
- SESSION_PROBE_NAVIGATION_TIMEOUT_MS
6547
- )
6548
- });
6549
- await sleep2(2e3);
6550
- 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() : "";
6551
6582
  const isAuthUrl = url.includes("auth") || url.includes("login");
6552
6583
  if (isAuthUrl) {
6553
- await executeReauth();
6584
+ const refreshed = await refreshAccountToken(accountId);
6585
+ if (!refreshed.success) {
6586
+ await executeReauth();
6587
+ }
6554
6588
  }
6555
6589
  } catch (navErr) {
6556
6590
  console.warn(
@@ -6607,7 +6641,7 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
6607
6641
  if (capturedPage && !capturedPage.isClosed()) {
6608
6642
  try {
6609
6643
  const currentUrl = new URL(capturedPage.url());
6610
- if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname !== "/") {
6644
+ if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname.includes("/auth") || currentUrl.pathname.includes("/login") || currentUrl.pathname.includes("_____tmd_____")) {
6611
6645
  await capturedPage.goto(qwenUrl("/"), {
6612
6646
  waitUntil: "domcontentloaded",
6613
6647
  timeout: Math.min(config.timeouts.navigation, boundedTimeoutMs)
@@ -6923,7 +6957,11 @@ function priorityOrderForEviction(accountIds) {
6923
6957
  const ranked = getAccountsByPriority(accountIds.map((id) => ({ id })));
6924
6958
  const rank = /* @__PURE__ */ new Map();
6925
6959
  for (const [index, account] of ranked.entries()) {
6926
- 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
+ }
6927
6965
  }
6928
6966
  return orderContextsForEviction(
6929
6967
  accountIds,
@@ -6960,14 +6998,15 @@ async function closeIdlePlaywrightAccounts(idleMs) {
6960
6998
  }
6961
6999
  return closed;
6962
7000
  }
6963
- async function evictIdlePlaywrightContextsToLimit() {
6964
- const max = config.playwright.maxActiveContexts;
6965
- 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;
6966
7005
  if (accountPages.size <= max) return 0;
6967
7006
  const candidates = priorityOrderForEviction(
6968
7007
  Array.from(accountPages.keys()).filter((accountId) => {
6969
7008
  if (isAccountServingStream(accountId)) return false;
6970
- if (isAccountRecentlyActive(accountId, 3e4)) return false;
7009
+ if (hasActiveAccountLease(accountId)) return false;
6971
7010
  const mutex = accountMutexes.get(accountId);
6972
7011
  return mutex?.isIdle();
6973
7012
  })
@@ -6980,6 +7019,7 @@ async function evictIdlePlaywrightContextsToLimit() {
6980
7019
  for (const candidate of candidates) {
6981
7020
  if (accountPages.size <= max) break;
6982
7021
  if (isAccountServingStream(candidate.accountId)) continue;
7022
+ if (hasActiveAccountLease(candidate.accountId)) continue;
6983
7023
  const mutex = accountMutexes.get(candidate.accountId);
6984
7024
  if (!mutex?.isIdle()) continue;
6985
7025
  await closePlaywrightForAccount(candidate.accountId).catch((error) => {
@@ -7818,6 +7858,7 @@ var init_model_registry = __esm({
7818
7858
  var captcha_coordinator_exports = {};
7819
7859
  __export(captcha_coordinator_exports, {
7820
7860
  isCaptchaRecoveryActive: () => isCaptchaRecoveryActive,
7861
+ isCaptchaRecoveryInBackoff: () => isCaptchaRecoveryInBackoff,
7821
7862
  recoverBaxiaCaptcha: () => recoverBaxiaCaptcha,
7822
7863
  solveChallengeOnPage: () => solveChallengeOnPage
7823
7864
  });
@@ -7856,6 +7897,11 @@ async function solveChallengeOnPage(page, challengeUrl, waitForMs = config.captc
7856
7897
  }
7857
7898
  }
7858
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
+ }
7859
7905
  async function recoverBaxiaCaptcha(accountId, label, options = {}) {
7860
7906
  if (!config.captcha.enabled || !accountId) return false;
7861
7907
  const solver = "baxia";
@@ -7873,17 +7919,34 @@ async function recoverBaxiaCaptcha(accountId, label, options = {}) {
7873
7919
  const challengeUrl = options.challengeBody ? extractBaxiaChallengeUrl(options.challengeBody, config.qwen.baseUrl) : null;
7874
7920
  activeCaptchaRecoveries.add(accountId);
7875
7921
  try {
7876
- const { abortActiveBrowserStreamsForAccount: abortActiveBrowserStreamsForAccount2 } = await Promise.resolve().then(() => (init_qwen(), qwen_exports));
7877
- abortActiveBrowserStreamsForAccount2(
7878
- accountId,
7879
- "Account stream interrupted by captcha challenge recovery"
7880
- );
7881
- } catch {
7882
- }
7883
- try {
7922
+ const { hasActiveBrowserStreamsForAccount: hasActiveBrowserStreamsForAccount2 } = await Promise.resolve().then(() => (init_qwen(), qwen_exports));
7923
+ const hasConcurrentStream = hasActiveBrowserStreamsForAccount2(accountId);
7884
7924
  const solved = await withAccountPage(
7885
7925
  accountId,
7886
- (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
+ },
7887
7950
  solverOperationTimeoutMs,
7888
7951
  solverOperationTimeoutMs,
7889
7952
  false
@@ -8682,6 +8745,7 @@ __export(qwen_exports, {
8682
8745
  getLogicalThreadState: () => getLogicalThreadState,
8683
8746
  getQwenErrorCode: () => getQwenErrorCode,
8684
8747
  getSessionParent: () => getSessionParent,
8748
+ hasActiveBrowserStreamsForAccount: () => hasActiveBrowserStreamsForAccount,
8685
8749
  invalidateLogicalThreadParent: () => invalidateLogicalThreadParent,
8686
8750
  isChatSessionActive: () => isChatSessionActive,
8687
8751
  isRetryableFetchErrorMessage: () => isRetryableFetchErrorMessage,
@@ -8703,6 +8767,14 @@ __export(qwen_exports, {
8703
8767
  });
8704
8768
  import crypto5 from "crypto";
8705
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
+ }
8706
8778
  function abortActiveBrowserStreamsForAccount(accountId, reason = "Account browser stream interrupted (page context unloaded)") {
8707
8779
  let count = 0;
8708
8780
  for (const [requestId, state] of browserStreamStates) {
@@ -9480,7 +9552,7 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
9480
9552
  const onFrameNavigated = (frame) => {
9481
9553
  if (!settled && typeof page.mainFrame === "function" && frame === page.mainFrame()) {
9482
9554
  const url2 = typeof frame.url === "function" ? frame.url() : String(frame.url || "");
9483
- 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")) {
9484
9556
  return;
9485
9557
  }
9486
9558
  handleBrowserStreamEvent(requestId, {
@@ -9501,10 +9573,12 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
9501
9573
  };
9502
9574
  page.on("framenavigated", onFrameNavigated);
9503
9575
  page.on("close", onPageClosed);
9576
+ page.on("crash", onPageClosed);
9504
9577
  pageCleanup = () => {
9505
9578
  try {
9506
9579
  page.off?.("framenavigated", onFrameNavigated);
9507
9580
  page.off?.("close", onPageClosed);
9581
+ page.off?.("crash", onPageClosed);
9508
9582
  } catch {
9509
9583
  }
9510
9584
  };
@@ -11292,6 +11366,7 @@ async function deleteChatsForConfiguredAccounts(keepBrowserOpen = false) {
11292
11366
  const account = accounts[i];
11293
11367
  const currentIdx = i + 1;
11294
11368
  const totalCount = accounts.length;
11369
+ const wasInitialized = isPlaywrightInitialized(account.id);
11295
11370
  try {
11296
11371
  const ok = await deleteChatsForAccount(account, currentIdx, totalCount);
11297
11372
  if (ok) succeeded++;
@@ -11301,7 +11376,7 @@ async function deleteChatsForConfiguredAccounts(keepBrowserOpen = false) {
11301
11376
  error instanceof Error ? error.message : String(error)
11302
11377
  );
11303
11378
  } finally {
11304
- if (!keepBrowserOpen) {
11379
+ if ((!keepBrowserOpen || !wasInitialized) && !isAuthMockEnabled()) {
11305
11380
  const { closePlaywrightForAccount: closePlaywrightForAccount2 } = await Promise.resolve().then(() => (init_playwright(), playwright_exports));
11306
11381
  await closePlaywrightForAccount2(account.id).catch(() => {
11307
11382
  });