From 9ed0a0919488f99693a43ea8a25897cd29d4bcf4 Mon Sep 17 00:00:00 2001 From: Simon <10131203+gaomeng1900@users.noreply.github.com> Date: Tue, 9 Jun 2026 21:30:27 +0800 Subject: [PATCH 1/2] feat(core): make execute_javascript honor AbortSignal Expose the task AbortSignal as `signal` in the script scope so cooperative code can cancel promptly, and re-check signal.throwIfAborted() after the script settles to discard stale results. Closes #537. --- packages/core/src/tools/index.ts | 9 ++++++--- packages/extension/src/agent/RemotePageController.ts | 6 ++++-- packages/page-controller/src/PageController.ts | 12 +++++++----- 3 files changed, 17 insertions(+), 10 deletions(-) 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}`, From 768b07e28cc072a0a0dfd5a5514f92d5c07484fe Mon Sep 17 00:00:00 2001 From: Simon <10131203+gaomeng1900@users.noreply.github.com> Date: Thu, 11 Jun 2026 20:16:09 +0800 Subject: [PATCH 2/2] feat: disable ScriptExecutionTool for MultiPageAgent --- packages/extension/src/agent/MultiPageAgent.ts | 2 ++ packages/extension/src/agent/RemotePageController.ts | 6 +----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/extension/src/agent/MultiPageAgent.ts b/packages/extension/src/agent/MultiPageAgent.ts index aa768bc..e7a3f4b 100644 --- a/packages/extension/src/agent/MultiPageAgent.ts +++ b/packages/extension/src/agent/MultiPageAgent.ts @@ -50,6 +50,8 @@ export class MultiPageAgent extends PageAgentCore { super({ ...config, + // Disabled: AbortSignal cannot cross contexts + experimentalScriptExecutionTool: false, pageController: pageController as any, customTools: customTools, customSystemPrompt: systemPrompt, diff --git a/packages/extension/src/agent/RemotePageController.ts b/packages/extension/src/agent/RemotePageController.ts index aaa8234..580f9c2 100644 --- a/packages/extension/src/agent/RemotePageController.ts +++ b/packages/extension/src/agent/RemotePageController.ts @@ -133,11 +133,7 @@ export class RemotePageController { return this.remoteCallDomAction('scroll_horizontally', 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]) - } + // `execute_javascript` is intentionally not implemented: AbortSignal cannot cross context /** @note Managed by content script via storage polling. */ async showMask(): Promise {}