From d1b7e76f5b7c2aeb517700c56e6b5101b5afbe6f Mon Sep 17 00:00:00 2001 From: matevip Date: Tue, 19 May 2026 16:53:18 +0800 Subject: [PATCH] fix(tool): support top-level await in the browser eval action --- .../vip/mate/tool/builtin/BrowserUseTool.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/mateclaw-server/src/main/java/vip/mate/tool/builtin/BrowserUseTool.java b/mateclaw-server/src/main/java/vip/mate/tool/builtin/BrowserUseTool.java index d8c57a90..2d668bed 100644 --- a/mateclaw-server/src/main/java/vip/mate/tool/builtin/BrowserUseTool.java +++ b/mateclaw-server/src/main/java/vip/mate/tool/builtin/BrowserUseTool.java @@ -26,6 +26,7 @@ import java.nio.file.Paths; import java.util.Base64; import java.util.List; import java.util.concurrent.*; +import java.util.regex.Pattern; /** * 浏览器自动化工具 @@ -98,7 +99,7 @@ public class BrowserUseTool { - screenshot: Take a screenshot. Optional path to save file; returns base64 if no path. - click: Click an element. Requires selector (CSS selector). - type: Type text into an element. Requires selector and text. - - eval: Execute JavaScript on the page. Requires code parameter. + - eval: Execute JavaScript on the page. Requires code parameter. Top-level await is supported; use `return` to surface a value. - connect_cdp: Connect to an existing Chrome via CDP. Requires url (e.g. "http://localhost:9222"). - list_cdp_targets: Scan local ports (9000-10000) for CDP endpoints. Optional cdpPort for single port. - navigate_back: Go back in browser history. @@ -109,7 +110,7 @@ public class BrowserUseTool { @ToolParam(description = "URL to navigate to (for open), or CDP base URL (for connect_cdp, e.g. http://localhost:9222)", required = false) String url, @ToolParam(description = "CSS selector for target element (for click/type)", required = false) String selector, @ToolParam(description = "Text to type (for action=type)", required = false) String text, - @ToolParam(description = "JavaScript code to execute (for action=eval)", required = false) String code, + @ToolParam(description = "JavaScript code to execute (for action=eval). Top-level await is allowed; add `return` to return a value when the snippet uses await.", required = false) String code, @ToolParam(description = "File path to save screenshot (for action=screenshot)", required = false) String path, @ToolParam(description = "Launch visible browser window (for action=start, default false)", required = false) Boolean headed, @ToolParam(description = "Single CDP port to scan (for action=list_cdp_targets)", required = false) Integer cdpPort, @@ -642,6 +643,9 @@ public class BrowserUseTool { return JSONUtil.toJsonPrettyStr(result); } + /** Detects the {@code await} keyword as a whole word to decide whether eval code needs an async wrapper. */ + private static final Pattern TOP_LEVEL_AWAIT = Pattern.compile("\\bawait\\b"); + private String doEval(String sessionKey, String code) { if (code == null || code.isBlank()) { return error("code is required for action=eval"); @@ -655,7 +659,13 @@ public class BrowserUseTool { session.touch(); Page page = session.page; - Object evalResult = page.evaluate(code); + // Playwright evaluates the supplied string as a plain expression, which + // rejects top-level `await`. Wrap such snippets in an async IIFE so the + // agent can await promises; Playwright auto-resolves the returned one. + String script = TOP_LEVEL_AWAIT.matcher(code).find() + ? "(async () => {" + code + "})()" + : code; + Object evalResult = page.evaluate(script); String resultStr = evalResult != null ? evalResult.toString() : "null"; if (resultStr.length() > 10_000) {