qwenproxy-cli 1.5.3 → 1.6.1

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.
@@ -13,6 +13,44 @@ var __export = (target, all) => {
13
13
  __defProp(target, name, { get: all[name], enumerable: true });
14
14
  };
15
15
 
16
+ // src/core/process-guard.ts
17
+ function isPlaywrightAlreadyClosedMessage(message) {
18
+ if (!message) return false;
19
+ return message.includes("Target page, context or browser has been closed") || message.includes("Browser has been closed") || message.includes("Target closed") || message.includes("Target crashed") || message.includes("Page crashed") || message.includes("Assertion error") || message.includes("Cannot find parent object") || message.includes("Connection closed") || message.includes("session closed") || message.includes("Session closed") || message.includes("Network.setCacheDisabled") || message.includes("Protocol error") || message.includes("storageState timed out");
20
+ }
21
+ function isBenignTeardownError(error) {
22
+ if (!error) return false;
23
+ const message = error instanceof Error ? error.message : typeof error === "object" && "message" in error ? String(error.message) : String(error);
24
+ return isPlaywrightAlreadyClosedMessage(message);
25
+ }
26
+ function installProcessTeardownGuards(label = "Process") {
27
+ if (guardsInstalled) return;
28
+ guardsInstalled = true;
29
+ process.on("uncaughtException", (error) => {
30
+ if (isBenignTeardownError(error)) {
31
+ const msg = error instanceof Error ? error.message : typeof error === "object" && error !== null && "message" in error ? String(error.message) : String(error);
32
+ console.warn(`\u26A0\uFE0F [${label}] Handled benign driver teardown exception: ${msg}`);
33
+ return;
34
+ }
35
+ console.error(`\u274C [${label}] Uncaught Exception:`, error);
36
+ });
37
+ process.on("unhandledRejection", (reason) => {
38
+ if (isBenignTeardownError(reason)) {
39
+ const msg = reason instanceof Error ? reason.message : typeof reason === "object" && reason !== null && "message" in reason ? String(reason.message) : String(reason);
40
+ console.warn(`\u26A0\uFE0F [${label}] Handled benign driver teardown rejection: ${msg}`);
41
+ return;
42
+ }
43
+ console.error(`\u274C [${label}] Unhandled Rejection:`, reason);
44
+ });
45
+ }
46
+ var guardsInstalled;
47
+ var init_process_guard = __esm({
48
+ "src/core/process-guard.ts"() {
49
+ "use strict";
50
+ guardsInstalled = false;
51
+ }
52
+ });
53
+
16
54
  // src/core/paths.ts
17
55
  import fs from "node:fs";
18
56
  import path from "node:path";
@@ -296,7 +334,26 @@ function runMigrations(db2) {
296
334
  instruction_hash TEXT NOT NULL,
297
335
  updated_at TEXT NOT NULL DEFAULT (datetime('now'))
298
336
  );
337
+
338
+ CREATE TABLE IF NOT EXISTS account_daily_usage (
339
+ account_id TEXT NOT NULL,
340
+ date_utc TEXT NOT NULL,
341
+ prompt_tokens INTEGER NOT NULL DEFAULT 0,
342
+ completion_tokens INTEGER NOT NULL DEFAULT 0,
343
+ weighted_tokens REAL NOT NULL DEFAULT 0,
344
+ total_tokens INTEGER NOT NULL DEFAULT 0,
345
+ turns_count INTEGER NOT NULL DEFAULT 0,
346
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
347
+ PRIMARY KEY (account_id, date_utc)
348
+ );
349
+
350
+ CREATE INDEX IF NOT EXISTS idx_daily_usage_date ON account_daily_usage(date_utc);
299
351
  `);
352
+ try {
353
+ db2.exec(`ALTER TABLE account_daily_usage ADD COLUMN weighted_tokens REAL DEFAULT 0;`);
354
+ } catch (err) {
355
+ if (!isDuplicateColumnError(err)) throw err;
356
+ }
300
357
  try {
301
358
  db2.exec(
302
359
  `ALTER TABLE accounts ADD COLUMN cooldown_until INTEGER DEFAULT 0;`
@@ -397,7 +454,11 @@ function saveAuthSession(accountId, session) {
397
454
  ) VALUES (
398
455
  ?, ?, ?, ?, ?, ?,
399
456
  ?, ?, ?, ?,
400
- ?, ?, ?, ?, datetime('now')
457
+ COALESCE(?, (SELECT user_id FROM qwen_auth_sessions WHERE account_id = ?)),
458
+ ?,
459
+ COALESCE(?, (SELECT refresh_token FROM qwen_auth_sessions WHERE account_id = ?)),
460
+ ?,
461
+ datetime('now')
401
462
  )
402
463
  `);
403
464
  stmt.run(
@@ -412,8 +473,10 @@ function saveAuthSession(accountId, session) {
412
473
  session.secChUaPlatform || null,
413
474
  session.version || null,
414
475
  session.userId || null,
476
+ accountId,
415
477
  session.tokenExpiresAt || null,
416
478
  session.refreshToken || null,
479
+ accountId,
417
480
  session.capturedAt ?? Date.now()
418
481
  );
419
482
  }
@@ -1046,11 +1109,11 @@ var init_config = __esm({
1046
1109
  // nothing in this window is dead and should fail fast, retryable).
1047
1110
  QWEN_FIRST_CHUNK_TIMEOUT: z.string().default("60000"),
1048
1111
  TOTAL_REQUEST_TIMEOUT: z.string().default("600000"),
1049
- // Mid-stream silence window for thinking models: 3 min with ZERO upstream
1112
+ // Mid-stream silence window for thinking models: 5 min with ZERO upstream
1050
1113
  // bytes is a dead stream (WAF swallow / dropped connection) — fail fast and
1051
1114
  // let the retry policy rotate accounts. Flowing reasoning chunks RESET this
1052
1115
  // timer, so legitimate slow thinking is never cut; only total silence is.
1053
- REASONING_MODEL_TIMEOUT: z.string().default("180000"),
1116
+ REASONING_MODEL_TIMEOUT: z.string().default("300000"),
1054
1117
  CACHE_TTL: z.string().default("3600"),
1055
1118
  RESPONSE_TTL: z.string().default("1800"),
1056
1119
  CACHE_COMPRESSION_ENABLED: z.string().default("true"),
@@ -3080,6 +3143,8 @@ var init_clean_cache = __esm({
3080
3143
  "src/clean-cache.ts"() {
3081
3144
  "use strict";
3082
3145
  init_playwright();
3146
+ init_process_guard();
3147
+ installProcessTeardownGuards("CleanCache");
3083
3148
  isDirectRun = process.argv[1] && (process.argv[1].endsWith("clean-cache.ts") || process.argv[1].endsWith("clean-cache.js"));
3084
3149
  if (isDirectRun) {
3085
3150
  main().catch((err) => {
@@ -3304,15 +3369,18 @@ function loadStorageState(accountId) {
3304
3369
  return void 0;
3305
3370
  }
3306
3371
  }
3307
- async function saveStorageState(context, accountId, timeoutMs = 5e3) {
3372
+ async function saveStorageState(context, accountId, timeoutMs = 1e4) {
3308
3373
  try {
3309
3374
  const stateFile = getStorageStatePath(accountId);
3310
3375
  const dir = path5.dirname(stateFile);
3311
3376
  if (!fs8.existsSync(dir)) {
3312
3377
  fs8.mkdirSync(dir, { recursive: true });
3313
3378
  }
3379
+ const rawPromise = context.storageState({ path: stateFile }).catch(() => {
3380
+ return void 0;
3381
+ });
3314
3382
  await withTimeout(
3315
- context.storageState({ path: stateFile }),
3383
+ rawPromise,
3316
3384
  timeoutMs,
3317
3385
  `storageState timed out after ${timeoutMs}ms`
3318
3386
  );
@@ -4039,6 +4107,11 @@ async function getBasicHeaders(accountId) {
4039
4107
  if (!page) {
4040
4108
  throw new Error(`Playwright not initialized for account: ${accountId}`);
4041
4109
  }
4110
+ const inFlightInit = inFlightAccountInits.get(accountId);
4111
+ if (inFlightInit) {
4112
+ await inFlightInit.catch(() => {
4113
+ });
4114
+ }
4042
4115
  const release = await acquireAccountMutex(
4043
4116
  accountId,
4044
4117
  `headers:${accountId.substring(0, 12)}`
@@ -5513,6 +5586,11 @@ async function captureQwenHeaders(accountId, pageOverride, timeoutMs = config.ti
5513
5586
  const { saveAuthSession: saveAuthSession2 } = await Promise.resolve().then(() => (init_database(), database_exports));
5514
5587
  const { parseJwtExpiry: parseJwtExpiry2 } = await Promise.resolve().then(() => (init_jwt(), jwt_exports));
5515
5588
  const tokenExpiry = parseJwtExpiry2(capturedHeaders.cookie);
5589
+ let rTok;
5590
+ try {
5591
+ rTok = (await page.evaluate(() => localStorage.getItem("refresh_token") || "")).trim() || void 0;
5592
+ } catch {
5593
+ }
5516
5594
  saveAuthSession2(accountId, {
5517
5595
  cookie: capturedHeaders.cookie,
5518
5596
  userAgent: capturedHeaders["user-agent"],
@@ -5524,6 +5602,7 @@ async function captureQwenHeaders(accountId, pageOverride, timeoutMs = config.ti
5524
5602
  secChUaPlatform: capturedHeaders["sec-ch-ua-platform"],
5525
5603
  version: capturedHeaders["version"],
5526
5604
  tokenExpiresAt: tokenExpiry || void 0,
5605
+ refreshToken: rTok,
5527
5606
  capturedAt: Date.now()
5528
5607
  });
5529
5608
  } catch {
@@ -5555,38 +5634,57 @@ async function captureQwenHeaders(accountId, pageOverride, timeoutMs = config.ti
5555
5634
  const isAuthUrl = currentUrl.includes("/auth") || currentUrl.includes("/login");
5556
5635
  const isSuspectedGuest = isAuthUrl || attempt >= 2 && !await isPageLoggedIn(page, 5e3);
5557
5636
  if (isSuspectedGuest) {
5558
- const { getAccountCredentials: getAccountCredentials2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
5559
- const creds = getAccountCredentials2(accountId);
5560
- if (creds && creds.email && creds.password) {
5561
- console.warn(
5562
- `\u26A0\uFE0F [Playwright] Session expired or guest state detected during header capture for ${accountId}; re-authenticating...`
5637
+ const silentRefresh = await refreshAccountToken(accountId);
5638
+ if (silentRefresh.success) {
5639
+ console.log(
5640
+ `\u26A1 [Playwright] Token refreshed silently during header capture for ${accountId}`
5563
5641
  );
5564
- armOverallDeadline();
5565
- const ok = await loginToQwen(accountId, creds.email, creds.password);
5566
- if (ok) {
5642
+ if (isAuthUrl) {
5567
5643
  await page.goto(qwenUrl("/"), {
5568
5644
  waitUntil: "domcontentloaded",
5569
5645
  timeout: Math.min(config.timeouts.navigation, timeoutMs)
5570
5646
  });
5571
- await sleep2(2e3);
5647
+ await sleep2(1500);
5648
+ }
5649
+ if (await isPageLoggedIn(page, 5e3)) {
5650
+ armOverallDeadline();
5572
5651
  }
5573
- if (!ok || !await isPageLoggedIn(page, 6e3)) {
5652
+ }
5653
+ const stillSuspected = !silentRefresh.success || !await isPageLoggedIn(page, 2e3);
5654
+ if (stillSuspected) {
5655
+ const { getAccountCredentials: getAccountCredentials2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
5656
+ const creds = getAccountCredentials2(accountId);
5657
+ if (creds && creds.email && creds.password) {
5658
+ console.warn(
5659
+ `\u26A0\uFE0F [Playwright] Session expired or guest state detected during header capture for ${accountId}; re-authenticating...`
5660
+ );
5661
+ armOverallDeadline();
5662
+ const ok = await loginToQwen(accountId, creds.email, creds.password);
5663
+ if (ok) {
5664
+ await page.goto(qwenUrl("/"), {
5665
+ waitUntil: "domcontentloaded",
5666
+ timeout: Math.min(config.timeouts.navigation, timeoutMs)
5667
+ });
5668
+ await sleep2(2e3);
5669
+ }
5670
+ if (!ok || !await isPageLoggedIn(page, 6e3)) {
5671
+ settle(
5672
+ new Error(
5673
+ `Header capture failed for ${accountId}: re-login after session expiry did not succeed`
5674
+ )
5675
+ );
5676
+ return;
5677
+ }
5678
+ armOverallDeadline();
5679
+ if (settled || page.isClosed()) return;
5680
+ } else {
5574
5681
  settle(
5575
5682
  new Error(
5576
- `Header capture failed for ${accountId}: re-login after session expiry did not succeed`
5683
+ `Header capture failed for ${accountId}: session expired and no credentials available for re-login (run 'qpx login')`
5577
5684
  )
5578
5685
  );
5579
5686
  return;
5580
5687
  }
5581
- armOverallDeadline();
5582
- if (settled || page.isClosed()) return;
5583
- } else {
5584
- settle(
5585
- new Error(
5586
- `Header capture failed for ${accountId}: session expired and no credentials available for re-login (run 'qpx login')`
5587
- )
5588
- );
5589
- return;
5590
5688
  }
5591
5689
  }
5592
5690
  if (settled || page.isClosed()) return;
@@ -5902,7 +6000,13 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
5902
6000
  `[Playwright] Navigation check failed during refresh for ${accountId}:`,
5903
6001
  navErr.message
5904
6002
  );
5905
- await executeReauth();
6003
+ if (await isPageLoggedIn(page, 3e3)) {
6004
+ } else {
6005
+ const refreshed = await refreshAccountToken(accountId);
6006
+ if (!refreshed.success) {
6007
+ await executeReauth();
6008
+ }
6009
+ }
5906
6010
  }
5907
6011
  }
5908
6012
  if (hasRequiredQwenHeaders(cache.headers)) {
@@ -5916,6 +6020,11 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
5916
6020
  const { saveAuthSession: saveAuthSession2 } = await Promise.resolve().then(() => (init_database(), database_exports));
5917
6021
  const tokenCookie = liveCookies.find((c) => c.name === "token");
5918
6022
  const exp = tokenCookie ? parseJwtExpiry(tokenCookie.value) : void 0;
6023
+ let rTok;
6024
+ try {
6025
+ rTok = (await page.evaluate(() => localStorage.getItem("refresh_token") || "")).trim() || void 0;
6026
+ } catch {
6027
+ }
5919
6028
  saveAuthSession2(accountId, {
5920
6029
  cookie: cookieStr,
5921
6030
  userAgent: cache.headers["user-agent"] || "",
@@ -5927,6 +6036,7 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
5927
6036
  secChUaPlatform: cache.headers["sec-ch-ua-platform"] || void 0,
5928
6037
  version: cache.headers["version"] || void 0,
5929
6038
  tokenExpiresAt: exp || void 0,
6039
+ refreshToken: rTok,
5930
6040
  capturedAt: Date.now()
5931
6041
  });
5932
6042
  } catch {
@@ -6313,8 +6423,10 @@ async function keepAlivePlaywrightAccount(accountId) {
6313
6423
  if (!page || page.isClosed()) return false;
6314
6424
  const now = Date.now();
6315
6425
  const currentUrl = page.url();
6426
+ const lastRefreshAttempt = lastProactiveTokenRefresh.get(accountId) ?? 0;
6316
6427
  const cookie = await getCookies(accountId);
6317
- if (cookie && isTokenExpiringSoon(cookie, 5)) {
6428
+ if (cookie && isTokenExpiringSoon(cookie, 5) && now - lastRefreshAttempt > 6e4) {
6429
+ lastProactiveTokenRefresh.set(accountId, now);
6318
6430
  console.log(
6319
6431
  `\u{1F493} [SessionKeeper] Account ${accountId} access token expiring soon (<5m); silently refreshing...`
6320
6432
  );
@@ -6358,6 +6470,7 @@ function cleanupPlaywrightAccountState(accountId) {
6358
6470
  cookieCaches.delete(accountId);
6359
6471
  lastAccountActivity.delete(accountId);
6360
6472
  lastKeepAliveNavigation.delete(accountId);
6473
+ lastProactiveTokenRefresh.delete(accountId);
6361
6474
  clearFingerprintCache(accountId);
6362
6475
  unmarkAccountHeadersReady(accountId);
6363
6476
  }
@@ -6541,7 +6654,7 @@ async function getTokenDiagnostics(accountId) {
6541
6654
  }
6542
6655
  };
6543
6656
  }
6544
- var requireLocal, contextInitHooks, ACCOUNT_MUTEX_MAX_HOLD_MS, accountMutexes, accountContexts, accountPages, cachedUserAgents, inFlightAccountInits, sharedBrowser, sharedBrowserPromise, headerCaches, HEADER_CACHE_TTL, HEADER_REFRESH_THRESHOLD, COOKIE_CACHE_TTL, cookieCaches, lastAccountActivity, lastKeepAliveNavigation, 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;
6657
+ var requireLocal, contextInitHooks, ACCOUNT_MUTEX_MAX_HOLD_MS, accountMutexes, accountContexts, accountPages, cachedUserAgents, inFlightAccountInits, sharedBrowser, sharedBrowserPromise, headerCaches, HEADER_CACHE_TTL, HEADER_REFRESH_THRESHOLD, 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;
6545
6658
  var init_playwright = __esm({
6546
6659
  "src/services/playwright.ts"() {
6547
6660
  "use strict";
@@ -6577,6 +6690,7 @@ var init_playwright = __esm({
6577
6690
  cookieCaches = /* @__PURE__ */ new Map();
6578
6691
  lastAccountActivity = /* @__PURE__ */ new Map();
6579
6692
  lastKeepAliveNavigation = /* @__PURE__ */ new Map();
6693
+ lastProactiveTokenRefresh = /* @__PURE__ */ new Map();
6580
6694
  profileResetQueue = /* @__PURE__ */ new Map();
6581
6695
  profileResetChain = Promise.resolve();
6582
6696
  closingAllPlaywright = false;
@@ -7112,9 +7226,13 @@ var init_model_registry = __esm({
7112
7226
  // src/services/captcha-coordinator.ts
7113
7227
  var captcha_coordinator_exports = {};
7114
7228
  __export(captcha_coordinator_exports, {
7229
+ isCaptchaRecoveryActive: () => isCaptchaRecoveryActive,
7115
7230
  recoverBaxiaCaptcha: () => recoverBaxiaCaptcha,
7116
7231
  solveChallengeOnPage: () => solveChallengeOnPage
7117
7232
  });
7233
+ function isCaptchaRecoveryActive(accountId) {
7234
+ return activeCaptchaRecoveries.has(accountId);
7235
+ }
7118
7236
  async function gotoBestEffort(page, url) {
7119
7237
  await page.goto(url, {
7120
7238
  waitUntil: "domcontentloaded",
@@ -7157,11 +7275,12 @@ async function recoverBaxiaCaptcha(accountId, label, options = {}) {
7157
7275
  return false;
7158
7276
  }
7159
7277
  metrics.increment("captcha.challenges.detected", 1, { solver });
7160
- const challengeUrl = options.challengeBody ? extractBaxiaChallengeUrl(options.challengeBody, config.qwen.baseUrl) : null;
7161
7278
  const solverOperationTimeoutMs = Math.max(
7162
7279
  15e3,
7163
7280
  config.captcha.timeoutMs + config.captcha.maxAttempts * (3e3 + config.captcha.retryDelayMs + config.captcha.settleMs) + 2 * CHALLENGE_NAVIGATION_TIMEOUT_MS
7164
7281
  );
7282
+ const challengeUrl = options.challengeBody ? extractBaxiaChallengeUrl(options.challengeBody, config.qwen.baseUrl) : null;
7283
+ activeCaptchaRecoveries.add(accountId);
7165
7284
  try {
7166
7285
  const solved = await withAccountPage(
7167
7286
  accountId,
@@ -7194,9 +7313,11 @@ async function recoverBaxiaCaptcha(accountId, label, options = {}) {
7194
7313
  detail
7195
7314
  });
7196
7315
  return false;
7316
+ } finally {
7317
+ activeCaptchaRecoveries.delete(accountId);
7197
7318
  }
7198
7319
  }
7199
- var CHALLENGE_NAVIGATION_TIMEOUT_MS, CHALLENGE_PATH_MARKER, FAILED_RECOVERY_BACKOFF_MS, lastFailedRecoveryAt;
7320
+ var CHALLENGE_NAVIGATION_TIMEOUT_MS, CHALLENGE_PATH_MARKER, FAILED_RECOVERY_BACKOFF_MS, lastFailedRecoveryAt, activeCaptchaRecoveries;
7200
7321
  var init_captcha_coordinator = __esm({
7201
7322
  "src/services/captcha-coordinator.ts"() {
7202
7323
  "use strict";
@@ -7209,6 +7330,7 @@ var init_captcha_coordinator = __esm({
7209
7330
  CHALLENGE_PATH_MARKER = "_____tmd_____";
7210
7331
  FAILED_RECOVERY_BACKOFF_MS = 3e4;
7211
7332
  lastFailedRecoveryAt = /* @__PURE__ */ new Map();
7333
+ activeCaptchaRecoveries = /* @__PURE__ */ new Set();
7212
7334
  }
7213
7335
  });
7214
7336
 
@@ -7941,6 +8063,7 @@ __export(qwen_exports, {
7941
8063
  fetchRemoteQwenChats: () => fetchRemoteQwenChats,
7942
8064
  flushLogicalThreadState: () => flushLogicalThreadState,
7943
8065
  getAnyCachedQwenModels: () => getAnyCachedQwenModels,
8066
+ getInFlightPersonalizationCount: () => getInFlightPersonalizationCount,
7944
8067
  getLogicalThreadState: () => getLogicalThreadState,
7945
8068
  getQwenErrorCode: () => getQwenErrorCode,
7946
8069
  getSessionParent: () => getSessionParent,
@@ -8873,12 +8996,51 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
8873
8996
  captchaWatcher?.stop();
8874
8997
  }
8875
8998
  }
8999
+ function getInFlightPersonalizationCount() {
9000
+ return inFlightPersonalizationSyncs.size;
9001
+ }
8876
9002
  async function syncQwenRequestPersonalization(instruction, accountId, metadata = {}) {
8877
- if (isAuthMockEnabled()) {
8878
- if (process.env.TEST_PERSONALIZATION_SYNC_FAIL === "true") return false;
8879
- return true;
8880
- }
8881
9003
  const cacheKey = accountId || "global";
9004
+ const sent = textSize(instruction);
9005
+ const syncHash = sent.hash ? `${sent.hash}:${QWEN_SAFE_SETTINGS_HASH}` : null;
9006
+ const inFlightKey = `${cacheKey}:${syncHash || "empty"}`;
9007
+ const existingInFlight = inFlightPersonalizationSyncs.get(inFlightKey);
9008
+ if (existingInFlight) {
9009
+ logger.debug("[Qwen] Personalization sync already in flight, sharing promise", {
9010
+ accountId: cacheKey
9011
+ });
9012
+ return existingInFlight;
9013
+ }
9014
+ let syncPromise;
9015
+ syncPromise = (async () => {
9016
+ await Promise.resolve();
9017
+ try {
9018
+ if (isAuthMockEnabled()) {
9019
+ const delay = parseInt(process.env.TEST_PERSONALIZATION_DELAY_MS || "0", 10);
9020
+ if (delay > 0) {
9021
+ await sleep4(delay);
9022
+ }
9023
+ if (process.env.TEST_PERSONALIZATION_SYNC_FAIL === "true") return false;
9024
+ return true;
9025
+ }
9026
+ return await syncQwenRequestPersonalizationInternal(
9027
+ instruction,
9028
+ accountId,
9029
+ metadata,
9030
+ cacheKey,
9031
+ sent,
9032
+ syncHash
9033
+ );
9034
+ } finally {
9035
+ if (inFlightPersonalizationSyncs.get(inFlightKey) === syncPromise) {
9036
+ inFlightPersonalizationSyncs.delete(inFlightKey);
9037
+ }
9038
+ }
9039
+ })();
9040
+ inFlightPersonalizationSyncs.set(inFlightKey, syncPromise);
9041
+ return syncPromise;
9042
+ }
9043
+ async function syncQwenRequestPersonalizationInternal(instruction, accountId, metadata, cacheKey, sent, syncHash) {
8882
9044
  let forceRefresh = false;
8883
9045
  try {
8884
9046
  const basic = await getBasicHeaders2(accountId);
@@ -8896,8 +9058,6 @@ async function syncQwenRequestPersonalization(instruction, accountId, metadata =
8896
9058
  });
8897
9059
  let currentSettings = null;
8898
9060
  let payload = buildQwenSettingsUpdatePayload(currentSettings, instruction);
8899
- const sent = textSize(instruction);
8900
- const syncHash = sent.hash ? `${sent.hash}:${QWEN_SAFE_SETTINGS_HASH}` : null;
8901
9061
  const bypassCache = metadata.forceSync === true;
8902
9062
  const cachedHash = lastSyncedPersonalizationHashes.get(cacheKey);
8903
9063
  if (!bypassCache && syncHash && cachedHash === syncHash) {
@@ -10242,7 +10402,7 @@ async function createQwenStreamInternal(prompt, enableThinking, modelId, forcedP
10242
10402
  clearTimeout(timeoutId);
10243
10403
  }
10244
10404
  }
10245
- var sleep4, BROWSER_STREAM_BINDING, BROWSER_ABORTERS_KEY, BROWSER_STREAM_FLUSH_BYTES, BROWSER_STREAM_FLUSH_MS, METADATA_TIMEOUT_PER_PAYLOAD_MB_MS, POST_CAPTCHA_METADATA_GRACE_MS, browserStreamStates, browserStreamBindingPages, browserStreamBindingContexts, MODEL_CACHE_TTL_MS, modelsCache, nativeToolsDisabled, disablingNativeToolsInProgress, lastSyncedPersonalizationHashes, directSettingsFetchConsecutiveFailures, DIRECT_SETTINGS_FETCH_BLOCK_THRESHOLD, directSettingsFetchBlocked, DIRECT_SETTINGS_FETCH_TIMEOUT_MS, activePersonalizationByAccount, accountStreamMutexes, QWEN_SAFE_SETTINGS_PATCH, QWEN_SAFE_SETTINGS_HASH, DISABLE_TOOLS_MAX_RETRIES, DISABLE_TOOLS_BACKOFF_MS, UPSTREAM_RESPONSE_PREVIEW_BYTES;
10405
+ var sleep4, BROWSER_STREAM_BINDING, BROWSER_ABORTERS_KEY, BROWSER_STREAM_FLUSH_BYTES, BROWSER_STREAM_FLUSH_MS, METADATA_TIMEOUT_PER_PAYLOAD_MB_MS, POST_CAPTCHA_METADATA_GRACE_MS, browserStreamStates, browserStreamBindingPages, browserStreamBindingContexts, MODEL_CACHE_TTL_MS, modelsCache, nativeToolsDisabled, disablingNativeToolsInProgress, lastSyncedPersonalizationHashes, directSettingsFetchConsecutiveFailures, DIRECT_SETTINGS_FETCH_BLOCK_THRESHOLD, directSettingsFetchBlocked, DIRECT_SETTINGS_FETCH_TIMEOUT_MS, activePersonalizationByAccount, accountStreamMutexes, QWEN_SAFE_SETTINGS_PATCH, QWEN_SAFE_SETTINGS_HASH, inFlightPersonalizationSyncs, DISABLE_TOOLS_MAX_RETRIES, DISABLE_TOOLS_BACKOFF_MS, UPSTREAM_RESPONSE_PREVIEW_BYTES;
10246
10406
  var init_qwen = __esm({
10247
10407
  "src/services/qwen.ts"() {
10248
10408
  "use strict";
@@ -10329,6 +10489,7 @@ var init_qwen = __esm({
10329
10489
  }
10330
10490
  };
10331
10491
  QWEN_SAFE_SETTINGS_HASH = crypto5.createHash("sha256").update(JSON.stringify(QWEN_SAFE_SETTINGS_PATCH)).digest("hex").slice(0, 12);
10492
+ inFlightPersonalizationSyncs = /* @__PURE__ */ new Map();
10332
10493
  DISABLE_TOOLS_MAX_RETRIES = 3;
10333
10494
  DISABLE_TOOLS_BACKOFF_MS = 2e3;
10334
10495
  UPSTREAM_RESPONSE_PREVIEW_BYTES = 8 * 1024;
@@ -10336,6 +10497,7 @@ var init_qwen = __esm({
10336
10497
  });
10337
10498
 
10338
10499
  // src/delete-chats.ts
10500
+ init_process_guard();
10339
10501
  import "dotenv/config";
10340
10502
 
10341
10503
  // src/services/chat-cleanup.ts
@@ -10399,6 +10561,12 @@ async function deleteChatsForConfiguredAccounts(keepBrowserOpen = false) {
10399
10561
  `\u274C [DeleteChats] [${currentIdx}/${totalCount}] Falha ao apagar conversas de ${maskEmail(account.email)}:`,
10400
10562
  error instanceof Error ? error.message : String(error)
10401
10563
  );
10564
+ } finally {
10565
+ if (!keepBrowserOpen) {
10566
+ const { closePlaywrightForAccount: closePlaywrightForAccount2 } = await Promise.resolve().then(() => (init_playwright(), playwright_exports));
10567
+ await closePlaywrightForAccount2(account.id).catch(() => {
10568
+ });
10569
+ }
10402
10570
  }
10403
10571
  }
10404
10572
  } finally {
@@ -10419,6 +10587,7 @@ async function deleteChatsForConfiguredAccounts(keepBrowserOpen = false) {
10419
10587
  }
10420
10588
 
10421
10589
  // src/delete-chats.ts
10590
+ installProcessTeardownGuards("DeleteChats");
10422
10591
  async function run() {
10423
10592
  console.log("\u{1F5D1}\uFE0F [DeleteChats] Using Playwright sessions");
10424
10593
  const result = await deleteChatsForConfiguredAccounts();