From fa814eb9ed68f3d0971330c6eff4f99f2e83e83c Mon Sep 17 00:00:00 2001 From: "clawsweeper[bot]" <274271284+clawsweeper[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 17:30:33 +0000 Subject: [PATCH] feat(browser): add evaluate timeout CLI option (#83696) Summary: - The branch adds `openclaw browser evaluate --timeout-ms`, forwards it to the evaluate body and request timeo ... ents and tests it, adds a changelog entry, and includes a config.patch no-op shortcut from the repair pass. - Reproducibility: not applicable. this is a feature PR rather than a bug report. Source inspection shows current main lacks the CLI flag while the branch wires it into an already-supported evaluate `timeoutMs` payload. Automerge notes: - PR branch already contained follow-up commit before automerge: feat(browser): add evaluate timeout CLI option Validation: - ClawSweeper review passed for head 0d81d3d93e4110b463706c91d6aca04dd5c5f82e. - Required merge gates passed before the squash merge. Prepared head SHA: 0d81d3d93e4110b463706c91d6aca04dd5c5f82e Review: https://github.com/openclaw/openclaw/pull/83696#issuecomment-4479900502 Co-authored-by: fred Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com> Approved-by: takhoffman Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com> --- CHANGELOG.md | 1 + docs/cli/browser.md | 4 +++ docs/tools/browser-control.md | 3 +++ .../register.form-wait-eval.test.ts | 25 +++++++++++++++++++ .../register.form-wait-eval.ts | 8 ++++++ src/gateway/server-methods/config.ts | 19 +++++++++++++- 6 files changed, 59 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b8b11461f52..338345e7d20d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ Docs: https://docs.openclaw.ai - Skills: add a meme-maker skill for curated template search, local SVG/PNG rendering, Imgflip hosted rendering, and Know Your Meme provenance links. - Skills CLI: allow `openclaw skills install` and `openclaw skills update` to target shared managed skills with `--global`. (#74466) Thanks @Marvae. - Browser: surface pending and recently handled modal dialogs in snapshots, return `blockedByDialog` when an action opens a modal, and allow `browser dialog --dialog-id` to answer pending dialogs. +- Browser CLI: add `openclaw browser evaluate --timeout-ms` so long-running page functions can extend both the evaluate action and request timeout budgets. (#83447) Thanks @eefreenyc. - Codex app-server: scope OpenClaw prompt guidance by runtime surface so native Codex keeps Codex-owned base/personality instructions while OpenClaw contributes only runtime context, delivery guidance, and explicitly scoped command hints. (#83454) Thanks @100yenadmin. - Agents/tools: shorten built-in tool descriptions and schema hints across media, messaging, sessions, cron, Gateway, web, image/PDF, TTS, nodes, and plan tools while preserving routing guardrails. - Skills: add node inspector debugging, fused diagram generation, and throwaway spike workflow skills. diff --git a/docs/cli/browser.md b/docs/cli/browser.md index 9190ba810e0f..ffdc93cfa4fd 100644 --- a/docs/cli/browser.md +++ b/docs/cli/browser.md @@ -191,8 +191,12 @@ openclaw browser select OptionA OptionB openclaw browser fill --fields '[{"ref":"1","value":"Ada"}]' openclaw browser wait --text "Done" openclaw browser evaluate --fn '(el) => el.textContent' --ref +openclaw browser evaluate --timeout-ms 30000 --fn 'async () => { await window.ready; return true; }' ``` +Use `evaluate --timeout-ms ` when the page-side function may need longer +than the default evaluate timeout. + Action responses return the current raw `targetId` after action-triggered page replacement when OpenClaw can prove the replacement tab. Scripts should still store and pass `suggestedTargetId`/labels for long-lived workflows. diff --git a/docs/tools/browser-control.md b/docs/tools/browser-control.md index 8e258f491a36..0bb611278a37 100644 --- a/docs/tools/browser-control.md +++ b/docs/tools/browser-control.md @@ -197,6 +197,7 @@ openclaw browser dialog --dismiss --dialog-id d1 openclaw browser wait --text "Done" openclaw browser wait "#main" --url "**/dash" --load networkidle --fn "window.ready===true" openclaw browser evaluate --fn '(el) => el.textContent' --ref 7 +openclaw browser evaluate --timeout-ms 30000 --fn 'async () => { await window.ready; return true; }' openclaw browser highlight e12 openclaw browser trace start openclaw browser trace stop @@ -362,6 +363,8 @@ These are useful for "make the site behave like X" workflows: - `browser act kind=evaluate` / `openclaw browser evaluate` and `wait --fn` execute arbitrary JavaScript in the page context. Prompt injection can steer this. Disable it with `browser.evaluateEnabled=false` if you do not need it. +- Use `openclaw browser evaluate --timeout-ms ` when the page-side function + may need longer than the default evaluate timeout. - For logins and anti-bot notes (X/Twitter, etc.), see [Browser login + X/Twitter posting](/tools/browser-login). - Keep the Gateway/node host private (loopback or tailnet-only). - Remote CDP endpoints are powerful; tunnel and protect them. diff --git a/extensions/browser/src/cli/browser-cli-actions-input/register.form-wait-eval.test.ts b/extensions/browser/src/cli/browser-cli-actions-input/register.form-wait-eval.test.ts index ed39700fb794..88d11046605a 100644 --- a/extensions/browser/src/cli/browser-cli-actions-input/register.form-wait-eval.test.ts +++ b/extensions/browser/src/cli/browser-cli-actions-input/register.form-wait-eval.test.ts @@ -65,3 +65,28 @@ describe("browser action input wait command", () => { expect(options?.timeoutMs).toBeGreaterThan(21000); }); }); + +describe("browser action input evaluate command", () => { + beforeEach(() => { + mocks.callBrowserRequest.mockClear(); + getBrowserCliRuntimeCapture().resetRuntimeCapture(); + }); + + it("passes timeout-ms through to the evaluate action and outer request", async () => { + const program = createActionInputProgram(); + + await program.parseAsync( + ["browser", "evaluate", "--fn", "() => true", "--timeout-ms", "30000"], + { from: "user" }, + ); + + const request = mocks.callBrowserRequest.mock.calls.at(-1)?.[1] as + | { body?: { timeoutMs?: number } } + | undefined; + const options = mocks.callBrowserRequest.mock.calls.at(-1)?.[2] as + | { timeoutMs?: number } + | undefined; + expect(request?.body?.timeoutMs).toBe(30000); + expect(options?.timeoutMs).toBeGreaterThan(30000); + }); +}); diff --git a/extensions/browser/src/cli/browser-cli-actions-input/register.form-wait-eval.ts b/extensions/browser/src/cli/browser-cli-actions-input/register.form-wait-eval.ts index c643a01d2a53..c82410caca6d 100644 --- a/extensions/browser/src/cli/browser-cli-actions-input/register.form-wait-eval.ts +++ b/extensions/browser/src/cli/browser-cli-actions-input/register.form-wait-eval.ts @@ -107,6 +107,11 @@ export function registerBrowserFormWaitEvalCommands( .description("Evaluate a function against the page or a ref") .option("--fn ", "Function source, e.g. (el) => el.textContent") .option("--ref ", "Ref from snapshot") + .option( + "--timeout-ms ", + "How long to allow the evaluate function to run (default: 20000)", + (v: string) => Number(v), + ) .option("--target-id ", "CDP target id (or unique prefix)") .action(async (opts, cmd) => { const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts); @@ -116,6 +121,7 @@ export function registerBrowserFormWaitEvalCommands( return; } try { + const timeoutMs = Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : undefined; const result = await callBrowserAct<{ result?: unknown }>({ parent, profile, @@ -124,7 +130,9 @@ export function registerBrowserFormWaitEvalCommands( fn: opts.fn, ref: normalizeOptionalString(opts.ref), targetId: normalizeOptionalString(opts.targetId), + timeoutMs, }, + timeoutMs, }); if (parent?.json) { defaultRuntime.writeJson(result); diff --git a/src/gateway/server-methods/config.ts b/src/gateway/server-methods/config.ts index 1af90132f663..a92ccba28d63 100644 --- a/src/gateway/server-methods/config.ts +++ b/src/gateway/server-methods/config.ts @@ -433,6 +433,24 @@ export const configHandlers: GatewayRequestHandlers = { ); return; } + const restoredChangedPaths = diffConfigPaths(snapshot.config, restoredMerge.result); + const actor = resolveControlPlaneActor(client); + if (restoredChangedPaths.length === 0) { + context?.logGateway?.info( + `config.patch noop ${formatControlPlaneActor(actor)} (no changed paths)`, + ); + respond( + true, + { + ok: true, + noop: true, + path: resolveGatewayConfigPath(snapshot), + config: redactConfigObject(snapshot.config, schemaPatch.uiHints), + }, + undefined, + ); + return; + } const validated = validateConfigObjectWithPlugins(restoredMerge.result); if (!validated.ok) { respond( @@ -452,7 +470,6 @@ export const configHandlers: GatewayRequestHandlers = { return; } const changedPaths = diffConfigPaths(snapshot.config, validated.config); - const actor = resolveControlPlaneActor(client); // No-op: if the validated config is identical to the current config, // skip the file write and SIGUSR1 restart entirely. This avoids a full