diff --git a/packages/page-agent/src/PageAgent.ts b/packages/page-agent/src/PageAgent.ts index 67cad4e..75bfb9d 100644 --- a/packages/page-agent/src/PageAgent.ts +++ b/packages/page-agent/src/PageAgent.ts @@ -68,7 +68,6 @@ export class PageAgent extends EventTarget { #llm: LLM #abortController = new AbortController() - #beforeUnloadListener: ((e: Event) => void) | null = null /** PageController for DOM operations */ pageController: PageController @@ -92,10 +91,12 @@ export class PageAgent extends EventTarget { this.tools = new Map(tools) // Initialize PageController with config (mask enabled by default) - this.pageController = new PageController({ - ...this.config, - enableMask: this.config.enableMask ?? true, - }) + this.pageController = + this.config.pageController ?? + new PageController({ + ...this.config, + enableMask: this.config.enableMask ?? true, + }) // Listen to LLM retry events this.#llm.addEventListener('retry', (e) => { @@ -137,11 +138,6 @@ export class PageAgent extends EventTarget { if (!this.config.experimentalScriptExecutionTool) { this.tools.delete('execute_javascript') } - - this.#beforeUnloadListener = (e) => { - if (!this.disposed) this.dispose('PAGE_UNLOADING') - } - window.addEventListener('beforeunload', this.#beforeUnloadListener) } /** Get current agent status */ @@ -591,12 +587,6 @@ export class PageAgent extends EventTarget { this.history = [] this.#abortController.abort(reason ?? 'PageAgent disposed') - // Clean up window event listeners - if (this.#beforeUnloadListener) { - window.removeEventListener('beforeunload', this.#beforeUnloadListener) - this.#beforeUnloadListener = null - } - // Emit dispose event for UI cleanup this.dispatchEvent(new Event('dispose')) diff --git a/packages/page-agent/src/config/index.ts b/packages/page-agent/src/config/index.ts index fc25dea..bbaeae4 100644 --- a/packages/page-agent/src/config/index.ts +++ b/packages/page-agent/src/config/index.ts @@ -1,5 +1,5 @@ import type { LLMConfig } from '@page-agent/llms' -import type { PageControllerConfig } from '@page-agent/page-controller' +import type { PageController, PageControllerConfig } from '@page-agent/page-controller' import type { PageAgent } from '../PageAgent' import type { PageAgentTool } from '../tools' @@ -74,7 +74,6 @@ export interface AgentConfig { /** * @note this hook can block the disposal process - * @note when dispose caused by page unload, reason will be 'PAGE_UNLOADING'. this method CANNOT block unloading. async operations may be cut. * @todo remove `this` binding, pass agent as explicit parameter instead */ onDispose?: (this: PageAgent, reason?: string) => void @@ -105,6 +104,13 @@ export interface AgentConfig { */ transformPageContent?: (content: string) => Promise | string + /** + * @experimental + * Custom PageController instance to control page navigation and actions + * @note If not provided, a default PageController will be created + */ + pageController?: PageController + /** * TODO: @unimplemented * hook when action causes a new page to be opened diff --git a/packages/page-controller/src/PageController.ts b/packages/page-controller/src/PageController.ts index 30f750e..ef0984e 100644 --- a/packages/page-controller/src/PageController.ts +++ b/packages/page-controller/src/PageController.ts @@ -18,11 +18,8 @@ import { VIEWPORT_EXPANSION } from './constants' import * as dom from './dom' import type { FlatDomTree, InteractiveElementDomNode } from './dom/dom_tree/type' import { getPageInfo } from './dom/getPageInfo' -import { SimulatorMask } from './mask/SimulatorMask' import { patchReact } from './patches/react' -export { SimulatorMask } - /** * Configuration for PageController */ @@ -84,7 +81,7 @@ export class PageController extends EventTarget { private lastTimeUpdate = 0 /** Visual mask overlay for blocking user interaction during automation */ - private mask: SimulatorMask | null = null + private mask: InstanceType | null = null constructor(config: PageControllerConfig = {}) { super() @@ -94,10 +91,16 @@ export class PageController extends EventTarget { patchReact(this) if (config.enableMask) { - this.mask = new SimulatorMask() + this.initMask() } } - + /** + * Initialize mask asynchronously (dynamic import to avoid CSS loading in Node) + */ + private async initMask(): Promise { + const { SimulatorMask } = await import('./mask/SimulatorMask') + this.mask = new SimulatorMask() + } // ======= State Queries ======= /**