diff --git a/packages/core/src/tools/index.ts b/packages/core/src/tools/index.ts index b6839b9..e2e1503 100644 --- a/packages/core/src/tools/index.ts +++ b/packages/core/src/tools/index.ts @@ -181,12 +181,15 @@ tools.set( 'execute_javascript', tool({ description: - 'Execute JavaScript code on the current page. Supports async/await syntax. Use with caution!', + 'Execute JavaScript code on the current page. Supports async/await syntax. Use with caution! ' + + 'An `AbortSignal` named `signal` is available in scope: long-running async code MUST honor it ' + + '(e.g. `await fetch(url, { signal })`, or `signal.throwIfAborted()` in loops)', inputSchema: z.object({ script: z.string(), }), - execute: async function (this: PageAgentCore, input) { - const result = await this.pageController.executeJavascript(input.script) + execute: async function (this: PageAgentCore, input, { signal }) { + const result = await this.pageController.executeJavascript(input.script, signal) + signal.throwIfAborted() return result.message }, }) diff --git a/packages/extension/src/agent/RemotePageController.ts b/packages/extension/src/agent/RemotePageController.ts index 67f6336..aaa8234 100644 --- a/packages/extension/src/agent/RemotePageController.ts +++ b/packages/extension/src/agent/RemotePageController.ts @@ -133,8 +133,10 @@ export class RemotePageController { return this.remoteCallDomAction('scroll_horizontally', args) } - async executeJavascript(...args: any[]): Promise { - return this.remoteCallDomAction('execute_javascript', args) + async executeJavascript(script: string, _signal?: AbortSignal): Promise { + // `AbortSignal` is not structured-cloneable across the messaging boundary. + // The remote script runs without it + return this.remoteCallDomAction('execute_javascript', [script]) } /** @note Managed by content script via storage polling. */ diff --git a/packages/page-controller/src/PageController.ts b/packages/page-controller/src/PageController.ts index 0369185..ad93528 100644 --- a/packages/page-controller/src/PageController.ts +++ b/packages/page-controller/src/PageController.ts @@ -376,13 +376,15 @@ export class PageController extends EventTarget { } /** - * Execute arbitrary JavaScript on the page + * Execute arbitrary JavaScript on the page. + * The optional `signal` is exposed to the script scope so cooperative code + * can abort promptly when the task is stopped. */ - async executeJavascript(script: string): Promise { + async executeJavascript(script: string, signal?: AbortSignal): Promise { try { - // Wrap script in async function to support await - const asyncFunction = eval(`(async () => { ${script} })`) - const result = await asyncFunction() + // Wrap script in async function to support await, exposing `signal`. + const asyncFunction = eval(`(async (signal) => { ${script} })`) + const result = await asyncFunction(signal) return { success: true, message: `✅ Executed JavaScript. Result: ${result}`,