Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions bunfig.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[test]
# Redirect .qwen data writes to a temp dir before any test module loads,
# so the suite never clobbers the real .qwen/accounts.json.
preload = ["./src/tests/setup.ts"]
14 changes: 8 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@
"scripts": {
"postinstall": "node scripts/setup.js || exit 0",
"build": "tsc -p tsconfig.build.json",
"start": "bun src/index.tsx",
"start:prod": "bun dist/index.js",
"start": "node --import tsx src/index.tsx",
"start:bun": "bun src/index.tsx",
"start:prod": "node --import tsx dist/index.js",
"start:node": "node --import tsx src/index.tsx",
"dev": "bun --watch src/index.tsx",
"start:firefox": "bun src/index.tsx --browser=firefox",
"start:chrome": "bun src/index.tsx --browser=chrome",
"start:edge": "bun src/index.tsx --browser=edge",
"dev": "node --import tsx --watch src/index.tsx",
"dev:bun": "bun --watch src/index.tsx",
"start:firefox": "node --import tsx src/index.tsx --browser=firefox",
"start:chrome": "node --import tsx src/index.tsx --browser=chrome",
"start:edge": "node --import tsx src/index.tsx --browser=edge",
"cluster": "bun src/cluster.ts",
"setup": "node scripts/setup.js",
"test": "bun test",
Expand Down
14 changes: 10 additions & 4 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,18 @@ async function startServer(args: string[]) {
if (hostIdx !== -1 && args[hostIdx + 1]) extraArgs.push('--host', args[hostIdx + 1]);

const entry = findEntry();
const runner = 'bun';

out(`Starting server (bun ${entry})...`);
// Run the server under Node, not Bun. cloakbrowser drives Chrome over a CDP
// pipe that hangs under Bun on Windows (browser-profile launch never resolves
// -> 30s timeout -> fetch-login with no baxia/WAF cookie harvest -> captchas).
// Node drives the pipe correctly and harvests the WAF cookies. `tsx` lets Node
// execute the .tsx entry directly.
const runner = 'node';
const runnerArgs = ['--import', 'tsx', entry, ...extraArgs];

out(`Starting server (node ${entry})...`);
if (extraArgs.length) out(`Extra args: ${extraArgs.join(' ')}`);

const server = spawn(runner, [entry, ...extraArgs], {
const server = spawn(runner, runnerArgs, {
stdio: 'inherit',
shell: true,
});
Expand Down
11 changes: 5 additions & 6 deletions src/routes/chatNonStreaming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,12 +202,11 @@ function parseQwenResponse(line: string, state: StreamProcessorState, ctx: NonSt
// so we must extract them here to avoid losing tool calls.
const localToolCalls = extractLocalMcpToolCalls(chunk);
if (localToolCalls.length > 0) {
const parsed = localToolCalls.map((tc) => ({
id: tc.id,
type: 'function' as const,
function: { name: tc.name, arguments: JSON.stringify(tc.arguments) },
}));
processToolCallsThroughGuard(parsed, state.toolCallsOut, {
// extractLocalMcpToolCalls returns flat ParsedToolCall {id,name,arguments}.
// processToolCallsThroughGuard validates that flat shape and wraps into
// {function:{name,arguments}} itself — pre-wrapping here hid tc.name from
// the guard (tc.name undefined), failing validation and dropping the call.
processToolCallsThroughGuard(localToolCalls, state.toolCallsOut, {
logId: ctx.logId,
toolSpamGuard: state.toolSpamGuard,
correctionPrompts: state.correctionPrompts,
Expand Down
19 changes: 19 additions & 0 deletions src/services/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ import { loginFresh } from './loginService.ts';
import { logStore } from './logStore.ts';
import { getActivePage, getBrowser } from './playwright.ts';
import { ensureAccountFresh, needsRefresh } from './tokenRefresh.ts';
import { isBun } from '../utils/env.ts';

/** Log the bun-pipe warning at most once per process. */
let warnedBunPipe = false;

export {
addAccount,
Expand Down Expand Up @@ -235,6 +239,21 @@ export async function loadCookiesFromProfile(email: string): Promise<AuthState |
}

logStore.log('info', 'auth', `Loading token from profile for ${email}...`);
// cloakbrowser drives Chrome over --remote-debugging-pipe, which hangs under
// the Bun runtime on Windows (the launch never resolves -> 30s timeout ->
// fetch-login fallback with no baxia/WAF cookie harvest). Node drives the
// pipe correctly. Warn once so the operator can switch to `npm run start:node`
// when they want full WAF-cookie harvesting (silent-refresh, fewer captchas).
if (isBun && !warnedBunPipe) {
warnedBunPipe = true;
logStore.log(
'warn',
'auth',
'Running under Bun: browser profile launch may hang (CDP pipe). ' +
'For full baxia/WAF cookie harvesting run with `npm run start:node`. ' +
'Auth still works via fetch-login fallback.',
);
}
const { BROWSER_DEFAULT_ARGS } = await import('./playwright.ts');
const { launchPersistentContext } = await import('cloakbrowser');
const PROFILE_LAUNCH_TIMEOUT_MS = 30_000;
Expand Down
14 changes: 14 additions & 0 deletions src/tests/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Test preload — runs before any test module is imported (via bunfig.toml
* [test].preload). Redirects all .qwen data writes (accounts.json, master.key,
* monitor.json, browser-profiles) into a throwaway temp dir so the test suite
* can never clobber the real .qwen/accounts.json. Must set the env BEFORE
* accountManager.ts loads, because ACCOUNTS_FILE is resolved at module load.
*/
import { mkdtempSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';

if (!process.env.QWEN_DATA_DIR) {
process.env.QWEN_DATA_DIR = mkdtempSync(join(tmpdir(), 'qwen-gate-test-'));
}
10 changes: 10 additions & 0 deletions src/utils/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,17 @@ export const PROJECT_ROOT = resolve(__dirname, '..', '..');
* Resolve a path relative to the project root.
* Use this instead of process.cwd() to ensure paths work
* regardless of where the CLI is invoked from.
*
* Honors the QWEN_DATA_DIR env override, but ONLY for the mutable `.qwen` data
* dir (accounts.json, master.key, monitor.json, browser-profiles). Source-tree
* reads (package.json, etc.) always resolve under PROJECT_ROOT. Tests point
* QWEN_DATA_DIR at a throwaway temp dir so writes never clobber real
* .qwen/accounts.json, while still reading package.json from the real root.
*/
export function projectPath(...segments: string[]): string {
if (process.env.QWEN_DATA_DIR && segments[0] === '.qwen') {
return resolve(process.env.QWEN_DATA_DIR, ...segments);
}
return resolve(PROJECT_ROOT, ...segments);
}