diff --git a/public/index.html b/public/index.html
index 4e4c44e..02a0702 100644
--- a/public/index.html
+++ b/public/index.html
@@ -2357,13 +2357,13 @@
class="w-full" @keydown.enter.prevent="confirmGoogleAccountAgTask()">
-
+
- 供应数据中的辅助邮箱及后续字段会被忽略;数据库已有邮箱会跳过且不覆盖。临时比特浏览器固定使用直连(无代理),并严格按邮箱串行处理:完成一个、删除一个,再开始下一个。
+ 供应数据中的辅助邮箱及后续字段会被忽略;数据库已有邮箱会跳过且不覆盖。临时比特浏览器固定使用直连(无代理);每个并发工位完成一个账号并删除对应浏览器后,再领取下一个账号。
diff --git a/src/workers/task_runner.js b/src/workers/task_runner.js
index 8d705b8..fec4a59 100644
--- a/src/workers/task_runner.js
+++ b/src/workers/task_runner.js
@@ -271,18 +271,6 @@ function stopMonitorForTask(monitor, accountLog) {
// ==================== Pipeline 任务类型集合 ====================
const PIPELINE_TASKS = new Set([...Object.keys(RECIPES), 'custom_pipeline']);
-const TEMPORARY_BROWSER_TASKS = new Set([
- ...[...PIPELINE_TASKS].filter(type => type !== 'openai_query_plan'),
- 'google_account_ag_credential',
- 'ag_revoke_authorization',
- 'ag_get_credential',
- 'gemini_cli_get_credential',
- 'open',
- 'open_manual_aistudio',
- 'open_url',
- 'change_proxy',
-]);
-
// ==================== 任务入口 ====================
const { taskType, accounts, params, taskId } = workerData;
@@ -452,10 +440,9 @@ async function runTask() {
const threadCount = taskType === 'change_proxy' && accounts.length > 300
? Math.min(requestedThreadCount, 12)
: requestedThreadCount;
- const browserTaskConcurrency = TEMPORARY_BROWSER_TASKS.has(taskType) ? 1 : threadCount;
- if (TEMPORARY_BROWSER_TASKS.has(taskType) && threadCount > 1) {
- log(`[任务] 临时比特浏览器模式固定串行执行;已将浏览器任务并发从 ${threadCount} 调整为 1`);
- }
+ // 浏览器任务也遵循页面设置的并发数。每个并发槽独立完成
+ // “创建/复用当前账号窗口 → 执行业务 → 删除窗口”后再领取下一个账号。
+ const browserTaskConcurrency = threadCount;
const openRequestConcurrency = setOpenBrowserConcurrency(Math.min(browserTaskConcurrency, 12));
// 读取打开限速配置(每秒最多打开几个窗口),默认 9
@@ -997,7 +984,7 @@ async function runTask() {
case 'google_account_ag_credential': {
const requestedQuantity = Number.parseInt(params && params.quantity, 10);
- const taskConcurrency = 1;
+ const taskConcurrency = browserTaskConcurrency;
const defaultCustomerName = '企业字-Free';
log(`[链接取号] 开始处理,计划取号 ${requestedQuantity} 个,并发 ${taskConcurrency}`);
log('[链接取号] 执行方式:每个并发工位每次只取 1 个账号,完成浏览器创建和反重力凭证后再领取下一个');
diff --git a/tests/google_account_ag_feature.test.js b/tests/google_account_ag_feature.test.js
index 6dfe0b2..2501219 100644
--- a/tests/google_account_ag_feature.test.js
+++ b/tests/google_account_ag_feature.test.js
@@ -28,10 +28,10 @@ function responseStub() {
'获取Cli凭证按钮必须位于获取凭证和链接提取按钮之间');
assert.match(html, /Google 账号取号 API[\s\S]{0,2500}googleAccountCredentialForm\.value/,
'设置页应提供加密取号 API 配置');
- assert.match(html, /处理账号数量(1~5000)[\s\S]{0,800}并发参数(兼容保留)/,
- '任务弹窗应说明并发参数只为兼容保留');
- assert.match(html, /临时比特浏览器[\s\S]{0,120}严格按邮箱串行处理:完成一个、删除一个,再开始下一个/,
- '任务弹窗应告知用户临时比特浏览器严格串行');
+ assert.match(html, /处理账号数量(1~5000)[\s\S]{0,800}并发数量(1~30)/,
+ '任务弹窗应允许用户设置实际并发数量');
+ assert.match(html, /临时比特浏览器[\s\S]{0,180}每个并发工位完成一个账号并删除对应浏览器后,再领取下一个账号/,
+ '任务弹窗应说明每个并发工位独立执行完整浏览器生命周期');
assert(server.includes("/api/google-account-credentials"), '服务端应挂载取号凭据路由');
assert(operations.includes("'google_account_ag_credential'"), '取号任务必须受本机凭据任务保护');
assert(operations.includes('importAccountsFromText'), 'Worker 应通过主线程安全导入新账号');
@@ -46,8 +46,12 @@ function responseStub() {
'链接取号导入账号必须使用默认客户名“企业字-Free”');
assert.match(worker, /fetchGoogleAccounts\(\{[\s\S]{0,220}quantity:\s*1/,
'每次供应接口调用只能提取 1 个账号');
- assert(worker.includes('const taskConcurrency = 1;'),
- '浏览器账号必须固定串行,删除当前临时窗口后再处理下一个');
+ assert(worker.includes('const browserTaskConcurrency = threadCount;'),
+ '浏览器任务必须采用页面传入的并发数');
+ assert(worker.includes('const taskConcurrency = browserTaskConcurrency;'),
+ '链接取号任务必须采用页面传入的并发数');
+ assert(!worker.includes('已将浏览器任务并发'),
+ '浏览器任务不得静默把用户设置的并发数改成 1');
assert(worker.includes('const taskResults = await runConcurrent(taskSlots'),
'账号应通过滑动队列按顺序处理');
const fetchOneAt = worker.indexOf('quantity: 1,');