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 +0 -2
- package/package.json +3 -4
- package/src/api/server.ts +19 -12
- package/src/routes/chat/account.ts +16 -4
- package/src/services/playwright.ts +46 -2
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.
|
|
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
|
|
658
|
-
//
|
|
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 (${
|
|
679
|
+
`✅ [Server] Account ready (${readyAccountIds.size}/${totalAccounts}): ${maskEmail(warmOrder[i].email)}`,
|
|
674
680
|
);
|
|
675
|
-
|
|
676
|
-
|
|
681
|
+
if (readyAccountIds.size >= targetWarmCount) {
|
|
682
|
+
break;
|
|
683
|
+
}
|
|
677
684
|
}
|
|
678
685
|
}
|
|
679
686
|
|
|
680
687
|
const remainingAccounts = accounts.filter(
|
|
681
|
-
(account) => account.id
|
|
688
|
+
(account) => !readyAccountIds.has(account.id),
|
|
682
689
|
);
|
|
683
|
-
if (
|
|
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 ||
|
|
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
|
-
|
|
731
|
+
const creds = getAccountCredentials(account.id) ?? account;
|
|
732
|
+
// Validate login in background with real unmasked credentials
|
|
725
733
|
const ok = await validateAccountLogin(
|
|
726
|
-
|
|
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 ${
|
|
1203
|
+
syncFailure = `sync timed out after ${syncDeadlineMs}ms`;
|
|
1192
1204
|
}
|
|
1193
1205
|
resolve(false);
|
|
1194
|
-
},
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|