qwenproxy-cli 1.6.10 → 1.6.12

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
@@ -266,7 +266,7 @@ var init_config = __esm({
266
266
  // Version header on Qwen API requests = the deployed web bundle version
267
267
  // (fallback when dynamic DOM detection is not yet completed).
268
268
  // Override via env when Qwen ships a new bundle.
269
- QWEN_WEB_VERSION: z.string().default("0.3.26"),
269
+ QWEN_WEB_VERSION: z.string().default("0.3.43"),
270
270
  // Controls bx-ua/bx-umidtoken injection on the GENERAL API paths
271
271
  // (chats/new, settings): those work without them (live-probed). The
272
272
  // completions path is the exception — the 0.2.86 HAR shows the real
@@ -309,6 +309,7 @@ var init_config = __esm({
309
309
  CAPTCHA_SOLVER_ENABLED: z.string().default("true"),
310
310
  CAPTCHA_SOLVER_MAX_ATTEMPTS: z.string().default("3"),
311
311
  CAPTCHA_SOLVER_TIMEOUT_MS: z.string().default("15000"),
312
+ CAPTCHA_SLIDER_TIMEOUT_MS: z.string().default("8000"),
312
313
  CAPTCHA_SOLVER_RETRY_DELAY_MS: z.string().default("1000"),
313
314
  CAPTCHA_SOLVER_SETTLE_MS: z.string().default("2000"),
314
315
  CAPTCHA_ACCOUNT_COOLDOWN_MS: z.string().default("120000"),
@@ -460,6 +461,8 @@ var init_config = __esm({
460
461
  enabled: env.CAPTCHA_SOLVER_ENABLED === "true",
461
462
  maxAttempts: Math.max(1, Math.min(5, parseInt(env.CAPTCHA_SOLVER_MAX_ATTEMPTS))),
462
463
  timeoutMs: Math.max(0, parseInt(env.CAPTCHA_SOLVER_TIMEOUT_MS)),
464
+ /** Max wait for a challenge slider to become visible (slow WAF renders). */
465
+ sliderTimeoutMs: Math.max(500, parseInt(env.CAPTCHA_SLIDER_TIMEOUT_MS)),
463
466
  retryDelayMs: Math.max(0, parseInt(env.CAPTCHA_SOLVER_RETRY_DELAY_MS)),
464
467
  settleMs: Math.max(0, parseInt(env.CAPTCHA_SOLVER_SETTLE_MS)),
465
468
  /** Rest an account whose challenge could not be cleared before reusing it. */
@@ -3619,6 +3622,294 @@ function extractBaxiaChallengeUrl(body, baseUrl) {
3619
3622
  }
3620
3623
  return null;
3621
3624
  }
3625
+ async function findVisibleLocatorWithin(page, selectors, timeoutMs) {
3626
+ const deadline = Date.now() + Math.max(0, timeoutMs);
3627
+ while (true) {
3628
+ const found = await findVisibleLocator(page, selectors);
3629
+ if (found) return found;
3630
+ if (Date.now() >= deadline) return null;
3631
+ await sleep(Math.min(150, Math.max(1, deadline - Date.now())));
3632
+ }
3633
+ }
3634
+ async function waitForHidden(locator, timeoutMs) {
3635
+ const deadline = Date.now() + Math.max(0, timeoutMs);
3636
+ while (true) {
3637
+ if (!await isVisible(locator)) return true;
3638
+ if (Date.now() >= deadline) return false;
3639
+ await sleep(Math.min(100, Math.max(1, deadline - Date.now())));
3640
+ }
3641
+ }
3642
+ async function computeAliyunPuzzleOffset(page) {
3643
+ return page.evaluate(() => {
3644
+ const background = document.querySelector(
3645
+ "#aliyunCaptcha-img"
3646
+ );
3647
+ const piece = document.querySelector(
3648
+ "#aliyunCaptcha-puzzle"
3649
+ );
3650
+ if (!background || !piece) {
3651
+ return Promise.resolve({ ok: false, reason: "missing puzzle images" });
3652
+ }
3653
+ const compute = () => {
3654
+ try {
3655
+ const width = background.naturalWidth;
3656
+ const height = background.naturalHeight;
3657
+ const pieceWidth = piece.naturalWidth;
3658
+ const pieceHeight = piece.naturalHeight;
3659
+ if (!width || !height || !pieceWidth || !pieceHeight) {
3660
+ return { ok: false, reason: "image dimensions unavailable" };
3661
+ }
3662
+ const backgroundCanvas = document.createElement("canvas");
3663
+ backgroundCanvas.id = "captcha-solver-canvas";
3664
+ backgroundCanvas.width = width;
3665
+ backgroundCanvas.height = height;
3666
+ const backgroundContext = backgroundCanvas.getContext("2d");
3667
+ if (!backgroundContext) {
3668
+ return { ok: false, reason: "no background context" };
3669
+ }
3670
+ const pieceCanvas = document.createElement("canvas");
3671
+ pieceCanvas.id = "captcha-solver-canvas";
3672
+ pieceCanvas.width = pieceWidth;
3673
+ pieceCanvas.height = pieceHeight;
3674
+ const pieceContext = pieceCanvas.getContext("2d");
3675
+ if (!pieceContext) {
3676
+ return { ok: false, reason: "no piece context" };
3677
+ }
3678
+ try {
3679
+ backgroundContext.drawImage(background, 0, 0);
3680
+ } catch {
3681
+ return { ok: false, reason: "background canvas tainted" };
3682
+ }
3683
+ try {
3684
+ pieceContext.drawImage(piece, 0, 0);
3685
+ } catch {
3686
+ return { ok: false, reason: "puzzle canvas tainted" };
3687
+ }
3688
+ const backgroundData = backgroundContext.getImageData(
3689
+ 0,
3690
+ 0,
3691
+ width,
3692
+ height
3693
+ ).data;
3694
+ const pieceData = pieceContext.getImageData(
3695
+ 0,
3696
+ 0,
3697
+ pieceWidth,
3698
+ pieceHeight
3699
+ ).data;
3700
+ let pieceMinX = pieceWidth;
3701
+ let pieceMaxX = -1;
3702
+ let pieceMinY = pieceHeight;
3703
+ let pieceMaxY = -1;
3704
+ for (let y = 0; y < pieceHeight; y++) {
3705
+ for (let x = 0; x < pieceWidth; x++) {
3706
+ if (pieceData[(y * pieceWidth + x) * 4 + 3] > 100) {
3707
+ if (x < pieceMinX) pieceMinX = x;
3708
+ if (x > pieceMaxX) pieceMaxX = x;
3709
+ if (y < pieceMinY) pieceMinY = y;
3710
+ if (y > pieceMaxY) pieceMaxY = y;
3711
+ }
3712
+ }
3713
+ }
3714
+ const puzzleWidth = pieceMaxX - pieceMinX + 1;
3715
+ if (puzzleWidth <= 0) {
3716
+ return { ok: false, reason: "empty puzzle piece" };
3717
+ }
3718
+ const yStart = Math.max(1, Math.min(height - 2, pieceMinY + 5));
3719
+ const yEnd = Math.max(
3720
+ yStart,
3721
+ Math.min(height - 2, pieceMaxY - 5)
3722
+ );
3723
+ const columnGradients = new Float32Array(width);
3724
+ for (let x = 1; x < width - 1; x++) {
3725
+ let gradient = 0;
3726
+ for (let y = yStart; y <= yEnd; y++) {
3727
+ const left = (y * width + x - 1) * 4;
3728
+ const right = (y * width + x + 1) * 4;
3729
+ gradient += Math.abs(backgroundData[right] - backgroundData[left]) + Math.abs(backgroundData[right + 1] - backgroundData[left + 1]) + Math.abs(backgroundData[right + 2] - backgroundData[left + 2]);
3730
+ }
3731
+ columnGradients[x] = gradient;
3732
+ }
3733
+ let bestX = 0;
3734
+ let bestScore = 0;
3735
+ const minCandidateX = 30;
3736
+ const maxCandidateX = width - puzzleWidth - 20;
3737
+ for (let candidateX = minCandidateX; candidateX <= maxCandidateX; candidateX++) {
3738
+ let leftMax = 0;
3739
+ for (let dx = -2; dx <= 2; dx++) {
3740
+ const value = columnGradients[candidateX + pieceMinX + dx] || 0;
3741
+ if (value > leftMax) leftMax = value;
3742
+ }
3743
+ let rightMax = 0;
3744
+ for (let dx = -3; dx <= 1; dx++) {
3745
+ const value = columnGradients[candidateX + pieceMaxX + dx] || 0;
3746
+ if (value > rightMax) rightMax = value;
3747
+ }
3748
+ const score = Math.sqrt(leftMax * rightMax);
3749
+ if (score > bestScore) {
3750
+ bestScore = score;
3751
+ bestX = candidateX;
3752
+ }
3753
+ }
3754
+ if (bestScore <= 0) {
3755
+ return { ok: false, reason: "no matching hole edge" };
3756
+ }
3757
+ return {
3758
+ ok: true,
3759
+ offsetX: bestX - pieceMinX,
3760
+ naturalWidth: width
3761
+ };
3762
+ } catch (error) {
3763
+ return {
3764
+ ok: false,
3765
+ reason: error instanceof Error ? error.message : String(error)
3766
+ };
3767
+ }
3768
+ };
3769
+ return new Promise((resolve) => {
3770
+ if (background.complete && piece.complete) {
3771
+ resolve(compute());
3772
+ return;
3773
+ }
3774
+ let settled = false;
3775
+ const finish = () => {
3776
+ if (settled) return;
3777
+ settled = true;
3778
+ resolve(compute());
3779
+ };
3780
+ const fail = () => {
3781
+ if (settled) return;
3782
+ settled = true;
3783
+ resolve({ ok: false, reason: "puzzle image load error" });
3784
+ };
3785
+ background.addEventListener("load", finish, { once: true });
3786
+ piece.addEventListener("load", finish, { once: true });
3787
+ background.addEventListener("error", fail, { once: true });
3788
+ piece.addEventListener("error", fail, { once: true });
3789
+ setTimeout(() => {
3790
+ if (settled) return;
3791
+ settled = true;
3792
+ resolve({ ok: false, reason: "puzzle image load timeout" });
3793
+ }, 2e3);
3794
+ });
3795
+ });
3796
+ }
3797
+ async function solveAliyunWafCaptcha(page, options = {}) {
3798
+ const waitForMs = Math.max(0, options.waitForMs ?? 0);
3799
+ const maxAttempts = Math.max(
3800
+ 1,
3801
+ Math.min(5, options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS)
3802
+ );
3803
+ const retryDelayMs = Math.max(
3804
+ 0,
3805
+ options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS
3806
+ );
3807
+ const settleMs = Math.max(0, options.settleMs ?? DEFAULT_SETTLE_MS);
3808
+ const sliderTimeoutMs = Math.max(
3809
+ 500,
3810
+ options.sliderTimeoutMs ?? DEFAULT_SLIDER_TIMEOUT_MS
3811
+ );
3812
+ const detected = await findVisibleLocatorWithin(
3813
+ page,
3814
+ ALIYUN_WAF_BLOCK_SELECTORS,
3815
+ waitForMs
3816
+ );
3817
+ if (!detected) return false;
3818
+ return withCaptchaMouseLock(async () => {
3819
+ const block = detected.locator;
3820
+ if (!await isVisible(block)) return false;
3821
+ logBaxiaCaptcha("aliyun_detected", { selector: detected.selector });
3822
+ let lastReason = "unknown";
3823
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
3824
+ try {
3825
+ const offset = await computeAliyunPuzzleOffset(page);
3826
+ if (!offset.ok || typeof offset.offsetX !== "number" || typeof offset.naturalWidth !== "number" || offset.naturalWidth <= 0) {
3827
+ lastReason = offset.reason || "offset_unavailable";
3828
+ logBaxiaCaptcha("aliyun_offset_failed", {
3829
+ attempt,
3830
+ reason: lastReason
3831
+ });
3832
+ } else {
3833
+ const slider = page.locator(ALIYUN_PUZZLE_SLIDER_SELECTOR).first();
3834
+ const track = page.locator(ALIYUN_PUZZLE_TRACK_SELECTOR).first();
3835
+ await slider.waitFor({ state: "visible", timeout: sliderTimeoutMs });
3836
+ const [sliderBox, trackBox] = await Promise.all([
3837
+ slider.boundingBox(),
3838
+ track.boundingBox()
3839
+ ]);
3840
+ if (!sliderBox || !trackBox || trackBox.width <= 0) {
3841
+ lastReason = "bounds_unavailable";
3842
+ logBaxiaCaptcha("aliyun_offset_failed", {
3843
+ attempt,
3844
+ reason: lastReason
3845
+ });
3846
+ } else {
3847
+ const scale = trackBox.width / offset.naturalWidth;
3848
+ const dragDistance = Math.round(offset.offsetX * scale);
3849
+ logBaxiaCaptcha("aliyun_geometry", {
3850
+ attempt,
3851
+ offset: offset.offsetX,
3852
+ scale: Number(scale.toFixed(2)),
3853
+ distance: dragDistance
3854
+ });
3855
+ if (dragDistance <= 0) {
3856
+ lastReason = "non_positive_distance";
3857
+ } else {
3858
+ const startX = sliderBox.x + sliderBox.width / 2;
3859
+ const startY = sliderBox.y + sliderBox.height / 2;
3860
+ await humanDrag(
3861
+ page,
3862
+ startX,
3863
+ startY,
3864
+ startX + dragDistance,
3865
+ startY
3866
+ );
3867
+ if (await waitForHidden(block, settleMs)) {
3868
+ logBaxiaCaptcha(
3869
+ "aliyun_solve_succeeded",
3870
+ { attempt, distance: dragDistance },
3871
+ true
3872
+ );
3873
+ return true;
3874
+ }
3875
+ lastReason = "not_solved";
3876
+ }
3877
+ }
3878
+ }
3879
+ } catch (error) {
3880
+ lastReason = error instanceof Error ? error.name : "UnknownError";
3881
+ logBaxiaCaptcha("aliyun_offset_failed", {
3882
+ attempt,
3883
+ error: lastReason,
3884
+ detail: sanitizeCaptchaErrorDetail(error)
3885
+ });
3886
+ }
3887
+ if (attempt < maxAttempts && retryDelayMs > 0) {
3888
+ await sleep(retryDelayMs);
3889
+ }
3890
+ }
3891
+ logBaxiaCaptcha(
3892
+ "aliyun_solve_failed",
3893
+ { attempts: maxAttempts, reason: lastReason },
3894
+ true
3895
+ );
3896
+ return false;
3897
+ });
3898
+ }
3899
+ async function solveVisibleCaptcha(page, options = {}) {
3900
+ if (typeof page.isClosed === "function" && page.isClosed()) return false;
3901
+ const totalWaitMs = Math.max(0, options.waitForMs ?? 0);
3902
+ const aliyunWaitMs = Math.min(totalWaitMs, ALIYUN_PROBE_MAX_WAIT_MS);
3903
+ const startedAt = Date.now();
3904
+ if (await solveAliyunWafCaptcha(page, { ...options, waitForMs: aliyunWaitMs })) {
3905
+ return true;
3906
+ }
3907
+ const remainingWaitMs = Math.max(
3908
+ 0,
3909
+ totalWaitMs - (Date.now() - startedAt)
3910
+ );
3911
+ return solveBaxiaCaptcha(page, { ...options, waitForMs: remainingWaitMs });
3912
+ }
3622
3913
  async function isVisible(locator) {
3623
3914
  return locator.isVisible().catch(() => false);
3624
3915
  }
@@ -3888,7 +4179,7 @@ function startBaxiaCaptchaWatcher(page, timeoutMs, options = {}) {
3888
4179
  promise
3889
4180
  };
3890
4181
  }
3891
- var BAXIA_DIALOG_SELECTOR, BAXIA_CONTENT_SELECTOR, CAPTCHA_EVENT_EMOJI, captchaDebugEnabled, BAXIA_IFRAME_SELECTORS, BAXIA_DOCUMENT_SELECTORS, BAXIA_IFRAME_SELECTOR, BAXIA_SLIDER_SELECTOR, BAXIA_TRACK_SELECTOR, BAXIA_SUCCESS_SELECTOR, BAXIA_RELOAD_SELECTOR, DEFAULT_MAX_ATTEMPTS, DEFAULT_RETRY_DELAY_MS, DEFAULT_SETTLE_MS, DEFAULT_SLIDER_TIMEOUT_MS, captchaMouseLock;
4182
+ var BAXIA_DIALOG_SELECTOR, BAXIA_CONTENT_SELECTOR, CAPTCHA_EVENT_EMOJI, captchaDebugEnabled, BAXIA_IFRAME_SELECTORS, BAXIA_DOCUMENT_SELECTORS, BAXIA_IFRAME_SELECTOR, BAXIA_SLIDER_SELECTOR, BAXIA_TRACK_SELECTOR, BAXIA_SUCCESS_SELECTOR, BAXIA_RELOAD_SELECTOR, DEFAULT_MAX_ATTEMPTS, DEFAULT_RETRY_DELAY_MS, DEFAULT_SETTLE_MS, DEFAULT_SLIDER_TIMEOUT_MS, ALIYUN_WAF_CAPTCHA_SELECTOR, ALIYUN_WAF_CAPTCHA_WIDGET, ALIYUN_WAF_BLOCK_SELECTORS, ALIYUN_PUZZLE_SLIDER_SELECTOR, ALIYUN_PUZZLE_TRACK_SELECTOR, ALIYUN_PROBE_MAX_WAIT_MS, captchaMouseLock;
3892
4183
  var init_captcha_solver = __esm({
3893
4184
  "src/services/captcha-solver.ts"() {
3894
4185
  "use strict";
@@ -3914,14 +4205,20 @@ var init_captcha_solver = __esm({
3914
4205
  solve_failed: "\u274C",
3915
4206
  recovery_succeeded: "\u2705",
3916
4207
  recovery_not_solved: "\u26A0\uFE0F",
3917
- recovery_failed: "\u274C"
4208
+ recovery_failed: "\u274C",
4209
+ aliyun_detected: "\u{1F9E9}",
4210
+ aliyun_offset_failed: "\u{1F4D0}",
4211
+ aliyun_geometry: "\u{1F4D0}",
4212
+ aliyun_solve_succeeded: "\u2705",
4213
+ aliyun_solve_failed: "\u274C"
3918
4214
  };
3919
4215
  captchaDebugEnabled = process.env.CAPTCHA_DEBUG === "true";
3920
4216
  BAXIA_IFRAME_SELECTORS = [
3921
4217
  `${BAXIA_DIALOG_SELECTOR} ${BAXIA_CONTENT_SELECTOR} iframe`,
3922
4218
  `${BAXIA_CONTENT_SELECTOR} iframe`,
3923
4219
  "iframe#baxia-dialog-content",
3924
- 'iframe[src*="_____tmd_____/punish"]'
4220
+ 'iframe[src*="_____tmd_____/punish"]',
4221
+ 'iframe[src*="nocaptcha"]'
3925
4222
  ];
3926
4223
  BAXIA_DOCUMENT_SELECTORS = [
3927
4224
  "#nocaptcha",
@@ -3929,14 +4226,23 @@ var init_captcha_solver = __esm({
3929
4226
  "#baxia-punish"
3930
4227
  ];
3931
4228
  BAXIA_IFRAME_SELECTOR = BAXIA_IFRAME_SELECTORS.join(", ");
3932
- BAXIA_SLIDER_SELECTOR = "#nc_1_n1z, .nc_1_n1z, .btn_slide, .nc_wrapper .btn_slide, ._nc .btn_slide, .nc-container .btn_slide";
3933
- BAXIA_TRACK_SELECTOR = "#nc_1_n1t, .nc_scale, .nc_wrapper .nc_scale, ._nc .nc_scale, .nc-container .nc_scale";
4229
+ BAXIA_SLIDER_SELECTOR = '#nc_1_n1z, .nc_1_n1z, .btn_slide, .nc_iconfont.btn_slide, span[id*="_n1z"], .nc_wrapper .btn_slide, ._nc .btn_slide, .nc-container .btn_slide';
4230
+ BAXIA_TRACK_SELECTOR = '#nc_1_n1t, .nc_scale, span[id*="_n1t"], .nc_wrapper .nc_scale, ._nc .nc_scale, .nc-container .nc_scale';
3934
4231
  BAXIA_SUCCESS_SELECTOR = '.btn_ok, .nc_ok, .nc_success, .nc_result, .nc_wrapper.nc-success, .nc_wrapper.success, [data-nc-lang="SUCCESS"], [data-nc-lang="success"], #nc-loading-circle';
3935
4232
  BAXIA_RELOAD_SELECTOR = '#nc_1_refresh1, .errloading a, .nc-container .errloading a, .btn_refresh, .clickCaptcha_text .btn_refresh, [data-nc-lang="REFRESH"], a[id*="refresh"]';
3936
4233
  DEFAULT_MAX_ATTEMPTS = 3;
3937
4234
  DEFAULT_RETRY_DELAY_MS = 1e3;
3938
4235
  DEFAULT_SETTLE_MS = 2e3;
3939
4236
  DEFAULT_SLIDER_TIMEOUT_MS = 8e3;
4237
+ ALIYUN_WAF_CAPTCHA_SELECTOR = "#waf_nc_block";
4238
+ ALIYUN_WAF_CAPTCHA_WIDGET = "#aliyunCaptcha-window-embed";
4239
+ ALIYUN_WAF_BLOCK_SELECTORS = [
4240
+ ALIYUN_WAF_CAPTCHA_SELECTOR,
4241
+ ALIYUN_WAF_CAPTCHA_WIDGET
4242
+ ];
4243
+ ALIYUN_PUZZLE_SLIDER_SELECTOR = "#aliyunCaptcha-sliding-slider";
4244
+ ALIYUN_PUZZLE_TRACK_SELECTOR = "#aliyunCaptcha-sliding-body";
4245
+ ALIYUN_PROBE_MAX_WAIT_MS = 2e3;
3940
4246
  captchaMouseLock = Promise.resolve();
3941
4247
  }
3942
4248
  });
@@ -4090,6 +4396,14 @@ function updateQwenWebVersion(version) {
4090
4396
  function getQwenWebVersion() {
4091
4397
  return dynamicWebVersion || config.qwen.webVersion;
4092
4398
  }
4399
+ function updateQwenBxVersion(version) {
4400
+ if (version && typeof version === "string" && /^\d+\.\d+\.\d+/.test(version)) {
4401
+ dynamicBxVersion = version.trim();
4402
+ }
4403
+ }
4404
+ function getQwenBxVersion() {
4405
+ return dynamicBxVersion || config.auth.bxV;
4406
+ }
4093
4407
  function buildQwenRequestHeaders(opts) {
4094
4408
  const headers = {
4095
4409
  ...opts.extra ?? {},
@@ -4106,7 +4420,7 @@ function buildQwenRequestHeaders(opts) {
4106
4420
  Connection: "keep-alive",
4107
4421
  "User-Agent": opts.userAgent || getDefaultQwenUserAgent(),
4108
4422
  "X-Request-Id": uuidv4(),
4109
- "bx-v": opts.bxV || "2.5.37",
4423
+ "bx-v": opts.bxV || getQwenBxVersion(),
4110
4424
  source: "web",
4111
4425
  version: opts.version || getQwenWebVersion(),
4112
4426
  timezone: opts.extra?.timezone || opts.extra?.Timezone || formatAsciiTimezone(),
@@ -4130,7 +4444,7 @@ function buildQwenRequestHeaders(opts) {
4130
4444
  }
4131
4445
  return headers;
4132
4446
  }
4133
- var dynamicWebVersion, QWEN_WEB_VERSION, DEFAULT_QWEN_USER_AGENT, QWEN_TIMEZONE_HEADER;
4447
+ var dynamicWebVersion, dynamicBxVersion, QWEN_WEB_VERSION, DEFAULT_QWEN_USER_AGENT, QWEN_TIMEZONE_HEADER;
4134
4448
  var init_qwen_headers = __esm({
4135
4449
  "src/services/qwen-headers.ts"() {
4136
4450
  "use strict";
@@ -4139,6 +4453,7 @@ var init_qwen_headers = __esm({
4139
4453
  init_fingerprint();
4140
4454
  init_timezone();
4141
4455
  dynamicWebVersion = null;
4456
+ dynamicBxVersion = null;
4142
4457
  QWEN_WEB_VERSION = config.qwen.webVersion;
4143
4458
  DEFAULT_QWEN_USER_AGENT = getDefaultQwenUserAgent(151);
4144
4459
  QWEN_TIMEZONE_HEADER = (/* @__PURE__ */ new Date()).toString().split(" (")[0];
@@ -4752,11 +5067,12 @@ async function getOrLaunchSharedBrowser(browserType = "chromium", headless = con
4752
5067
  async function clearVisibleChallenge(page) {
4753
5068
  if (page.isClosed()) return;
4754
5069
  if (config.captcha.enabled) {
4755
- await solveBaxiaCaptcha(page, {
5070
+ await solveVisibleCaptcha(page, {
4756
5071
  waitForMs: 0,
4757
5072
  maxAttempts: config.captcha.maxAttempts,
4758
5073
  retryDelayMs: config.captcha.retryDelayMs,
4759
- settleMs: config.captcha.settleMs
5074
+ settleMs: config.captcha.settleMs,
5075
+ sliderTimeoutMs: config.captcha.sliderTimeoutMs
4760
5076
  }).catch(() => false);
4761
5077
  }
4762
5078
  try {
@@ -5369,6 +5685,7 @@ async function getBasicHeaders(accountId) {
5369
5685
  mobile = data.mobile ? "?1" : "?0";
5370
5686
  }
5371
5687
  let bundleVersion = null;
5688
+ let baxiaVersion = null;
5372
5689
  try {
5373
5690
  const el = document.querySelector('link[href*="qwen-chat-fe"], script[src*="qwen-chat-fe"], img[src*="qwen-chat-fe"]');
5374
5691
  const src = el ? el.getAttribute("href") || el.getAttribute("src") || "" : "";
@@ -5376,7 +5693,14 @@ async function getBasicHeaders(accountId) {
5376
5693
  if (match) bundleVersion = match[1];
5377
5694
  } catch {
5378
5695
  }
5379
- return { ua, secChUa, platform, mobile, bundleVersion };
5696
+ try {
5697
+ const baxia = window.baxiaCommon;
5698
+ if (typeof baxia?.version === "string") {
5699
+ baxiaVersion = baxia.version;
5700
+ }
5701
+ } catch {
5702
+ }
5703
+ return { ua, secChUa, platform, mobile, bundleVersion, baxiaVersion };
5380
5704
  }),
5381
5705
  config.timeouts.page,
5382
5706
  `User-agent lookup timed out for ${accountId}`
@@ -5385,6 +5709,9 @@ async function getBasicHeaders(accountId) {
5385
5709
  if (nav.bundleVersion) {
5386
5710
  updateQwenWebVersion(nav.bundleVersion);
5387
5711
  }
5712
+ if (nav.baxiaVersion) {
5713
+ updateQwenBxVersion(nav.baxiaVersion);
5714
+ }
5388
5715
  cachedUserAgents.set(accountId, userAgent);
5389
5716
  const hints = getHeaderCache(accountId).headers;
5390
5717
  clientHints = {
@@ -5416,7 +5743,7 @@ async function getBasicHeaders(accountId) {
5416
5743
  markAccountHeadersReady(accountId);
5417
5744
  const bxUa2 = cache3.headers["bx-ua"];
5418
5745
  const bxUmidtoken2 = cache3.headers["bx-umidtoken"];
5419
- const bxV2 = cache3.headers["bx-v"] || "2.5.37";
5746
+ const bxV2 = cache3.headers["bx-v"] || getQwenBxVersion();
5420
5747
  const cookie2 = await getCookies(accountId);
5421
5748
  return { cookie: cookie2, userAgent, bxV: bxV2, bxUa: bxUa2, bxUmidtoken: bxUmidtoken2, ...clientHints };
5422
5749
  }
@@ -5427,7 +5754,7 @@ async function getBasicHeaders(accountId) {
5427
5754
  cookieCaches.set(accountId, { cookie: cookie2, timestamp: Date.now() });
5428
5755
  const bxUa2 = cache3.headers["bx-ua"];
5429
5756
  const bxUmidtoken2 = cache3.headers["bx-umidtoken"];
5430
- const bxV2 = cache3.headers["bx-v"] || "2.5.37";
5757
+ const bxV2 = cache3.headers["bx-v"] || getQwenBxVersion();
5431
5758
  cache3.lastRefresh = Date.now();
5432
5759
  markAccountHeadersReady(accountId);
5433
5760
  console.log(
@@ -5447,14 +5774,15 @@ async function getBasicHeaders(accountId) {
5447
5774
  `\u26A0\uFE0F [Playwright] Header refresh failed for ${accountId}; retaining the previous valid cache: ${getErrorMessage(error)}`
5448
5775
  );
5449
5776
  }
5450
- } else if (hadUsableHeaders && headersAge > HEADER_CACHE_TTL * HEADER_REFRESH_THRESHOLD && !cache3.refreshInProgress) {
5451
- cache3.refreshInProgress = true;
5777
+ } 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
5778
+ // the account's page. Doing that while the account is serving a stream
5779
+ // would mutate the live conversation (and can navigate the SPA), so the
5780
+ // proactive refresh waits until the account has no active lease.
5781
+ !hasActiveAccountLease(accountId)) {
5452
5782
  refreshHeadersInternal(accountId).catch((error) => {
5453
5783
  console.warn(
5454
5784
  `\u26A0\uFE0F [Playwright] Background header refresh failed for ${accountId}: ${getErrorMessage(error)}`
5455
5785
  );
5456
- }).finally(() => {
5457
- cache3.refreshInProgress = false;
5458
5786
  });
5459
5787
  }
5460
5788
  if (!hasRequiredQwenHeaders(cache3.headers)) {
@@ -5501,6 +5829,9 @@ async function getBasicHeaders(accountId) {
5501
5829
  if (persisted.version) {
5502
5830
  updateQwenWebVersion(persisted.version);
5503
5831
  }
5832
+ if (persisted.bxV) {
5833
+ updateQwenBxVersion(persisted.bxV);
5834
+ }
5504
5835
  cache3.lastRefresh = persisted.capturedAt;
5505
5836
  markAccountHeadersReady(accountId);
5506
5837
  }
@@ -5528,7 +5859,7 @@ async function getBasicHeaders(accountId) {
5528
5859
  markAccountHeadersReady(accountId);
5529
5860
  const bxUa = cache3.headers["bx-ua"];
5530
5861
  const bxUmidtoken = cache3.headers["bx-umidtoken"];
5531
- const bxV = cache3.headers["bx-v"] || "2.5.37";
5862
+ const bxV = cache3.headers["bx-v"] || getQwenBxVersion();
5532
5863
  cookieCaches.delete(accountId);
5533
5864
  const cookie = await getCookies(accountId, true);
5534
5865
  if (cache3.headers) {
@@ -5592,8 +5923,12 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
5592
5923
  if (accountPages.has(account.id)) {
5593
5924
  return;
5594
5925
  }
5595
- await evictIdlePlaywrightContextsToLimit().catch(() => {
5596
- });
5926
+ if (config.playwright.maxActiveContexts > 0) {
5927
+ await evictIdlePlaywrightContextsToLimit(
5928
+ Math.max(1, config.playwright.maxActiveContexts - 1)
5929
+ ).catch(() => {
5930
+ });
5931
+ }
5597
5932
  const profilePath = getAccountProfilePath(account.id);
5598
5933
  const fingerprint = getFingerprintProfile(account.id);
5599
5934
  const { engine, channel } = resolveBrowserEngine(browserType);
@@ -5769,6 +6104,9 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
5769
6104
  if (persisted.version) {
5770
6105
  updateQwenWebVersion(persisted.version);
5771
6106
  }
6107
+ if (persisted.bxV) {
6108
+ updateQwenBxVersion(persisted.bxV);
6109
+ }
5772
6110
  cache3.lastRefresh = persisted.capturedAt;
5773
6111
  markAccountHeadersReady(account.id);
5774
6112
  restoredFromDb = true;
@@ -5862,7 +6200,7 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
5862
6200
  if (!acctPage.isClosed()) {
5863
6201
  try {
5864
6202
  const currentUrl = new URL(acctPage.url());
5865
- if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname !== "/") {
6203
+ if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname.includes("/auth") || currentUrl.pathname.includes("/login") || currentUrl.pathname.includes("_____tmd_____")) {
5866
6204
  await acctPage.goto(qwenUrl("/"), {
5867
6205
  waitUntil: "domcontentloaded",
5868
6206
  timeout: config.timeouts.navigation
@@ -5880,6 +6218,8 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
5880
6218
  } finally {
5881
6219
  release();
5882
6220
  }
6221
+ await evictIdlePlaywrightContextsToLimit().catch(() => {
6222
+ });
5883
6223
  })();
5884
6224
  try {
5885
6225
  await initPromise;
@@ -6116,7 +6456,7 @@ async function startEmailOtpLogin(email, options = {}) {
6116
6456
  {
6117
6457
  email: normalizedEmail,
6118
6458
  requestId,
6119
- version: getQwenWebVersion() || "0.3.26",
6459
+ version: getQwenWebVersion(),
6120
6460
  timezone: formatAsciiTimezone()
6121
6461
  }
6122
6462
  ).catch((err) => ({ ok: false, error: err?.message || String(err) }));
@@ -6211,7 +6551,7 @@ async function verifyEmailOtpLogin(sessionId, code) {
6211
6551
  email: session.email,
6212
6552
  code: cleanCode,
6213
6553
  requestId,
6214
- version: getQwenWebVersion() || "0.3.26",
6554
+ version: getQwenWebVersion(),
6215
6555
  timezone: formatAsciiTimezone()
6216
6556
  }
6217
6557
  ).catch((err) => ({ ok: false, error: err?.message || String(err) }));
@@ -6258,8 +6598,8 @@ async function verifyEmailOtpLogin(sessionId, code) {
6258
6598
  saveAuthSession2(session.accountId, {
6259
6599
  cookie: `token=${accessToken}`,
6260
6600
  userAgent: session.fingerprint.userAgent,
6261
- bxV: "2.5.37",
6262
- version: getQwenWebVersion() || "0.3.26",
6601
+ bxV: getQwenBxVersion(),
6602
+ version: getQwenWebVersion(),
6263
6603
  userId: userId || void 0,
6264
6604
  tokenExpiresAt: exp || void 0,
6265
6605
  refreshToken: refreshToken || void 0,
@@ -6369,7 +6709,7 @@ async function loginToQwen(accountId, email, password) {
6369
6709
  saveAuthSession2(accountId, {
6370
6710
  cookie: cookieStr,
6371
6711
  userAgent: cache3.headers["user-agent"] || "",
6372
- bxV: cache3.headers["bx-v"] || "2.5.37",
6712
+ bxV: cache3.headers["bx-v"] || getQwenBxVersion(),
6373
6713
  bxUa: cache3.headers["bx-ua"] || "",
6374
6714
  bxUmidtoken: cache3.headers["bx-umidtoken"] || "",
6375
6715
  secChUa: cache3.headers["sec-ch-ua"] || void 0,
@@ -6441,7 +6781,7 @@ async function loginToQwen(accountId, email, password) {
6441
6781
  saveAuthSession2(accountId, {
6442
6782
  cookie: cookieStr,
6443
6783
  userAgent: cache3.headers["user-agent"] || "",
6444
- bxV: cache3.headers["bx-v"] || "2.5.37",
6784
+ bxV: cache3.headers["bx-v"] || getQwenBxVersion(),
6445
6785
  bxUa: cache3.headers["bx-ua"] || "",
6446
6786
  bxUmidtoken: cache3.headers["bx-umidtoken"] || "",
6447
6787
  secChUa: cache3.headers["sec-ch-ua"] || void 0,
@@ -6512,7 +6852,7 @@ async function loginViaApi(page, email, password) {
6512
6852
  }
6513
6853
  const signinUrl = "https://auth.qwen.ai/api/v2/auths/signin";
6514
6854
  const requestId = crypto5.randomUUID();
6515
- const webVersion = getQwenWebVersion() || "0.3.26";
6855
+ const webVersion = getQwenWebVersion();
6516
6856
  const evalRes = await page.evaluate(
6517
6857
  async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2, tz, version }) => {
6518
6858
  try {
@@ -6684,6 +7024,15 @@ async function loginViaUi(page, email, password) {
6684
7024
  if (!page.url().includes("/auth") && await isPageLoggedIn(page, 3e3).catch(() => false)) {
6685
7025
  return { success: true };
6686
7026
  }
7027
+ if (config.captcha.enabled) {
7028
+ await solveVisibleCaptcha(page, {
7029
+ waitForMs: 1500,
7030
+ maxAttempts: config.captcha.maxAttempts,
7031
+ retryDelayMs: config.captcha.retryDelayMs,
7032
+ settleMs: config.captcha.settleMs,
7033
+ sliderTimeoutMs: config.captcha.sliderTimeoutMs
7034
+ }).catch(() => false);
7035
+ }
6687
7036
  const emailSelector = [
6688
7037
  'input[type="email"]',
6689
7038
  'input[name="email"]',
@@ -6839,10 +7188,11 @@ async function loginViaUi(page, email, password) {
6839
7188
  while (Date.now() < postSubmitDeadline) {
6840
7189
  if (page.isClosed()) break;
6841
7190
  if (config.captcha.enabled) {
6842
- await solveBaxiaCaptcha(page, {
7191
+ await solveVisibleCaptcha(page, {
6843
7192
  waitForMs: 1500,
6844
7193
  maxAttempts: config.captcha.maxAttempts,
6845
- retryDelayMs: config.captcha.retryDelayMs
7194
+ retryDelayMs: config.captcha.retryDelayMs,
7195
+ sliderTimeoutMs: config.captcha.sliderTimeoutMs
6846
7196
  }).catch(() => {
6847
7197
  });
6848
7198
  }
@@ -6965,7 +7315,7 @@ async function refreshAccountToken(accountId) {
6965
7315
  }
6966
7316
  if (refreshToken) {
6967
7317
  try {
6968
- const webVersion = getQwenWebVersion() || "0.3.26";
7318
+ const webVersion = getQwenWebVersion();
6969
7319
  const refreshResult = await page.evaluate(
6970
7320
  async ({ rTok, tz, version }) => {
6971
7321
  try {
@@ -7030,7 +7380,7 @@ async function refreshAccountToken(accountId) {
7030
7380
  saveAuthSession2(accountId, {
7031
7381
  cookie: cookieStr,
7032
7382
  userAgent: cache3.headers["user-agent"] || session?.userAgent || "",
7033
- bxV: cache3.headers["bx-v"] || session?.bxV || "2.5.37",
7383
+ bxV: cache3.headers["bx-v"] || session?.bxV || getQwenBxVersion(),
7034
7384
  bxUa: cache3.headers["bx-ua"] || session?.bxUa || "",
7035
7385
  bxUmidtoken: cache3.headers["bx-umidtoken"] || session?.bxUmidtoken || "",
7036
7386
  secChUa: cache3.headers["sec-ch-ua"] || session?.secChUa,
@@ -7065,7 +7415,7 @@ async function refreshAccountToken(accountId) {
7065
7415
  saveAuthSession2(accountId, {
7066
7416
  cookie: cookieStr,
7067
7417
  userAgent: cache3.headers["user-agent"] || session?.userAgent || "",
7068
- bxV: cache3.headers["bx-v"] || session?.bxV || "2.5.37",
7418
+ bxV: cache3.headers["bx-v"] || session?.bxV || getQwenBxVersion(),
7069
7419
  bxUa: cache3.headers["bx-ua"] || session?.bxUa || "",
7070
7420
  bxUmidtoken: cache3.headers["bx-umidtoken"] || session?.bxUmidtoken || "",
7071
7421
  secChUa: cache3.headers["sec-ch-ua"] || session?.secChUa,
@@ -7213,7 +7563,7 @@ async function captureQwenHeaders(accountId, pageOverride, timeoutMs = config.ti
7213
7563
  cookie: reqHeaders["cookie"] || "",
7214
7564
  "bx-ua": reqHeaders["bx-ua"] || "",
7215
7565
  "bx-umidtoken": reqHeaders["bx-umidtoken"] || "",
7216
- "bx-v": reqHeaders["bx-v"] || "2.5.37",
7566
+ "bx-v": reqHeaders["bx-v"] || getQwenBxVersion(),
7217
7567
  "user-agent": reqHeaders["user-agent"] || "",
7218
7568
  "x-request-id": reqHeaders["x-request-id"] || "",
7219
7569
  "version": reqHeaders["version"] || "",
@@ -7245,6 +7595,9 @@ async function captureQwenHeaders(accountId, pageOverride, timeoutMs = config.ti
7245
7595
  if (capturedHeaders["version"]) {
7246
7596
  updateQwenWebVersion(capturedHeaders["version"]);
7247
7597
  }
7598
+ if (capturedHeaders["bx-v"]) {
7599
+ updateQwenBxVersion(capturedHeaders["bx-v"]);
7600
+ }
7248
7601
  markAccountHeadersReady(accountId);
7249
7602
  cache3.lastRefresh = Date.now();
7250
7603
  cookieCaches.delete(accountId);
@@ -7648,19 +8001,26 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
7648
8001
  await executeReauth();
7649
8002
  } else {
7650
8003
  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();
8004
+ const currentUrl = typeof page.url === "function" ? page.url() : "";
8005
+ const needsNavigation = !currentUrl.startsWith(qwenOrigin()) || currentUrl.includes("/auth") || currentUrl.includes("/login") || currentUrl.includes("_____tmd_____");
8006
+ if (needsNavigation) {
8007
+ await page.goto(qwenUrl("/"), {
8008
+ waitUntil: "domcontentloaded",
8009
+ timeout: Math.min(
8010
+ config.timeouts.navigation,
8011
+ boundedTimeoutMs,
8012
+ SESSION_PROBE_NAVIGATION_TIMEOUT_MS
8013
+ )
8014
+ });
8015
+ await sleep2(2e3);
8016
+ }
8017
+ const url = typeof page.url === "function" ? page.url() : "";
7661
8018
  const isAuthUrl = url.includes("auth") || url.includes("login");
7662
8019
  if (isAuthUrl) {
7663
- await executeReauth();
8020
+ const refreshed = await refreshAccountToken(accountId);
8021
+ if (!refreshed.success) {
8022
+ await executeReauth();
8023
+ }
7664
8024
  }
7665
8025
  } catch (navErr) {
7666
8026
  console.warn(
@@ -7695,7 +8055,7 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
7695
8055
  saveAuthSession2(accountId, {
7696
8056
  cookie: cookieStr,
7697
8057
  userAgent: cache3.headers["user-agent"] || "",
7698
- bxV: cache3.headers["bx-v"] || "2.5.37",
8058
+ bxV: cache3.headers["bx-v"] || getQwenBxVersion(),
7699
8059
  bxUa: cache3.headers["bx-ua"] || "",
7700
8060
  bxUmidtoken: cache3.headers["bx-umidtoken"] || "",
7701
8061
  secChUa: cache3.headers["sec-ch-ua"] || void 0,
@@ -7717,7 +8077,7 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
7717
8077
  if (capturedPage && !capturedPage.isClosed()) {
7718
8078
  try {
7719
8079
  const currentUrl = new URL(capturedPage.url());
7720
- if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname !== "/") {
8080
+ if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname.includes("/auth") || currentUrl.pathname.includes("/login") || currentUrl.pathname.includes("_____tmd_____")) {
7721
8081
  await capturedPage.goto(qwenUrl("/"), {
7722
8082
  waitUntil: "domcontentloaded",
7723
8083
  timeout: Math.min(config.timeouts.navigation, boundedTimeoutMs)
@@ -8033,7 +8393,11 @@ function priorityOrderForEviction(accountIds) {
8033
8393
  const ranked = getAccountsByPriority(accountIds.map((id) => ({ id })));
8034
8394
  const rank = /* @__PURE__ */ new Map();
8035
8395
  for (const [index, account] of ranked.entries()) {
8036
- rank.set(account.id, index);
8396
+ if (getAccountCooldownInfo(account.id)) {
8397
+ rank.set(account.id, Number.MAX_SAFE_INTEGER);
8398
+ } else {
8399
+ rank.set(account.id, index);
8400
+ }
8037
8401
  }
8038
8402
  return orderContextsForEviction(
8039
8403
  accountIds,
@@ -8070,14 +8434,15 @@ async function closeIdlePlaywrightAccounts(idleMs) {
8070
8434
  }
8071
8435
  return closed;
8072
8436
  }
8073
- async function evictIdlePlaywrightContextsToLimit() {
8074
- const max = config.playwright.maxActiveContexts;
8075
- if (max <= 0) return 0;
8437
+ async function evictIdlePlaywrightContextsToLimit(targetLimit) {
8438
+ const configuredMax = config.playwright.maxActiveContexts;
8439
+ if (configuredMax <= 0) return 0;
8440
+ const max = targetLimit !== void 0 ? Math.max(0, targetLimit) : configuredMax;
8076
8441
  if (accountPages.size <= max) return 0;
8077
8442
  const candidates = priorityOrderForEviction(
8078
8443
  Array.from(accountPages.keys()).filter((accountId) => {
8079
8444
  if (isAccountServingStream(accountId)) return false;
8080
- if (isAccountRecentlyActive(accountId, 3e4)) return false;
8445
+ if (hasActiveAccountLease(accountId)) return false;
8081
8446
  const mutex = accountMutexes.get(accountId);
8082
8447
  return mutex?.isIdle();
8083
8448
  })
@@ -8090,6 +8455,7 @@ async function evictIdlePlaywrightContextsToLimit() {
8090
8455
  for (const candidate of candidates) {
8091
8456
  if (accountPages.size <= max) break;
8092
8457
  if (isAccountServingStream(candidate.accountId)) continue;
8458
+ if (hasActiveAccountLease(candidate.accountId)) continue;
8093
8459
  const mutex = accountMutexes.get(candidate.accountId);
8094
8460
  if (!mutex?.isIdle()) continue;
8095
8461
  await closePlaywrightForAccount(candidate.accountId).catch((error) => {
@@ -8520,12 +8886,13 @@ var init_watchdog = __esm({
8520
8886
  await new Promise((resolve) => setTimeout(resolve, 100));
8521
8887
  this.emit("recovery:ram:freed");
8522
8888
  try {
8523
- const { closeIdlePlaywrightAccounts: closeIdlePlaywrightAccounts2 } = await Promise.resolve().then(() => (init_playwright(), playwright_exports));
8889
+ const { closeIdlePlaywrightAccounts: closeIdlePlaywrightAccounts2, evictIdlePlaywrightContextsToLimit: evictIdlePlaywrightContextsToLimit2 } = await Promise.resolve().then(() => (init_playwright(), playwright_exports));
8890
+ const evicted = await evictIdlePlaywrightContextsToLimit2(1);
8524
8891
  const closed = await closeIdlePlaywrightAccounts2(
8525
8892
  config.sessionKeeper.idleMs
8526
8893
  );
8527
- if (closed > 0) {
8528
- this.emit("recovery:ram:contexts-closed", closed);
8894
+ if (evicted + closed > 0) {
8895
+ this.emit("recovery:ram:contexts-closed", evicted + closed);
8529
8896
  }
8530
8897
  } catch {
8531
8898
  }
@@ -8691,13 +9058,13 @@ async function getBasicHeaders2(accountId) {
8691
9058
  return {
8692
9059
  cookie: "token=mock",
8693
9060
  userAgent: "mock",
8694
- bxV: "2.5.37",
9061
+ bxV: getQwenBxVersion(),
8695
9062
  bxUa: "mock-bx-ua",
8696
9063
  bxUmidtoken: "mock-bx-umidtoken",
8697
9064
  secChUa: "",
8698
9065
  secChUaMobile: "?0",
8699
9066
  secChUaPlatform: "",
8700
- version: "0.3.26"
9067
+ version: getQwenWebVersion()
8701
9068
  };
8702
9069
  }
8703
9070
  const resolvedAccountId = accountId ?? loadAccounts()[0]?.id;
@@ -8758,6 +9125,7 @@ var init_auth_playwright = __esm({
8758
9125
  init_errors();
8759
9126
  init_accounts();
8760
9127
  init_config();
9128
+ init_qwen_headers();
8761
9129
  init_playwright();
8762
9130
  init_jwt();
8763
9131
  }
@@ -8882,6 +9250,7 @@ var init_model_alias = __esm({
8882
9250
  DEFAULT_FALLBACK_MODELS = [
8883
9251
  "qwen3.8-max",
8884
9252
  "qwen3.7-plus",
9253
+ "qwen4.0-max-preview",
8885
9254
  "qwen3.8-omni-flash",
8886
9255
  "qwen3.7-max",
8887
9256
  "qwen3.6-plus",
@@ -9208,6 +9577,7 @@ var init_model_registry = __esm({
9208
9577
  var captcha_coordinator_exports = {};
9209
9578
  __export(captcha_coordinator_exports, {
9210
9579
  isCaptchaRecoveryActive: () => isCaptchaRecoveryActive,
9580
+ isCaptchaRecoveryInBackoff: () => isCaptchaRecoveryInBackoff,
9211
9581
  recoverBaxiaCaptcha: () => recoverBaxiaCaptcha,
9212
9582
  solveChallengeOnPage: () => solveChallengeOnPage
9213
9583
  });
@@ -9227,9 +9597,10 @@ async function solveChallengeOnPage(page, challengeUrl, waitForMs = config.captc
9227
9597
  const solverOptions = {
9228
9598
  maxAttempts: config.captcha.maxAttempts,
9229
9599
  retryDelayMs: config.captcha.retryDelayMs,
9230
- settleMs: config.captcha.settleMs
9600
+ settleMs: config.captcha.settleMs,
9601
+ sliderTimeoutMs: config.captcha.sliderTimeoutMs
9231
9602
  };
9232
- if (await solveBaxiaCaptcha(page, { ...solverOptions, waitForMs: 0 })) {
9603
+ if (await solveVisibleCaptcha(page, { ...solverOptions, waitForMs: 0 })) {
9233
9604
  return true;
9234
9605
  }
9235
9606
  logBaxiaCaptcha(
@@ -9239,13 +9610,18 @@ async function solveChallengeOnPage(page, challengeUrl, waitForMs = config.captc
9239
9610
  );
9240
9611
  await gotoBestEffort(page, challengeUrl ?? qwenUrl("/"));
9241
9612
  try {
9242
- return await solveBaxiaCaptcha(page, { ...solverOptions, waitForMs });
9613
+ return await solveVisibleCaptcha(page, { ...solverOptions, waitForMs });
9243
9614
  } finally {
9244
9615
  if (page.url().includes(CHALLENGE_PATH_MARKER)) {
9245
9616
  await gotoBestEffort(page, qwenUrl("/"));
9246
9617
  }
9247
9618
  }
9248
9619
  }
9620
+ function isCaptchaRecoveryInBackoff(accountId) {
9621
+ if (!accountId) return false;
9622
+ const lastFailure = lastFailedRecoveryAt.get(accountId) ?? 0;
9623
+ return Date.now() - lastFailure < FAILED_RECOVERY_BACKOFF_MS;
9624
+ }
9249
9625
  async function recoverBaxiaCaptcha(accountId, label, options = {}) {
9250
9626
  if (!config.captcha.enabled || !accountId) return false;
9251
9627
  const solver = "baxia";
@@ -9263,17 +9639,35 @@ async function recoverBaxiaCaptcha(accountId, label, options = {}) {
9263
9639
  const challengeUrl = options.challengeBody ? extractBaxiaChallengeUrl(options.challengeBody, config.qwen.baseUrl) : null;
9264
9640
  activeCaptchaRecoveries.add(accountId);
9265
9641
  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 {
9642
+ const { hasActiveBrowserStreamsForAccount: hasActiveBrowserStreamsForAccount2 } = await Promise.resolve().then(() => (init_qwen(), qwen_exports));
9643
+ const hasConcurrentStream = hasActiveBrowserStreamsForAccount2(accountId);
9274
9644
  const solved = await withAccountPage(
9275
9645
  accountId,
9276
- (page) => solveChallengeOnPage(page, challengeUrl),
9646
+ async (page) => {
9647
+ if (hasConcurrentStream) {
9648
+ const solverOptions = {
9649
+ maxAttempts: config.captcha.maxAttempts,
9650
+ retryDelayMs: config.captcha.retryDelayMs,
9651
+ settleMs: config.captcha.settleMs,
9652
+ sliderTimeoutMs: config.captcha.sliderTimeoutMs
9653
+ };
9654
+ if (await solveVisibleCaptcha(page, { ...solverOptions, waitForMs: 0 })) {
9655
+ return true;
9656
+ }
9657
+ const ctx = typeof page.context === "function" ? page.context() : null;
9658
+ if (ctx && typeof ctx.newPage === "function") {
9659
+ const tempPage = await ctx.newPage();
9660
+ try {
9661
+ return await solveChallengeOnPage(tempPage, challengeUrl);
9662
+ } finally {
9663
+ await tempPage.close({ runBeforeUnload: false }).catch(() => {
9664
+ });
9665
+ }
9666
+ }
9667
+ return false;
9668
+ }
9669
+ return await solveChallengeOnPage(page, challengeUrl);
9670
+ },
9277
9671
  solverOperationTimeoutMs,
9278
9672
  solverOperationTimeoutMs,
9279
9673
  false
@@ -11574,7 +11968,7 @@ var init_media_generation = __esm({
11574
11968
 
11575
11969
  // src/services/qwen-chat-pool.ts
11576
11970
  import crypto6 from "crypto";
11577
- async function createQwenChatSession(headers, model, accountId, chatMode = "thread") {
11971
+ async function createQwenChatSession(headers, model, accountId, chatMode = "thread", captchaRecoveryAttempts = 1) {
11578
11972
  if (isAuthMockEnabled()) {
11579
11973
  return process.env.TEST_SESSION_ID || "mock-session";
11580
11974
  }
@@ -11602,16 +11996,29 @@ async function createQwenChatSession(headers, model, accountId, chatMode = "thre
11602
11996
  if (!chatId || typeof chatId !== "string") {
11603
11997
  const rawLower = raw.toLowerCase();
11604
11998
  if (looksLikeAntiBotChallengeText(raw)) {
11605
- try {
11606
- const { recoverBaxiaCaptcha: recoverBaxiaCaptcha2 } = await Promise.resolve().then(() => (init_captcha_coordinator(), captcha_coordinator_exports));
11607
- const solved = await recoverBaxiaCaptcha2(accountId, "createQwenChatSession", {
11608
- challengeBody: raw
11609
- });
11610
- if (solved) {
11611
- return createQwenChatSession(headers, model, accountId, chatMode);
11999
+ if (captchaRecoveryAttempts > 0) {
12000
+ try {
12001
+ const { recoverBaxiaCaptcha: recoverBaxiaCaptcha2 } = await Promise.resolve().then(() => (init_captcha_coordinator(), captcha_coordinator_exports));
12002
+ const solved = await recoverBaxiaCaptcha2(accountId, "createQwenChatSession", {
12003
+ challengeBody: raw
12004
+ });
12005
+ if (solved) {
12006
+ return createQwenChatSession(
12007
+ headers,
12008
+ model,
12009
+ accountId,
12010
+ chatMode,
12011
+ captchaRecoveryAttempts - 1
12012
+ );
12013
+ }
12014
+ } catch {
11612
12015
  }
11613
- } catch {
11614
12016
  }
12017
+ throw new QwenUpstreamError(
12018
+ `Qwen create chat blocked by WAF challenge: ${raw.substring(0, 300)}`,
12019
+ "FAIL_SYS_USER_VALIDATE",
12020
+ 403
12021
+ );
11615
12022
  }
11616
12023
  const isUnauthorized = json?.data?.code?.toLowerCase() === "unauthorized" || json?.code?.toLowerCase() === "unauthorized" || rawLower.includes("unauthorized") || rawLower.includes("permission to access") || rawLower.includes("token has expired") || rawLower.includes("401");
11617
12024
  if (isUnauthorized) {
@@ -11788,6 +12195,20 @@ async function refillQwenChatPool(headers, model, accountId) {
11788
12195
  console.warn(
11789
12196
  `[WarmPool] Account ${accountId || "global"} rate-limited during chat creation. Marked for cooldown.`
11790
12197
  );
12198
+ } else if (accountId && (err.upstreamCode === "FAIL_SYS_USER_VALIDATE" || err.upstreamCode === "RGV587_ERROR" || /_____tmd_____|rgv587_error|fail_sys_user_validate/i.test(
12199
+ err.message
12200
+ ))) {
12201
+ const existing = getAccountCooldownInfo(accountId);
12202
+ if (existing?.reason === "WafChallenge") {
12203
+ console.warn(
12204
+ `[WarmPool] Account ${accountId} still on WAF quarantine (${Math.ceil(existing.remainingMs / 1e3)}s left); skipping re-escalation.`
12205
+ );
12206
+ } else {
12207
+ const block = recordWafHardBlock(accountId);
12208
+ console.warn(
12209
+ `[WarmPool] Unsolved WAF challenge on ${accountId} during chat creation; quarantined ${Math.ceil(block.cooldownMs / 1e3)}s and rotated the fingerprint.`
12210
+ );
12211
+ }
11791
12212
  }
11792
12213
  }
11793
12214
  if (isToolcallDebugEnabled()) {
@@ -11823,6 +12244,7 @@ var init_qwen_chat_pool = __esm({
11823
12244
  init_config();
11824
12245
  init_logger();
11825
12246
  init_account_manager();
12247
+ init_waf_isolation();
11826
12248
  init_model_alias();
11827
12249
  init_qwen_url();
11828
12250
  init_qwen_errors();
@@ -11872,6 +12294,7 @@ __export(qwen_exports, {
11872
12294
  getLogicalThreadState: () => getLogicalThreadState,
11873
12295
  getQwenErrorCode: () => getQwenErrorCode,
11874
12296
  getSessionParent: () => getSessionParent,
12297
+ hasActiveBrowserStreamsForAccount: () => hasActiveBrowserStreamsForAccount,
11875
12298
  invalidateLogicalThreadParent: () => invalidateLogicalThreadParent,
11876
12299
  isChatSessionActive: () => isChatSessionActive,
11877
12300
  isRetryableFetchErrorMessage: () => isRetryableFetchErrorMessage,
@@ -11893,6 +12316,14 @@ __export(qwen_exports, {
11893
12316
  });
11894
12317
  import crypto7 from "crypto";
11895
12318
  import { v4 as uuidv43 } from "uuid";
12319
+ function hasActiveBrowserStreamsForAccount(accountId) {
12320
+ for (const state of browserStreamStates.values()) {
12321
+ if (state.accountId === accountId && !state.done) {
12322
+ return true;
12323
+ }
12324
+ }
12325
+ return false;
12326
+ }
11896
12327
  function abortActiveBrowserStreamsForAccount(accountId, reason = "Account browser stream interrupted (page context unloaded)") {
11897
12328
  let count = 0;
11898
12329
  for (const [requestId, state] of browserStreamStates) {
@@ -12383,7 +12814,7 @@ async function requestQwenTextInBrowser(accountId, method, path21, headers, body
12383
12814
  browserHeaders["Content-Type"] = "application/json";
12384
12815
  }
12385
12816
  const evaluateRequest = (page) => page.evaluate(
12386
- async ({ url: url2, method: method2, headers: headers2, body: body2, referrer, timeoutMs, tz }) => {
12817
+ async ({ url: url2, method: method2, headers: headers2, body: body2, referrer, timeoutMs, tz, version }) => {
12387
12818
  const controller = new AbortController();
12388
12819
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
12389
12820
  try {
@@ -12405,7 +12836,7 @@ async function requestQwenTextInBrowser(accountId, method, path21, headers, body
12405
12836
  headers: {
12406
12837
  accept: "application/json, text/plain, */*",
12407
12838
  source: "web",
12408
- version: "0.3.11",
12839
+ version,
12409
12840
  "x-request-origin": "https://chat.qwen.ai",
12410
12841
  timezone: tz,
12411
12842
  authorization: `Bearer ${rTok}`
@@ -12453,7 +12884,8 @@ async function requestQwenTextInBrowser(accountId, method, path21, headers, body
12453
12884
  body,
12454
12885
  referrer: options.referrer,
12455
12886
  timeoutMs: options.timeoutMs ?? Math.min(config.timeouts.page, 2e4),
12456
- tz: formatAsciiTimezone()
12887
+ tz: formatAsciiTimezone(),
12888
+ version: getQwenWebVersion()
12457
12889
  }
12458
12890
  );
12459
12891
  const recoverOnTimeout = !options.noMutexRecovery;
@@ -12670,7 +13102,7 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
12670
13102
  const onFrameNavigated = (frame) => {
12671
13103
  if (!settled && typeof page.mainFrame === "function" && frame === page.mainFrame()) {
12672
13104
  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")) {
13105
+ if (url2.startsWith(qwenOrigin()) && !url2.includes("/auth") && !url2.includes("/login") && !url2.includes("_____tmd_____") && !url2.includes("/punish")) {
12674
13106
  return;
12675
13107
  }
12676
13108
  handleBrowserStreamEvent(requestId, {
@@ -12691,10 +13123,12 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
12691
13123
  };
12692
13124
  page.on("framenavigated", onFrameNavigated);
12693
13125
  page.on("close", onPageClosed);
13126
+ page.on("crash", onPageClosed);
12694
13127
  pageCleanup = () => {
12695
13128
  try {
12696
13129
  page.off?.("framenavigated", onFrameNavigated);
12697
13130
  page.off?.("close", onPageClosed);
13131
+ page.off?.("crash", onPageClosed);
12698
13132
  } catch {
12699
13133
  }
12700
13134
  };
@@ -13408,7 +13842,7 @@ async function deleteSingleQwenChat(accountId, chatId) {
13408
13842
  }
13409
13843
  const requestHeaders = {
13410
13844
  source: "web",
13411
- version: getQwenWebVersion() || "0.3.26",
13845
+ version: getQwenWebVersion(),
13412
13846
  timezone: formatAsciiTimezone(),
13413
13847
  "x-request-id": crypto7.randomUUID(),
13414
13848
  Referer: qwenUrl(`/c/${encodeURIComponent(chatId)}`)
@@ -13452,7 +13886,7 @@ async function fetchRemoteQwenChats(accountId) {
13452
13886
  return [];
13453
13887
  }
13454
13888
  const requestHeaders = {
13455
- version: getQwenWebVersion() || "0.3.26",
13889
+ version: getQwenWebVersion(),
13456
13890
  timezone: formatAsciiTimezone(),
13457
13891
  "x-request-id": crypto7.randomUUID(),
13458
13892
  Referer: qwenUrl("/settings/chats")
@@ -16843,7 +17277,16 @@ async function deleteChatsForAccountId(accountId) {
16843
17277
  if (!credentials) {
16844
17278
  throw new Error(`Credenciais da conta ${account.email} n\xE3o encontradas.`);
16845
17279
  }
16846
- return deleteChatsForAccount(credentials);
17280
+ const wasInitialized = isPlaywrightInitialized(account.id);
17281
+ try {
17282
+ return await deleteChatsForAccount(credentials);
17283
+ } finally {
17284
+ if (!wasInitialized && !isAuthMockEnabled()) {
17285
+ const { closePlaywrightForAccount: closePlaywrightForAccount2 } = await Promise.resolve().then(() => (init_playwright(), playwright_exports));
17286
+ await closePlaywrightForAccount2(account.id).catch(() => {
17287
+ });
17288
+ }
17289
+ }
16847
17290
  }
16848
17291
  async function deleteChatsForConfiguredAccounts(keepBrowserOpen = false) {
16849
17292
  const accounts = loadAccounts();
@@ -16858,6 +17301,7 @@ async function deleteChatsForConfiguredAccounts(keepBrowserOpen = false) {
16858
17301
  const account = accounts[i];
16859
17302
  const currentIdx = i + 1;
16860
17303
  const totalCount = accounts.length;
17304
+ const wasInitialized = isPlaywrightInitialized(account.id);
16861
17305
  try {
16862
17306
  const ok = await deleteChatsForAccount(account, currentIdx, totalCount);
16863
17307
  if (ok) succeeded++;
@@ -16867,7 +17311,7 @@ async function deleteChatsForConfiguredAccounts(keepBrowserOpen = false) {
16867
17311
  error instanceof Error ? error.message : String(error)
16868
17312
  );
16869
17313
  } finally {
16870
- if (!keepBrowserOpen) {
17314
+ if ((!keepBrowserOpen || !wasInitialized) && !isAuthMockEnabled()) {
16871
17315
  const { closePlaywrightForAccount: closePlaywrightForAccount2 } = await Promise.resolve().then(() => (init_playwright(), playwright_exports));
16872
17316
  await closePlaywrightForAccount2(account.id).catch(() => {
16873
17317
  });
@@ -18154,6 +18598,9 @@ ${loopReminder}` : truncation.prompt;
18154
18598
  if (policy.reason === "corrupted_chat_history") {
18155
18599
  invalidateLogicalThreadParent(params.sessionId);
18156
18600
  }
18601
+ if (policy.reason === "anti_bot" && isCaptchaRecoveryInBackoff(currentAccountId)) {
18602
+ return { success: false, error: err };
18603
+ }
18157
18604
  const retryInvalidInputOnSameAccount = shouldRetryInvalidInputOnSameAccount(
18158
18605
  policy.reason,
18159
18606
  invalidInputSameAccountRetried
@@ -18342,6 +18789,7 @@ var init_account = __esm({
18342
18789
  init_account_manager();
18343
18790
  init_account_priority();
18344
18791
  init_waf_isolation();
18792
+ init_captcha_coordinator();
18345
18793
  init_accounts();
18346
18794
  init_config();
18347
18795
  init_errors();
@@ -21965,8 +22413,8 @@ async function processStreamingResponse(params) {
21965
22413
  );
21966
22414
  };
21967
22415
  const emitAnswerText = async (textChunk) => {
21968
- if (textChunk) emittedModelOutput = true;
21969
22416
  if (!toolParser) {
22417
+ if (textChunk) emittedModelOutput = true;
21970
22418
  finalContent += textChunk;
21971
22419
  writeDeltaEvent({ content: textChunk });
21972
22420
  return;
@@ -22342,6 +22790,23 @@ async function processStreamingResponse(params) {
22342
22790
  if (fastMatch) {
22343
22791
  const unescaped = fastMatch[1].replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
22344
22792
  if (unescaped) {
22793
+ if (!emittedModelOutput && isOverloadMessage(unescaped)) {
22794
+ const overloadErr = toRetryableStreamError(
22795
+ "server_overloaded",
22796
+ unescaped,
22797
+ {
22798
+ switchAccount: true,
22799
+ forceNewChat: true,
22800
+ reason: "server_busy",
22801
+ accountCooldownMs: OVERLOAD_COOLDOWN_MS,
22802
+ accountCooldownReason: "ServerOverloaded"
22803
+ }
22804
+ );
22805
+ if (await recoverFromStreamError(overloadErr)) {
22806
+ break;
22807
+ }
22808
+ throw overloadErr;
22809
+ }
22345
22810
  const result = getIncrementalDelta(
22346
22811
  lastRawContent,
22347
22812
  unescaped,
@@ -22476,7 +22941,6 @@ async function processStreamingResponse(params) {
22476
22941
  if (foundStr && vStr !== "") {
22477
22942
  if (vStr === "FINISHED") continue;
22478
22943
  if (isThinkingChunk) {
22479
- emittedModelOutput = true;
22480
22944
  reasoningBuffer += vStr;
22481
22945
  writeDeltaEvent({ reasoning_content: vStr });
22482
22946
  } else {
@@ -27111,11 +27575,42 @@ var init_server_log_buffer = __esm({
27111
27575
  // src/services/session-keeper.ts
27112
27576
  var session_keeper_exports = {};
27113
27577
  __export(session_keeper_exports, {
27578
+ computeSessionKeeperBackoffMs: () => computeSessionKeeperBackoffMs,
27579
+ isSessionKeeperAccountBackedOff: () => isSessionKeeperAccountBackedOff,
27114
27580
  isSessionKeeperRunning: () => isSessionKeeperRunning,
27581
+ resetSessionKeeperRetryStateForTests: () => resetSessionKeeperRetryStateForTests,
27115
27582
  runSessionKeeperOnceForTesting: () => runSessionKeeperOnceForTesting,
27116
27583
  startSessionKeeper: () => startSessionKeeper,
27117
27584
  stopSessionKeeper: () => stopSessionKeeper
27118
27585
  });
27586
+ function computeSessionKeeperBackoffMs(failures) {
27587
+ const exponent = Math.max(0, Math.min(failures - 1, 8));
27588
+ return Math.min(
27589
+ KEEP_ALIVE_BACKOFF_MAX_MS,
27590
+ KEEP_ALIVE_BACKOFF_BASE_MS * 2 ** exponent
27591
+ );
27592
+ }
27593
+ function isKeepAliveBackedOff(accountId) {
27594
+ const state = keepAliveRetries.get(accountId);
27595
+ return Boolean(state && Date.now() < state.nextAttemptAt);
27596
+ }
27597
+ function recordKeepAliveFailure(accountId) {
27598
+ const state = keepAliveRetries.get(accountId) ?? {
27599
+ failures: 0,
27600
+ nextAttemptAt: 0
27601
+ };
27602
+ state.failures += 1;
27603
+ const backoffMs = computeSessionKeeperBackoffMs(state.failures);
27604
+ state.nextAttemptAt = Date.now() + backoffMs;
27605
+ keepAliveRetries.set(accountId, state);
27606
+ return backoffMs;
27607
+ }
27608
+ function resetSessionKeeperRetryStateForTests() {
27609
+ keepAliveRetries.clear();
27610
+ }
27611
+ function isSessionKeeperAccountBackedOff(accountId) {
27612
+ return isKeepAliveBackedOff(accountId);
27613
+ }
27119
27614
  function isSessionKeeperRunning() {
27120
27615
  return running;
27121
27616
  }
@@ -27126,13 +27621,25 @@ async function runKeepAliveCycle() {
27126
27621
  if (config.sessionKeeper.enabled) {
27127
27622
  const accountIds = getActivePlaywrightAccountIds();
27128
27623
  for (const accountId of accountIds) {
27129
- await keepAlivePlaywrightAccount(accountId).catch((error) => {
27130
- if (isPlaywrightAlreadyClosedError(error)) return;
27131
- const message = error instanceof Error ? error.message : String(error);
27132
- console.warn(
27133
- `[SessionKeeper] Keep-alive failed for ${accountId}: ${message}`
27134
- );
27135
- });
27624
+ if (isKeepAliveBackedOff(accountId)) {
27625
+ continue;
27626
+ }
27627
+ try {
27628
+ const keptAlive = await keepAlivePlaywrightAccount(accountId);
27629
+ if (keptAlive) {
27630
+ keepAliveRetries.delete(accountId);
27631
+ }
27632
+ } catch (error) {
27633
+ if (isPlaywrightAlreadyClosedError(error)) {
27634
+ keepAliveRetries.delete(accountId);
27635
+ } else {
27636
+ const message = error instanceof Error ? error.message : String(error);
27637
+ const backoffMs = recordKeepAliveFailure(accountId);
27638
+ console.warn(
27639
+ `[SessionKeeper] Keep-alive failed for ${accountId}: ${message} (retry in ${Math.round(backoffMs / 1e3)}s)`
27640
+ );
27641
+ }
27642
+ }
27136
27643
  await sleep(humanDelay(250, 900));
27137
27644
  }
27138
27645
  }
@@ -27172,11 +27679,12 @@ function stopSessionKeeper() {
27172
27679
  intervalId = null;
27173
27680
  }
27174
27681
  cycleInProgress = false;
27682
+ keepAliveRetries.clear();
27175
27683
  }
27176
27684
  async function runSessionKeeperOnceForTesting() {
27177
27685
  await runKeepAliveCycle();
27178
27686
  }
27179
- var running, intervalId, cycleInProgress;
27687
+ var running, intervalId, cycleInProgress, KEEP_ALIVE_BACKOFF_BASE_MS, KEEP_ALIVE_BACKOFF_MAX_MS, keepAliveRetries;
27180
27688
  var init_session_keeper = __esm({
27181
27689
  "src/services/session-keeper.ts"() {
27182
27690
  "use strict";
@@ -27186,6 +27694,9 @@ var init_session_keeper = __esm({
27186
27694
  running = false;
27187
27695
  intervalId = null;
27188
27696
  cycleInProgress = false;
27697
+ KEEP_ALIVE_BACKOFF_BASE_MS = 2e3;
27698
+ KEEP_ALIVE_BACKOFF_MAX_MS = 5 * 6e4;
27699
+ keepAliveRetries = /* @__PURE__ */ new Map();
27189
27700
  }
27190
27701
  });
27191
27702
 
@@ -27211,7 +27722,7 @@ function getAppVersion() {
27211
27722
  }
27212
27723
  } catch {
27213
27724
  }
27214
- cachedVersion = "v1.6.10";
27725
+ cachedVersion = "v1.6.12";
27215
27726
  return cachedVersion;
27216
27727
  }
27217
27728
  var cachedVersion;
@@ -27617,10 +28128,11 @@ async function startServer(options) {
27617
28128
  } else if (remainingAccounts.length > 0) {
27618
28129
  void (async () => {
27619
28130
  const { ensureAccountInPriority: ensureAccountInPriority2 } = await Promise.resolve().then(() => (init_account_priority(), account_priority_exports));
27620
- const targetWarmTotal = Math.min(
28131
+ const maxWarmCap = config.playwright.maxActiveContexts > 0 ? Math.min(
27621
28132
  config.playwright.startupWarmAccounts,
27622
- totalAccounts
27623
- );
28133
+ config.playwright.maxActiveContexts
28134
+ ) : config.playwright.startupWarmAccounts;
28135
+ const targetWarmTotal = Math.min(maxWarmCap, totalAccounts);
27624
28136
  let currentReadyCount = readyAccountIds.size;
27625
28137
  if (currentReadyCount < targetWarmTotal && remainingAccounts.length > 0) {
27626
28138
  for (const reserveAccount of remainingAccounts) {