qwenproxy-cli 1.0.12 → 1.0.14

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/bin/qwenproxy.js CHANGED
@@ -46,9 +46,7 @@ if (rawArgs.includes("-h") || rawArgs.includes("--help") || firstArg === "help")
46
46
  QwenProxy — High-performance AI Coding Gateway & Management TUI
47
47
 
48
48
  Usage:
49
- qwenproxy [options] [command]
50
49
  qpx [options] [command]
51
-
52
50
  Commands:
53
51
  (default) Launch interactive TUI management dashboard and proxy
54
52
  start Run headless HTTP/SSE proxy server
package/package.json CHANGED
@@ -1,10 +1,9 @@
1
1
  {
2
2
  "name": "qwenproxy-cli",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
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": {
7
- "qwenproxy": "bin/qwenproxy.js",
8
7
  "qpx": "bin/qwenproxy.js"
9
8
  },
10
9
  "files": [
@@ -25,8 +24,8 @@
25
24
  "reset": "npx tsx src/reset-cooldowns.ts",
26
25
  "purge": "npx tsx src/delete-chats.ts",
27
26
  "test": "npm run test:mock && npm run test:live",
28
- "test:mock": "tsx --test --test-concurrency=1 --env-file=.env.test src/tests/account-concurrency.test.ts src/tests/concurrency-latest-wins.test.ts src/tests/mutex.test.ts src/tests/context-truncation.test.ts src/tests/streaming-thinking-summary.test.ts src/tests/tool-call-guard.test.ts src/tests/utils-logger-full.test.ts src/tests/logger-toolcall-errors.test.ts src/tests/logger-toolcall-disabled.test.ts src/tests/utils-json-full.test.ts src/tests/utils-session-guard-full.test.ts src/tests/core-logic-full.test.ts src/tests/core-logic-accounts.test.ts src/tests/chat-validation-full.test.ts src/tests/chat-logic-full.test.ts src/tests/account-stickiness.test.ts src/tests/accounts-security.test.ts src/tests/advanced.test.ts src/tests/auth-playwright.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chromium-args.test.ts src/tests/config-current.test.ts src/tests/context-meter.test.ts src/tests/concurrency.test.ts src/tests/delta.test.ts src/tests/fingerprint.test.ts src/tests/human-behavior.test.ts src/tests/index.test.ts src/tests/json-recovery.test.ts src/tests/jsonFix.test.ts src/tests/memory-cache.test.ts src/tests/model-alias.test.ts src/tests/model-registry-thinking.test.ts src/tests/models-endpoint.test.ts src/tests/multimodal.test.ts src/tests/oss-upload.test.ts src/tests/parser.test.ts src/tests/personalization-flow.test.ts src/tests/personalization-required.test.ts src/tests/retry-policy.test.ts src/tests/rotation.test.ts src/tests/server-lifecycle.test.ts src/tests/session-keeper.test.ts src/tests/session-state.test.ts src/tests/single-account-mode.test.ts src/tests/stream-retry.test.ts src/tests/tool-calls-endpoint.test.ts src/tests/tool-instructions-cache.test.ts src/tests/session-id.test.ts src/tests/watchdog-ram.test.ts src/tests/tool-recovery-t1t2.test.ts src/tests/abort-silent-t4.test.ts src/tests/t3-close-tag-escape.test.ts src/tests/t5-tool-cap.test.ts src/tests/logger-redaction.test.ts src/tests/stall-timeout-retryable.test.ts src/tests/t6-missing-quote-array-close.test.ts src/tests/watchdog-rss.test.ts src/tests/logical-thread-batch.test.ts src/tests/chat-pool-style.test.ts src/tests/queue-wait-cap.test.ts src/tests/context-eviction-order.test.ts src/tests/chat-in-progress-settle.test.ts src/tests/stream-lock-leak.test.ts src/tests/stream-semaphore.test.ts src/tests/waf-isolation.test.ts src/tests/legacy-completions.test.ts src/tests/response-format.test.ts src/tests/cors-headers.test.ts src/tests/stream-lock-terminal-cancel.test.ts src/tests/parser-truncated-tool-call.test.ts src/tests/metrics-prometheus.test.ts src/tests/context-death-cleanup.test.ts src/tests/personalization-direct-fetch.test.ts src/tests/completion-headers-parity.test.ts src/tests/parallel-escape-repro.test.ts src/tests/chat-in-progress-scale-cost.test.ts src/tests/temp-chat-mode.test.ts src/tests/profile-reset.test.ts src/tests/error-classification.test.ts src/tests/quota-cooldown.test.ts src/tests/account-ready-gate.test.ts src/tests/tool-cap-notice.test.ts src/tests/interaction-flows.test.ts src/tests/headers-gate-cooldown-fallback.test.ts src/tests/unwrapped-tool-calls.test.ts src/tests/anthropic.test.ts src/tests/sync-clients.test.ts src/tests/cli.test.ts src/tests/paths.test.ts src/tests/tool-manifest.test.ts src/tests/tui.test.ts",
29
- "test:mock:fast": "tsx --test --test-concurrency=2 --env-file=.env.test src/tests/account-concurrency.test.ts src/tests/concurrency-latest-wins.test.ts src/tests/mutex.test.ts src/tests/context-truncation.test.ts src/tests/streaming-thinking-summary.test.ts src/tests/tool-call-guard.test.ts src/tests/utils-logger-full.test.ts src/tests/logger-toolcall-errors.test.ts src/tests/logger-toolcall-disabled.test.ts src/tests/utils-json-full.test.ts src/tests/utils-session-guard-full.test.ts src/tests/core-logic-full.test.ts src/tests/core-logic-accounts.test.ts src/tests/chat-validation-full.test.ts src/tests/chat-logic-full.test.ts src/tests/account-stickiness.test.ts src/tests/accounts-security.test.ts src/tests/advanced.test.ts src/tests/auth-playwright.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chromium-args.test.ts src/tests/config-current.test.ts src/tests/context-meter.test.ts src/tests/concurrency.test.ts src/tests/delta.test.ts src/tests/fingerprint.test.ts src/tests/human-behavior.test.ts src/tests/index.test.ts src/tests/json-recovery.test.ts src/tests/jsonFix.test.ts src/tests/memory-cache.test.ts src/tests/model-alias.test.ts src/tests/model-registry-thinking.test.ts src/tests/models-endpoint.test.ts src/tests/multimodal.test.ts src/tests/oss-upload.test.ts src/tests/parser.test.ts src/tests/personalization-flow.test.ts src/tests/personalization-required.test.ts src/tests/retry-policy.test.ts src/tests/rotation.test.ts src/tests/server-lifecycle.test.ts src/tests/session-keeper.test.ts src/tests/session-state.test.ts src/tests/single-account-mode.test.ts src/tests/stream-retry.test.ts src/tests/tool-calls-endpoint.test.ts src/tests/tool-instructions-cache.test.ts src/tests/session-id.test.ts src/tests/watchdog-ram.test.ts src/tests/tool-recovery-t1t2.test.ts src/tests/abort-silent-t4.test.ts src/tests/t3-close-tag-escape.test.ts src/tests/t5-tool-cap.test.ts src/tests/logger-redaction.test.ts src/tests/stall-timeout-retryable.test.ts src/tests/t6-missing-quote-array-close.test.ts src/tests/watchdog-rss.test.ts src/tests/logical-thread-batch.test.ts src/tests/chat-pool-style.test.ts src/tests/queue-wait-cap.test.ts src/tests/context-eviction-order.test.ts src/tests/chat-in-progress-settle.test.ts src/tests/stream-lock-leak.test.ts src/tests/stream-semaphore.test.ts src/tests/waf-isolation.test.ts src/tests/legacy-completions.test.ts src/tests/response-format.test.ts src/tests/cors-headers.test.ts src/tests/stream-lock-terminal-cancel.test.ts src/tests/parser-truncated-tool-call.test.ts src/tests/metrics-prometheus.test.ts src/tests/context-death-cleanup.test.ts src/tests/personalization-direct-fetch.test.ts src/tests/completion-headers-parity.test.ts src/tests/parallel-escape-repro.test.ts src/tests/chat-in-progress-scale-cost.test.ts src/tests/temp-chat-mode.test.ts src/tests/profile-reset.test.ts src/tests/error-classification.test.ts src/tests/quota-cooldown.test.ts src/tests/account-ready-gate.test.ts src/tests/tool-cap-notice.test.ts src/tests/interaction-flows.test.ts src/tests/headers-gate-cooldown-fallback.test.ts src/tests/unwrapped-tool-calls.test.ts src/tests/anthropic.test.ts src/tests/sync-clients.test.ts src/tests/cli.test.ts src/tests/paths.test.ts src/tests/tool-manifest.test.ts src/tests/tui.test.ts",
27
+ "test:mock": "tsx --test --test-concurrency=1 --env-file=.env.test src/tests/account-concurrency.test.ts src/tests/concurrency-latest-wins.test.ts src/tests/mutex.test.ts src/tests/context-truncation.test.ts src/tests/streaming-thinking-summary.test.ts src/tests/tool-call-guard.test.ts src/tests/utils-logger-full.test.ts src/tests/logger-toolcall-errors.test.ts src/tests/logger-toolcall-disabled.test.ts src/tests/utils-json-full.test.ts src/tests/utils-session-guard-full.test.ts src/tests/core-logic-full.test.ts src/tests/core-logic-accounts.test.ts src/tests/chat-validation-full.test.ts src/tests/chat-logic-full.test.ts src/tests/account-stickiness.test.ts src/tests/accounts-security.test.ts src/tests/advanced.test.ts src/tests/auth-playwright.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chromium-args.test.ts src/tests/config-current.test.ts src/tests/context-meter.test.ts src/tests/concurrency.test.ts src/tests/delta.test.ts src/tests/fingerprint.test.ts src/tests/human-behavior.test.ts src/tests/index.test.ts src/tests/json-recovery.test.ts src/tests/jsonFix.test.ts src/tests/memory-cache.test.ts src/tests/model-alias.test.ts src/tests/model-registry-thinking.test.ts src/tests/models-endpoint.test.ts src/tests/multimodal.test.ts src/tests/oss-upload.test.ts src/tests/parser.test.ts src/tests/personalization-flow.test.ts src/tests/personalization-required.test.ts src/tests/retry-policy.test.ts src/tests/rotation.test.ts src/tests/server-lifecycle.test.ts src/tests/session-keeper.test.ts src/tests/session-state.test.ts src/tests/single-account-mode.test.ts src/tests/stream-retry.test.ts src/tests/tool-calls-endpoint.test.ts src/tests/tool-instructions-cache.test.ts src/tests/session-id.test.ts src/tests/watchdog-ram.test.ts src/tests/tool-recovery-t1t2.test.ts src/tests/abort-silent-t4.test.ts src/tests/t3-close-tag-escape.test.ts src/tests/t5-tool-cap.test.ts src/tests/logger-redaction.test.ts src/tests/stall-timeout-retryable.test.ts src/tests/t6-missing-quote-array-close.test.ts src/tests/watchdog-rss.test.ts src/tests/logical-thread-batch.test.ts src/tests/chat-pool-style.test.ts src/tests/queue-wait-cap.test.ts src/tests/context-eviction-order.test.ts src/tests/chat-in-progress-settle.test.ts src/tests/stream-lock-leak.test.ts src/tests/stream-semaphore.test.ts src/tests/waf-isolation.test.ts src/tests/legacy-completions.test.ts src/tests/response-format.test.ts src/tests/cors-headers.test.ts src/tests/stream-lock-terminal-cancel.test.ts src/tests/parser-truncated-tool-call.test.ts src/tests/metrics-prometheus.test.ts src/tests/context-death-cleanup.test.ts src/tests/personalization-direct-fetch.test.ts src/tests/completion-headers-parity.test.ts src/tests/parallel-escape-repro.test.ts src/tests/chat-in-progress-scale-cost.test.ts src/tests/temp-chat-mode.test.ts src/tests/profile-reset.test.ts src/tests/error-classification.test.ts src/tests/quota-cooldown.test.ts src/tests/account-ready-gate.test.ts src/tests/tool-cap-notice.test.ts src/tests/interaction-flows.test.ts src/tests/headers-gate-cooldown-fallback.test.ts src/tests/unwrapped-tool-calls.test.ts src/tests/anthropic.test.ts src/tests/sync-clients.test.ts src/tests/cli.test.ts src/tests/paths.test.ts src/tests/tool-manifest.test.ts src/tests/tui.test.ts src/tests/personalization-deadline.test.ts",
28
+ "test:mock:fast": "tsx --test --test-concurrency=2 --env-file=.env.test src/tests/account-concurrency.test.ts src/tests/concurrency-latest-wins.test.ts src/tests/mutex.test.ts src/tests/context-truncation.test.ts src/tests/streaming-thinking-summary.test.ts src/tests/tool-call-guard.test.ts src/tests/utils-logger-full.test.ts src/tests/logger-toolcall-errors.test.ts src/tests/logger-toolcall-disabled.test.ts src/tests/utils-json-full.test.ts src/tests/utils-session-guard-full.test.ts src/tests/core-logic-full.test.ts src/tests/core-logic-accounts.test.ts src/tests/chat-validation-full.test.ts src/tests/chat-logic-full.test.ts src/tests/account-stickiness.test.ts src/tests/accounts-security.test.ts src/tests/advanced.test.ts src/tests/auth-playwright.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chromium-args.test.ts src/tests/config-current.test.ts src/tests/context-meter.test.ts src/tests/concurrency.test.ts src/tests/delta.test.ts src/tests/fingerprint.test.ts src/tests/human-behavior.test.ts src/tests/index.test.ts src/tests/json-recovery.test.ts src/tests/jsonFix.test.ts src/tests/memory-cache.test.ts src/tests/model-alias.test.ts src/tests/model-registry-thinking.test.ts src/tests/models-endpoint.test.ts src/tests/multimodal.test.ts src/tests/oss-upload.test.ts src/tests/parser.test.ts src/tests/personalization-flow.test.ts src/tests/personalization-required.test.ts src/tests/retry-policy.test.ts src/tests/rotation.test.ts src/tests/server-lifecycle.test.ts src/tests/session-keeper.test.ts src/tests/session-state.test.ts src/tests/single-account-mode.test.ts src/tests/stream-retry.test.ts src/tests/tool-calls-endpoint.test.ts src/tests/tool-instructions-cache.test.ts src/tests/session-id.test.ts src/tests/watchdog-ram.test.ts src/tests/tool-recovery-t1t2.test.ts src/tests/abort-silent-t4.test.ts src/tests/t3-close-tag-escape.test.ts src/tests/t5-tool-cap.test.ts src/tests/logger-redaction.test.ts src/tests/stall-timeout-retryable.test.ts src/tests/t6-missing-quote-array-close.test.ts src/tests/watchdog-rss.test.ts src/tests/logical-thread-batch.test.ts src/tests/chat-pool-style.test.ts src/tests/queue-wait-cap.test.ts src/tests/context-eviction-order.test.ts src/tests/chat-in-progress-settle.test.ts src/tests/stream-lock-leak.test.ts src/tests/stream-semaphore.test.ts src/tests/waf-isolation.test.ts src/tests/legacy-completions.test.ts src/tests/response-format.test.ts src/tests/cors-headers.test.ts src/tests/stream-lock-terminal-cancel.test.ts src/tests/parser-truncated-tool-call.test.ts src/tests/metrics-prometheus.test.ts src/tests/context-death-cleanup.test.ts src/tests/personalization-direct-fetch.test.ts src/tests/completion-headers-parity.test.ts src/tests/parallel-escape-repro.test.ts src/tests/chat-in-progress-scale-cost.test.ts src/tests/temp-chat-mode.test.ts src/tests/profile-reset.test.ts src/tests/error-classification.test.ts src/tests/quota-cooldown.test.ts src/tests/account-ready-gate.test.ts src/tests/tool-cap-notice.test.ts src/tests/interaction-flows.test.ts src/tests/headers-gate-cooldown-fallback.test.ts src/tests/unwrapped-tool-calls.test.ts src/tests/anthropic.test.ts src/tests/sync-clients.test.ts src/tests/cli.test.ts src/tests/paths.test.ts src/tests/tool-manifest.test.ts src/tests/tui.test.ts src/tests/personalization-deadline.test.ts",
30
29
  "test:live": "tsx --test src/tests/concurrentChat.test.ts src/tests/agenticStress.test.ts",
31
30
  "benchmark:proxy": "npx tsx src/benchmarks/proxy-baseline.ts",
32
31
  "typecheck": "tsc --noEmit"
package/src/api/server.ts CHANGED
@@ -650,15 +650,20 @@ export async function startServer(options?: {
650
650
  const BATCH_SIZE = config.playwright.initBatchSize;
651
651
 
652
652
  if (accounts.length > 0) {
653
- let readyAccountId: string | null = null;
654
653
  const totalAccounts = accounts.length;
655
654
 
656
655
  // Warm accounts in priority order (recently successful accounts first),
657
- // skipping accounts still on cooldown, so the startup account matches
658
- // the one request routing will pick first.
656
+ // skipping accounts still on cooldown. Warm up to maxActiveContexts
657
+ // accounts (default 2: main + reserve) so an immediate failover has a
658
+ // live browser ready instead of incurring a cold start.
659
659
  const warmOrder = getAccountsByPriority(accounts).filter(
660
660
  (account) => !getAccountCooldownInfo(account.id),
661
661
  );
662
+ const targetWarmCount = Math.min(
663
+ warmOrder.length,
664
+ Math.max(1, config.playwright.maxActiveContexts),
665
+ );
666
+ const readyAccountIds = new Set<string>();
662
667
 
663
668
  for (let i = 0; i < warmOrder.length; i++) {
664
669
  const ok = await prepareAccountRuntime(
@@ -669,24 +674,26 @@ export async function startServer(options?: {
669
674
  warmQwenChatPool,
670
675
  );
671
676
  if (ok) {
677
+ readyAccountIds.add(warmOrder[i].id);
672
678
  console.log(
673
- `✅ [Server] Account ready (${i + 1}/${totalAccounts}): ${maskEmail(warmOrder[i].email)}`,
679
+ `✅ [Server] Account ready (${readyAccountIds.size}/${totalAccounts}): ${maskEmail(warmOrder[i].email)}`,
674
680
  );
675
- readyAccountId = warmOrder[i].id;
676
- break;
681
+ if (readyAccountIds.size >= targetWarmCount) {
682
+ break;
683
+ }
677
684
  }
678
685
  }
679
686
 
680
687
  const remainingAccounts = accounts.filter(
681
- (account) => account.id !== readyAccountId,
688
+ (account) => !readyAccountIds.has(account.id),
682
689
  );
683
- if (readyAccountId === null) {
690
+ if (readyAccountIds.size === 0) {
684
691
  console.warn(
685
692
  `⚠️ [Server] No account ready during startup; continuing in background`,
686
693
  );
687
694
  }
688
695
 
689
- if (config.playwright.prepareAllOnStartup || readyAccountId === null) {
696
+ if (config.playwright.prepareAllOnStartup || readyAccountIds.size === 0) {
690
697
  if (config.playwright.prepareAllOnStartup && remainingAccounts.length > 0) {
691
698
  console.log(
692
699
  `🪶 [Server] Preparing ${remainingAccounts.length} standby account(s) in background`,
@@ -721,13 +728,13 @@ export async function startServer(options?: {
721
728
 
722
729
  for (const account of remainingAccounts) {
723
730
  try {
724
- // Validate login in background
731
+ const creds = getAccountCredentials(account.id) ?? account;
732
+ // Validate login in background with real unmasked credentials
725
733
  const ok = await validateAccountLogin(
726
- account,
734
+ creds,
727
735
  config.playwright.headless,
728
736
  config.playwright.browser,
729
737
  );
730
-
731
738
  if (ok) {
732
739
  // Add to priority list only once validated
733
740
  ensureAccountInPriority(account.id);
@@ -35,7 +35,7 @@ import {
35
35
  type AccountLease,
36
36
  } from "../../core/account-concurrency.ts";
37
37
  import { isAuthMockEnabled } from "../../services/auth-playwright.ts";
38
- import { refreshHeaders } from "../../services/playwright.ts";
38
+ import { isPlaywrightInitialized, refreshHeaders } from "../../services/playwright.ts";
39
39
  import {
40
40
  clearAllSessionsForAccount,
41
41
  createQwenStream,
@@ -77,7 +77,18 @@ const MAX_ANTI_BOT_ROTATIONS = 1;
77
77
  * a stuck account page (closed context / WAF) can otherwise hold each browser
78
78
  * op for 60s and keep the personalization mutex blocked for minutes.
79
79
  */
80
- const PERSONALIZATION_SYNC_DEADLINE_MS = 30_000;
80
+ export const PERSONALIZATION_SYNC_DEADLINE_MS = 30_000;
81
+ export const COLD_ACCOUNT_PERSONALIZATION_SYNC_DEADLINE_MS = 60_000;
82
+
83
+ export function computePersonalizationDeadlineMs(
84
+ accountId: string | undefined,
85
+ navigationTimeoutMs = config.timeouts.navigation,
86
+ ): number {
87
+ if (accountId && accountId !== "global" && isPlaywrightInitialized(accountId)) {
88
+ return PERSONALIZATION_SYNC_DEADLINE_MS;
89
+ }
90
+ return Math.max(COLD_ACCOUNT_PERSONALIZATION_SYNC_DEADLINE_MS, navigationTimeoutMs);
91
+ }
81
92
 
82
93
  /**
83
94
  * Hard deadline for a single stream-acquire attempt (models sync + truncation
@@ -1183,15 +1194,16 @@ async function tryCreateStreamWithRetry(
1183
1194
  return false;
1184
1195
  },
1185
1196
  );
1197
+ const syncDeadlineMs = computePersonalizationDeadlineMs(currentAccountId);
1186
1198
  personalizationApplied = await Promise.race([
1187
1199
  syncPromise,
1188
1200
  new Promise<boolean>((resolve) => {
1189
1201
  personalizationDeadlineTimer = setTimeout(() => {
1190
1202
  if (!syncSettled) {
1191
- syncFailure = `sync timed out after ${PERSONALIZATION_SYNC_DEADLINE_MS}ms`;
1203
+ syncFailure = `sync timed out after ${syncDeadlineMs}ms`;
1192
1204
  }
1193
1205
  resolve(false);
1194
- }, PERSONALIZATION_SYNC_DEADLINE_MS);
1206
+ }, syncDeadlineMs);
1195
1207
  }),
1196
1208
  ]);
1197
1209
  // The sync won the race: stop the deadline so it cannot keep the
@@ -1194,11 +1194,26 @@ export async function getBasicHeaders(accountId: string): Promise<{
1194
1194
  }
1195
1195
  }
1196
1196
 
1197
+ async function resolveAccountCredentials(account: QwenAccount): Promise<QwenAccount> {
1198
+ if (account.password && account.password !== "***") {
1199
+ return account;
1200
+ }
1201
+ try {
1202
+ const { getAccountCredentials } = await import("../core/accounts.ts");
1203
+ const creds = getAccountCredentials(account.id);
1204
+ if (creds && creds.password && creds.password !== "***") {
1205
+ return creds;
1206
+ }
1207
+ } catch {}
1208
+ return account;
1209
+ }
1210
+
1197
1211
  export async function initPlaywrightForAccount(
1198
- account: QwenAccount,
1212
+ rawAccount: QwenAccount,
1199
1213
  headless = true,
1200
1214
  browserType: BrowserType = "chromium",
1201
1215
  ): Promise<void> {
1216
+ const account = await resolveAccountCredentials(rawAccount);
1202
1217
  if (accountPages.has(account.id)) {
1203
1218
  console.log(
1204
1219
  `[Playwright] Already initialized for ${maskEmail(account.email)}`,
@@ -1404,10 +1419,11 @@ export async function initPlaywrightForAccount(
1404
1419
  * This is much lighter than full initPlaywrightForAccount (no header capture).
1405
1420
  */
1406
1421
  export async function validateAccountLogin(
1407
- account: QwenAccount,
1422
+ rawAccount: QwenAccount,
1408
1423
  headless = true,
1409
1424
  browserType: BrowserType = "chromium",
1410
1425
  ): Promise<boolean> {
1426
+ const account = await resolveAccountCredentials(rawAccount);
1411
1427
  if (accountPages.has(account.id)) {
1412
1428
  // Already initialized, no need to validate
1413
1429
  return true;
@@ -1596,6 +1612,22 @@ async function loginToQwen(
1596
1612
  email: string,
1597
1613
  password: string,
1598
1614
  ): Promise<boolean> {
1615
+ if (!password || password === "***") {
1616
+ try {
1617
+ const { getAccountCredentials } = await import("../core/accounts.ts");
1618
+ const creds = getAccountCredentials(accountId);
1619
+ if (creds && creds.password && creds.password !== "***") {
1620
+ password = creds.password;
1621
+ } else {
1622
+ console.error(
1623
+ `❌ [Playwright] Cannot login for ${maskEmail(email)}: password is masked ('***') and real credentials not found in store`,
1624
+ );
1625
+ return false;
1626
+ }
1627
+ } catch {
1628
+ return false;
1629
+ }
1630
+ }
1599
1631
  const page = accountPages.get(accountId);
1600
1632
  if (!page) return false;
1601
1633
 
@@ -1827,6 +1859,12 @@ async function loginViaUi(
1827
1859
  }
1828
1860
  await sleep(3000);
1829
1861
 
1862
+ // If a slider puzzle captcha appeared after submit, solve it automatically
1863
+ await solveBaxiaCaptcha(page, {
1864
+ waitForMs: 2000,
1865
+ maxAttempts: config.captcha.maxAttempts,
1866
+ retryDelayMs: config.captcha.retryDelayMs,
1867
+ }).catch(() => {});
1830
1868
  // Check for UI error elements in DOM (Ant Design errors, alerts, toasts)
1831
1869
  const errorSelector = [
1832
1870
  ".ant-form-item-explain-error",
@@ -3119,6 +3157,12 @@ export function registerPlaywrightAccountForTests(
3119
3157
  lastAccountActivity.set(accountId, lastActivityAt);
3120
3158
  }
3121
3159
 
3160
+ export function unregisterPlaywrightAccountForTests(accountId: string): void {
3161
+ accountPages.delete(accountId);
3162
+ accountContexts.delete(accountId);
3163
+ lastAccountActivity.delete(accountId);
3164
+ }
3165
+
3122
3166
  // ─── Token TTL Diagnostics ───────────────────────────────────────────────────
3123
3167
 
3124
3168
  export interface CookieDiagnostic {