fix(browser): reject existing-session type timeouts

This commit is contained in:
Peter Steinberger
2026-04-24 08:29:25 +01:00
parent aa21d4ea7e
commit a437666a37
8 changed files with 29 additions and 6 deletions

View File

@@ -30,7 +30,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Browser/tool: tell agents not to pass per-call `timeoutMs` on existing-session evaluate and other Chrome MCP actions that reject timeout overrides.
- Browser/tool: tell agents not to pass per-call `timeoutMs` on existing-session type, evaluate, and other Chrome MCP actions that reject timeout overrides.
- Voice-call/Telnyx: preserve inbound/outbound callback metadata and read transcription text from Telnyx's current `transcription_data` payload.
- Codex harness: send verbose tool progress to chat channels for native app-server runs, matching the Pi harness `/verbose on` and `/verbose full` behavior. (#70966) Thanks @jalehman.
- Codex models: fetch paginated Codex app-server model catalogs, mark truncated `/codex models` output, and keep ChatGPT OAuth defaults on the `openai-codex/gpt-5.5` route instead of the OpenAI API-key route.

View File

@@ -482,6 +482,7 @@ Common signatures:
- `existing-session file uploads do not support element selectors; use ref/inputRef.` → Chrome MCP upload hooks need snapshot refs, not CSS selectors.
- `existing-session file uploads currently support one file at a time.` → send one upload per call on Chrome MCP profiles.
- `existing-session dialog handling does not support timeoutMs.` → dialog hooks on Chrome MCP profiles do not support timeout overrides.
- `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.
- `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.
- `response body is not supported for existing-session profiles yet.``responsebody` still requires a managed browser or raw CDP profile.
- 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.

View File

@@ -484,7 +484,7 @@ Notes:
Compared to the managed `openclaw` profile, existing-session drivers are more constrained:
- **Screenshots** — page captures and `--ref` element captures work; CSS `--element` selectors do not. `--full-page` cannot combine with `--ref` or `--element`. Playwright is not required for page or ref-based element screenshots.
- **Actions** — `click`, `type`, `hover`, `scrollIntoView`, `drag`, and `select` require snapshot refs (no CSS selectors). `click` is left-button only. `type` does not support `slowly=true`; use `fill` or `press`. `press` does not support `delayMs`. `hover`, `scrollIntoView`, `drag`, `select`, `fill`, and `evaluate` do not support per-call timeouts. `select` accepts a single value.
- **Actions** — `click`, `type`, `hover`, `scrollIntoView`, `drag`, and `select` require snapshot refs (no CSS selectors). `click` is left-button only. `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 timeouts. `select` accepts a single value.
- **Wait / upload / dialog** — `wait --url` supports exact, substring, and glob patterns; `wait --load networkidle` is not supported. Upload hooks require `ref` or `inputRef`, one file at a time, no CSS `element`. Dialog hooks do not support timeout overrides.
- **Managed-only features** — batch actions, PDF export, download interception, and `responsebody` still require the managed browser path.

View File

@@ -304,7 +304,7 @@ describe("browser tool description", () => {
const tool = createBrowserTool();
expect(tool.description).toContain('profile="user"');
expect(tool.description).toContain("omit timeoutMs on act:evaluate");
expect(tool.description).toContain("omit timeoutMs on act:type");
expect(tool.description).toContain("existing-session profiles");
});
});

View File

@@ -382,7 +382,7 @@ export function createBrowserTool(opts?: {
"Control the browser via OpenClaw's browser control server (status/start/stop/profiles/tabs/open/snapshot/screenshot/actions).",
"Browser choice: omit profile by default for the isolated OpenClaw-managed browser (`openclaw`).",
'For the logged-in user browser, use profile="user". A supported Chromium-based browser (v144+) must be running on the selected host or browser node. Use only when existing logins/cookies matter and the user is present.',
'For profile="user" or other existing-session profiles, omit timeoutMs on act:evaluate, hover, scrollIntoView, drag, select, and fill; that driver rejects per-call timeout overrides for those actions.',
'For profile="user" or 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.',
'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".',
"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 stable, self-resolving refs across calls, use snapshot with refs="aria" (Playwright aria-ref ids). Default refs="role" are role+name-based.',

View File

@@ -286,7 +286,7 @@ function getExistingSessionUnsupportedMessage(action: BrowserActRequest): string
if (action.slowly) {
return EXISTING_SESSION_LIMITS.act.typeSlowly;
}
return null;
return action.timeoutMs ? EXISTING_SESSION_LIMITS.act.typeTimeout : null;
case "press":
return action.delayMs ? EXISTING_SESSION_LIMITS.act.pressDelay : null;
case "hover":

View File

@@ -12,6 +12,7 @@ const chromeMcpMocks = vi.hoisted(() => ({
evaluateChromeMcpScript: vi.fn(
async (_params: { profileName: string; targetId: string; fn: string }) => true,
),
fillChromeMcpElement: vi.fn(async () => {}),
navigateChromeMcpPage: vi.fn(async ({ url }: { url: string }) => ({ url })),
takeChromeMcpScreenshot: vi.fn(async () => Buffer.from("png")),
takeChromeMcpSnapshot: vi.fn(async () => ({
@@ -33,7 +34,7 @@ vi.mock("../chrome-mcp.js", () => ({
closeChromeMcpTab: vi.fn(async () => {}),
dragChromeMcpElement: vi.fn(async () => {}),
evaluateChromeMcpScript: chromeMcpMocks.evaluateChromeMcpScript,
fillChromeMcpElement: vi.fn(async () => {}),
fillChromeMcpElement: chromeMcpMocks.fillChromeMcpElement,
fillChromeMcpForm: vi.fn(async () => {}),
hoverChromeMcpElement: vi.fn(async () => {}),
navigateChromeMcpPage: chromeMcpMocks.navigateChromeMcpPage,
@@ -109,6 +110,7 @@ describe("existing-session browser routes", () => {
routeState.profileCtx.listTabs.mockClear();
chromeMcpMocks.clickChromeMcpElement.mockClear();
chromeMcpMocks.evaluateChromeMcpScript.mockReset();
chromeMcpMocks.fillChromeMcpElement.mockClear();
chromeMcpMocks.navigateChromeMcpPage.mockClear();
chromeMcpMocks.takeChromeMcpScreenshot.mockClear();
chromeMcpMocks.takeChromeMcpSnapshot.mockClear();
@@ -238,6 +240,25 @@ describe("existing-session browser routes", () => {
expect(chromeMcpMocks.evaluateChromeMcpScript).not.toHaveBeenCalled();
});
it("fails closed for existing-session type timeout overrides", async () => {
const handler = getActPostHandler();
const response = createBrowserRouteResponse();
await handler?.(
{
params: {},
query: {},
body: { kind: "type", ref: "input-1", text: "hello", timeoutMs: 1234 },
},
response.res,
);
expect(response.statusCode).toBe(501);
expect(response.body).toMatchObject({
error: expect.stringContaining("type does not support timeoutMs"),
});
expect(chromeMcpMocks.fillChromeMcpElement).not.toHaveBeenCalled();
});
it("supports glob URL waits for existing-session profiles", async () => {
chromeMcpMocks.evaluateChromeMcpScript.mockReset();
chromeMcpMocks.evaluateChromeMcpScript.mockImplementation(

View File

@@ -5,6 +5,7 @@ export const EXISTING_SESSION_LIMITS = {
"existing-session click currently supports left-click only (no button overrides/modifiers).",
typeSelector: "existing-session type does not support selector targeting yet; use ref.",
typeSlowly: "existing-session type does not support slowly=true; use fill/press instead.",
typeTimeout: "existing-session type does not support timeoutMs overrides.",
pressDelay: "existing-session press does not support delayMs.",
hoverSelector: "existing-session hover does not support selector targeting yet; use ref.",
hoverTimeout: "existing-session hover does not support timeoutMs overrides.",