diff --git a/CHANGELOG.md b/CHANGELOG.md index 25409e0..1201717 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.4.8] - 2026-01-18 + +### 🔄 Changed +- **崩溃重启** + - 非登录模式下浏览器崩溃或者被关闭时不导致项目退出而是重启 + ## [3.4.7] - 2026-01-14 ### ✨ Added diff --git a/src/backend/engine/launcher.js b/src/backend/engine/launcher.js index 33d604f..a663470 100644 --- a/src/backend/engine/launcher.js +++ b/src/backend/engine/launcher.js @@ -331,11 +331,12 @@ export async function initBrowserBase(config, options = {}) { // 注册清理处理器 registerCleanupHandlers(); - // 注册断开连接事件 + // 注册断开连接事件(不再自动退出进程,由 Worker 决定后续行为) context.on('close', async () => { logger.warn('浏览器', `[${markLabel}] 浏览器已断开连接`); - await cleanup(); - process.exit(0); + // 清理全局状态,但不退出进程 + globalContext = null; + globalBrowserProcess = null; }); // 获取或创建 Page diff --git a/src/backend/pool/PoolManager.js b/src/backend/pool/PoolManager.js index 91cfb50..8f51da3 100644 --- a/src/backend/pool/PoolManager.js +++ b/src/backend/pool/PoolManager.js @@ -104,12 +104,17 @@ export class PoolManager { logger.debug('工作池', `[${worker.name}] 将与其他 Worker 共享浏览器 (${worker.userDataDir})`); await worker.init(existing.browser); + + // 建立共享关系:设置所有者引用,并添加到所有者的共享列表 + worker._browserOwner = existing.ownerWorker; + existing.ownerWorker._sharedWorkers.push(worker); } else { await worker.init(); browserMap.set(worker.userDataDir, { browser: worker.browser, proxyConfig: worker.proxyConfig, - firstWorkerName: worker.name + firstWorkerName: worker.name, + ownerWorker: worker // 保存所有者 Worker 引用 }); } diff --git a/src/backend/pool/Worker.js b/src/backend/pool/Worker.js index be421ea..419b49e 100644 --- a/src/backend/pool/Worker.js +++ b/src/backend/pool/Worker.js @@ -35,6 +35,11 @@ export class Worker { this.page = null; this.busyCount = 0; this.initialized = false; + + // 浏览器所有权(用于共享浏览器场景的协调重启) + this._isBrowserOwner = false; // 是否是浏览器的所有者(负责重启) + this._browserOwner = null; // 如果是共享者,指向所有者 Worker + this._sharedWorkers = []; // 如果是所有者,保存共享该浏览器的 Worker 列表 } /** @@ -93,8 +98,10 @@ export class Worker { if (sharedBrowser) { await this._initWithSharedBrowser(sharedBrowser, targetUrl, navigationHandler); + this._isBrowserOwner = false; } else { await this._initNewBrowser(targetUrl, navigationHandler); + this._isBrowserOwner = true; } this.initialized = true; @@ -111,6 +118,10 @@ export class Worker { this.page.authState = { isHandlingAuth: false }; this.page.cursor = createCursor(this.page); + // 保存参数用于重新初始化 + this._targetUrl = targetUrl; + this._navigationHandler = navigationHandler; + await this._navigateToTarget(targetUrl); if (navigationHandler) { @@ -119,9 +130,57 @@ export class Worker { }); } + // 监听标签页关闭事件,自动重新创建(仅针对共享者) + this._registerPageCloseHandler(); + logger.info('工作池', `[${this.name}] 初始化完成`); } + /** + * 注册标签页关闭事件处理器 + * @private + */ + _registerPageCloseHandler() { + if (!this.page) return; + + this.page.on('close', async () => { + // 如果浏览器还在运行,说明只是标签页被关闭 + if (this.browser && !this.browser.isClosed?.()) { + logger.warn('工作池', `[${this.name}] 标签页已关闭,正在重新创建...`); + this.initialized = false; + this.page = null; + try { + await this._recreatePage(); + } catch (e) { + logger.error('工作池', `[${this.name}] 重新创建标签页失败: ${e.message}`); + } + } + }); + } + + /** + * 重新创建标签页(标签页关闭恢复) + * @private + */ + async _recreatePage() { + this.page = await this.browser.newPage(); + this.page.authState = { isHandlingAuth: false }; + this.page.cursor = createCursor(this.page); + await this._navigateToTarget(this._targetUrl || 'about:blank'); + + if (this._navigationHandler) { + this.page.on('framenavigated', async () => { + try { await this._navigationHandler(this.page); } catch (e) { /* ignore */ } + }); + } + + // 重新注册标签页关闭处理器 + this._registerPageCloseHandler(); + + this.initialized = true; + logger.info('工作池', `[${this.name}] 标签页已成功重新创建`); + } + /** * 启动新浏览器初始化 * @private @@ -144,10 +203,14 @@ export class Worker { }); } + // 保存 navigationHandler 用于重新初始化 + this._navigationHandler = navigationHandler; + this._targetUrl = targetUrl; + logger.info('工作池', `[${this.name}] 正在连接目标页面...`); await this._navigateToTarget(targetUrl); - // 登录模式:注册浏览器关闭事件(不阻塞) + // 登录模式:注册浏览器关闭事件(不阻塞,关闭后退出进程) const isLoginMode = process.argv.some(arg => arg.startsWith('-login')); if (isLoginMode) { logger.info('工作池', `[${this.name}] 登录模式已就绪,请在浏览器中完成登录`); @@ -155,6 +218,48 @@ export class Worker { logger.info('工作池', `[${this.name}] 浏览器已关闭,登录模式结束`); process.exit(0); }); + } else { + // 非登录模式:注册断开事件,所有者负责重启并同步到共享者 + this.browser.on('close', async () => { + logger.warn('工作池', `[${this.name}] 浏览器已断开连接,正在自动重新初始化...`); + + // 标记自己和所有共享者为未初始化 + this.initialized = false; + this.browser = null; + this.page = null; + for (const sharedWorker of this._sharedWorkers) { + sharedWorker.initialized = false; + sharedWorker.browser = null; + sharedWorker.page = null; + } + + try { + // 重新初始化浏览器 + await this._reinit(); + + // 为所有共享者创建新的标签页 + for (const sharedWorker of this._sharedWorkers) { + try { + logger.info('工作池', `[${sharedWorker.name}] 正在恢复共享浏览器连接...`); + sharedWorker.browser = this.browser; + sharedWorker.page = await this.browser.newPage(); + sharedWorker.page.authState = { isHandlingAuth: false }; + sharedWorker.page.cursor = createCursor(sharedWorker.page); + await sharedWorker._navigateToTarget(sharedWorker._targetUrl || 'about:blank'); + sharedWorker._registerPageCloseHandler(); // 重新注册标签页关闭处理器 + sharedWorker.initialized = true; + logger.info('工作池', `[${sharedWorker.name}] 共享浏览器连接已恢复`); + } catch (e) { + logger.error('工作池', `[${sharedWorker.name}] 恢复共享浏览器连接失败: ${e.message}`); + } + } + } catch (e) { + logger.error('工作池', `[${this.name}] 自动重新初始化失败: ${e.message}`); + } + }); + + // 所有者也需要监听标签页关闭事件 + this._registerPageCloseHandler(); } logger.info('工作池', `[${this.name}] 初始化完成`); @@ -330,6 +435,17 @@ export class Worker { * @private */ async _executeAdapter(ctx, type, modelId, prompt, paths, meta) { + // 检查 Worker 是否已初始化(浏览器崩溃后会被标记为 false) + if (!this.initialized || !this.page || this.page.isClosed()) { + logger.info('工作池', `[${this.name}] 浏览器已断开,正在自动重新初始化...`, meta); + try { + await this._reinit(); + } catch (e) { + logger.error('工作池', `[${this.name}] 重新初始化失败`, { error: e.message, ...meta }); + return { error: `Worker 重新初始化失败: ${e.message}` }; + } + } + const adapter = registry.getAdapter(type); if (!adapter) { return { error: `适配器不存在: ${type}` }; @@ -354,6 +470,21 @@ export class Worker { } } + /** + * 重新初始化浏览器(崩溃恢复) + * @private + */ + async _reinit() { + this.initialized = false; + this.browser = null; + this.page = null; + + // 使用保存的参数重新初始化 + await this._initNewBrowser(this._targetUrl || 'about:blank', this._navigationHandler || null); + this.initialized = true; + logger.info('工作池', `[${this.name}] 浏览器已成功重新初始化`); + } + /** * 获取支持的模型列表 */