qwenproxy-cli 1.6.2 → 1.6.3

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.
@@ -14,6 +14,25 @@ var __export = (target, all) => {
14
14
  };
15
15
 
16
16
  // src/core/paths.ts
17
+ var paths_exports = {};
18
+ __export(paths_exports, {
19
+ ensureDataDirs: () => ensureDataDirs,
20
+ getAccountPriorityPath: () => getAccountPriorityPath,
21
+ getAccountProfilePath: () => getAccountProfilePath,
22
+ getDataDir: () => getDataDir,
23
+ getDbDir: () => getDbDir,
24
+ getDbPath: () => getDbPath,
25
+ getEncryptionKeyPath: () => getEncryptionKeyPath,
26
+ getEnvFilePath: () => getEnvFilePath,
27
+ getLogsDir: () => getLogsDir,
28
+ getOsGlobalDataDir: () => getOsGlobalDataDir,
29
+ getProfilesDir: () => getProfilesDir,
30
+ getServerLogFilePath: () => getServerLogFilePath,
31
+ getSyncStatePath: () => getSyncStatePath,
32
+ getTuiSettingsPath: () => getTuiSettingsPath,
33
+ isRunningUnderNodeTest: () => isRunningUnderNodeTest,
34
+ resolveDataDir: () => resolveDataDir
35
+ });
17
36
  import fs from "node:fs";
18
37
  import path from "node:path";
19
38
  import os from "node:os";
@@ -78,6 +97,12 @@ function getAccountProfilePath(accountId, customDataDir) {
78
97
  function getAccountPriorityPath(customDataDir) {
79
98
  return path.join(customDataDir || getDataDir(), "account-priority.json");
80
99
  }
100
+ function getSyncStatePath(customDataDir) {
101
+ return path.join(customDataDir || getDataDir(), "sync-state.json");
102
+ }
103
+ function getTuiSettingsPath(customDataDir) {
104
+ return path.join(customDataDir || getDataDir(), "tui-settings.json");
105
+ }
81
106
  function getEnvFilePath(customDataDir) {
82
107
  if (isRunningUnderNodeTest()) {
83
108
  return path.join(customDataDir || getDataDir(), ".env");
@@ -88,6 +113,26 @@ function getEnvFilePath(customDataDir) {
88
113
  }
89
114
  return path.join(customDataDir || getDataDir(), ".env");
90
115
  }
116
+ function getLogsDir(customDataDir) {
117
+ return path.join(customDataDir || getDataDir(), "logs");
118
+ }
119
+ function getServerLogFilePath(customDataDir) {
120
+ return path.join(getLogsDir(customDataDir), "server.log");
121
+ }
122
+ function ensureDataDirs(targetDataDir) {
123
+ const root = targetDataDir || getDataDir();
124
+ const dbDir = getDbDir(root);
125
+ const profilesDir = getProfilesDir(root);
126
+ const logsDir = getLogsDir(root);
127
+ try {
128
+ if (!fs.existsSync(root)) fs.mkdirSync(root, { recursive: true });
129
+ if (!fs.existsSync(dbDir)) fs.mkdirSync(dbDir, { recursive: true });
130
+ if (!fs.existsSync(profilesDir)) fs.mkdirSync(profilesDir, { recursive: true });
131
+ if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir, { recursive: true });
132
+ } catch (err) {
133
+ console.error(`[Paths] Error ensuring data directories at ${root}:`, err?.message || String(err));
134
+ }
135
+ }
91
136
  var init_paths = __esm({
92
137
  "src/core/paths.ts"() {
93
138
  "use strict";
@@ -4470,7 +4515,24 @@ async function refreshAccountToken(accountId) {
4470
4515
  return { success: false, error: "Could not refresh or re-authenticate token" };
4471
4516
  }
4472
4517
  async function captureQwenHeaders(accountId, pageOverride, timeoutMs = config.timeouts.headers, triggerGraceMs = HEADER_CAPTURE_TRIGGER_GRACE_MS) {
4473
- const page = pageOverride ?? accountPages.get(accountId);
4518
+ let page = pageOverride ?? accountPages.get(accountId);
4519
+ if (!page || page.isClosed()) {
4520
+ const { isRunningUnderNodeTest: isRunningUnderNodeTest3 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
4521
+ if (!isRunningUnderNodeTest3()) {
4522
+ const { getAccountCredentials: getAccountCredentials2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
4523
+ const creds = getAccountCredentials2(accountId);
4524
+ if (creds) {
4525
+ await initPlaywrightForAccount(
4526
+ creds,
4527
+ config.playwright.headless,
4528
+ config.playwright.browser,
4529
+ { skipHeaderCapture: true }
4530
+ ).catch(() => {
4531
+ });
4532
+ page = accountPages.get(accountId);
4533
+ }
4534
+ }
4535
+ }
4474
4536
  if (!page || page.isClosed()) {
4475
4537
  throw new Error(`Playwright page unavailable for header capture: ${accountId}`);
4476
4538
  }
@@ -5171,8 +5233,10 @@ async function evictIdlePlaywrightContextsToLimit() {
5171
5233
  if (accountPages.size <= max) return 0;
5172
5234
  const candidates = priorityOrderForEviction(
5173
5235
  Array.from(accountPages.keys()).filter((accountId) => {
5236
+ if (isAccountServingStream(accountId)) return false;
5237
+ if (isAccountRecentlyActive(accountId, 3e4)) return false;
5174
5238
  const mutex = accountMutexes.get(accountId);
5175
- return mutex?.isIdle() && !isAccountServingStream(accountId);
5239
+ return mutex?.isIdle();
5176
5240
  })
5177
5241
  ).map((accountId) => ({
5178
5242
  accountId,
@@ -5285,6 +5349,10 @@ async function closePlaywrightForAccount(accountId) {
5285
5349
  release();
5286
5350
  }
5287
5351
  }
5352
+ function isAccountRecentlyActive(accountId, maxIdleMs = 3e5) {
5353
+ const last = lastAccountActivity.get(accountId) ?? 0;
5354
+ return last > 0 && Date.now() - last < maxIdleMs;
5355
+ }
5288
5356
  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;
5289
5357
  var init_playwright = __esm({
5290
5358
  "src/services/playwright.ts"() {
@@ -52,6 +52,25 @@ var init_process_guard = __esm({
52
52
  });
53
53
 
54
54
  // src/core/paths.ts
55
+ var paths_exports = {};
56
+ __export(paths_exports, {
57
+ ensureDataDirs: () => ensureDataDirs,
58
+ getAccountPriorityPath: () => getAccountPriorityPath,
59
+ getAccountProfilePath: () => getAccountProfilePath,
60
+ getDataDir: () => getDataDir,
61
+ getDbDir: () => getDbDir,
62
+ getDbPath: () => getDbPath,
63
+ getEncryptionKeyPath: () => getEncryptionKeyPath,
64
+ getEnvFilePath: () => getEnvFilePath,
65
+ getLogsDir: () => getLogsDir,
66
+ getOsGlobalDataDir: () => getOsGlobalDataDir,
67
+ getProfilesDir: () => getProfilesDir,
68
+ getServerLogFilePath: () => getServerLogFilePath,
69
+ getSyncStatePath: () => getSyncStatePath,
70
+ getTuiSettingsPath: () => getTuiSettingsPath,
71
+ isRunningUnderNodeTest: () => isRunningUnderNodeTest,
72
+ resolveDataDir: () => resolveDataDir
73
+ });
55
74
  import fs from "node:fs";
56
75
  import path from "node:path";
57
76
  import os from "node:os";
@@ -116,6 +135,12 @@ function getAccountProfilePath(accountId, customDataDir) {
116
135
  function getAccountPriorityPath(customDataDir) {
117
136
  return path.join(customDataDir || getDataDir(), "account-priority.json");
118
137
  }
138
+ function getSyncStatePath(customDataDir) {
139
+ return path.join(customDataDir || getDataDir(), "sync-state.json");
140
+ }
141
+ function getTuiSettingsPath(customDataDir) {
142
+ return path.join(customDataDir || getDataDir(), "tui-settings.json");
143
+ }
119
144
  function getEnvFilePath(customDataDir) {
120
145
  if (isRunningUnderNodeTest()) {
121
146
  return path.join(customDataDir || getDataDir(), ".env");
@@ -126,6 +151,26 @@ function getEnvFilePath(customDataDir) {
126
151
  }
127
152
  return path.join(customDataDir || getDataDir(), ".env");
128
153
  }
154
+ function getLogsDir(customDataDir) {
155
+ return path.join(customDataDir || getDataDir(), "logs");
156
+ }
157
+ function getServerLogFilePath(customDataDir) {
158
+ return path.join(getLogsDir(customDataDir), "server.log");
159
+ }
160
+ function ensureDataDirs(targetDataDir) {
161
+ const root = targetDataDir || getDataDir();
162
+ const dbDir = getDbDir(root);
163
+ const profilesDir = getProfilesDir(root);
164
+ const logsDir = getLogsDir(root);
165
+ try {
166
+ if (!fs.existsSync(root)) fs.mkdirSync(root, { recursive: true });
167
+ if (!fs.existsSync(dbDir)) fs.mkdirSync(dbDir, { recursive: true });
168
+ if (!fs.existsSync(profilesDir)) fs.mkdirSync(profilesDir, { recursive: true });
169
+ if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir, { recursive: true });
170
+ } catch (err) {
171
+ console.error(`[Paths] Error ensuring data directories at ${root}:`, err?.message || String(err));
172
+ }
173
+ }
129
174
  var init_paths = __esm({
130
175
  "src/core/paths.ts"() {
131
176
  "use strict";
@@ -5727,7 +5772,24 @@ async function refreshAccountToken(accountId) {
5727
5772
  return { success: false, error: "Could not refresh or re-authenticate token" };
5728
5773
  }
5729
5774
  async function captureQwenHeaders(accountId, pageOverride, timeoutMs = config.timeouts.headers, triggerGraceMs = HEADER_CAPTURE_TRIGGER_GRACE_MS) {
5730
- const page = pageOverride ?? accountPages.get(accountId);
5775
+ let page = pageOverride ?? accountPages.get(accountId);
5776
+ if (!page || page.isClosed()) {
5777
+ const { isRunningUnderNodeTest: isRunningUnderNodeTest4 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
5778
+ if (!isRunningUnderNodeTest4()) {
5779
+ const { getAccountCredentials: getAccountCredentials2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
5780
+ const creds = getAccountCredentials2(accountId);
5781
+ if (creds) {
5782
+ await initPlaywrightForAccount(
5783
+ creds,
5784
+ config.playwright.headless,
5785
+ config.playwright.browser,
5786
+ { skipHeaderCapture: true }
5787
+ ).catch(() => {
5788
+ });
5789
+ page = accountPages.get(accountId);
5790
+ }
5791
+ }
5792
+ }
5731
5793
  if (!page || page.isClosed()) {
5732
5794
  throw new Error(`Playwright page unavailable for header capture: ${accountId}`);
5733
5795
  }
@@ -6370,16 +6432,38 @@ async function refreshHeaders(accountId, timeoutMs = config.timeouts.headers, fo
6370
6432
  release();
6371
6433
  }
6372
6434
  }
6373
- async function withAccountPage(accountId, fn, timeoutMs = ACCOUNT_PAGE_OPERATION_TIMEOUT_MS, mutexTimeoutMs = PLAYWRIGHT_MUTEX_WAIT_MS, recoverOnTimeout = true) {
6435
+ async function ensureAccountPageAvailable(accountId) {
6374
6436
  const inFlightInit = inFlightAccountInits.get(accountId);
6375
6437
  if (inFlightInit) {
6376
6438
  await inFlightInit.catch(() => {
6377
6439
  });
6378
6440
  }
6379
- const page = accountPages.get(accountId);
6441
+ let page = accountPages.get(accountId);
6442
+ if (page && !page.isClosed()) {
6443
+ return page;
6444
+ }
6445
+ const { isRunningUnderNodeTest: isRunningUnderNodeTest4 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
6446
+ if (isRunningUnderNodeTest4()) {
6447
+ throw new Error(`Playwright page unavailable for account: ${accountId}`);
6448
+ }
6449
+ const { getAccountCredentials: getAccountCredentials2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
6450
+ const creds = getAccountCredentials2(accountId);
6451
+ if (!creds) {
6452
+ throw new Error(`Playwright page unavailable for account: ${accountId}`);
6453
+ }
6454
+ await initPlaywrightForAccount(
6455
+ creds,
6456
+ config.playwright.headless,
6457
+ config.playwright.browser
6458
+ );
6459
+ page = accountPages.get(accountId);
6380
6460
  if (!page || page.isClosed()) {
6381
6461
  throw new Error(`Playwright page unavailable for account: ${accountId}`);
6382
6462
  }
6463
+ return page;
6464
+ }
6465
+ async function withAccountPage(accountId, fn, timeoutMs = ACCOUNT_PAGE_OPERATION_TIMEOUT_MS, mutexTimeoutMs = PLAYWRIGHT_MUTEX_WAIT_MS, recoverOnTimeout = true) {
6466
+ const page = await ensureAccountPageAvailable(accountId);
6383
6467
  const release = await acquireAccountMutex(
6384
6468
  accountId,
6385
6469
  `page:${accountId.substring(0, 12)}`,
@@ -6680,8 +6764,10 @@ async function evictIdlePlaywrightContextsToLimit() {
6680
6764
  if (accountPages.size <= max) return 0;
6681
6765
  const candidates = priorityOrderForEviction(
6682
6766
  Array.from(accountPages.keys()).filter((accountId) => {
6767
+ if (isAccountServingStream(accountId)) return false;
6768
+ if (isAccountRecentlyActive(accountId, 3e4)) return false;
6683
6769
  const mutex = accountMutexes.get(accountId);
6684
- return mutex?.isIdle() && !isAccountServingStream(accountId);
6770
+ return mutex?.isIdle();
6685
6771
  })
6686
6772
  ).map((accountId) => ({
6687
6773
  accountId,
package/dist/index.js CHANGED
@@ -6882,7 +6882,24 @@ async function refreshAccountToken(accountId) {
6882
6882
  return { success: false, error: "Could not refresh or re-authenticate token" };
6883
6883
  }
6884
6884
  async function captureQwenHeaders(accountId, pageOverride, timeoutMs = config.timeouts.headers, triggerGraceMs = HEADER_CAPTURE_TRIGGER_GRACE_MS) {
6885
- const page = pageOverride ?? accountPages.get(accountId);
6885
+ let page = pageOverride ?? accountPages.get(accountId);
6886
+ if (!page || page.isClosed()) {
6887
+ const { isRunningUnderNodeTest: isRunningUnderNodeTest4 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
6888
+ if (!isRunningUnderNodeTest4()) {
6889
+ const { getAccountCredentials: getAccountCredentials2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
6890
+ const creds = getAccountCredentials2(accountId);
6891
+ if (creds) {
6892
+ await initPlaywrightForAccount(
6893
+ creds,
6894
+ config.playwright.headless,
6895
+ config.playwright.browser,
6896
+ { skipHeaderCapture: true }
6897
+ ).catch(() => {
6898
+ });
6899
+ page = accountPages.get(accountId);
6900
+ }
6901
+ }
6902
+ }
6886
6903
  if (!page || page.isClosed()) {
6887
6904
  throw new Error(`Playwright page unavailable for header capture: ${accountId}`);
6888
6905
  }
@@ -7525,16 +7542,38 @@ async function refreshHeaders(accountId, timeoutMs = config.timeouts.headers, fo
7525
7542
  release();
7526
7543
  }
7527
7544
  }
7528
- async function withAccountPage(accountId, fn, timeoutMs = ACCOUNT_PAGE_OPERATION_TIMEOUT_MS, mutexTimeoutMs = PLAYWRIGHT_MUTEX_WAIT_MS, recoverOnTimeout = true) {
7545
+ async function ensureAccountPageAvailable(accountId) {
7529
7546
  const inFlightInit = inFlightAccountInits.get(accountId);
7530
7547
  if (inFlightInit) {
7531
7548
  await inFlightInit.catch(() => {
7532
7549
  });
7533
7550
  }
7534
- const page = accountPages.get(accountId);
7551
+ let page = accountPages.get(accountId);
7552
+ if (page && !page.isClosed()) {
7553
+ return page;
7554
+ }
7555
+ const { isRunningUnderNodeTest: isRunningUnderNodeTest4 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
7556
+ if (isRunningUnderNodeTest4()) {
7557
+ throw new Error(`Playwright page unavailable for account: ${accountId}`);
7558
+ }
7559
+ const { getAccountCredentials: getAccountCredentials2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
7560
+ const creds = getAccountCredentials2(accountId);
7561
+ if (!creds) {
7562
+ throw new Error(`Playwright page unavailable for account: ${accountId}`);
7563
+ }
7564
+ await initPlaywrightForAccount(
7565
+ creds,
7566
+ config.playwright.headless,
7567
+ config.playwright.browser
7568
+ );
7569
+ page = accountPages.get(accountId);
7535
7570
  if (!page || page.isClosed()) {
7536
7571
  throw new Error(`Playwright page unavailable for account: ${accountId}`);
7537
7572
  }
7573
+ return page;
7574
+ }
7575
+ async function withAccountPage(accountId, fn, timeoutMs = ACCOUNT_PAGE_OPERATION_TIMEOUT_MS, mutexTimeoutMs = PLAYWRIGHT_MUTEX_WAIT_MS, recoverOnTimeout = true) {
7576
+ const page = await ensureAccountPageAvailable(accountId);
7538
7577
  const release = await acquireAccountMutex(
7539
7578
  accountId,
7540
7579
  `page:${accountId.substring(0, 12)}`,
@@ -7835,8 +7874,10 @@ async function evictIdlePlaywrightContextsToLimit() {
7835
7874
  if (accountPages.size <= max) return 0;
7836
7875
  const candidates = priorityOrderForEviction(
7837
7876
  Array.from(accountPages.keys()).filter((accountId) => {
7877
+ if (isAccountServingStream(accountId)) return false;
7878
+ if (isAccountRecentlyActive(accountId, 3e4)) return false;
7838
7879
  const mutex = accountMutexes.get(accountId);
7839
- return mutex?.isIdle() && !isAccountServingStream(accountId);
7880
+ return mutex?.isIdle();
7840
7881
  })
7841
7882
  ).map((accountId) => ({
7842
7883
  accountId,
@@ -9827,6 +9868,15 @@ function classifyRetryAction(err, options) {
9827
9868
  if (isTerminalLocalError(err)) {
9828
9869
  return makeRetryAction("terminal_local", { retryable: false });
9829
9870
  }
9871
+ if (err instanceof PersonalizationSyncError) {
9872
+ return makeRetryAction("personalization_sync_failed", {
9873
+ switchAccount: true,
9874
+ forceNewChat: true,
9875
+ retryAfterMs: baseDelayMs,
9876
+ accountCooldownMs: 0,
9877
+ accountCooldownReason: "PersonalizationFailed"
9878
+ });
9879
+ }
9830
9880
  const message = errMessage(err).toLowerCase();
9831
9881
  const code = errCode(err).toLowerCase();
9832
9882
  if (isAccountInitializationError(err)) {
@@ -9843,15 +9893,6 @@ function classifyRetryAction(err, options) {
9843
9893
  retryAfterMs: Math.min(baseDelayMs, 1e3)
9844
9894
  });
9845
9895
  }
9846
- if (err instanceof PersonalizationSyncError) {
9847
- return makeRetryAction("personalization_sync_failed", {
9848
- switchAccount: true,
9849
- forceNewChat: true,
9850
- retryAfterMs: baseDelayMs,
9851
- accountCooldownMs: 0,
9852
- accountCooldownReason: "PersonalizationFailed"
9853
- });
9854
- }
9855
9896
  if (code === "createchatinvalidresponse" || code === "createchatfailed" || code === "unauthorized" || message.includes("401 n\xE3o autorizado") || message.includes("n\xE3o tem permiss\xE3o para acessar") || message.includes("401 unauthorized") || message.includes('"code":"unauthorized"') || message.includes('"code": "unauthorized"') || message.includes("token has expired")) {
9856
9897
  return makeRetryAction("account_initialization_failed", {
9857
9898
  switchAccount: true,
@@ -26782,7 +26823,7 @@ function getAppVersion() {
26782
26823
  }
26783
26824
  } catch {
26784
26825
  }
26785
- cachedVersion = "v1.6.2";
26826
+ cachedVersion = "v1.6.3";
26786
26827
  return cachedVersion;
26787
26828
  }
26788
26829
  var cachedVersion;
package/dist/login.js CHANGED
@@ -5749,7 +5749,24 @@ async function refreshAccountToken(accountId) {
5749
5749
  return { success: false, error: "Could not refresh or re-authenticate token" };
5750
5750
  }
5751
5751
  async function captureQwenHeaders(accountId, pageOverride, timeoutMs = config.timeouts.headers, triggerGraceMs = HEADER_CAPTURE_TRIGGER_GRACE_MS) {
5752
- const page = pageOverride ?? accountPages.get(accountId);
5752
+ let page = pageOverride ?? accountPages.get(accountId);
5753
+ if (!page || page.isClosed()) {
5754
+ const { isRunningUnderNodeTest: isRunningUnderNodeTest3 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
5755
+ if (!isRunningUnderNodeTest3()) {
5756
+ const { getAccountCredentials: getAccountCredentials2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
5757
+ const creds = getAccountCredentials2(accountId);
5758
+ if (creds) {
5759
+ await initPlaywrightForAccount(
5760
+ creds,
5761
+ config.playwright.headless,
5762
+ config.playwright.browser,
5763
+ { skipHeaderCapture: true }
5764
+ ).catch(() => {
5765
+ });
5766
+ page = accountPages.get(accountId);
5767
+ }
5768
+ }
5769
+ }
5753
5770
  if (!page || page.isClosed()) {
5754
5771
  throw new Error(`Playwright page unavailable for header capture: ${accountId}`);
5755
5772
  }
@@ -6392,16 +6409,38 @@ async function refreshHeaders(accountId, timeoutMs = config.timeouts.headers, fo
6392
6409
  release();
6393
6410
  }
6394
6411
  }
6395
- async function withAccountPage(accountId, fn, timeoutMs = ACCOUNT_PAGE_OPERATION_TIMEOUT_MS, mutexTimeoutMs = PLAYWRIGHT_MUTEX_WAIT_MS, recoverOnTimeout = true) {
6412
+ async function ensureAccountPageAvailable(accountId) {
6396
6413
  const inFlightInit = inFlightAccountInits.get(accountId);
6397
6414
  if (inFlightInit) {
6398
6415
  await inFlightInit.catch(() => {
6399
6416
  });
6400
6417
  }
6401
- const page = accountPages.get(accountId);
6418
+ let page = accountPages.get(accountId);
6419
+ if (page && !page.isClosed()) {
6420
+ return page;
6421
+ }
6422
+ const { isRunningUnderNodeTest: isRunningUnderNodeTest3 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
6423
+ if (isRunningUnderNodeTest3()) {
6424
+ throw new Error(`Playwright page unavailable for account: ${accountId}`);
6425
+ }
6426
+ const { getAccountCredentials: getAccountCredentials2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
6427
+ const creds = getAccountCredentials2(accountId);
6428
+ if (!creds) {
6429
+ throw new Error(`Playwright page unavailable for account: ${accountId}`);
6430
+ }
6431
+ await initPlaywrightForAccount(
6432
+ creds,
6433
+ config.playwright.headless,
6434
+ config.playwright.browser
6435
+ );
6436
+ page = accountPages.get(accountId);
6402
6437
  if (!page || page.isClosed()) {
6403
6438
  throw new Error(`Playwright page unavailable for account: ${accountId}`);
6404
6439
  }
6440
+ return page;
6441
+ }
6442
+ async function withAccountPage(accountId, fn, timeoutMs = ACCOUNT_PAGE_OPERATION_TIMEOUT_MS, mutexTimeoutMs = PLAYWRIGHT_MUTEX_WAIT_MS, recoverOnTimeout = true) {
6443
+ const page = await ensureAccountPageAvailable(accountId);
6405
6444
  const release = await acquireAccountMutex(
6406
6445
  accountId,
6407
6446
  `page:${accountId.substring(0, 12)}`,
@@ -6702,8 +6741,10 @@ async function evictIdlePlaywrightContextsToLimit() {
6702
6741
  if (accountPages.size <= max) return 0;
6703
6742
  const candidates = priorityOrderForEviction(
6704
6743
  Array.from(accountPages.keys()).filter((accountId) => {
6744
+ if (isAccountServingStream(accountId)) return false;
6745
+ if (isAccountRecentlyActive(accountId, 3e4)) return false;
6705
6746
  const mutex = accountMutexes.get(accountId);
6706
- return mutex?.isIdle() && !isAccountServingStream(accountId);
6747
+ return mutex?.isIdle();
6707
6748
  })
6708
6749
  ).map((accountId) => ({
6709
6750
  accountId,
@@ -14,6 +14,25 @@ var __export = (target, all) => {
14
14
  };
15
15
 
16
16
  // src/core/paths.ts
17
+ var paths_exports = {};
18
+ __export(paths_exports, {
19
+ ensureDataDirs: () => ensureDataDirs,
20
+ getAccountPriorityPath: () => getAccountPriorityPath,
21
+ getAccountProfilePath: () => getAccountProfilePath,
22
+ getDataDir: () => getDataDir,
23
+ getDbDir: () => getDbDir,
24
+ getDbPath: () => getDbPath,
25
+ getEncryptionKeyPath: () => getEncryptionKeyPath,
26
+ getEnvFilePath: () => getEnvFilePath,
27
+ getLogsDir: () => getLogsDir,
28
+ getOsGlobalDataDir: () => getOsGlobalDataDir,
29
+ getProfilesDir: () => getProfilesDir,
30
+ getServerLogFilePath: () => getServerLogFilePath,
31
+ getSyncStatePath: () => getSyncStatePath,
32
+ getTuiSettingsPath: () => getTuiSettingsPath,
33
+ isRunningUnderNodeTest: () => isRunningUnderNodeTest,
34
+ resolveDataDir: () => resolveDataDir
35
+ });
17
36
  import fs from "node:fs";
18
37
  import path from "node:path";
19
38
  import os from "node:os";
@@ -78,6 +97,12 @@ function getAccountProfilePath(accountId, customDataDir) {
78
97
  function getAccountPriorityPath(customDataDir) {
79
98
  return path.join(customDataDir || getDataDir(), "account-priority.json");
80
99
  }
100
+ function getSyncStatePath(customDataDir) {
101
+ return path.join(customDataDir || getDataDir(), "sync-state.json");
102
+ }
103
+ function getTuiSettingsPath(customDataDir) {
104
+ return path.join(customDataDir || getDataDir(), "tui-settings.json");
105
+ }
81
106
  function getEnvFilePath(customDataDir) {
82
107
  if (isRunningUnderNodeTest()) {
83
108
  return path.join(customDataDir || getDataDir(), ".env");
@@ -88,6 +113,26 @@ function getEnvFilePath(customDataDir) {
88
113
  }
89
114
  return path.join(customDataDir || getDataDir(), ".env");
90
115
  }
116
+ function getLogsDir(customDataDir) {
117
+ return path.join(customDataDir || getDataDir(), "logs");
118
+ }
119
+ function getServerLogFilePath(customDataDir) {
120
+ return path.join(getLogsDir(customDataDir), "server.log");
121
+ }
122
+ function ensureDataDirs(targetDataDir) {
123
+ const root = targetDataDir || getDataDir();
124
+ const dbDir = getDbDir(root);
125
+ const profilesDir = getProfilesDir(root);
126
+ const logsDir = getLogsDir(root);
127
+ try {
128
+ if (!fs.existsSync(root)) fs.mkdirSync(root, { recursive: true });
129
+ if (!fs.existsSync(dbDir)) fs.mkdirSync(dbDir, { recursive: true });
130
+ if (!fs.existsSync(profilesDir)) fs.mkdirSync(profilesDir, { recursive: true });
131
+ if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir, { recursive: true });
132
+ } catch (err) {
133
+ console.error(`[Paths] Error ensuring data directories at ${root}:`, err?.message || String(err));
134
+ }
135
+ }
91
136
  var init_paths = __esm({
92
137
  "src/core/paths.ts"() {
93
138
  "use strict";
@@ -4733,7 +4778,24 @@ async function refreshAccountToken(accountId) {
4733
4778
  return { success: false, error: "Could not refresh or re-authenticate token" };
4734
4779
  }
4735
4780
  async function captureQwenHeaders(accountId, pageOverride, timeoutMs = config.timeouts.headers, triggerGraceMs = HEADER_CAPTURE_TRIGGER_GRACE_MS) {
4736
- const page = pageOverride ?? accountPages.get(accountId);
4781
+ let page = pageOverride ?? accountPages.get(accountId);
4782
+ if (!page || page.isClosed()) {
4783
+ const { isRunningUnderNodeTest: isRunningUnderNodeTest3 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
4784
+ if (!isRunningUnderNodeTest3()) {
4785
+ const { getAccountCredentials: getAccountCredentials2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
4786
+ const creds = getAccountCredentials2(accountId);
4787
+ if (creds) {
4788
+ await initPlaywrightForAccount(
4789
+ creds,
4790
+ config.playwright.headless,
4791
+ config.playwright.browser,
4792
+ { skipHeaderCapture: true }
4793
+ ).catch(() => {
4794
+ });
4795
+ page = accountPages.get(accountId);
4796
+ }
4797
+ }
4798
+ }
4737
4799
  if (!page || page.isClosed()) {
4738
4800
  throw new Error(`Playwright page unavailable for header capture: ${accountId}`);
4739
4801
  }
@@ -5434,8 +5496,10 @@ async function evictIdlePlaywrightContextsToLimit() {
5434
5496
  if (accountPages.size <= max) return 0;
5435
5497
  const candidates = priorityOrderForEviction(
5436
5498
  Array.from(accountPages.keys()).filter((accountId) => {
5499
+ if (isAccountServingStream(accountId)) return false;
5500
+ if (isAccountRecentlyActive(accountId, 3e4)) return false;
5437
5501
  const mutex = accountMutexes.get(accountId);
5438
- return mutex?.isIdle() && !isAccountServingStream(accountId);
5502
+ return mutex?.isIdle();
5439
5503
  })
5440
5504
  ).map((accountId) => ({
5441
5505
  accountId,
@@ -5548,6 +5612,10 @@ async function closePlaywrightForAccount(accountId) {
5548
5612
  release();
5549
5613
  }
5550
5614
  }
5615
+ function isAccountRecentlyActive(accountId, maxIdleMs = 3e5) {
5616
+ const last = lastAccountActivity.get(accountId) ?? 0;
5617
+ return last > 0 && Date.now() - last < maxIdleMs;
5618
+ }
5551
5619
  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;
5552
5620
  var init_playwright = __esm({
5553
5621
  "src/services/playwright.ts"() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qwenproxy-cli",
3
- "version": "1.6.2",
3
+ "version": "1.6.3",
4
4
  "description": "High-performance OpenAI & Anthropic compatible API gateway for Qwen with multi-account rotation, interactive TUI, and resilient tool calling.",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -17,6 +17,6 @@ export function getAppVersion(): string {
17
17
  }
18
18
  }
19
19
  } catch {}
20
- cachedVersion = "v1.6.2";
20
+ cachedVersion = "v1.6.3";
21
21
  return cachedVersion;
22
22
  }
@@ -499,6 +499,21 @@ export function classifyRetryAction(
499
499
  return makeRetryAction("terminal_local", { retryable: false });
500
500
  }
501
501
 
502
+ // Agent instructions ride ONLY the account-level personalization. An
503
+ // unconfirmed sync means this account cannot serve the request as-is —
504
+ // rotate to another account (each attempt re-syncs on its own account).
505
+ // Do NOT park the account with a 300s cooldown: rotation across accounts
506
+ // is already guarded by triedAccounts, preventing pool collapse.
507
+ if (err instanceof PersonalizationSyncError) {
508
+ return makeRetryAction("personalization_sync_failed", {
509
+ switchAccount: true,
510
+ forceNewChat: true,
511
+ retryAfterMs: baseDelayMs,
512
+ accountCooldownMs: 0,
513
+ accountCooldownReason: "PersonalizationFailed",
514
+ });
515
+ }
516
+
502
517
  const message = errMessage(err).toLowerCase();
503
518
  const code = errCode(err).toLowerCase();
504
519
  if (isAccountInitializationError(err)) {
@@ -521,21 +536,6 @@ export function classifyRetryAction(
521
536
  });
522
537
  }
523
538
 
524
- // Agent instructions ride ONLY the account-level personalization. An
525
- // unconfirmed sync means this account cannot serve the request as-is —
526
- // rotate to another account (each attempt re-syncs on its own account).
527
- // Do NOT park the account with a 300s cooldown: rotation across accounts
528
- // is already guarded by triedAccounts, preventing pool collapse.
529
- if (err instanceof PersonalizationSyncError) {
530
- return makeRetryAction("personalization_sync_failed", {
531
- switchAccount: true,
532
- forceNewChat: true,
533
- retryAfterMs: baseDelayMs,
534
- accountCooldownMs: 0,
535
- accountCooldownReason: "PersonalizationFailed",
536
- });
537
- }
538
-
539
539
  // Upstream 401 / Unauthorized on chat creation or API requests:
540
540
  // Access token expired (15m window). Rotate without locking into a 300s cooldown
541
541
  // so the account can immediately be renewed via refresh_token.
@@ -3242,7 +3242,24 @@ export async function captureQwenHeaders(
3242
3242
  timeoutMs = config.timeouts.headers,
3243
3243
  triggerGraceMs = HEADER_CAPTURE_TRIGGER_GRACE_MS,
3244
3244
  ): Promise<void> {
3245
- const page = pageOverride ?? accountPages.get(accountId);
3245
+ let page = pageOverride ?? accountPages.get(accountId);
3246
+ if (!page || page.isClosed()) {
3247
+ const { isRunningUnderNodeTest } = await import("../core/paths.ts");
3248
+ if (!isRunningUnderNodeTest()) {
3249
+ const { getAccountCredentials } = await import("../core/accounts.ts");
3250
+ const creds = getAccountCredentials(accountId);
3251
+ if (creds) {
3252
+ await initPlaywrightForAccount(
3253
+ creds,
3254
+ config.playwright.headless,
3255
+ config.playwright.browser,
3256
+ { skipHeaderCapture: true },
3257
+ ).catch(() => {});
3258
+ page = accountPages.get(accountId);
3259
+ }
3260
+ }
3261
+ }
3262
+
3246
3263
  if (!page || page.isClosed()) {
3247
3264
  throw new Error(`Playwright page unavailable for header capture: ${accountId}`);
3248
3265
  }
@@ -4100,22 +4117,51 @@ export async function refreshHeaders(
4100
4117
  * Run work against the account Playwright page under the per-account mutex.
4101
4118
  * Used by captcha recovery so it cannot race header capture / login.
4102
4119
  */
4103
- export async function withAccountPage<T>(
4104
- accountId: string,
4105
- fn: (page: Page) => Promise<T>,
4106
- timeoutMs = ACCOUNT_PAGE_OPERATION_TIMEOUT_MS,
4107
- mutexTimeoutMs = PLAYWRIGHT_MUTEX_WAIT_MS,
4108
- recoverOnTimeout = true,
4109
- ): Promise<T> {
4120
+ async function ensureAccountPageAvailable(accountId: string): Promise<Page> {
4110
4121
  const inFlightInit = inFlightAccountInits.get(accountId);
4111
4122
  if (inFlightInit) {
4112
4123
  await inFlightInit.catch(() => {});
4113
4124
  }
4114
4125
 
4115
- const page = accountPages.get(accountId);
4126
+ let page = accountPages.get(accountId);
4127
+ if (page && !page.isClosed()) {
4128
+ return page;
4129
+ }
4130
+
4131
+ // Under Node.js test suites, preserve strict mock failure contract
4132
+ const { isRunningUnderNodeTest } = await import("../core/paths.ts");
4133
+ if (isRunningUnderNodeTest()) {
4134
+ throw new Error(`Playwright page unavailable for account: ${accountId}`);
4135
+ }
4136
+
4137
+ // Lazy boot / recovery: if the account was evicted or in standby, initialize it transparently on demand
4138
+ const { getAccountCredentials } = await import("../core/accounts.ts");
4139
+ const creds = getAccountCredentials(accountId);
4140
+ if (!creds) {
4141
+ throw new Error(`Playwright page unavailable for account: ${accountId}`);
4142
+ }
4143
+
4144
+ await initPlaywrightForAccount(
4145
+ creds,
4146
+ config.playwright.headless,
4147
+ config.playwright.browser,
4148
+ );
4149
+
4150
+ page = accountPages.get(accountId);
4116
4151
  if (!page || page.isClosed()) {
4117
4152
  throw new Error(`Playwright page unavailable for account: ${accountId}`);
4118
4153
  }
4154
+ return page;
4155
+ }
4156
+
4157
+ export async function withAccountPage<T>(
4158
+ accountId: string,
4159
+ fn: (page: Page) => Promise<T>,
4160
+ timeoutMs = ACCOUNT_PAGE_OPERATION_TIMEOUT_MS,
4161
+ mutexTimeoutMs = PLAYWRIGHT_MUTEX_WAIT_MS,
4162
+ recoverOnTimeout = true,
4163
+ ): Promise<T> {
4164
+ const page = await ensureAccountPageAvailable(accountId);
4119
4165
  const release = await acquireAccountMutex(
4120
4166
  accountId,
4121
4167
  `page:${accountId.substring(0, 12)}`,
@@ -4542,8 +4588,10 @@ export async function evictIdlePlaywrightContextsToLimit(): Promise<number> {
4542
4588
 
4543
4589
  const candidates = priorityOrderForEviction(
4544
4590
  Array.from(accountPages.keys()).filter((accountId) => {
4591
+ if (isAccountServingStream(accountId)) return false;
4592
+ if (isAccountRecentlyActive(accountId, 30_000)) return false;
4545
4593
  const mutex = accountMutexes.get(accountId);
4546
- return mutex?.isIdle() && !isAccountServingStream(accountId);
4594
+ return mutex?.isIdle();
4547
4595
  }),
4548
4596
  ).map((accountId) => ({
4549
4597
  accountId,