qwenproxy-cli 1.0.30 → 1.0.31

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.0.30",
3
+ "version": "1.0.31",
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": {
@@ -35,7 +35,11 @@ import {
35
35
  type AccountLease,
36
36
  } from "../../core/account-concurrency.ts";
37
37
  import { isAuthMockEnabled } from "../../services/auth-playwright.ts";
38
- import { isPlaywrightInitialized, refreshHeaders } from "../../services/playwright.ts";
38
+ import {
39
+ isPlaywrightInitialized,
40
+ isAccountRecentlyActive,
41
+ refreshHeaders,
42
+ } from "../../services/playwright.ts";
39
43
  import { enqueueOrphanChatDeletion } from "../../services/chat-cleanup.ts";
40
44
  import {
41
45
  clearAllSessionsForAccount,
@@ -78,14 +82,19 @@ const MAX_ANTI_BOT_ROTATIONS = 1;
78
82
  * a stuck account page (closed context / WAF) can otherwise hold each browser
79
83
  * op for 60s and keep the personalization mutex blocked for minutes.
80
84
  */
81
- export const PERSONALIZATION_SYNC_DEADLINE_MS = 30_000;
85
+ export const PERSONALIZATION_SYNC_DEADLINE_MS = 45_000;
82
86
  export const COLD_ACCOUNT_PERSONALIZATION_SYNC_DEADLINE_MS = 60_000;
83
87
 
84
88
  export function computePersonalizationDeadlineMs(
85
89
  accountId: string | undefined,
86
90
  navigationTimeoutMs = config.timeouts.navigation,
87
91
  ): number {
88
- if (accountId && accountId !== "global" && isPlaywrightInitialized(accountId)) {
92
+ if (
93
+ accountId &&
94
+ accountId !== "global" &&
95
+ isPlaywrightInitialized(accountId) &&
96
+ isAccountRecentlyActive(accountId, 5 * 60 * 1000)
97
+ ) {
89
98
  return PERSONALIZATION_SYNC_DEADLINE_MS;
90
99
  }
91
100
  return Math.max(COLD_ACCOUNT_PERSONALIZATION_SYNC_DEADLINE_MS, navigationTimeoutMs);
@@ -3342,6 +3342,14 @@ export function isPlaywrightInitialized(accountId: string): boolean {
3342
3342
  return accountPages.has(accountId);
3343
3343
  }
3344
3344
 
3345
+ export function isAccountRecentlyActive(
3346
+ accountId: string,
3347
+ maxIdleMs = 300_000,
3348
+ ): boolean {
3349
+ const last = lastAccountActivity.get(accountId) ?? 0;
3350
+ return last > 0 && Date.now() - last < maxIdleMs;
3351
+ }
3352
+
3345
3353
  /**
3346
3354
  * Register an account as if it had been initialized, with a chosen last
3347
3355
  * activity timestamp. Lets the idle/keep-alive selection be exercised without
@@ -2993,19 +2993,6 @@ async function createQwenStreamInternal(
2993
2993
  const preview = await readResponsePreview(response);
2994
2994
  const htmlBody = isHtmlResponseBody(preview);
2995
2995
  const antiBotChallenge = isWafChallengeResponse(preview);
2996
- logger.warn(
2997
- htmlBody || isHtmlResponseContentType(responseContentType)
2998
- ? "[Qwen] Completion returned HTML instead of SSE"
2999
- : "[Qwen] Completion returned a non-SSE body",
3000
- {
3001
- accountId: accountId ?? "global",
3002
- chatId: chatSessionId ?? "new",
3003
- status: response.status,
3004
- contentType: responseContentType,
3005
- antiBotChallenge,
3006
- previewBytes: Buffer.byteLength(preview, "utf8"),
3007
- },
3008
- );
3009
2996
 
3010
2997
  if (
3011
2998
  antiBotChallenge &&
@@ -3026,6 +3013,19 @@ async function createQwenStreamInternal(
3026
3013
  continue;
3027
3014
  }
3028
3015
 
3016
+ logger.warn(
3017
+ htmlBody || isHtmlResponseContentType(responseContentType)
3018
+ ? "[Qwen] Completion returned HTML instead of SSE"
3019
+ : "[Qwen] Completion returned a non-SSE body",
3020
+ {
3021
+ accountId: accountId ?? "global",
3022
+ chatId: chatSessionId ?? "new",
3023
+ status: response.status,
3024
+ contentType: responseContentType,
3025
+ antiBotChallenge,
3026
+ previewBytes: Buffer.byteLength(preview, "utf8"),
3027
+ },
3028
+ );
3029
3029
  throw withCreatedChatMetadata(
3030
3030
  new QwenUpstreamError(
3031
3031
  antiBotChallenge
@@ -3070,16 +3070,6 @@ async function createQwenStreamInternal(
3070
3070
  const htmlResponse = isHtmlResponseBody(errText);
3071
3071
  const antiBotChallenge = isWafChallengeResponse(errText);
3072
3072
  if (antiBotChallenge || htmlResponse) {
3073
- logger.warn(
3074
- "[Qwen] Completion returned an HTML or anti-bot challenge body instead of SSE.",
3075
- {
3076
- accountId: accountId ?? "global",
3077
- chatId: chatSessionId ?? "new",
3078
- antiBotChallenge,
3079
- previewBytes: Buffer.byteLength(errText, "utf8"),
3080
- },
3081
- );
3082
-
3083
3073
  if (
3084
3074
  antiBotChallenge &&
3085
3075
  (await retryAfterCaptchaRecovery(
@@ -3099,6 +3089,15 @@ async function createQwenStreamInternal(
3099
3089
  continue;
3100
3090
  }
3101
3091
 
3092
+ logger.warn(
3093
+ "[Qwen] Completion returned an HTML or anti-bot challenge body instead of SSE.",
3094
+ {
3095
+ accountId: accountId ?? "global",
3096
+ chatId: chatSessionId ?? "new",
3097
+ antiBotChallenge,
3098
+ previewBytes: Buffer.byteLength(errText, "utf8"),
3099
+ },
3100
+ );
3102
3101
  throw withCreatedChatMetadata(
3103
3102
  new QwenUpstreamError(
3104
3103
  antiBotChallenge
package/src/sync/cline.ts CHANGED
@@ -84,7 +84,7 @@ export function syncCline(options: SyncOptions): ClientSyncResult {
84
84
  backupPath,
85
85
  success: true,
86
86
  action: backupPath ? "updated" : "created",
87
- message: `Configured Cline & Zoo Code in state.vscdb with model ${model} and reasoning effort ${reasoningEffort}`,
87
+ message: `Configured Cline in state.vscdb with model ${model} and reasoning effort ${reasoningEffort}`,
88
88
  };
89
89
  } catch (err: any) {
90
90
  return {
package/src/sync/index.ts CHANGED
@@ -544,7 +544,7 @@ export function syncAllClients(options: SyncAllOptions = {}): SyncAllResult {
544
544
  }
545
545
  }
546
546
 
547
- // 8. Cline & Zoo Code
547
+ // 8. Cline
548
548
  if (shouldSync("cline")) {
549
549
  const clineExisted = fs.existsSync(paths.cline);
550
550
  const clineRes = syncCline({
@@ -67,7 +67,7 @@ Exemplos:
67
67
  npm run sync claude # Sincroniza apenas o Claude Code
68
68
  npm run sync openclaw # Sincroniza apenas o OpenClaw
69
69
  npm run sync kilo # Sincroniza apenas o Kilo Code
70
- npm run sync cline # Sincroniza Cline & Zoo Code
70
+ npm run sync cline # Sincroniza apenas o Cline
71
71
  npm run sync omp # Sincroniza apenas o OMP (Oh My Pi)
72
72
  npm run sync codex # Sincroniza apenas o Codex CLI
73
73
  npm run sync zed # Sincroniza apenas o Zed Editor
@@ -108,7 +108,7 @@ async function main() {
108
108
  { id: "claude-code", name: "3. Claude Code", path: defaultPaths.claudeCode },
109
109
  { id: "openclaw", name: "4. OpenClaw", path: defaultPaths.openClaw },
110
110
  { id: "kilo", name: "5. Kilo Code", path: defaultPaths.kilo },
111
- { id: "cline", name: "6. Cline & Zoo", path: defaultPaths.cline },
111
+ { id: "cline", name: "6. Cline", path: defaultPaths.cline },
112
112
  { id: "omp", name: "7. OMP (Oh My Pi)", path: defaultPaths.omp },
113
113
  { id: "codex", name: "8. Codex CLI", path: defaultPaths.codex },
114
114
  { id: "zed", name: "9. Zed Editor", path: defaultPaths.zed },
@@ -11,10 +11,11 @@ import {
11
11
  getDefaultPaths,
12
12
  inspectClientSyncStatus,
13
13
  } from "../../sync/index.ts";
14
+ import type { SyncClientName } from "../../sync/types.ts";
14
15
  import { fetchLiveModels } from "../proxy-client.ts";
15
16
 
16
17
  interface ClientOption {
17
- id: "claude-code" | "codex" | "opencode" | "omp";
18
+ id: SyncClientName;
18
19
  name: string;
19
20
  path: string;
20
21
  selected: boolean;
@@ -27,7 +28,7 @@ export class SyncView implements TuiView {
27
28
  public readonly title = "Sync";
28
29
  public readonly tabNumber = 3;
29
30
  private clients: ClientOption[] = [];
30
- private selectedRowIndex = 0; // 0..3 for clients, 4 for model, 5 for scope, 6 for sync, 7 for restore
31
+ private selectedRowIndex = 0; // 0..9 for clients, 10 for model, 11 for scope, 12 for sync, 13 for restore
31
32
  private hoveredActionRow: number | null = null;
32
33
  private availableModels = [
33
34
  "qwen3.8-max",
@@ -60,11 +61,17 @@ export class SyncView implements TuiView {
60
61
 
61
62
  private detectClients(): void {
62
63
  const paths = getDefaultPaths();
63
- const defs: Array<{ id: "claude-code" | "codex" | "opencode" | "omp"; name: string; path: string }> = [
64
- { id: "claude-code", name: "Claude Code", path: paths.claudeCode },
65
- { id: "codex", name: "OpenAI Codex", path: paths.codex },
64
+ const defs: Array<{ id: SyncClientName; name: string; path: string }> = [
65
+ { id: "hermes", name: "Hermes Agent", path: paths.hermes },
66
66
  { id: "opencode", name: "OpenCode", path: paths.openCode },
67
+ { id: "claude-code", name: "Claude Code", path: paths.claudeCode },
68
+ { id: "openclaw", name: "OpenClaw", path: paths.openClaw },
69
+ { id: "kilo", name: "Kilo Code", path: paths.kilo },
70
+ { id: "cline", name: "Cline", path: paths.cline },
67
71
  { id: "omp", name: "OMP (Oh My Pi)", path: paths.omp },
72
+ { id: "codex", name: "Codex CLI", path: paths.codex },
73
+ { id: "zed", name: "Zed Editor", path: paths.zed },
74
+ { id: "aider", name: "Aider", path: paths.aider },
68
75
  ];
69
76
 
70
77
  this.clients = defs.map((d) => {
@@ -96,23 +103,23 @@ export class SyncView implements TuiView {
96
103
  const { row, col } = key.mouse;
97
104
  const leftW = this.lastLeftW || 46;
98
105
  if (col >= 2 && col <= leftW - 1) {
99
- if (row >= 8 && row <= 11) {
106
+ if (row >= 8 && row <= 17) {
100
107
  const targetRow = row - 8;
101
108
  if (this.selectedRowIndex !== targetRow) {
102
109
  this.selectedRowIndex = targetRow;
103
110
  return true;
104
111
  }
105
- } else if (row === 14) {
106
- if (this.selectedRowIndex !== 4) {
107
- this.selectedRowIndex = 4;
112
+ } else if (row === 20 || row === 14) {
113
+ if (this.selectedRowIndex !== 10) {
114
+ this.selectedRowIndex = 10;
108
115
  return true;
109
116
  }
110
- } else if (row === 15) {
111
- if (this.selectedRowIndex !== 5) {
112
- this.selectedRowIndex = 5;
117
+ } else if (row === 21 || row === 15) {
118
+ if (this.selectedRowIndex !== 11) {
119
+ this.selectedRowIndex = 11;
113
120
  return true;
114
121
  }
115
- } else if (row === 18 || row === 19) {
122
+ } else if (row === 24 || row === 18 || row === 25 || row === 19) {
116
123
  if (this.hoveredActionRow !== row) {
117
124
  this.hoveredActionRow = row;
118
125
  return true;
@@ -132,8 +139,8 @@ export class SyncView implements TuiView {
132
139
  const { row, col } = key.mouse;
133
140
  const leftW = this.lastLeftW || 46;
134
141
  if (col >= 2 && col <= leftW - 1) {
135
- // Rows 8, 9, 10, 11: Toggle client
136
- if (row >= 8 && row <= 11) {
142
+ // Rows 8..17: Toggle client
143
+ if (row >= 8 && row <= 17) {
137
144
  const client = this.clients[row - 8];
138
145
  if (client) {
139
146
  client.selected = !client.selected;
@@ -141,27 +148,27 @@ export class SyncView implements TuiView {
141
148
  return true;
142
149
  }
143
150
  }
144
- // Row 14: Model selector
145
- if (row === 14) {
151
+ // Model selector
152
+ if (row === 20 || row === 14) {
146
153
  this.modelIndex = (this.modelIndex + 1) % this.availableModels.length;
147
- this.selectedRowIndex = 4;
154
+ this.selectedRowIndex = 10;
148
155
  return true;
149
156
  }
150
- // Row 15: Scope selector
151
- if (row === 15) {
157
+ // Scope selector
158
+ if (row === 21 || row === 15) {
152
159
  this.syncAllModels = !this.syncAllModels;
153
- this.selectedRowIndex = 5;
160
+ this.selectedRowIndex = 11;
154
161
  return true;
155
162
  }
156
- // Row 18: Sincronizar button
157
- if (row === 18) {
158
- this.selectedRowIndex = 6;
163
+ // Sincronizar button
164
+ if (row === 24 || row === 18) {
165
+ this.selectedRowIndex = 12;
159
166
  this.executeSync();
160
167
  return true;
161
168
  }
162
- // Row 19: Restaurar button
163
- if (row === 19) {
164
- this.selectedRowIndex = 7;
169
+ // Restaurar button
170
+ if (row === 25 || row === 19) {
171
+ this.selectedRowIndex = 13;
165
172
  this.executeRollback();
166
173
  return true;
167
174
  }
@@ -174,28 +181,28 @@ export class SyncView implements TuiView {
174
181
  return true;
175
182
  }
176
183
  if (key.name === "down" || key.name === "wheeldown" || (key.name === "j" && !key.ctrl)) {
177
- this.selectedRowIndex = Math.min(7, this.selectedRowIndex + 1);
184
+ this.selectedRowIndex = Math.min(13, this.selectedRowIndex + 1);
178
185
  return true;
179
186
  }
180
187
 
181
188
  // Toggle client selection with Space
182
189
  if (key.name === "space") {
183
- if (this.selectedRowIndex < 4) {
190
+ if (this.selectedRowIndex < this.clients.length) {
184
191
  const client = this.clients[this.selectedRowIndex];
185
192
  if (client) {
186
193
  client.selected = !client.selected;
187
194
  }
188
- } else if (this.selectedRowIndex === 4) {
195
+ } else if (this.selectedRowIndex === 10) {
189
196
  // Cycle model with space
190
197
  this.modelIndex = (this.modelIndex + 1) % this.availableModels.length;
191
- } else if (this.selectedRowIndex === 5) {
198
+ } else if (this.selectedRowIndex === 11) {
192
199
  this.syncAllModels = !this.syncAllModels;
193
200
  }
194
201
  return true;
195
202
  }
196
203
 
197
- // Cycle model left/right on row 4
198
- if (this.selectedRowIndex === 4 && (key.name === "left" || key.name === "right")) {
204
+ // Cycle model left/right on row 10
205
+ if (this.selectedRowIndex === 10 && (key.name === "left" || key.name === "right")) {
199
206
  if (key.name === "left") {
200
207
  this.modelIndex =
201
208
  (this.modelIndex - 1 + this.availableModels.length) %
@@ -205,7 +212,6 @@ export class SyncView implements TuiView {
205
212
  }
206
213
  return true;
207
214
  }
208
-
209
215
  // Toggle all with 'a'
210
216
  if (key.name === "a" && !key.ctrl) {
211
217
  const allSelected = this.clients.every((c) => c.selected);
@@ -226,7 +232,7 @@ export class SyncView implements TuiView {
226
232
 
227
233
  // Confirm action on Enter
228
234
  if (key.name === "return") {
229
- if (this.selectedRowIndex === 7) {
235
+ if (this.selectedRowIndex === 13) {
230
236
  this.executeRollback();
231
237
  } else {
232
238
  this.executeSync();
@@ -289,8 +295,8 @@ export class SyncView implements TuiView {
289
295
  }
290
296
 
291
297
  public render(width: number, height: number): string[] {
292
- const contentH = Math.max(12, height);
293
- const leftW = Math.max(46, Math.floor(width * 0.52));
298
+ const contentH = Math.max(22, height);
299
+ const leftW = Math.max(48, Math.floor(width * 0.52));
294
300
  this.lastLeftW = leftW;
295
301
  const rightW = Math.max(30, width - leftW - 1);
296
302
 
@@ -323,8 +329,8 @@ export class SyncView implements TuiView {
323
329
  leftContent.push("");
324
330
  leftContent.push(` ${theme.bold("Modelo:")}`);
325
331
 
326
- // Row index 4: Model Selector
327
- const isModelFocused = this.selectedRowIndex === 4;
332
+ // Row index 10: Model Selector
333
+ const isModelFocused = this.selectedRowIndex === 10;
328
334
  const modelPointer = isModelFocused ? theme.cyan(`${glyphs.pointer} `) : " ";
329
335
  const currentModel = this.availableModels[this.modelIndex] || "qwen3.8-max";
330
336
  const modelText = `${currentModel} (${this.modelIndex + 1}/${this.availableModels.length})`;
@@ -335,23 +341,23 @@ export class SyncView implements TuiView {
335
341
 
336
342
  leftContent.push(isModelFocused ? theme.bgSelected(modelLine) : modelLine);
337
343
 
338
- // Row index 5: Scope Selector
339
- const isScopeFocused = this.selectedRowIndex === 5;
344
+ // Row index 11: Scope Selector
345
+ const isScopeFocused = this.selectedRowIndex === 11;
340
346
  const scopePointer = isScopeFocused ? theme.cyan(`${glyphs.pointer} `) : " ";
341
347
  const scopeCheck = this.syncAllModels ? theme.green(glyphs.radioOn) : theme.muted(glyphs.radioOff);
342
348
  const scopeLine = `${scopePointer}${scopeCheck} Registrar todos os modelos`;
343
349
  leftContent.push(isScopeFocused ? theme.bgSelected(scopeLine) : scopeLine);
344
350
  leftContent.push("");
345
351
  leftContent.push(` ${theme.bold("Ações:")}`);
346
- // Row 18: Sincronizar
347
- const isSyncFocused = this.selectedRowIndex === 6;
348
- const isSyncHovered = this.hoveredActionRow === 18;
352
+ // Row index 12: Sincronizar
353
+ const isSyncFocused = this.selectedRowIndex === 12;
354
+ const isSyncHovered = this.hoveredActionRow === 24 || this.hoveredActionRow === 18;
349
355
  const syncLine = ` ${isSyncHovered || isSyncFocused ? theme.bgHover(` ${theme.cyan("[ Enter ] Sincronizar")} `) : `${theme.cyan("[ Enter ]")} Sincronizar`}`;
350
356
  leftContent.push(syncLine);
351
357
 
352
- // Row 19: Restaurar
353
- const isRestoreFocused = this.selectedRowIndex === 7;
354
- const isRestoreHovered = this.hoveredActionRow === 19;
358
+ // Row index 13: Restaurar
359
+ const isRestoreFocused = this.selectedRowIndex === 13;
360
+ const isRestoreHovered = this.hoveredActionRow === 25 || this.hoveredActionRow === 19;
355
361
  const restoreLine = ` ${isRestoreHovered || isRestoreFocused ? theme.bgHover(` ${theme.yellow("[ R ] Restaurar")} `) : `${theme.yellow("[ R ]")} Restaurar`}`;
356
362
  leftContent.push(restoreLine);
357
363