Skip to content

Commit 8a4463e

Browse files
HOYALIMsteipete
andauthored
feat(browser): support evaluate timeouts for existing sessions (#103181)
* fix(browser): honor evaluate timeout for existing sessions Signed-off-by: Ho Lim <[email protected]> * docs(browser): align existing-session evaluate timeouts --------- Signed-off-by: Ho Lim <[email protected]> Co-authored-by: Peter Steinberger <[email protected]>
1 parent 522e253 commit 8a4463e

10 files changed

Lines changed: 50 additions & 8 deletions

File tree

docs/cli/browser.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ Current existing-session limits:
255255
- `click` is left-click only.
256256
- `type` does not support `slowly=true`.
257257
- `press` does not support `delayMs`.
258-
- `hover`, `scrollintoview`, `drag`, `select`, `fill`, and `evaluate` reject per-call timeout overrides.
258+
- `hover`, `scrollintoview`, `drag`, `select`, and `fill` reject per-call timeout overrides; `evaluate` accepts `--timeout-ms`.
259259
- `select` supports one value only.
260260
- `wait --load networkidle` is not supported (works on managed and raw/remote CDP profiles).
261261
- File uploads require `--ref` / `--input-ref`, do not support CSS `--element`, and support one file at a time.

docs/gateway/troubleshooting.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -756,7 +756,6 @@ Look for:
756756
- `existing-session file uploads currently support one file at a time.` → send one upload per call on Chrome MCP profiles.
757757
- `existing-session dialog handling does not support timeoutMs.` → dialog hooks on Chrome MCP profiles do not support timeout overrides.
758758
- `existing-session type does not support timeoutMs overrides.` → omit `timeoutMs` for `act:type` on `profile="user"` / Chrome MCP existing-session profiles, or use a managed/CDP browser profile when a custom timeout is required.
759-
- `existing-session evaluate does not support timeoutMs overrides.` → omit `timeoutMs` for `act:evaluate` on `profile="user"` / Chrome MCP existing-session profiles, or use a managed/CDP browser profile when a custom timeout is required.
760759
- `response body is not supported for existing-session profiles yet.` → `responsebody` still requires a managed browser or raw CDP profile.
761760
- Stale viewport / dark-mode / locale / offline overrides on attach-only or remote CDP profiles → run `openclaw browser stop --browser-profile <name>` to close the active control session and release Playwright/CDP emulation state without restarting the whole gateway.
762761

docs/tools/browser.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,7 @@ directory.
786786
Compared to the managed `openclaw` profile, existing-session drivers are more constrained:
787787

788788
- **Screenshots** - page captures and `--ref` element captures work; CSS `--element` selectors do not. Playwright is not required for page or ref-based element screenshots. (`--full-page` cannot combine with `--ref` or `--element` on any profile, not just existing-session.)
789-
- **Actions** - `click`, `type`, `hover`, `scrollIntoView`, `drag`, and `select` require snapshot refs (no CSS selectors). `click-coords` clicks visible viewport coordinates and does not require a snapshot ref. `click` is left-button only (no button overrides or modifiers). `type` does not support `slowly=true`; use `fill` or `press`. `press` does not support `delayMs`. `type`, `hover`, `scrollIntoView`, `drag`, `select`, `fill`, and `evaluate` do not support per-call `timeoutMs` overrides. `select` accepts a single value. `batch` is not supported; send actions individually.
789+
- **Actions** - `click`, `type`, `hover`, `scrollIntoView`, `drag`, and `select` require snapshot refs (no CSS selectors). `click-coords` clicks visible viewport coordinates and does not require a snapshot ref. `click` is left-button only (no button overrides or modifiers). `type` does not support `slowly=true`; use `fill` or `press`. `press` does not support `delayMs`. `type`, `hover`, `scrollIntoView`, `drag`, `select`, and `fill` do not support per-call `timeoutMs` overrides; `evaluate` does. `select` accepts a single value. `batch` is not supported; send actions individually.
790790
- **Wait / upload / dialog** - `wait --url` supports exact, substring, and glob patterns (same as managed); `wait --load networkidle` is not supported on existing-session profiles (it works on managed and raw/remote CDP profiles). Upload hooks require `ref` or `inputRef`, one file at a time, no CSS `element`. Dialog hooks do not support timeout overrides or `dialogId`.
791791
- **Dialog visibility** - Managed browser action responses include `blockedByDialog` and `browserState.dialogs.pending` when an action opens a modal dialog; snapshots also include pending dialog state. Respond with `browser dialog --accept/--dismiss --dialog-id <id>` while a dialog is pending. Dialogs handled outside OpenClaw appear under `browserState.dialogs.recent`.
792792
- **Managed-only features** - PDF export, download interception, and `responsebody` still require the managed browser path.

extensions/browser/skills/browser-automation/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ Use `profile="user"` only when existing cookies/login matter. This attaches to t
7979

8080
On macOS, `action="importprofile"` is the alternative when the agent should use an isolated managed browser with cookies copied from a real Chrome-family profile. First use `action="profiles"` and inspect `systemProfiles`, then import into a fresh managed profile name. Import asks for one Keychain/Touch ID consent prompt. It copies cookies, not local storage or IndexedDB; device-bound session credentials (DBSC) mean some Google sessions may still require re-authentication.
8181

82-
For `profile="user"` and other existing-session profiles, omit `timeoutMs` on `act:type`, `evaluate`, `hover`, `scrollIntoView`, `drag`, `select`, and `fill`; that driver rejects per-call timeout overrides for those actions.
82+
For `profile="user"` and other existing-session profiles, omit `timeoutMs` on `act:type`, `hover`, `scrollIntoView`, `drag`, `select`, and `fill`; that driver rejects per-call timeout overrides for those actions. `act:evaluate` accepts `timeoutMs`.
8383

8484
## Google Meet Notes
8585

extensions/browser/src/browser-tool-description.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export function describeBrowserTool(opts: {
88
"Browser choice: omit profile to use the configured default (normally the isolated OpenClaw-managed `openclaw` browser).",
99
"When existing logins/cookies matter, use action=profiles to inspect available profiles, then select the appropriate profile by name. Do not assume a profile name. Use only when the task requires an existing session and the user has authorized it.",
1010
"Use action=importprofile on macOS to copy cookies from an authorized Chrome-family system profile into a fresh managed profile; this may show a Keychain consent prompt.",
11-
"For Chrome MCP existing-session profiles, omit timeoutMs on act:type, evaluate, hover, scrollIntoView, drag, select, and fill; that driver rejects per-call timeout overrides for those actions.",
11+
"For Chrome MCP existing-session profiles, omit timeoutMs on act:type, hover, scrollIntoView, drag, select, and fill; that driver rejects per-call timeout overrides for those actions. act:evaluate supports timeoutMs.",
1212
'When a node-hosted browser proxy is available, the tool may auto-route to it. Pin a node with node=<id|name> or target="node".',
1313
"When using refs from snapshot (e.g. e12), keep the same tab: prefer passing targetId from the snapshot response into subsequent actions (act/click/type/etc). For tab operations, targetId also accepts tabId handles (t1) and labels from action=tabs.",
1414
"For multi-step browser work, login checks, stale refs, duplicate tabs, or Google Meet flows, use the bundled browser-automation skill when it is available.",

extensions/browser/src/browser-tool.actions.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ function existingSessionRejectsActTimeout(request: BrowserActRequest): boolean {
9090
case "drag":
9191
case "select":
9292
case "fill":
93-
case "evaluate":
9493
return true;
9594
default:
9695
return false;

extensions/browser/src/browser-tool.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,7 @@ describe("browser tool description", () => {
523523
expect(tool.description).toContain("Do not assume a profile name");
524524
expect(tool.description).not.toContain('profile="user"');
525525
expect(tool.description).toContain("omit timeoutMs on act:type");
526+
expect(tool.description).toContain("act:evaluate supports timeoutMs");
526527
expect(tool.description).toContain("existing-session profiles");
527528
expect(tool.description).toContain("browser-automation skill");
528529
expect(tool.description).toContain("trigger ref with paths in the same upload call");
@@ -1898,6 +1899,32 @@ describe("browser tool act compatibility", () => {
18981899
expect(opts.profile).toBe("user");
18991900
});
19001901

1902+
it("injects configured action timeout for existing-session evaluate actions", async () => {
1903+
setResolvedBrowserProfiles({
1904+
user: { driver: "existing-session", attachOnly: true, color: "#00AA00" },
1905+
});
1906+
configMocks.loadConfig.mockReturnValue({ browser: { actionTimeoutMs: 45_000 } });
1907+
1908+
const tool = createBrowserTool();
1909+
await tool.execute?.("call-1", {
1910+
action: "act",
1911+
profile: "user",
1912+
target: "host",
1913+
request: {
1914+
kind: "evaluate",
1915+
fn: "() => 1 + 1",
1916+
},
1917+
});
1918+
1919+
const request = lastMockCallArg<{ kind?: string; fn?: string; timeoutMs?: number }>(
1920+
browserActionsMocks.browserAct,
1921+
1,
1922+
);
1923+
const opts = lastMockCallArg<{ profile?: string }>(browserActionsMocks.browserAct, 2);
1924+
expect(request).toEqual({ kind: "evaluate", fn: "() => 1 + 1", timeoutMs: 45_000 });
1925+
expect(opts.profile).toBe("user");
1926+
});
1927+
19011928
it("passes configured act timeout through node proxy with transport slack", async () => {
19021929
mockSingleBrowserProxyNode();
19031930
configMocks.loadConfig.mockReturnValue({

extensions/browser/src/browser/routes/agent.act.existing-session-navigation-guard.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,24 @@ describe("existing-session interaction navigation guard", () => {
234234
);
235235
});
236236

237+
it("forwards evaluate timeoutMs to Chrome MCP existing-session execution", async () => {
238+
chromeMcpMocks.evaluateChromeMcpScript.mockResolvedValueOnce(42 as never);
239+
240+
const response = await runAction(
241+
{ kind: "evaluate", fn: "() => 1 + 1", timeoutMs: 60_000 },
242+
null,
243+
);
244+
245+
expect(response.statusCode).toBe(200);
246+
expect(chromeMcpMocks.evaluateChromeMcpScript).toHaveBeenCalledOnce();
247+
expect(chromeMcpMocks.evaluateChromeMcpScript).toHaveBeenCalledWith(
248+
expect.objectContaining({
249+
fn: "() => 1 + 1",
250+
timeoutMs: 60_000,
251+
}),
252+
);
253+
});
254+
237255
it("normalizes ref-scoped statement-body evaluate sources before Chrome MCP execution", async () => {
238256
chromeMcpMocks.evaluateChromeMcpScript.mockResolvedValueOnce("Ada" as never);
239257

extensions/browser/src/browser/routes/agent.act.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ function getExistingSessionUnsupportedMessage(action: BrowserActRequest): string
335335
? EXISTING_SESSION_LIMITS.act.waitNetworkIdle
336336
: null;
337337
case "evaluate":
338-
return action.timeoutMs !== undefined ? EXISTING_SESSION_LIMITS.act.evaluateTimeout : null;
338+
return null;
339339
case "batch":
340340
return EXISTING_SESSION_LIMITS.act.batch;
341341
case "resize":

extensions/browser/src/browser/routes/existing-session-limits.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ export const EXISTING_SESSION_LIMITS = {
2727
selectTimeout: "existing-session select does not support timeoutMs overrides.",
2828
fillTimeout: "existing-session fill does not support timeoutMs overrides.",
2929
waitNetworkIdle: "existing-session wait does not support loadState=networkidle yet.",
30-
evaluateTimeout: "existing-session evaluate does not support timeoutMs overrides.",
3130
batch: "existing-session batch is not supported yet; send actions individually.",
3231
},
3332
hooks: {

0 commit comments

Comments
 (0)