qwenproxy-cli 1.5.0 → 1.5.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qwenproxy-cli",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
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": {
@@ -29,8 +29,8 @@
29
29
  "reset": "npx tsx src/reset-cooldowns.ts",
30
30
  "purge": "npx tsx src/delete-chats.ts",
31
31
  "test": "npm run test:mock && npm run test:live",
32
- "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/auth-session-persistence.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chat-cleanup-auto.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/local-auth.test.ts src/tests/safe-remote-url.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/sync-top10-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/tui-settings.test.ts src/tests/personalization-deadline.test.ts src/tests/update-cli.test.ts src/tests/batch-accounts.test.ts src/tests/shared-browser.test.ts src/tests/auth-credential-classification.test.ts src/tests/responses-api.test.ts src/tests/media-generation.test.ts src/tests/infer-tool-name.test.ts src/tests/native-session-headers.test.ts src/tests/omni-thinking.test.ts src/tests/protect-stuck-mutex.test.ts src/tests/stream-error-notice.test.ts src/tests/parser-deep-coverage.test.ts src/tests/tui-logs-copy.test.ts src/tests/auth-jwt-and-validation.test.ts src/tests/tui-port-and-key-modal.test.ts",
33
- "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/auth-session-persistence.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chat-cleanup-auto.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/local-auth.test.ts src/tests/safe-remote-url.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/sync-top10-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/tui-settings.test.ts src/tests/personalization-deadline.test.ts src/tests/update-cli.test.ts src/tests/batch-accounts.test.ts src/tests/shared-browser.test.ts src/tests/auth-credential-classification.test.ts src/tests/responses-api.test.ts src/tests/media-generation.test.ts src/tests/infer-tool-name.test.ts src/tests/native-session-headers.test.ts src/tests/omni-thinking.test.ts src/tests/protect-stuck-mutex.test.ts src/tests/stream-error-notice.test.ts src/tests/parser-deep-coverage.test.ts src/tests/tui-logs-copy.test.ts src/tests/auth-jwt-and-validation.test.ts src/tests/tui-port-and-key-modal.test.ts",
32
+ "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/auth-session-persistence.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chat-cleanup-auto.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/local-auth.test.ts src/tests/safe-remote-url.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/sync-top10-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/tui-settings.test.ts src/tests/personalization-deadline.test.ts src/tests/update-cli.test.ts src/tests/batch-accounts.test.ts src/tests/shared-browser.test.ts src/tests/auth-credential-classification.test.ts src/tests/responses-api.test.ts src/tests/media-generation.test.ts src/tests/infer-tool-name.test.ts src/tests/native-session-headers.test.ts src/tests/omni-thinking.test.ts src/tests/protect-stuck-mutex.test.ts src/tests/stream-error-notice.test.ts src/tests/parser-deep-coverage.test.ts src/tests/tui-logs-copy.test.ts src/tests/auth-jwt-and-validation.test.ts src/tests/tui-port-and-key-modal.test.ts src/tests/verify-zero-fake-cooldown.test.ts",
33
+ "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/auth-session-persistence.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chat-cleanup-auto.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/local-auth.test.ts src/tests/safe-remote-url.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/sync-top10-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/tui-settings.test.ts src/tests/personalization-deadline.test.ts src/tests/update-cli.test.ts src/tests/batch-accounts.test.ts src/tests/shared-browser.test.ts src/tests/auth-credential-classification.test.ts src/tests/responses-api.test.ts src/tests/media-generation.test.ts src/tests/infer-tool-name.test.ts src/tests/native-session-headers.test.ts src/tests/omni-thinking.test.ts src/tests/protect-stuck-mutex.test.ts src/tests/stream-error-notice.test.ts src/tests/parser-deep-coverage.test.ts src/tests/tui-logs-copy.test.ts src/tests/auth-jwt-and-validation.test.ts src/tests/tui-port-and-key-modal.test.ts src/tests/verify-zero-fake-cooldown.test.ts",
34
34
  "test:live": "tsx --test src/tests/concurrentChat.test.ts src/tests/agenticStress.test.ts",
35
35
  "test:stress": "npx tsx src/benchmarks/stress-test.ts",
36
36
  "test:stress:personalization": "npx tsx src/benchmarks/stress-test-personalization.ts",
package/src/api/server.ts CHANGED
@@ -877,23 +877,20 @@ export async function startServer(options?: {
877
877
  );
878
878
  });
879
879
  } else if (remainingAccounts.length > 0) {
880
- console.log(
881
- `ðŸŠķ [Server] ${remainingAccounts.length} standby account(s) will initialize on demand`,
882
- );
883
-
884
- // In background: warm 1 reserve account (if maxActiveContexts > 1) and
885
- // validate the rest of the standby accounts
880
+ // In background: warm reserve accounts up to a safe limit (default 4 total ready accounts)
881
+ // to prevent IP-level login flood / anti-bot challenge rate-limits on Alibaba.
886
882
  void (async () => {
887
- const { validateAccountLogin } = await import("../services/playwright.ts");
888
883
  const { ensureAccountInPriority } = await import("../core/account-priority.ts");
889
884
 
890
- let accountsToValidate = remainingAccounts;
885
+ const targetWarmTotal = Math.min(
886
+ config.playwright.startupWarmAccounts,
887
+ totalAccounts,
888
+ );
889
+ let currentReadyCount = readyAccountIds.size;
891
890
 
892
- // Warm reserve account in background for fast failover without delaying startup
893
- if (config.playwright.maxActiveContexts > 1 && remainingAccounts.length > 0) {
894
- let reserveCandidateIdx = 0;
895
- for (; reserveCandidateIdx < remainingAccounts.length; reserveCandidateIdx++) {
896
- const reserveAccount = remainingAccounts[reserveCandidateIdx];
891
+ if (currentReadyCount < targetWarmTotal && remainingAccounts.length > 0) {
892
+ for (const reserveAccount of remainingAccounts) {
893
+ if (currentReadyCount >= targetWarmTotal) break;
897
894
  try {
898
895
  const ok = await prepareAccountRuntime(
899
896
  reserveAccount,
@@ -903,12 +900,12 @@ export async function startServer(options?: {
903
900
  warmQwenChatPool,
904
901
  );
905
902
  if (ok) {
903
+ readyAccountIds.add(reserveAccount.id);
906
904
  ensureAccountInPriority(reserveAccount.id);
905
+ currentReadyCount++;
907
906
  console.log(
908
- `✅ [Server] Reserve account ready (2/${totalAccounts}): ${maskEmail(reserveAccount.email)}`,
907
+ `✅ [Server] Reserve account ready (${currentReadyCount}/${totalAccounts}): ${maskEmail(reserveAccount.email)}`,
909
908
  );
910
- reserveCandidateIdx++;
911
- break;
912
909
  }
913
910
  } catch (err) {
914
911
  console.warn(
@@ -916,56 +913,17 @@ export async function startServer(options?: {
916
913
  );
917
914
  }
918
915
  }
919
- accountsToValidate = remainingAccounts.slice(reserveCandidateIdx);
920
916
  }
921
917
 
922
- let validated = 0;
923
- let failed = 0;
924
-
925
- for (const account of accountsToValidate) {
926
- try {
927
- const creds = getAccountCredentials(account.id) ?? account;
928
- // Validate login in background with real unmasked credentials
929
- const ok = await validateAccountLogin(
930
- creds,
931
- config.playwright.headless,
932
- config.playwright.browser,
933
- );
934
- if (ok) {
935
- // Add to priority list only once validated
936
- ensureAccountInPriority(account.id);
937
- validated++;
938
- console.log(
939
- `✅ [Server] Standby account validated: ${maskEmail(account.email)}`,
940
- );
941
- } else {
942
- failed++;
943
- console.warn(
944
- `⚠ïļ [Server] Standby account login failed: ${maskEmail(account.email)} (quarantined)`,
945
- );
946
- }
947
- } catch (error) {
948
- failed++;
949
- console.warn(
950
- `⚠ïļ [Server] Standby account validation error: ${maskEmail(account.email)}: ${getErrorMessage(error)} (quarantined)`,
951
- );
952
- const { markAccountRateLimited } = await import("../core/account-manager.ts");
953
- markAccountRateLimited(
954
- account.id,
955
- 24 * 3600 * 1000,
956
- `StandbyValidationError: ${getErrorMessage(error)}`,
957
- );
958
- }
959
- }
960
-
961
- if (validated > 0 || failed > 0) {
918
+ const standbyCount = totalAccounts - currentReadyCount;
919
+ if (standbyCount > 0) {
962
920
  console.log(
963
- `✅ [Server] Standby validation complete: ${validated} account(s) ready${failed > 0 ? `, ${failed} failed` : ""}`,
921
+ `ðŸŠķ [Server] ${standbyCount} standby account(s) ready in pool (will initialize on demand when active accounts rotate)`,
964
922
  );
965
923
  }
966
924
  })().catch((error) => {
967
925
  console.warn(
968
- `❌ [Server] Background standby validation failed: ${getErrorMessage(error)}`,
926
+ `❌ [Server] Background account preparation failed: ${getErrorMessage(error)}`,
969
927
  );
970
928
  });
971
929
  }
@@ -76,6 +76,7 @@ const envSchema = z
76
76
  // own context while serving. Accounts in cooldown (rate-limited) sit idle,
77
77
  // drop out of the warm set and get evicted.
78
78
  PLAYWRIGHT_MAX_ACTIVE_CONTEXTS: z.string().default("2"),
79
+ PLAYWRIGHT_STARTUP_WARM_ACCOUNTS: z.string().default("4"),
79
80
  PLAYWRIGHT_PREPARE_ALL_ON_STARTUP: z.string().default("false"),
80
81
  CAPTCHA_SOLVER_ENABLED: z.string().default("true"),
81
82
  CAPTCHA_SOLVER_MAX_ATTEMPTS: z.string().default("3"),
@@ -235,6 +236,7 @@ export const config = {
235
236
  jsHeapMb: Math.max(64, parseInt(env.PLAYWRIGHT_JS_HEAP_MB)),
236
237
  lowMemoryFlags: env.PLAYWRIGHT_LOW_MEMORY_FLAGS !== "false",
237
238
  maxActiveContexts: Math.max(0, parseInt(env.PLAYWRIGHT_MAX_ACTIVE_CONTEXTS)),
239
+ startupWarmAccounts: Math.max(1, parseInt(env.PLAYWRIGHT_STARTUP_WARM_ACCOUNTS)),
238
240
  prepareAllOnStartup: env.PLAYWRIGHT_PREPARE_ALL_ON_STARTUP !== "false",
239
241
  },
240
242
  captcha: {
@@ -17,6 +17,6 @@ export function getAppVersion(): string {
17
17
  }
18
18
  }
19
19
  } catch {}
20
- cachedVersion = "v1.5.0";
20
+ cachedVersion = "v1.5.2";
21
21
  return cachedVersion;
22
22
  }
@@ -2075,7 +2075,7 @@ export async function processStreamingResponse(
2075
2075
  if (!toolParser) break;
2076
2076
  const allToolsFailed =
2077
2077
  toolParser.getMalformedToolCalls().length > 0 &&
2078
- toolParser.getEmittedToolCallCount() === 0;
2078
+ toolParser.getSuccessfulToolCallCount() === 0;
2079
2079
  if (!allToolsFailed) break;
2080
2080
 
2081
2081
  const malformedCalls = toolParser.getMalformedToolCalls();
@@ -56,6 +56,11 @@ async function ensurePlaywrightInitialized(accountId: string): Promise<void> {
56
56
  } catch {
57
57
  // Non-fatal: chat creation will still work with default account settings.
58
58
  }
59
+
60
+ try {
61
+ const { ensureAccountInPriority } = await import("../core/account-priority.ts");
62
+ ensureAccountInPriority(accountId);
63
+ } catch {}
59
64
  }
60
65
 
61
66
  export async function getBasicHeaders(accountId?: string): Promise<{
@@ -66,6 +66,7 @@ import { Mutex } from "../core/mutex.ts";
66
66
  import {
67
67
  markAccountHeadersReady,
68
68
  unmarkAccountHeadersReady,
69
+ isAccountHeadersReady,
69
70
  markAccountRateLimited,
70
71
  } from "../core/account-manager.ts";
71
72
  import { getAccountsByPriority } from "../core/account-priority.ts";
@@ -408,27 +409,6 @@ export async function isPageLoggedIn(
408
409
  }
409
410
  } catch {}
410
411
 
411
- // Fallback cross-origin refresh probe
412
- try {
413
- const refreshRes = await fetch("https://auth.qwen.ai/api/v2/auths/refresh", {
414
- method: "GET",
415
- credentials: "include",
416
- signal: AbortSignal.timeout(3000),
417
- });
418
- if (refreshRes.status === 200) {
419
- const refreshJson: any = await refreshRes.json().catch(() => null);
420
- if (refreshJson && refreshJson.success === false) {
421
- const code = refreshJson.data?.code || refreshJson.code;
422
- const details = String(refreshJson.data?.details || "");
423
- if (code === "Unauthorized" || details.includes("revogado") || details.includes("revoked")) {
424
- return false;
425
- }
426
- }
427
- } else if (refreshRes.status === 401 || refreshRes.status === 403) {
428
- return false;
429
- }
430
- } catch {}
431
-
432
412
  // If an authenticated user object is confirmed with a real user identity,
433
413
  // the session is 100% valid and verified by upstream.
434
414
  return true;
@@ -1811,7 +1791,7 @@ export async function initPlaywrightForAccount(
1811
1791
  );
1812
1792
  }
1813
1793
 
1814
- if (!options.skipHeaderCapture) {
1794
+ if (!options.skipHeaderCapture && !isAccountHeadersReady(account.id)) {
1815
1795
  (acctPage as any).__qwenChatHomeLoaded = true;
1816
1796
  await captureQwenHeaders(account.id);
1817
1797
  }
@@ -2224,7 +2204,7 @@ async function loginToQwen(
2224
2204
  );
2225
2205
  markAccountRateLimited(
2226
2206
  accountId,
2227
- 24 * 3600 * 1000,
2207
+ 300 * 1000,
2228
2208
  "AuthFailed: All login methods exhausted",
2229
2209
  );
2230
2210
  return false;
@@ -2582,41 +2562,52 @@ async function loginViaUi(
2582
2562
  await page
2583
2563
  .evaluate(() => {
2584
2564
  try {
2585
- const emailEl = document.querySelector('input[name="email"], input[type="email"], input[type="text"]');
2586
- const passEl = document.querySelector('input[type="password"], input[name="password"]');
2587
- if (emailEl) {
2588
- emailEl.dispatchEvent(new Event("input", { bubbles: true }));
2589
- emailEl.dispatchEvent(new Event("change", { bubbles: true }));
2590
- emailEl.dispatchEvent(new Event("blur", { bubbles: true }));
2591
- }
2592
- if (passEl) {
2593
- passEl.dispatchEvent(new Event("input", { bubbles: true }));
2594
- passEl.dispatchEvent(new Event("change", { bubbles: true }));
2595
- passEl.dispatchEvent(new Event("blur", { bubbles: true }));
2596
- }
2565
+ const emailEl = document.querySelector('input[name="email"], input[type="email"], input[type="text"]') as HTMLInputElement | null;
2566
+ const passEl = document.querySelector('input[type="password"], input[name="password"]') as HTMLInputElement | null;
2567
+ const setReactValue = (el: HTMLInputElement, val: string) => {
2568
+ const proto = Object.getPrototypeOf(el);
2569
+ const setter = Object.getOwnPropertyDescriptor(proto, "value")?.set;
2570
+ if (setter) {
2571
+ setter.call(el, val);
2572
+ } else {
2573
+ el.value = val;
2574
+ }
2575
+ el.dispatchEvent(new Event("input", { bubbles: true }));
2576
+ el.dispatchEvent(new Event("change", { bubbles: true }));
2577
+ el.dispatchEvent(new Event("blur", { bubbles: true }));
2578
+ };
2579
+ if (emailEl) setReactValue(emailEl, emailEl.value);
2580
+ if (passEl) setReactValue(passEl, passEl.value);
2597
2581
  } catch {}
2598
2582
  })
2599
2583
  .catch(() => {});
2600
2584
 
2601
- // Prefer clicking the submit button; do NOT press Enter if disabled
2585
+ await sleep(500);
2586
+
2587
+ // Multi-modal submit: DOM click, Playwright force click, and Enter press
2602
2588
  const submitSelector =
2603
2589
  'button.qwenchat-auth-pc-submit-button, button[type="submit"], button:has-text("Log in"), button:has-text("Sign in"), button:has-text("Fazer login"), button:has-text("Entrar"), button:has-text("Iniciar sesiÃģn"), button:has-text("į™ŧå―•")';
2604
2590
  const submitButton = page.locator(submitSelector).first();
2605
- try {
2606
- await page.waitForSelector('button[type="submit"]:not([disabled])', {
2607
- timeout: 4_000,
2608
- });
2609
- await submitButton.click();
2610
- } catch {
2611
- const isDisabled = await submitButton.isDisabled().catch(() => true);
2612
- if (!isDisabled) {
2613
- await submitButton.click({ force: true }).catch(() => {});
2614
- } else {
2615
- if (!page.url().includes("/auth") && (await isPageLoggedIn(page, 2000))) {
2616
- return { success: true };
2591
+
2592
+ await page.evaluate((sel) => {
2593
+ try {
2594
+ const btn = document.querySelector(sel) as HTMLButtonElement | null;
2595
+ if (btn) {
2596
+ btn.removeAttribute("disabled");
2597
+ btn.classList.remove("ant-btn-disabled");
2598
+ btn.click();
2617
2599
  }
2600
+ } catch {}
2601
+ }, submitSelector).catch(() => {});
2602
+
2603
+ try {
2604
+ if (await submitButton.isVisible({ timeout: 2000 }).catch(() => false)) {
2605
+ await submitButton.click({ force: true, timeout: 2000 }).catch(() => {});
2618
2606
  }
2619
- }
2607
+ } catch {}
2608
+
2609
+ const passInput = page.locator(passwordSelector).first();
2610
+ await passInput.press("Enter", { timeout: 2000 }).catch(() => {});
2620
2611
  // Post-submit verification loop: wait up to 15s for captcha, redirect, cookies, or errors
2621
2612
  const postSubmitDeadline = Date.now() + 15_000;
2622
2613
  while (Date.now() < postSubmitDeadline) {
@@ -81,12 +81,27 @@ export function buildToolInstructions(
81
81
  ? Object.keys(properties)[0]
82
82
  : null;
83
83
 
84
- let sampleArgs1 = "{}";
85
- let sampleArgs2 = "{}";
86
- if (firstPropKey) {
87
- sampleArgs1 = JSON.stringify({ [firstPropKey]: "value1" });
88
- sampleArgs2 = JSON.stringify({ [firstPropKey]: "value2" });
89
- }
84
+ const sampleArgs1 = firstPropKey
85
+ ? JSON.stringify({ [firstPropKey]: "value1" })
86
+ : "{}";
87
+
88
+ // Pick second tool dynamically when available to teach tool diversity
89
+ const secondTool = toolList.length > 1 ? toolList[1] : null;
90
+ const secondToolName = secondTool
91
+ ? (typeof secondTool?.function?.name === "string" && secondTool.function.name) ||
92
+ (typeof secondTool?.name === "string" && secondTool.name) ||
93
+ sampleToolName
94
+ : null;
95
+ const secondProperties =
96
+ secondTool?.function?.parameters?.properties ||
97
+ secondTool?.parameters?.properties;
98
+ const secondPropKey =
99
+ secondProperties && typeof secondProperties === "object"
100
+ ? Object.keys(secondProperties)[0]
101
+ : null;
102
+ const sampleArgs2 = secondPropKey
103
+ ? JSON.stringify({ [secondPropKey]: "value2" })
104
+ : "{}";
90
105
 
91
106
  let forcedInstruction = "";
92
107
  if (
@@ -103,32 +118,30 @@ export function buildToolInstructions(
103
118
  forcedInstruction = `\nCRITICAL: You MUST call at least one tool from the list above in this response.\n`;
104
119
  }
105
120
 
121
+ const secondExample = secondToolName
122
+ ? `\n${TOOL_CALL_OPEN}\n{"name": "${secondToolName}", "arguments": ${sampleArgs2}}\n${TOOL_CALL_CLOSE}`
123
+ : "";
124
+
106
125
  let instructions = `
107
126
 
108
127
  # TOOLS AVAILABLE
128
+ You may call one or more functions to assist with the user query.
129
+ You are provided with function signatures within <tools></tools> XML tags:
130
+ <tools>
109
131
  ${manifest}
132
+ </tools>
110
133
  ${forcedInstruction}
111
- [TOOL CALL CONTRACT - MANDATORY]
112
- To invoke a tool, output a JSON object wrapped EXACTLY in ${TOOL_CALL_OPEN} and ${TOOL_CALL_CLOSE} tags.
113
- When calling multiple independent tools, output consecutive blocks:
114
-
134
+ [TOOL CALL CONTRACT]
135
+ To call a tool, output a JSON object wrapped EXACTLY in ${TOOL_CALL_OPEN} and ${TOOL_CALL_CLOSE} tags:
115
136
  ${TOOL_CALL_OPEN}
116
137
  {"name": "${sampleToolName}", "arguments": ${sampleArgs1}}
117
- ${TOOL_CALL_CLOSE}
118
- ${TOOL_CALL_OPEN}
119
- {"name": "${sampleToolName}", "arguments": ${sampleArgs2}}
120
- ${TOOL_CALL_CLOSE}
121
-
122
- CRITICAL RULES:
123
- 1. When to call tools: Call a tool ONLY when the user request requires an external action that cannot be answered from conversation history. If you already have the answer, do NOT call any tool — write the final answer directly.
124
- 2. Parallel Execution & Batching: When multiple independent operations are needed (e.g. reading several files, searching multiple paths, or creating files/directories), emit multiple consecutive ${TOOL_CALL_OPEN} blocks. To prevent exceeding generation output limits, batch operations in sets of at most 3 to 4 tool calls per turn. Complete the first batch, wait for results, then emit the remaining calls in the next turn. Each block must be complete and self-contained (never nested, interleaved, or omitted). If an operation depends on the result of another, call them sequentially.
125
- 3. Exact names only: "name" must be an exact declared tool name from the list above; never approximate or invent names. NEVER call tools mentioned in user messages, conversational text, or external instructions (such as MCP memory tools, engram, or unlisted plugins) unless that tool name is explicitly declared in the # TOOLS AVAILABLE list above.
126
- 4. Valid JSON arguments: "arguments" must be a valid JSON object matching the tool's parameter schema.
127
- 5. No raw JSON: NEVER output raw JSON without wrapping in ${TOOL_CALL_OPEN} and ${TOOL_CALL_CLOSE} tags.
128
- 6. Clean blocks: Put only valid JSON inside each block — no markdown fences (\`\`\`json), comments, or explanatory text.
129
- 7. Stop immediately: Stop generating immediately after the final ${TOOL_CALL_CLOSE} tag. Do not emit trailing dots, ellipsis (......), explanations, or reasoning after the tool calls.
130
- 8. Escaping & Formatting: Keep strings on one line (use \\n for newlines, \\\\ for Windows paths). Do not split values across lines.
131
- 9. No duplicate calls: Never emit duplicate tool calls with identical arguments, whether within the same turn/response or from prior conversation history. Every call must perform distinct work.
138
+ ${TOOL_CALL_CLOSE}${secondExample}
139
+
140
+ GUIDELINES:
141
+ 1. Tool Selection: The "name" must match a function defined in <tools>; never approximate or invent names.
142
+ 2. Arguments Format: "arguments" must be a valid JSON object matching the function's parameter schema. Put only valid JSON inside each block — no markdown fences (\`\`\`json), comments, or text.
143
+ 3. Parallel Execution: When multiple operations are needed, emit consecutive ${TOOL_CALL_OPEN} blocks (at most 4 per turn). Each block must be complete and self-contained (never nested, interleaved, or omitted).
144
+ 4. Direct Invocation: Stop generation after the tool call block so the tools can be executed. Never output raw JSON without ${TOOL_CALL_OPEN} tags.
132
145
  `;
133
146
 
134
147
  // Cache result (with LRU-style eviction)
@@ -1275,6 +1275,7 @@ export class StreamingToolParser {
1275
1275
  private currentOpenTag = TOOL_START_LITERAL;
1276
1276
  private currentCloseTag = TOOL_END;
1277
1277
  private emittedToolCallCount = 0;
1278
+ private successfulToolCallsCount = 0;
1278
1279
  private pendingLeadIn = "";
1279
1280
  private tools: ToolDefinitionLike[] = [];
1280
1281
  private declaredToolNames: string[] = [];
@@ -1568,6 +1569,7 @@ export class StreamingToolParser {
1568
1569
  // or the arguments would be appended twice and corrupted.
1569
1570
  this.flushPendingToolCallDeltas(result);
1570
1571
  this.emittedToolCallCount++;
1572
+ this.successfulToolCallsCount++;
1571
1573
  this.pendingLeadIn = "";
1572
1574
  incremental.startEmitted = false;
1573
1575
  incremental.disabled = true;
@@ -1577,6 +1579,7 @@ export class StreamingToolParser {
1577
1579
  this.flushPendingToolCallDeltas(result);
1578
1580
  result.toolCalls.push(tc);
1579
1581
  this.emittedToolCallCount++;
1582
+ this.successfulToolCallsCount++;
1580
1583
  this.pendingLeadIn = "";
1581
1584
  }
1582
1585
 
@@ -2199,6 +2202,10 @@ export class StreamingToolParser {
2199
2202
  return this.emittedToolCallCount;
2200
2203
  }
2201
2204
 
2205
+ getSuccessfulToolCallCount(): number {
2206
+ return this.successfulToolCallsCount;
2207
+ }
2208
+
2202
2209
  isInsideTool(): boolean {
2203
2210
  return this.insideTool;
2204
2211
  }