From 3481aeec26ce8fe1c71a4498d32489eb4e58bc13 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Sat, 4 Oct 2025 00:45:16 +0200 Subject: [PATCH] Make browser_javascript tool abortable --- .../src/tools/browser-javascript.ts | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/packages/browser-extension/src/tools/browser-javascript.ts b/packages/browser-extension/src/tools/browser-javascript.ts index 09384776..634cd68a 100644 --- a/packages/browser-extension/src/tools/browser-javascript.ts +++ b/packages/browser-extension/src/tools/browser-javascript.ts @@ -80,8 +80,17 @@ Example: First call with just "history.back()", then a second call with other co Note: This requires the activeTab permission and only works on http/https pages, not on chrome:// URLs.`, parameters: browserJavaScriptSchema, - execute: async (_toolCallId: string, args: Static, _signal?: AbortSignal) => { + execute: async (_toolCallId: string, args: Static, signal?: AbortSignal) => { try { + // Check if already aborted + if (signal?.aborted) { + return { + output: "Tool execution was aborted", + isError: true, + details: { files: [] }, + }; + } + // Check if code contains navigation that will destroy execution context const navigationRegex = /\b(window\.location\s*=|location\.href\s*=|history\.(back|forward|go)\s*\(|window\.open\s*\(|document\.location\s*=)/; @@ -181,8 +190,8 @@ This ensures reliable execution.`, const canUseEval = cspCheckResults[0]?.result?.canEval ?? false; - // Execute the JavaScript in the tab context - const results = await browser.scripting.executeScript({ + // Execute the JavaScript in the tab context with abort handling + const executePromise = browser.scripting.executeScript({ target: { tabId: tab.id }, world: "MAIN", func: (code: string, useScriptTag: boolean) => { @@ -344,6 +353,17 @@ This ensures reliable execution.`, args: [args.code, !canUseEval], }); + // Race between execution and abort signal + let results: Awaited; + if (signal) { + const abortPromise = new Promise((_, reject) => { + signal.addEventListener("abort", () => reject(new Error("Aborted"))); + }); + results = await Promise.race([executePromise, abortPromise]); + } else { + results = await executePromise; + } + const result = results[0]?.result as | { success: boolean; @@ -442,6 +462,14 @@ This ensures reliable execution.`, }; } catch (error: unknown) { const err = error as Error; + // Check if this was an abort + if (err.message === "Aborted" || signal?.aborted) { + return { + output: "Tool execution was aborted by user", + isError: true, + details: { files: [] }, + }; + } return { output: `Error executing script: ${err.message}`, isError: true,