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.
package/dist/login.js CHANGED
@@ -341,7 +341,26 @@ function runMigrations(db2) {
341
341
  instruction_hash TEXT NOT NULL,
342
342
  updated_at TEXT NOT NULL DEFAULT (datetime('now'))
343
343
  );
344
+
345
+ CREATE TABLE IF NOT EXISTS account_daily_usage (
346
+ account_id TEXT NOT NULL,
347
+ date_utc TEXT NOT NULL,
348
+ prompt_tokens INTEGER NOT NULL DEFAULT 0,
349
+ completion_tokens INTEGER NOT NULL DEFAULT 0,
350
+ weighted_tokens REAL NOT NULL DEFAULT 0,
351
+ total_tokens INTEGER NOT NULL DEFAULT 0,
352
+ turns_count INTEGER NOT NULL DEFAULT 0,
353
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
354
+ PRIMARY KEY (account_id, date_utc)
355
+ );
356
+
357
+ CREATE INDEX IF NOT EXISTS idx_daily_usage_date ON account_daily_usage(date_utc);
344
358
  `);
359
+ try {
360
+ db2.exec(`ALTER TABLE account_daily_usage ADD COLUMN weighted_tokens REAL DEFAULT 0;`);
361
+ } catch (err) {
362
+ if (!isDuplicateColumnError(err)) throw err;
363
+ }
345
364
  try {
346
365
  db2.exec(
347
366
  `ALTER TABLE accounts ADD COLUMN cooldown_until INTEGER DEFAULT 0;`
@@ -442,7 +461,11 @@ function saveAuthSession(accountId, session) {
442
461
  ) VALUES (
443
462
  ?, ?, ?, ?, ?, ?,
444
463
  ?, ?, ?, ?,
445
- ?, ?, ?, ?, datetime('now')
464
+ COALESCE(?, (SELECT user_id FROM qwen_auth_sessions WHERE account_id = ?)),
465
+ ?,
466
+ COALESCE(?, (SELECT refresh_token FROM qwen_auth_sessions WHERE account_id = ?)),
467
+ ?,
468
+ datetime('now')
446
469
  )
447
470
  `);
448
471
  stmt.run(
@@ -457,8 +480,10 @@ function saveAuthSession(accountId, session) {
457
480
  session.secChUaPlatform || null,
458
481
  session.version || null,
459
482
  session.userId || null,
483
+ accountId,
460
484
  session.tokenExpiresAt || null,
461
485
  session.refreshToken || null,
486
+ accountId,
462
487
  session.capturedAt ?? Date.now()
463
488
  );
464
489
  }
@@ -1077,6 +1102,44 @@ var init_logger = __esm({
1077
1102
  }
1078
1103
  });
1079
1104
 
1105
+ // src/core/process-guard.ts
1106
+ function isPlaywrightAlreadyClosedMessage(message) {
1107
+ if (!message) return false;
1108
+ 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");
1109
+ }
1110
+ function isBenignTeardownError(error) {
1111
+ if (!error) return false;
1112
+ const message = error instanceof Error ? error.message : typeof error === "object" && "message" in error ? String(error.message) : String(error);
1113
+ return isPlaywrightAlreadyClosedMessage(message);
1114
+ }
1115
+ function installProcessTeardownGuards(label = "Process") {
1116
+ if (guardsInstalled) return;
1117
+ guardsInstalled = true;
1118
+ process.on("uncaughtException", (error) => {
1119
+ if (isBenignTeardownError(error)) {
1120
+ const msg = error instanceof Error ? error.message : typeof error === "object" && error !== null && "message" in error ? String(error.message) : String(error);
1121
+ console.warn(`\u26A0\uFE0F [${label}] Handled benign driver teardown exception: ${msg}`);
1122
+ return;
1123
+ }
1124
+ console.error(`\u274C [${label}] Uncaught Exception:`, error);
1125
+ });
1126
+ process.on("unhandledRejection", (reason) => {
1127
+ if (isBenignTeardownError(reason)) {
1128
+ const msg = reason instanceof Error ? reason.message : typeof reason === "object" && reason !== null && "message" in reason ? String(reason.message) : String(reason);
1129
+ console.warn(`\u26A0\uFE0F [${label}] Handled benign driver teardown rejection: ${msg}`);
1130
+ return;
1131
+ }
1132
+ console.error(`\u274C [${label}] Unhandled Rejection:`, reason);
1133
+ });
1134
+ }
1135
+ var guardsInstalled;
1136
+ var init_process_guard = __esm({
1137
+ "src/core/process-guard.ts"() {
1138
+ "use strict";
1139
+ guardsInstalled = false;
1140
+ }
1141
+ });
1142
+
1080
1143
  // src/core/config.ts
1081
1144
  var config_exports = {};
1082
1145
  __export(config_exports, {
@@ -1226,11 +1289,11 @@ var init_config = __esm({
1226
1289
  // nothing in this window is dead and should fail fast, retryable).
1227
1290
  QWEN_FIRST_CHUNK_TIMEOUT: z.string().default("60000"),
1228
1291
  TOTAL_REQUEST_TIMEOUT: z.string().default("600000"),
1229
- // Mid-stream silence window for thinking models: 3 min with ZERO upstream
1292
+ // Mid-stream silence window for thinking models: 5 min with ZERO upstream
1230
1293
  // bytes is a dead stream (WAF swallow / dropped connection) — fail fast and
1231
1294
  // let the retry policy rotate accounts. Flowing reasoning chunks RESET this
1232
1295
  // timer, so legitimate slow thinking is never cut; only total silence is.
1233
- REASONING_MODEL_TIMEOUT: z.string().default("180000"),
1296
+ REASONING_MODEL_TIMEOUT: z.string().default("300000"),
1234
1297
  CACHE_TTL: z.string().default("3600"),
1235
1298
  RESPONSE_TTL: z.string().default("1800"),
1236
1299
  CACHE_COMPRESSION_ENABLED: z.string().default("true"),
@@ -3102,6 +3165,8 @@ var init_clean_cache = __esm({
3102
3165
  "src/clean-cache.ts"() {
3103
3166
  "use strict";
3104
3167
  init_playwright();
3168
+ init_process_guard();
3169
+ installProcessTeardownGuards("CleanCache");
3105
3170
  isDirectRun = process.argv[1] && (process.argv[1].endsWith("clean-cache.ts") || process.argv[1].endsWith("clean-cache.js"));
3106
3171
  if (isDirectRun) {
3107
3172
  main().catch((err) => {
@@ -3326,15 +3391,18 @@ function loadStorageState(accountId) {
3326
3391
  return void 0;
3327
3392
  }
3328
3393
  }
3329
- async function saveStorageState(context, accountId, timeoutMs = 5e3) {
3394
+ async function saveStorageState(context, accountId, timeoutMs = 1e4) {
3330
3395
  try {
3331
3396
  const stateFile = getStorageStatePath(accountId);
3332
3397
  const dir = path5.dirname(stateFile);
3333
3398
  if (!fs8.existsSync(dir)) {
3334
3399
  fs8.mkdirSync(dir, { recursive: true });
3335
3400
  }
3401
+ const rawPromise = context.storageState({ path: stateFile }).catch(() => {
3402
+ return void 0;
3403
+ });
3336
3404
  await withTimeout(
3337
- context.storageState({ path: stateFile }),
3405
+ rawPromise,
3338
3406
  timeoutMs,
3339
3407
  `storageState timed out after ${timeoutMs}ms`
3340
3408
  );
@@ -4061,6 +4129,11 @@ async function getBasicHeaders(accountId) {
4061
4129
  if (!page) {
4062
4130
  throw new Error(`Playwright not initialized for account: ${accountId}`);
4063
4131
  }
4132
+ const inFlightInit = inFlightAccountInits.get(accountId);
4133
+ if (inFlightInit) {
4134
+ await inFlightInit.catch(() => {
4135
+ });
4136
+ }
4064
4137
  const release = await acquireAccountMutex(
4065
4138
  accountId,
4066
4139
  `headers:${accountId.substring(0, 12)}`
@@ -5535,6 +5608,11 @@ async function captureQwenHeaders(accountId, pageOverride, timeoutMs = config.ti
5535
5608
  const { saveAuthSession: saveAuthSession2 } = await Promise.resolve().then(() => (init_database(), database_exports));
5536
5609
  const { parseJwtExpiry: parseJwtExpiry2 } = await Promise.resolve().then(() => (init_jwt(), jwt_exports));
5537
5610
  const tokenExpiry = parseJwtExpiry2(capturedHeaders.cookie);
5611
+ let rTok;
5612
+ try {
5613
+ rTok = (await page.evaluate(() => localStorage.getItem("refresh_token") || "")).trim() || void 0;
5614
+ } catch {
5615
+ }
5538
5616
  saveAuthSession2(accountId, {
5539
5617
  cookie: capturedHeaders.cookie,
5540
5618
  userAgent: capturedHeaders["user-agent"],
@@ -5546,6 +5624,7 @@ async function captureQwenHeaders(accountId, pageOverride, timeoutMs = config.ti
5546
5624
  secChUaPlatform: capturedHeaders["sec-ch-ua-platform"],
5547
5625
  version: capturedHeaders["version"],
5548
5626
  tokenExpiresAt: tokenExpiry || void 0,
5627
+ refreshToken: rTok,
5549
5628
  capturedAt: Date.now()
5550
5629
  });
5551
5630
  } catch {
@@ -5577,38 +5656,57 @@ async function captureQwenHeaders(accountId, pageOverride, timeoutMs = config.ti
5577
5656
  const isAuthUrl = currentUrl.includes("/auth") || currentUrl.includes("/login");
5578
5657
  const isSuspectedGuest = isAuthUrl || attempt >= 2 && !await isPageLoggedIn(page, 5e3);
5579
5658
  if (isSuspectedGuest) {
5580
- const { getAccountCredentials: getAccountCredentials2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
5581
- const creds = getAccountCredentials2(accountId);
5582
- if (creds && creds.email && creds.password) {
5583
- console.warn(
5584
- `\u26A0\uFE0F [Playwright] Session expired or guest state detected during header capture for ${accountId}; re-authenticating...`
5659
+ const silentRefresh = await refreshAccountToken(accountId);
5660
+ if (silentRefresh.success) {
5661
+ console.log(
5662
+ `\u26A1 [Playwright] Token refreshed silently during header capture for ${accountId}`
5585
5663
  );
5586
- armOverallDeadline();
5587
- const ok = await loginToQwen(accountId, creds.email, creds.password);
5588
- if (ok) {
5664
+ if (isAuthUrl) {
5589
5665
  await page.goto(qwenUrl("/"), {
5590
5666
  waitUntil: "domcontentloaded",
5591
5667
  timeout: Math.min(config.timeouts.navigation, timeoutMs)
5592
5668
  });
5593
- await sleep2(2e3);
5669
+ await sleep2(1500);
5670
+ }
5671
+ if (await isPageLoggedIn(page, 5e3)) {
5672
+ armOverallDeadline();
5594
5673
  }
5595
- if (!ok || !await isPageLoggedIn(page, 6e3)) {
5674
+ }
5675
+ const stillSuspected = !silentRefresh.success || !await isPageLoggedIn(page, 2e3);
5676
+ if (stillSuspected) {
5677
+ const { getAccountCredentials: getAccountCredentials2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
5678
+ const creds = getAccountCredentials2(accountId);
5679
+ if (creds && creds.email && creds.password) {
5680
+ console.warn(
5681
+ `\u26A0\uFE0F [Playwright] Session expired or guest state detected during header capture for ${accountId}; re-authenticating...`
5682
+ );
5683
+ armOverallDeadline();
5684
+ const ok = await loginToQwen(accountId, creds.email, creds.password);
5685
+ if (ok) {
5686
+ await page.goto(qwenUrl("/"), {
5687
+ waitUntil: "domcontentloaded",
5688
+ timeout: Math.min(config.timeouts.navigation, timeoutMs)
5689
+ });
5690
+ await sleep2(2e3);
5691
+ }
5692
+ if (!ok || !await isPageLoggedIn(page, 6e3)) {
5693
+ settle(
5694
+ new Error(
5695
+ `Header capture failed for ${accountId}: re-login after session expiry did not succeed`
5696
+ )
5697
+ );
5698
+ return;
5699
+ }
5700
+ armOverallDeadline();
5701
+ if (settled || page.isClosed()) return;
5702
+ } else {
5596
5703
  settle(
5597
5704
  new Error(
5598
- `Header capture failed for ${accountId}: re-login after session expiry did not succeed`
5705
+ `Header capture failed for ${accountId}: session expired and no credentials available for re-login (run 'qpx login')`
5599
5706
  )
5600
5707
  );
5601
5708
  return;
5602
5709
  }
5603
- armOverallDeadline();
5604
- if (settled || page.isClosed()) return;
5605
- } else {
5606
- settle(
5607
- new Error(
5608
- `Header capture failed for ${accountId}: session expired and no credentials available for re-login (run 'qpx login')`
5609
- )
5610
- );
5611
- return;
5612
5710
  }
5613
5711
  }
5614
5712
  if (settled || page.isClosed()) return;
@@ -5924,7 +6022,13 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
5924
6022
  `[Playwright] Navigation check failed during refresh for ${accountId}:`,
5925
6023
  navErr.message
5926
6024
  );
5927
- await executeReauth();
6025
+ if (await isPageLoggedIn(page, 3e3)) {
6026
+ } else {
6027
+ const refreshed = await refreshAccountToken(accountId);
6028
+ if (!refreshed.success) {
6029
+ await executeReauth();
6030
+ }
6031
+ }
5928
6032
  }
5929
6033
  }
5930
6034
  if (hasRequiredQwenHeaders(cache.headers)) {
@@ -5938,6 +6042,11 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
5938
6042
  const { saveAuthSession: saveAuthSession2 } = await Promise.resolve().then(() => (init_database(), database_exports));
5939
6043
  const tokenCookie = liveCookies.find((c) => c.name === "token");
5940
6044
  const exp = tokenCookie ? parseJwtExpiry(tokenCookie.value) : void 0;
6045
+ let rTok;
6046
+ try {
6047
+ rTok = (await page.evaluate(() => localStorage.getItem("refresh_token") || "")).trim() || void 0;
6048
+ } catch {
6049
+ }
5941
6050
  saveAuthSession2(accountId, {
5942
6051
  cookie: cookieStr,
5943
6052
  userAgent: cache.headers["user-agent"] || "",
@@ -5949,6 +6058,7 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
5949
6058
  secChUaPlatform: cache.headers["sec-ch-ua-platform"] || void 0,
5950
6059
  version: cache.headers["version"] || void 0,
5951
6060
  tokenExpiresAt: exp || void 0,
6061
+ refreshToken: rTok,
5952
6062
  capturedAt: Date.now()
5953
6063
  });
5954
6064
  } catch {
@@ -6335,8 +6445,10 @@ async function keepAlivePlaywrightAccount(accountId) {
6335
6445
  if (!page || page.isClosed()) return false;
6336
6446
  const now = Date.now();
6337
6447
  const currentUrl = page.url();
6448
+ const lastRefreshAttempt = lastProactiveTokenRefresh.get(accountId) ?? 0;
6338
6449
  const cookie = await getCookies(accountId);
6339
- if (cookie && isTokenExpiringSoon(cookie, 5)) {
6450
+ if (cookie && isTokenExpiringSoon(cookie, 5) && now - lastRefreshAttempt > 6e4) {
6451
+ lastProactiveTokenRefresh.set(accountId, now);
6340
6452
  console.log(
6341
6453
  `\u{1F493} [SessionKeeper] Account ${accountId} access token expiring soon (<5m); silently refreshing...`
6342
6454
  );
@@ -6380,6 +6492,7 @@ function cleanupPlaywrightAccountState(accountId) {
6380
6492
  cookieCaches.delete(accountId);
6381
6493
  lastAccountActivity.delete(accountId);
6382
6494
  lastKeepAliveNavigation.delete(accountId);
6495
+ lastProactiveTokenRefresh.delete(accountId);
6383
6496
  clearFingerprintCache(accountId);
6384
6497
  unmarkAccountHeadersReady(accountId);
6385
6498
  }
@@ -6563,7 +6676,7 @@ async function getTokenDiagnostics(accountId) {
6563
6676
  }
6564
6677
  };
6565
6678
  }
6566
- 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;
6679
+ 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;
6567
6680
  var init_playwright = __esm({
6568
6681
  "src/services/playwright.ts"() {
6569
6682
  "use strict";
@@ -6599,6 +6712,7 @@ var init_playwright = __esm({
6599
6712
  cookieCaches = /* @__PURE__ */ new Map();
6600
6713
  lastAccountActivity = /* @__PURE__ */ new Map();
6601
6714
  lastKeepAliveNavigation = /* @__PURE__ */ new Map();
6715
+ lastProactiveTokenRefresh = /* @__PURE__ */ new Map();
6602
6716
  profileResetQueue = /* @__PURE__ */ new Map();
6603
6717
  profileResetChain = Promise.resolve();
6604
6718
  closingAllPlaywright = false;
@@ -6627,10 +6741,12 @@ var init_playwright = __esm({
6627
6741
  // src/login.ts
6628
6742
  init_accounts();
6629
6743
  init_logger();
6744
+ init_process_guard();
6630
6745
  import crypto4 from "crypto";
6631
6746
  import * as readline from "readline";
6632
6747
  import * as dotenv2 from "dotenv";
6633
6748
  dotenv2.config();
6749
+ installProcessTeardownGuards("Login");
6634
6750
  var rl = readline.createInterface({
6635
6751
  input: process.stdin,
6636
6752
  output: process.stdout