qwenproxy-cli 1.4.3 → 1.4.8

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.
@@ -314,6 +314,7 @@ function runMigrations(db2) {
314
314
  "sec_ch_ua_mobile TEXT",
315
315
  "sec_ch_ua_platform TEXT",
316
316
  "version TEXT",
317
+ "refresh_token TEXT",
317
318
  "captured_at INTEGER DEFAULT 0"
318
319
  ];
319
320
  for (const col of authSessionCols) {
@@ -392,11 +393,11 @@ function saveAuthSession(accountId, session) {
392
393
  INSERT OR REPLACE INTO qwen_auth_sessions (
393
394
  account_id, cookie, user_agent, bx_v, bx_ua, bx_umidtoken,
394
395
  sec_ch_ua, sec_ch_ua_mobile, sec_ch_ua_platform, version,
395
- user_id, token_expires_at, captured_at, updated_at
396
+ user_id, token_expires_at, refresh_token, captured_at, updated_at
396
397
  ) VALUES (
397
398
  ?, ?, ?, ?, ?, ?,
398
399
  ?, ?, ?, ?,
399
- ?, ?, ?, datetime('now')
400
+ ?, ?, ?, ?, datetime('now')
400
401
  )
401
402
  `);
402
403
  stmt.run(
@@ -412,6 +413,7 @@ function saveAuthSession(accountId, session) {
412
413
  session.version || null,
413
414
  session.userId || null,
414
415
  session.tokenExpiresAt || null,
416
+ session.refreshToken || null,
415
417
  session.capturedAt ?? Date.now()
416
418
  );
417
419
  }
@@ -420,7 +422,7 @@ function getValidAuthSession(accountId, maxAgeMs = 30 * 24 * 60 * 60 * 1e3) {
420
422
  const row = database.prepare(
421
423
  `SELECT account_id, cookie, user_agent, bx_v, bx_ua, bx_umidtoken,
422
424
  sec_ch_ua, sec_ch_ua_mobile, sec_ch_ua_platform, version,
423
- user_id, token_expires_at, captured_at
425
+ user_id, token_expires_at, refresh_token, captured_at
424
426
  FROM qwen_auth_sessions WHERE account_id = ?`
425
427
  ).get(accountId);
426
428
  if (!row) return null;
@@ -428,7 +430,7 @@ function getValidAuthSession(accountId, maxAgeMs = 30 * 24 * 60 * 60 * 1e3) {
428
430
  if (capturedAt <= 0 || Date.now() - capturedAt > maxAgeMs) {
429
431
  return null;
430
432
  }
431
- if (row.token_expires_at) {
433
+ if (!row.refresh_token && row.token_expires_at) {
432
434
  const tokenExpMs = Number(row.token_expires_at) * 1e3;
433
435
  if (tokenExpMs <= Date.now() + 5 * 60 * 1e3) {
434
436
  return null;
@@ -450,6 +452,7 @@ function getValidAuthSession(accountId, maxAgeMs = 30 * 24 * 60 * 60 * 1e3) {
450
452
  version: row.version || void 0,
451
453
  userId: row.user_id || void 0,
452
454
  tokenExpiresAt: row.token_expires_at ? Number(row.token_expires_at) : void 0,
455
+ refreshToken: row.refresh_token || void 0,
453
456
  capturedAt
454
457
  };
455
458
  }
@@ -3523,8 +3526,29 @@ function getStealthScript(profile) {
3523
3526
  const _toBlob = HTMLCanvasElement.prototype.toBlob;
3524
3527
  const _getImageData = CanvasRenderingContext2D.prototype.getImageData;
3525
3528
 
3529
+ function isCaptchaCanvas(canvas) {
3530
+ try {
3531
+ if (!canvas) return false;
3532
+ const id = (canvas.id || "").toLowerCase();
3533
+ const cls = (canvas.className || "").toLowerCase();
3534
+ return (
3535
+ id.includes("baxia") ||
3536
+ id.includes("nc_") ||
3537
+ id.includes("captcha") ||
3538
+ id.includes("punish") ||
3539
+ cls.includes("baxia") ||
3540
+ cls.includes("nc_") ||
3541
+ cls.includes("captcha") ||
3542
+ Boolean(canvas.closest?.("#baxia-dialog, #baxia-punish, [class*='baxia'], [class*='nc_'], .auth-layout"))
3543
+ );
3544
+ } catch(e) {
3545
+ return false;
3546
+ }
3547
+ }
3548
+
3526
3549
  function addNoise(canvas) {
3527
3550
  try {
3551
+ if (isCaptchaCanvas(canvas)) return;
3528
3552
  const ctx = canvas.getContext('2d');
3529
3553
  if (!ctx) return;
3530
3554
  const style = ctx.fillStyle;
@@ -3548,6 +3572,9 @@ function getStealthScript(profile) {
3548
3572
 
3549
3573
  CanvasRenderingContext2D.prototype.getImageData = function(x, y, w, h) {
3550
3574
  const imageData = _getImageData.apply(this, arguments);
3575
+ if (isCaptchaCanvas(this.canvas)) {
3576
+ return imageData;
3577
+ }
3551
3578
  const data = imageData.data;
3552
3579
  const maxPixels = Math.min(data.length / 4, 2500);
3553
3580
  for (let i = 0; i < maxPixels * 4; i += 4) {
@@ -4024,6 +4051,7 @@ async function loginToQwen(accountId, email, password) {
4024
4051
  secChUaPlatform: cache.headers["sec-ch-ua-platform"] || void 0,
4025
4052
  version: cache.headers["version"] || void 0,
4026
4053
  tokenExpiresAt: exp || void 0,
4054
+ refreshToken: apiResult.refreshToken,
4027
4055
  capturedAt: Date.now()
4028
4056
  });
4029
4057
  }
@@ -4057,6 +4085,13 @@ async function loginToQwen(accountId, email, password) {
4057
4085
  markAccountHeadersReady(accountId);
4058
4086
  const { saveAuthSession: saveAuthSession2 } = await Promise.resolve().then(() => (init_database(), database_exports));
4059
4087
  const exp = parseJwtExpiry(tokenCookie.value);
4088
+ let rTok = uiResult.refreshToken;
4089
+ if (!rTok) {
4090
+ try {
4091
+ rTok = await page.evaluate(() => localStorage.getItem("refresh_token") || "") || void 0;
4092
+ } catch {
4093
+ }
4094
+ }
4060
4095
  saveAuthSession2(accountId, {
4061
4096
  cookie: cookieStr,
4062
4097
  userAgent: cache.headers["user-agent"] || "",
@@ -4068,6 +4103,7 @@ async function loginToQwen(accountId, email, password) {
4068
4103
  secChUaPlatform: cache.headers["sec-ch-ua-platform"] || void 0,
4069
4104
  version: cache.headers["version"] || void 0,
4070
4105
  tokenExpiresAt: exp || void 0,
4106
+ refreshToken: rTok,
4071
4107
  capturedAt: Date.now()
4072
4108
  });
4073
4109
  }
@@ -4120,37 +4156,52 @@ async function loginToQwen(accountId, email, password) {
4120
4156
  async function loginViaApi(page, email, password) {
4121
4157
  try {
4122
4158
  const hashedPassword = crypto3.createHash("sha256").update(password).digest("hex");
4123
- const signinUrl = qwenUrl("/api/v2/auths/signin");
4124
- let signinSuccess = false;
4125
- let data = null;
4126
- if (page.request && typeof page.request.post === "function") {
4127
- try {
4128
- const response = await page.request.post(signinUrl, {
4129
- data: {
4130
- email,
4131
- password: hashedPassword,
4132
- login_type: "email"
4133
- },
4134
- headers: {
4135
- "content-type": "application/json",
4136
- accept: "application/json, text/plain, */*",
4137
- referer: qwenUrl("/auth"),
4138
- origin: qwenOrigin(),
4139
- source: "web"
4140
- },
4141
- timeout: 1e4
4142
- });
4143
- data = await response.json().catch(() => null);
4144
- signinSuccess = Boolean(data && (data.success === true || data.token));
4145
- } catch {
4146
- }
4159
+ if (!page.url().includes("/auth")) {
4160
+ await page.goto(qwenUrl("/auth"), {
4161
+ waitUntil: "domcontentloaded",
4162
+ timeout: config.timeouts.navigation
4163
+ }).catch(() => {
4164
+ });
4165
+ await sleep2(1e3);
4147
4166
  }
4148
- if (!signinSuccess && !data) {
4149
- const requestId = crypto3.randomUUID();
4150
- const evalRes = await page.evaluate(
4151
- async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2 }) => {
4167
+ const signinUrl = "https://auth.qwen.ai/api/v2/auths/signin";
4168
+ const requestId = crypto3.randomUUID();
4169
+ const evalRes = await page.evaluate(
4170
+ async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2 }) => {
4171
+ try {
4172
+ const response = await fetch(signinUrl2, {
4173
+ method: "POST",
4174
+ signal: AbortSignal.timeout(15e3),
4175
+ credentials: "include",
4176
+ headers: {
4177
+ accept: "application/json, text/plain, */*",
4178
+ "content-type": "application/json",
4179
+ source: "web",
4180
+ version: "0.3.11",
4181
+ "x-request-origin": "https://chat.qwen.ai",
4182
+ timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
4183
+ "x-request-id": requestId2
4184
+ },
4185
+ body: JSON.stringify({ email: email2, password: password2 })
4186
+ });
4187
+ const json = await response.json().catch(() => null);
4188
+ return { ok: response.ok, status: response.status, data: json };
4189
+ } catch (e) {
4190
+ return { ok: false, error: e.message };
4191
+ }
4192
+ },
4193
+ { email, password: hashedPassword, signinUrl, requestId }
4194
+ ).catch(() => null);
4195
+ let data = evalRes?.data;
4196
+ let signinSuccess = Boolean(
4197
+ data && (data.success === true || data.token || data.data?.access_token || data.data?.token)
4198
+ );
4199
+ if (!signinSuccess && !data?.data?.code) {
4200
+ const fallbackUrl = qwenUrl("/api/v2/auths/signin");
4201
+ const fallbackRes = await page.evaluate(
4202
+ async ({ email: email2, password: password2, fallbackUrl: fallbackUrl2, requestId: requestId2 }) => {
4152
4203
  try {
4153
- const response = await fetch(signinUrl2, {
4204
+ const response = await fetch(fallbackUrl2, {
4154
4205
  method: "POST",
4155
4206
  signal: AbortSignal.timeout(1e4),
4156
4207
  credentials: "include",
@@ -4158,10 +4209,11 @@ async function loginViaApi(page, email, password) {
4158
4209
  accept: "application/json, text/plain, */*",
4159
4210
  "content-type": "application/json",
4160
4211
  source: "web",
4212
+ version: "0.3.11",
4161
4213
  timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
4162
4214
  "x-request-id": requestId2
4163
4215
  },
4164
- body: JSON.stringify({ email: email2, password: password2, login_type: "email" })
4216
+ body: JSON.stringify({ email: email2, password: password2 })
4165
4217
  });
4166
4218
  const json = await response.json().catch(() => null);
4167
4219
  return { ok: response.ok, status: response.status, data: json };
@@ -4169,11 +4221,13 @@ async function loginViaApi(page, email, password) {
4169
4221
  return { ok: false, error: e.message };
4170
4222
  }
4171
4223
  },
4172
- { email, password: hashedPassword, signinUrl, requestId }
4224
+ { email, password: hashedPassword, fallbackUrl, requestId }
4173
4225
  ).catch(() => null);
4174
- if (evalRes?.data) {
4175
- data = evalRes.data;
4176
- signinSuccess = Boolean(data && (data.success === true || data.token));
4226
+ if (fallbackRes?.data) {
4227
+ data = fallbackRes.data;
4228
+ signinSuccess = Boolean(
4229
+ data && (data.success === true || data.token || data.data?.access_token || data.data?.token)
4230
+ );
4177
4231
  }
4178
4232
  }
4179
4233
  if (data?.success === false) {
@@ -4187,7 +4241,8 @@ async function loginViaApi(page, email, password) {
4187
4241
  };
4188
4242
  }
4189
4243
  if (signinSuccess) {
4190
- const token = data?.data?.token || data?.token;
4244
+ const token = data?.data?.access_token || data?.data?.token || data?.access_token || data?.token;
4245
+ const refreshToken = data?.data?.refresh_token || data?.refresh_token;
4191
4246
  if (token) {
4192
4247
  try {
4193
4248
  await page.context().addCookies([
@@ -4211,18 +4266,23 @@ async function loginViaApi(page, email, password) {
4211
4266
  }).catch(() => {
4212
4267
  });
4213
4268
  if (token) {
4214
- await page.evaluate((tok) => {
4215
- try {
4216
- localStorage.removeItem("qwen_token_logged_out_marker");
4217
- localStorage.setItem("token", tok);
4218
- document.cookie = `token=${encodeURIComponent(tok)}; path=/; domain=.qwen.ai; max-age=31536000`;
4219
- } catch {
4220
- }
4221
- }, token).catch(() => {
4269
+ await page.evaluate(
4270
+ ({ tok, rTok }) => {
4271
+ try {
4272
+ localStorage.removeItem("qwen_token_logged_out_marker");
4273
+ localStorage.setItem("token", tok);
4274
+ localStorage.setItem("access_token", tok);
4275
+ if (rTok) localStorage.setItem("refresh_token", rTok);
4276
+ document.cookie = `token=${encodeURIComponent(tok)}; path=/; domain=.qwen.ai; max-age=31536000`;
4277
+ } catch {
4278
+ }
4279
+ },
4280
+ { tok: token, rTok: refreshToken }
4281
+ ).catch(() => {
4222
4282
  });
4223
4283
  await page.reload({ waitUntil: "domcontentloaded" }).catch(() => {
4224
4284
  });
4225
- return { success: true };
4285
+ return { success: true, token, refreshToken };
4226
4286
  }
4227
4287
  }
4228
4288
  return {
@@ -4246,11 +4306,23 @@ async function loginViaApi(page, email, password) {
4246
4306
  }
4247
4307
  async function loginViaUi(page, email, password) {
4248
4308
  try {
4249
- await page.goto(qwenUrl("/auth"), {
4250
- waitUntil: "domcontentloaded",
4251
- timeout: config.timeouts.navigation
4252
- });
4253
- await sleep2(1500);
4309
+ if (!page.url().includes("/auth") && await isPageLoggedIn(page, 2e3).catch(() => false)) {
4310
+ return { success: true };
4311
+ }
4312
+ try {
4313
+ await page.goto(qwenUrl("/auth"), {
4314
+ waitUntil: "domcontentloaded",
4315
+ timeout: Math.min(config.timeouts.navigation, 15e3)
4316
+ });
4317
+ await sleep2(1e3);
4318
+ } catch (gotoErr) {
4319
+ if (!page.url().includes("/auth") && await isPageLoggedIn(page, 3e3).catch(() => false)) {
4320
+ return { success: true };
4321
+ }
4322
+ }
4323
+ if (!page.url().includes("/auth") && await isPageLoggedIn(page, 3e3).catch(() => false)) {
4324
+ return { success: true };
4325
+ }
4254
4326
  const emailSelector = [
4255
4327
  'input[type="email"]',
4256
4328
  'input[name="email"]',
@@ -4260,7 +4332,7 @@ async function loginViaUi(page, email, password) {
4260
4332
  ].join(", ");
4261
4333
  try {
4262
4334
  await page.waitForSelector(emailSelector, {
4263
- timeout: Math.min(config.timeouts.page, 8e3)
4335
+ timeout: Math.min(config.timeouts.page, 5e3)
4264
4336
  });
4265
4337
  } catch {
4266
4338
  if (!page.url().includes("/auth") && await isPageLoggedIn(page, 3e3)) {
@@ -4273,30 +4345,55 @@ async function loginViaUi(page, email, password) {
4273
4345
  return { success: false, reason: "Campo de e-mail n\xE3o encontrado (poss\xEDvel captcha)" };
4274
4346
  }
4275
4347
  await clearVisibleChallenge(page);
4276
- const findPasswordModeButton = () => {
4277
- return page.locator('button, [role="button"], a, div').filter({
4278
- hasText: /(?:log\s*in\s*with\s*(?:a\s*)?password|fazer\s*login\s*com\s*senha|entrar\s*com\s*(?:uma\s*)?senha|iniciar\s*sesi[oó]n\s*con\s*contrase[nñ]a|密码登录)/i
4279
- }).first();
4348
+ const isPasswordInputPresent = async () => {
4349
+ return await page.locator('input[type="password"], input[name="password"]').count().catch(() => 0) > 0;
4280
4350
  };
4281
- try {
4282
- const pwdModeBtn = findPasswordModeButton();
4283
- if (await pwdModeBtn.isVisible({ timeout: 2e3 }).catch(() => false)) {
4284
- await pwdModeBtn.click({ timeout: 2e3 }).catch(() => {
4351
+ const revealPasswordMode = async () => {
4352
+ if (page.url().includes("/forget")) {
4353
+ await page.goto(qwenUrl("/auth"), { waitUntil: "domcontentloaded", timeout: 1e4 }).catch(() => {
4285
4354
  });
4286
- await sleep2(500);
4355
+ await sleep2(1e3);
4287
4356
  }
4288
- } catch {
4289
- }
4290
- await page.fill(emailSelector, email);
4291
- await sleep2(300);
4292
- try {
4293
- const pwdModeBtn = findPasswordModeButton();
4294
- if (await pwdModeBtn.isVisible({ timeout: 1500 }).catch(() => false)) {
4295
- await pwdModeBtn.click({ timeout: 2e3 }).catch(() => {
4357
+ if (await isPasswordInputPresent()) return;
4358
+ const btn = page.locator("button").filter({
4359
+ hasText: /(?:log\s*in\s*with\s*(?:a\s*)?password|fazer\s*login\s*com\s*senha|entrar\s*com\s*(?:uma\s*)?senha|iniciar\s*sesi[oó]n\s*con\s*contrase[nñ]a|密码登录)/i
4360
+ }).filter({
4361
+ hasNotText: /(?:esqueci|forgot|olvid|reset|recover)/i
4362
+ }).first();
4363
+ if (await btn.isVisible({ timeout: 1500 }).catch(() => false)) {
4364
+ await btn.click({ timeout: 2e3 }).catch(() => {
4296
4365
  });
4297
4366
  await sleep2(500);
4298
4367
  }
4368
+ if (await isPasswordInputPresent()) return;
4369
+ await page.evaluate(() => {
4370
+ try {
4371
+ const buttons = Array.from(document.querySelectorAll("button, [role='button'], a"));
4372
+ for (const b of buttons) {
4373
+ const txt = (b.innerText || "").trim().toLowerCase();
4374
+ if (/(?:log\s*in\s*with\s*(?:a\s*)?password|fazer\s*login\s*com\s*senha|entrar\s*com\s*(?:uma\s*)?senha|iniciar\s*sesi[oó]n\s*con\s*contrase[nñ]a|密码登录)/i.test(txt) && !txt.includes("esqueci") && !txt.includes("forgot") && !txt.includes("olvid") && !txt.includes("reset")) {
4375
+ b.click();
4376
+ return true;
4377
+ }
4378
+ }
4379
+ } catch {
4380
+ }
4381
+ return false;
4382
+ }).catch(() => false);
4383
+ await sleep2(500);
4384
+ };
4385
+ await revealPasswordMode();
4386
+ try {
4387
+ await page.fill(emailSelector, email, { timeout: 8e3 });
4388
+ await sleep2(300);
4299
4389
  } catch {
4390
+ if (!page.url().includes("/auth") && await isPageLoggedIn(page, 3e3)) {
4391
+ return { success: true };
4392
+ }
4393
+ throw new Error(`Campo de e-mail n\xE3o encontrado na tela de autentica\xE7\xE3o`);
4394
+ }
4395
+ if (!await isPasswordInputPresent()) {
4396
+ await revealPasswordMode();
4300
4397
  }
4301
4398
  const passwordSelector = 'input[type="password"], input[name="password"]';
4302
4399
  try {
@@ -4315,17 +4412,50 @@ async function loginViaUi(page, email, password) {
4315
4412
  reason: "Campo de senha n\xE3o encontrado na tela de autentica\xE7\xE3o"
4316
4413
  };
4317
4414
  }
4318
- await page.fill(passwordSelector, password);
4319
- await sleep2(500);
4415
+ try {
4416
+ await page.fill(passwordSelector, password, { timeout: 8e3 });
4417
+ await sleep2(500);
4418
+ } catch {
4419
+ if (!page.url().includes("/auth") && await isPageLoggedIn(page, 3e3)) {
4420
+ return { success: true };
4421
+ }
4422
+ throw new Error(`Campo de senha n\xE3o encontrado na tela de autentica\xE7\xE3o`);
4423
+ }
4424
+ await page.evaluate(() => {
4425
+ try {
4426
+ const emailEl = document.querySelector('input[name="email"], input[type="email"], input[type="text"]');
4427
+ const passEl = document.querySelector('input[type="password"], input[name="password"]');
4428
+ if (emailEl) {
4429
+ emailEl.dispatchEvent(new Event("input", { bubbles: true }));
4430
+ emailEl.dispatchEvent(new Event("change", { bubbles: true }));
4431
+ emailEl.dispatchEvent(new Event("blur", { bubbles: true }));
4432
+ }
4433
+ if (passEl) {
4434
+ passEl.dispatchEvent(new Event("input", { bubbles: true }));
4435
+ passEl.dispatchEvent(new Event("change", { bubbles: true }));
4436
+ passEl.dispatchEvent(new Event("blur", { bubbles: true }));
4437
+ }
4438
+ } catch {
4439
+ }
4440
+ }).catch(() => {
4441
+ });
4320
4442
  const submitSelector = '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\xF3n"), button:has-text("\u767B\u5F55")';
4321
4443
  const submitButton = page.locator(submitSelector).first();
4322
4444
  try {
4323
4445
  await page.waitForSelector('button[type="submit"]:not([disabled])', {
4324
- timeout: 5e3
4446
+ timeout: 4e3
4325
4447
  });
4326
4448
  await submitButton.click();
4327
4449
  } catch {
4328
- await page.keyboard.press("Enter");
4450
+ const isDisabled = await submitButton.isDisabled().catch(() => true);
4451
+ if (!isDisabled) {
4452
+ await submitButton.click({ force: true }).catch(() => {
4453
+ });
4454
+ } else {
4455
+ if (!page.url().includes("/auth") && await isPageLoggedIn(page, 2e3)) {
4456
+ return { success: true };
4457
+ }
4458
+ }
4329
4459
  }
4330
4460
  const postSubmitDeadline = Date.now() + 15e3;
4331
4461
  while (Date.now() < postSubmitDeadline) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qwenproxy-cli",
3
- "version": "1.4.3",
3
+ "version": "1.4.8",
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": {
@@ -193,6 +193,7 @@ function runMigrations(db: Database.Database): void {
193
193
  "sec_ch_ua_mobile TEXT",
194
194
  "sec_ch_ua_platform TEXT",
195
195
  "version TEXT",
196
+ "refresh_token TEXT",
196
197
  "captured_at INTEGER DEFAULT 0",
197
198
  ];
198
199
  for (const col of authSessionCols) {
@@ -309,6 +310,7 @@ export interface PersistedAuthSession {
309
310
  version?: string;
310
311
  userId?: string;
311
312
  tokenExpiresAt?: number;
313
+ refreshToken?: string;
312
314
  capturedAt: number;
313
315
  }
314
316
 
@@ -326,6 +328,7 @@ export function saveAuthSession(
326
328
  version?: string;
327
329
  userId?: string;
328
330
  tokenExpiresAt?: number;
331
+ refreshToken?: string;
329
332
  capturedAt?: number;
330
333
  },
331
334
  ): void {
@@ -334,11 +337,11 @@ export function saveAuthSession(
334
337
  INSERT OR REPLACE INTO qwen_auth_sessions (
335
338
  account_id, cookie, user_agent, bx_v, bx_ua, bx_umidtoken,
336
339
  sec_ch_ua, sec_ch_ua_mobile, sec_ch_ua_platform, version,
337
- user_id, token_expires_at, captured_at, updated_at
340
+ user_id, token_expires_at, refresh_token, captured_at, updated_at
338
341
  ) VALUES (
339
342
  ?, ?, ?, ?, ?, ?,
340
343
  ?, ?, ?, ?,
341
- ?, ?, ?, datetime('now')
344
+ ?, ?, ?, ?, datetime('now')
342
345
  )
343
346
  `);
344
347
  stmt.run(
@@ -354,6 +357,7 @@ export function saveAuthSession(
354
357
  session.version || null,
355
358
  session.userId || null,
356
359
  session.tokenExpiresAt || null,
360
+ session.refreshToken || null,
357
361
  session.capturedAt ?? Date.now(),
358
362
  );
359
363
  }
@@ -367,7 +371,7 @@ export function getValidAuthSession(
367
371
  .prepare(
368
372
  `SELECT account_id, cookie, user_agent, bx_v, bx_ua, bx_umidtoken,
369
373
  sec_ch_ua, sec_ch_ua_mobile, sec_ch_ua_platform, version,
370
- user_id, token_expires_at, captured_at
374
+ user_id, token_expires_at, refresh_token, captured_at
371
375
  FROM qwen_auth_sessions WHERE account_id = ?`,
372
376
  )
373
377
  .get(accountId) as any;
@@ -379,9 +383,9 @@ export function getValidAuthSession(
379
383
  return null;
380
384
  }
381
385
 
382
- if (row.token_expires_at) {
386
+ if (!row.refresh_token && row.token_expires_at) {
383
387
  const tokenExpMs = Number(row.token_expires_at) * 1000;
384
- // Safety margin of 5 minutes before token expires
388
+ // Safety margin of 5 minutes before token expires (only when no refresh_token exists)
385
389
  if (tokenExpMs <= Date.now() + 5 * 60 * 1000) {
386
390
  return null;
387
391
  }
@@ -405,6 +409,7 @@ export function getValidAuthSession(
405
409
  version: row.version || undefined,
406
410
  userId: row.user_id || undefined,
407
411
  tokenExpiresAt: row.token_expires_at ? Number(row.token_expires_at) : undefined,
412
+ refreshToken: row.refresh_token || undefined,
408
413
  capturedAt,
409
414
  };
410
415
  }
@@ -17,6 +17,6 @@ export function getAppVersion(): string {
17
17
  }
18
18
  }
19
19
  } catch {}
20
- cachedVersion = "v1.4.3";
20
+ cachedVersion = "v1.4.8";
21
21
  return cachedVersion;
22
22
  }
@@ -479,8 +479,8 @@ export function classifyRetryAction(
479
479
  // Agent instructions ride ONLY the account-level personalization. An
480
480
  // unconfirmed sync means this account cannot serve the request as-is —
481
481
  // rotate to another account (each attempt re-syncs on its own account).
482
- // Park the failing account with PersonalizationFailed cooldown so it does
483
- // not enter an infinite ping-pong loop when multiple accounts fail.
482
+ // Do NOT park the account with a 300s cooldown: rotation across accounts
483
+ // is already guarded by triedAccounts, preventing pool collapse.
484
484
  if (err instanceof PersonalizationSyncError) {
485
485
  return makeRetryAction("personalization_sync_failed", {
486
486
  switchAccount: true,
@@ -502,7 +502,8 @@ export function classifyRetryAction(
502
502
  message.includes("não tem permissão para acessar") ||
503
503
  message.includes("401 unauthorized") ||
504
504
  message.includes('"code":"unauthorized"') ||
505
- message.includes('"code": "unauthorized"')
505
+ message.includes('"code": "unauthorized"') ||
506
+ message.includes("token has expired")
506
507
  ) {
507
508
  return makeRetryAction("account_initialization_failed", {
508
509
  switchAccount: true,