qwenproxy-cli 1.6.9 → 1.6.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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);
@@ -2990,7 +3010,19 @@ async function isPageLoggedIn(page, timeoutMs = SESSION_PROBE_NAVIGATION_TIMEOUT
2990
3010
  if (localStorage.getItem("qwen_token_logged_out_marker")) {
2991
3011
  return false;
2992
3012
  }
2993
- const res = await fetch("/api/v1/auths/", { method: "GET" });
3013
+ const lsToken = (localStorage.getItem("token") || "").trim();
3014
+ if (lsToken && !/(?:^|;\s*)token=/.test(document.cookie || "")) {
3015
+ try {
3016
+ document.cookie = `token=${encodeURIComponent(lsToken)}; path=/; domain=.qwen.ai; max-age=31536000`;
3017
+ } catch {
3018
+ }
3019
+ }
3020
+ const authHeaders = lsToken ? { Authorization: `Bearer ${lsToken}` } : {};
3021
+ const res = await fetch("/api/v1/auths/", {
3022
+ method: "GET",
3023
+ headers: authHeaders,
3024
+ credentials: "include"
3025
+ });
2994
3026
  if (res.status !== 200) return false;
2995
3027
  const json = await res.json().catch(() => null);
2996
3028
  if (!json) return false;
@@ -3008,6 +3040,7 @@ async function isPageLoggedIn(page, timeoutMs = SESSION_PROBE_NAVIGATION_TIMEOUT
3008
3040
  try {
3009
3041
  const settingsRes = await fetch("/api/v2/users/user/settings", {
3010
3042
  method: "GET",
3043
+ headers: authHeaders,
3011
3044
  credentials: "include",
3012
3045
  signal: AbortSignal.timeout(3e3)
3013
3046
  });
@@ -3610,8 +3643,12 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
3610
3643
  if (accountPages.has(account.id)) {
3611
3644
  return;
3612
3645
  }
3613
- await evictIdlePlaywrightContextsToLimit().catch(() => {
3614
- });
3646
+ if (config.playwright.maxActiveContexts > 0) {
3647
+ await evictIdlePlaywrightContextsToLimit(
3648
+ Math.max(1, config.playwright.maxActiveContexts - 1)
3649
+ ).catch(() => {
3650
+ });
3651
+ }
3615
3652
  const profilePath = getAccountProfilePath(account.id);
3616
3653
  const fingerprint = getFingerprintProfile(account.id);
3617
3654
  const { engine, channel } = resolveBrowserEngine(browserType);
@@ -3836,8 +3873,15 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
3836
3873
  const hasAuthCookie = cookies.some(
3837
3874
  (c) => c.name.toLowerCase().includes("token") || c.name.toLowerCase().includes("session")
3838
3875
  );
3876
+ let initialLoginAttempted = false;
3839
3877
  if (!hasAuthCookie && account.email && account.password) {
3840
- await loginToQwen(account.id, account.email, account.password);
3878
+ initialLoginAttempted = true;
3879
+ const loggedIn = await loginToQwen(account.id, account.email, account.password);
3880
+ if (!loggedIn) {
3881
+ throw new Error(
3882
+ `Authentication failed for account ${maskEmail(account.email)}`
3883
+ );
3884
+ }
3841
3885
  }
3842
3886
  try {
3843
3887
  await acctPage.goto(qwenUrl("/"), {
@@ -3847,13 +3891,21 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
3847
3891
  await sleep2(1500);
3848
3892
  const currentUrl = acctPage.url();
3849
3893
  const isAuthUrl = currentUrl.includes("/auth") || currentUrl.includes("/login");
3850
- if (isAuthUrl && account.email && account.password) {
3894
+ if (isAuthUrl && !initialLoginAttempted && account.email && account.password) {
3851
3895
  console.log(
3852
3896
  `[Playwright] Initial login needed for ${maskEmail(account.email)}, authenticating...`
3853
3897
  );
3854
- await loginToQwen(account.id, account.email, account.password);
3898
+ const loggedIn = await loginToQwen(account.id, account.email, account.password);
3899
+ if (!loggedIn) {
3900
+ throw new Error(
3901
+ `Authentication failed for account ${maskEmail(account.email)}`
3902
+ );
3903
+ }
3855
3904
  }
3856
3905
  } catch (err) {
3906
+ if (err?.message?.includes("Authentication failed for account")) {
3907
+ throw err;
3908
+ }
3857
3909
  console.warn(
3858
3910
  `\u26A0\uFE0F [Playwright] Initial navigation check for ${maskEmail(account.email)}: ${err.message}`
3859
3911
  );
@@ -3865,7 +3917,7 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
3865
3917
  if (!acctPage.isClosed()) {
3866
3918
  try {
3867
3919
  const currentUrl = new URL(acctPage.url());
3868
- if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname !== "/") {
3920
+ if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname.includes("/auth") || currentUrl.pathname.includes("/login") || currentUrl.pathname.includes("_____tmd_____")) {
3869
3921
  await acctPage.goto(qwenUrl("/"), {
3870
3922
  waitUntil: "domcontentloaded",
3871
3923
  timeout: config.timeouts.navigation
@@ -3883,6 +3935,8 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
3883
3935
  } finally {
3884
3936
  release();
3885
3937
  }
3938
+ await evictIdlePlaywrightContextsToLimit().catch(() => {
3939
+ });
3886
3940
  })();
3887
3941
  try {
3888
3942
  await initPromise;
@@ -3996,10 +4050,30 @@ async function loginToQwen(accountId, email, password) {
3996
4050
  const uiResult = await loginViaUi(page, email, password);
3997
4051
  if (uiResult.success) {
3998
4052
  cookieCaches.delete(accountId);
3999
- await saveStorageState(page.context(), accountId);
4000
4053
  try {
4001
- const liveCookies = await page.context().cookies();
4002
- const tokenCookie = liveCookies.find((c) => c.name === "token");
4054
+ let liveCookies = await page.context().cookies();
4055
+ let tokenCookie = liveCookies.find((c) => c.name === "token");
4056
+ if (!tokenCookie) {
4057
+ const lsTok = uiResult.token || await page.evaluate(() => (localStorage.getItem("token") || "").trim()).catch(() => "");
4058
+ if (lsTok) {
4059
+ await page.context().addCookies([
4060
+ {
4061
+ name: "token",
4062
+ value: lsTok,
4063
+ domain: ".qwen.ai",
4064
+ path: "/",
4065
+ expires: Math.floor(Date.now() / 1e3) + 31536e3,
4066
+ httpOnly: false,
4067
+ secure: true,
4068
+ sameSite: "Lax"
4069
+ }
4070
+ ]).catch(() => {
4071
+ });
4072
+ liveCookies = await page.context().cookies();
4073
+ tokenCookie = liveCookies.find((c) => c.name === "token");
4074
+ }
4075
+ }
4076
+ await saveStorageState(page.context(), accountId);
4003
4077
  if (tokenCookie) {
4004
4078
  const cookieStr = liveCookies.map((c) => `${c.name}=${c.value}`).join("; ");
4005
4079
  const cache = getHeaderCache(accountId);
@@ -4008,7 +4082,7 @@ async function loginToQwen(accountId, email, password) {
4008
4082
  markAccountHeadersReady(accountId);
4009
4083
  const { saveAuthSession: saveAuthSession2 } = await Promise.resolve().then(() => (init_database(), database_exports));
4010
4084
  const exp = parseJwtExpiry(tokenCookie.value);
4011
- let rTok = uiResult.refreshToken;
4085
+ let rTok = uiResult.refreshToken || liveCookies.find((c) => c.name === "refresh_token")?.value;
4012
4086
  if (!rTok) {
4013
4087
  try {
4014
4088
  rTok = await page.evaluate(() => localStorage.getItem("refresh_token") || "") || void 0;
@@ -4089,8 +4163,9 @@ async function loginViaApi(page, email, password) {
4089
4163
  }
4090
4164
  const signinUrl = "https://auth.qwen.ai/api/v2/auths/signin";
4091
4165
  const requestId = crypto3.randomUUID();
4166
+ const webVersion = getQwenWebVersion() || "0.3.26";
4092
4167
  const evalRes = await page.evaluate(
4093
- async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2, tz }) => {
4168
+ async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2, tz, version }) => {
4094
4169
  try {
4095
4170
  const response = await fetch(signinUrl2, {
4096
4171
  method: "POST",
@@ -4100,7 +4175,7 @@ async function loginViaApi(page, email, password) {
4100
4175
  accept: "application/json, text/plain, */*",
4101
4176
  "content-type": "application/json",
4102
4177
  source: "web",
4103
- version: getQwenWebVersion() || "0.3.26",
4178
+ version,
4104
4179
  "x-request-origin": "https://chat.qwen.ai",
4105
4180
  timezone: tz,
4106
4181
  "x-request-id": requestId2
@@ -4113,7 +4188,14 @@ async function loginViaApi(page, email, password) {
4113
4188
  return { ok: false, error: e.message };
4114
4189
  }
4115
4190
  },
4116
- { email, password: hashedPassword, signinUrl, requestId, tz: formatAsciiTimezone() }
4191
+ {
4192
+ email,
4193
+ password: hashedPassword,
4194
+ signinUrl,
4195
+ requestId,
4196
+ tz: formatAsciiTimezone(),
4197
+ version: webVersion
4198
+ }
4117
4199
  ).catch(() => null);
4118
4200
  let data = evalRes?.data;
4119
4201
  let signinSuccess = Boolean(
@@ -4122,7 +4204,7 @@ async function loginViaApi(page, email, password) {
4122
4204
  if (!signinSuccess && !data?.data?.code) {
4123
4205
  const fallbackUrl = qwenUrl("/api/v2/auths/signin");
4124
4206
  const fallbackRes = await page.evaluate(
4125
- async ({ email: email2, password: password2, fallbackUrl: fallbackUrl2, requestId: requestId2, tz }) => {
4207
+ async ({ email: email2, password: password2, fallbackUrl: fallbackUrl2, requestId: requestId2, tz, version }) => {
4126
4208
  try {
4127
4209
  const response = await fetch(fallbackUrl2, {
4128
4210
  method: "POST",
@@ -4132,7 +4214,7 @@ async function loginViaApi(page, email, password) {
4132
4214
  accept: "application/json, text/plain, */*",
4133
4215
  "content-type": "application/json",
4134
4216
  source: "web",
4135
- version: getQwenWebVersion() || "0.3.26",
4217
+ version,
4136
4218
  timezone: tz,
4137
4219
  "x-request-id": requestId2
4138
4220
  },
@@ -4144,7 +4226,14 @@ async function loginViaApi(page, email, password) {
4144
4226
  return { ok: false, error: e.message };
4145
4227
  }
4146
4228
  },
4147
- { email, password: hashedPassword, fallbackUrl, requestId, tz: formatAsciiTimezone() }
4229
+ {
4230
+ email,
4231
+ password: hashedPassword,
4232
+ fallbackUrl,
4233
+ requestId,
4234
+ tz: formatAsciiTimezone(),
4235
+ version: webVersion
4236
+ }
4148
4237
  ).catch(() => null);
4149
4238
  if (fallbackRes?.data) {
4150
4239
  data = fallbackRes.data;
@@ -4369,28 +4458,34 @@ async function loginViaUi(page, email, password) {
4369
4458
  await sleep2(500);
4370
4459
  const submitSelector = 'button.qwenchat-auth-pc-submit-button, button[type="submit"], button:has-text("Log in"), button:has-text("Sign in"), button:has-text("Fazer login"), button:has-text("Entrar"), button:has-text("Iniciar sesi\xF3n"), button:has-text("\u767B\u5F55")';
4371
4460
  const submitButton = page.locator(submitSelector).first();
4372
- await page.evaluate((sel) => {
4373
- try {
4374
- const btn = document.querySelector(sel);
4375
- if (btn) {
4376
- btn.removeAttribute("disabled");
4377
- btn.classList.remove("ant-btn-disabled");
4378
- btn.click();
4379
- }
4380
- } catch {
4381
- }
4382
- }, submitSelector).catch(() => {
4383
- });
4461
+ let submitted = false;
4384
4462
  try {
4385
4463
  if (await submitButton.isVisible({ timeout: 2e3 }).catch(() => false)) {
4386
- await submitButton.click({ force: true, timeout: 2e3 }).catch(() => {
4464
+ await submitButton.click({ timeout: 2e3 }).catch(() => {
4387
4465
  });
4466
+ submitted = true;
4388
4467
  }
4389
4468
  } catch {
4390
4469
  }
4391
- const passInput = page.locator(passwordSelector).first();
4392
- await passInput.press("Enter", { timeout: 2e3 }).catch(() => {
4393
- });
4470
+ if (!submitted) {
4471
+ await page.evaluate(() => {
4472
+ try {
4473
+ const btn = document.querySelector(
4474
+ 'button.qwenchat-auth-pc-submit-button, button[type="submit"]'
4475
+ );
4476
+ if (btn) {
4477
+ btn.removeAttribute("disabled");
4478
+ btn.classList.remove("ant-btn-disabled");
4479
+ btn.click();
4480
+ }
4481
+ } catch {
4482
+ }
4483
+ }).catch(() => {
4484
+ });
4485
+ const passInput = page.locator(passwordSelector).first();
4486
+ await passInput.press("Enter", { timeout: 2e3 }).catch(() => {
4487
+ });
4488
+ }
4394
4489
  const postSubmitDeadline = Date.now() + 15e3;
4395
4490
  while (Date.now() < postSubmitDeadline) {
4396
4491
  if (page.isClosed()) break;
@@ -4408,9 +4503,12 @@ async function loginViaUi(page, email, password) {
4408
4503
  ".ant-form-item-explain-error",
4409
4504
  ".ant-message-error",
4410
4505
  ".ant-message-notice",
4506
+ ".ant-message-custom-content",
4507
+ ".ant-notification-notice",
4411
4508
  '[role="alert"]',
4412
4509
  ".qwenchat-auth-error",
4413
- ".auth-error-message"
4510
+ ".auth-error-message",
4511
+ ".qwenchat-toast"
4414
4512
  ].join(", ");
4415
4513
  const errorEl = page.locator(errorSelector).first();
4416
4514
  if (await errorEl.isVisible().catch(() => false)) {
@@ -4424,7 +4522,15 @@ async function loginViaUi(page, email, password) {
4424
4522
  };
4425
4523
  }
4426
4524
  }
4427
- const codeSelector = 'input[placeholder*="code" i], input[placeholder*="c\xF3digo" i], input[name*="code" i]';
4525
+ const codeSelector = [
4526
+ 'input[placeholder*="code" i]',
4527
+ 'input[placeholder*="c\xF3digo" i]',
4528
+ 'input[name*="code" i]',
4529
+ ".qwenchat-verification-code-input",
4530
+ ".qwenchat-verification-code-input-cell",
4531
+ '[aria-label*="Verification code" i]',
4532
+ '[aria-label*="c\xF3digo de verifica\xE7\xE3o" i]'
4533
+ ].join(", ");
4428
4534
  const codeInput = page.locator(codeSelector).first();
4429
4535
  if (await codeInput.isVisible().catch(() => false)) {
4430
4536
  return {
@@ -4434,16 +4540,44 @@ async function loginViaUi(page, email, password) {
4434
4540
  }
4435
4541
  try {
4436
4542
  const liveCookies = await page.context().cookies();
4437
- if (liveCookies.some((c) => c.name === "token")) {
4438
- return { success: true };
4543
+ const rTokCookie = liveCookies.find((c) => c.name === "refresh_token")?.value;
4544
+ const tokCookie = liveCookies.find((c) => c.name === "token")?.value;
4545
+ if (tokCookie) {
4546
+ return { success: true, token: tokCookie, refreshToken: rTokCookie };
4439
4547
  }
4440
- } catch {
4441
- }
4442
- const currentUrl = page.url();
4443
- if (!currentUrl.includes("/auth") && !currentUrl.includes("/login")) {
4444
- if (await isPageLoggedIn(page, 3e3)) {
4445
- return { success: true };
4548
+ const currentUrl = page.url();
4549
+ if (!currentUrl.includes("/auth") && !currentUrl.includes("/login")) {
4550
+ const lsTok = await page.evaluate(() => {
4551
+ const t = (localStorage.getItem("token") || "").trim();
4552
+ if (t) {
4553
+ try {
4554
+ document.cookie = `token=${encodeURIComponent(t)}; path=/; domain=.qwen.ai; max-age=31536000`;
4555
+ } catch {
4556
+ }
4557
+ }
4558
+ return t;
4559
+ }).catch(() => "");
4560
+ if (lsTok) {
4561
+ await page.context().addCookies([
4562
+ {
4563
+ name: "token",
4564
+ value: lsTok,
4565
+ domain: ".qwen.ai",
4566
+ path: "/",
4567
+ expires: Math.floor(Date.now() / 1e3) + 31536e3,
4568
+ httpOnly: false,
4569
+ secure: true,
4570
+ sameSite: "Lax"
4571
+ }
4572
+ ]).catch(() => {
4573
+ });
4574
+ return { success: true, token: lsTok, refreshToken: rTokCookie };
4575
+ }
4576
+ if (await isPageLoggedIn(page, 3e3)) {
4577
+ return { success: true, refreshToken: rTokCookie };
4578
+ }
4446
4579
  }
4580
+ } catch {
4447
4581
  }
4448
4582
  await sleep2(1e3);
4449
4583
  }
@@ -4473,10 +4607,18 @@ async function refreshAccountToken(accountId) {
4473
4607
  } catch {
4474
4608
  }
4475
4609
  }
4610
+ if (!refreshToken) {
4611
+ try {
4612
+ const liveCookies = await page.context().cookies();
4613
+ refreshToken = liveCookies.find((c) => c.name === "refresh_token")?.value;
4614
+ } catch {
4615
+ }
4616
+ }
4476
4617
  if (refreshToken) {
4477
4618
  try {
4619
+ const webVersion = getQwenWebVersion() || "0.3.26";
4478
4620
  const refreshResult = await page.evaluate(
4479
- async ({ rTok, tz }) => {
4621
+ async ({ rTok, tz, version }) => {
4480
4622
  try {
4481
4623
  const response = await fetch("https://auth.qwen.ai/api/v2/auths/refresh", {
4482
4624
  method: "GET",
@@ -4484,7 +4626,7 @@ async function refreshAccountToken(accountId) {
4484
4626
  headers: {
4485
4627
  accept: "application/json, text/plain, */*",
4486
4628
  source: "web",
4487
- version: getQwenWebVersion() || "0.3.26",
4629
+ version,
4488
4630
  "x-request-origin": "https://chat.qwen.ai",
4489
4631
  timezone: tz,
4490
4632
  authorization: `Bearer ${rTok}`
@@ -4496,7 +4638,7 @@ async function refreshAccountToken(accountId) {
4496
4638
  return { ok: false, error: e.message };
4497
4639
  }
4498
4640
  },
4499
- { rTok: refreshToken, tz: formatAsciiTimezone() }
4641
+ { rTok: refreshToken, tz: formatAsciiTimezone(), version: webVersion }
4500
4642
  );
4501
4643
  const json = refreshResult?.data;
4502
4644
  if (json && json.success === true && json.data) {
@@ -5297,7 +5439,11 @@ function priorityOrderForEviction(accountIds) {
5297
5439
  const ranked = getAccountsByPriority(accountIds.map((id) => ({ id })));
5298
5440
  const rank = /* @__PURE__ */ new Map();
5299
5441
  for (const [index, account] of ranked.entries()) {
5300
- 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
+ }
5301
5447
  }
5302
5448
  return orderContextsForEviction(
5303
5449
  accountIds,
@@ -5305,14 +5451,15 @@ function priorityOrderForEviction(accountIds) {
5305
5451
  (id) => lastAccountActivity.get(id) ?? 0
5306
5452
  );
5307
5453
  }
5308
- async function evictIdlePlaywrightContextsToLimit() {
5309
- const max = config.playwright.maxActiveContexts;
5310
- 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;
5311
5458
  if (accountPages.size <= max) return 0;
5312
5459
  const candidates = priorityOrderForEviction(
5313
5460
  Array.from(accountPages.keys()).filter((accountId) => {
5314
5461
  if (isAccountServingStream(accountId)) return false;
5315
- if (isAccountRecentlyActive(accountId, 3e4)) return false;
5462
+ if (hasActiveAccountLease(accountId)) return false;
5316
5463
  const mutex = accountMutexes.get(accountId);
5317
5464
  return mutex?.isIdle();
5318
5465
  })
@@ -5325,6 +5472,7 @@ async function evictIdlePlaywrightContextsToLimit() {
5325
5472
  for (const candidate of candidates) {
5326
5473
  if (accountPages.size <= max) break;
5327
5474
  if (isAccountServingStream(candidate.accountId)) continue;
5475
+ if (hasActiveAccountLease(candidate.accountId)) continue;
5328
5476
  const mutex = accountMutexes.get(candidate.accountId);
5329
5477
  if (!mutex?.isIdle()) continue;
5330
5478
  await closePlaywrightForAccount(candidate.accountId).catch((error) => {
@@ -5427,10 +5575,6 @@ async function closePlaywrightForAccount(accountId) {
5427
5575
  release();
5428
5576
  }
5429
5577
  }
5430
- function isAccountRecentlyActive(accountId, maxIdleMs = 3e5) {
5431
- const last = lastAccountActivity.get(accountId) ?? 0;
5432
- return last > 0 && Date.now() - last < maxIdleMs;
5433
- }
5434
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;
5435
5579
  var init_playwright = __esm({
5436
5580
  "src/services/playwright.ts"() {