fix(browser): thread snapshot timeoutMs through agent tool and helpers (#75702)

Summary:
- Threads browser snapshot `timeoutMs` through the agent action, client/proxy request, snapshot route plan, Ch ...  Playwright/CDP helpers, regression tests, changelog, and one JSDoc-only shrinkwrap script type annotation.
- Reproducibility: yes. source reproduction is high-confidence: current main accepts top-level browser `timeou ...  helpers drop it. I did not rerun the original macOS or Browserbase live scenario in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(browser): apply default snapshot timeout to proxy path and add Pl…
- PR branch already contained follow-up commit before automerge: docs(changelog): add browser snapshot timeout propagation fix entry
- PR branch already contained follow-up commit before automerge: fix(browser): thread snapshot timeoutMs through agent tool and helpers
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-7570…

Validation:
- ClawSweeper review passed for head 0eec196962.
- Required merge gates passed before the squash merge.

Prepared head SHA: 0eec196962
Review: https://github.com/openclaw/openclaw/pull/75702#issuecomment-4359923127

Co-authored-by: masatohoshino <g515hoshino@gmail.com>
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>
This commit is contained in:
Masato Hoshino
2026-05-24 02:15:58 +00:00
committed by GitHub
co-authored by clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com> takhoffman
parent d581415026
commit 069c7b87eb
15 changed files with 384 additions and 7 deletions
+1
View File
@@ -3259,6 +3259,7 @@ Docs: https://docs.openclaw.ai
- CLI/plugins: refresh persisted plugin registry policy in place for `plugins enable` and `plugins disable`, so routine toggles no longer rebuild and hash every plugin source when the target is already indexed. Thanks @vincentkoc.
- Windows/install: run npm from a writable installer temp directory and pin the Bedrock runtime dependency below a Windows ARM Node 24 npm resolver failure, so global OpenClaw installs no longer fail before onboarding. Thanks @mariozechner.
- CLI/plugins: scope install and enable slot selection to the selected plugin manifest/runtime fallback, so plugin installs no longer load every plugin runtime or broad status snapshot just to update memory/context slots. Thanks @vincentkoc.
- Browser/snapshot: propagate the configured snapshot timeout through the agent tool, Chrome MCP, and Playwright snapshot paths so snapshot actions honor the requested deadline instead of hanging. Fixes #72934. Thanks @masatohoshino.
- Plugins/TTS: keep bundled speech-provider discovery available on cold package Gateway paths and add bundled plugin matrix runtime probes for health, readiness, RPC, TTS discovery, and post-ready runtime-deps watchdog coverage. Refs #75283. Thanks @vincentkoc.
- Google Meet/Twilio: show delegated voice call ID, DTMF, and intro-greeting state in `googlemeet doctor`, and avoid claiming DTMF was sent when no Meet PIN sequence was configured. Refs #72478. Thanks @DougButdorf.
- Plugins/tools: prefer built bundled plugin code during tool discovery and skip channel runtime hydration while preserving companion provider registrations, reducing per-run plugin-tool prep cost without dropping executable plugin tools. Fixes #75290. Thanks @thanos-openclaw.
@@ -16,7 +16,10 @@ import {
resolveRuntimeImageSanitization,
wrapExternalContent,
} from "./browser-tool.runtime.js";
import { DEFAULT_BROWSER_ACTION_TIMEOUT_MS } from "./browser/constants.js";
import {
DEFAULT_BROWSER_ACTION_TIMEOUT_MS,
DEFAULT_BROWSER_SNAPSHOT_TIMEOUT_MS,
} from "./browser/constants.js";
const browserToolActionDeps = {
browserAct,
@@ -365,6 +368,8 @@ export async function executeSnapshotAction(params: {
: hasMaxChars
? maxChars
: undefined;
const snapshotTimeoutMs =
normalizePositiveTimeoutMs(input.timeoutMs) ?? DEFAULT_BROWSER_SNAPSHOT_TIMEOUT_MS;
const snapshotQuery = {
...(format ? { format } : {}),
targetId,
@@ -379,6 +384,7 @@ export async function executeSnapshotAction(params: {
labels,
urls,
mode,
timeoutMs: snapshotTimeoutMs,
};
let refsFallback: "role" | undefined;
const readSnapshot = async (query: typeof snapshotQuery) =>
@@ -388,6 +394,7 @@ export async function executeSnapshotAction(params: {
path: "/snapshot",
profile,
query,
timeoutMs: snapshotTimeoutMs,
})) as Awaited<ReturnType<typeof browserSnapshot>>)
: await browserToolActionDeps.browserSnapshot(baseUrl, {
...query,
@@ -554,6 +554,86 @@ describe("browser tool snapshot maxChars", () => {
expect(opts.refs).toBe("aria");
});
it("propagates input.timeoutMs into the direct browser snapshot call", async () => {
const tool = createBrowserTool();
await tool.execute?.("call-1", {
action: "snapshot",
target: "host",
snapshotFormat: "ai",
timeoutMs: 9000,
});
expect(browserClientMocks.browserSnapshot).toHaveBeenCalledWith(
undefined,
expect.objectContaining({
format: "ai",
timeoutMs: 9000,
}),
);
});
it("falls back to the default snapshot timeout in the direct browser snapshot call", async () => {
const tool = createBrowserTool();
await tool.execute?.("call-1", {
action: "snapshot",
target: "host",
snapshotFormat: "ai",
});
expect(browserClientMocks.browserSnapshot).toHaveBeenCalledWith(
undefined,
expect.objectContaining({
format: "ai",
// DEFAULT_BROWSER_SNAPSHOT_TIMEOUT_MS = 20_000.
timeoutMs: 20_000,
}),
);
});
it("propagates input.timeoutMs into the proxied browser snapshot request", async () => {
mockSingleBrowserProxyNode();
setResolvedBrowserProfiles({
user: { driver: "existing-session", attachOnly: true, color: "#00AA00" },
});
gatewayMocks.callGatewayTool.mockResolvedValue({
ok: true,
payload: {
result: {
ok: true,
format: "ai",
targetId: "t1",
url: "https://x",
snapshot: "ok",
},
files: [],
},
});
const tool = createBrowserTool();
await tool.execute?.("call-1", {
action: "snapshot",
target: "node",
profile: "user",
snapshotFormat: "ai",
timeoutMs: 7777,
});
expect(gatewayMocks.callGatewayTool).toHaveBeenCalledWith(
"node.invoke",
// proxy adds a 5_000 ms slack on top of the per-request timeout.
expect.objectContaining({ timeoutMs: 7777 + 5_000 }),
expect.objectContaining({
command: "browser.proxy",
params: expect.objectContaining({
method: "GET",
path: "/snapshot",
profile: "user",
query: expect.objectContaining({ timeoutMs: 7777 }),
timeoutMs: 7777,
}),
}),
);
});
it("uses config snapshot defaults when mode is not provided", async () => {
configMocks.loadConfig.mockReturnValue({
browser: { snapshotDefaults: { mode: "efficient" } },
@@ -13,6 +13,7 @@ import {
resetChromeMcpSessionsForTest,
setChromeMcpSessionFactoryForTest,
takeChromeMcpScreenshot,
takeChromeMcpSnapshot,
} from "./chrome-mcp.js";
type ToolCall = {
@@ -856,6 +857,27 @@ describe("chrome MCP page parsing", () => {
expect(tabs).toHaveLength(2);
});
it("forwards an explicit timeoutMs to take_snapshot via the callTool race", async () => {
vi.useFakeTimers();
const session = createFakeSession();
session.client.callTool = vi.fn(
async () => new Promise<never>(() => {}),
) as typeof session.client.callTool;
setChromeMcpSessionFactoryForTest(async () => session);
const snapshotPromise = takeChromeMcpSnapshot({
profileName: "chrome-live",
targetId: "1",
timeoutMs: 75,
});
void snapshotPromise.catch(() => {});
await vi.advanceTimersByTimeAsync(75);
await expect(snapshotPromise).rejects.toThrow(/Chrome MCP "take_snapshot".*timed out/);
vi.useRealTimers();
});
it("honors timeoutMs for ephemeral availability probes", async () => {
vi.useFakeTimers();
const closeMock = vi.fn().mockResolvedValue(undefined);
@@ -1019,6 +1019,7 @@ export async function takeChromeMcpSnapshot(params: {
profile?: ChromeMcpProfileOptions;
userDataDir?: string;
targetId: string;
timeoutMs?: number;
}): Promise<ChromeMcpSnapshotNode> {
const result = await callTool(
params.profileName,
@@ -1027,6 +1028,7 @@ export async function takeChromeMcpSnapshot(params: {
{
pageId: parsePageId(params.targetId),
},
{ timeoutMs: params.timeoutMs },
);
return extractSnapshot(result);
}
@@ -112,6 +112,33 @@ describe("browser client", () => {
expect(parsed.searchParams.get("refs")).toBe("aria");
});
it("forwards an explicit snapshot timeoutMs into the query string", async () => {
const calls: string[] = [];
stubSnapshotFetch(calls);
await browserSnapshot("http://127.0.0.1:18791", {
format: "ai",
timeoutMs: 4321,
});
const snapshotCall = calls.find((url) => url.includes("/snapshot?"));
expect(snapshotCall).toBeTruthy();
const parsed = new URL(snapshotCall as string);
expect(parsed.searchParams.get("timeoutMs")).toBe("4321");
});
it("falls back to the default snapshot timeout when none is supplied", async () => {
const calls: string[] = [];
stubSnapshotFetch(calls);
await browserSnapshot("http://127.0.0.1:18791", { format: "ai" });
const snapshotCall = calls.find((url) => url.includes("/snapshot?"));
expect(snapshotCall).toBeTruthy();
const parsed = new URL(snapshotCall as string);
expect(parsed.searchParams.get("timeoutMs")).toBe("20000");
});
it("omits format when the caller wants server-side snapshot capability defaults", async () => {
const calls: string[] = [];
stubSnapshotFetch(calls);
+8 -1
View File
@@ -6,6 +6,7 @@ import type {
BrowserTransport,
SnapshotAriaNode,
} from "./client.types.js";
import { DEFAULT_BROWSER_SNAPSHOT_TIMEOUT_MS } from "./constants.js";
import type { BrowserDoctorReport } from "./doctor.js";
export type { BrowserStatus, BrowserTab, BrowserTransport } from "./client.types.js";
@@ -320,6 +321,7 @@ export async function browserSnapshot(
urls?: boolean;
mode?: "efficient";
profile?: string;
timeoutMs?: number;
},
): Promise<SnapshotResult> {
const q = new URLSearchParams();
@@ -365,8 +367,13 @@ export async function browserSnapshot(
if (opts.profile) {
q.set("profile", opts.profile);
}
const resolvedTimeoutMs =
typeof opts.timeoutMs === "number" && Number.isFinite(opts.timeoutMs) && opts.timeoutMs > 0
? Math.floor(opts.timeoutMs)
: DEFAULT_BROWSER_SNAPSHOT_TIMEOUT_MS;
q.set("timeoutMs", String(resolvedTimeoutMs));
return await fetchBrowserJson<SnapshotResult>(withBaseUrl(baseUrl, `/snapshot?${q.toString()}`), {
timeoutMs: 20000,
timeoutMs: resolvedTimeoutMs,
});
}
@@ -7,6 +7,7 @@ export const DEFAULT_BROWSER_ACTION_TIMEOUT_MS = 60_000;
export const DEFAULT_BROWSER_LOCAL_LAUNCH_TIMEOUT_MS = 15_000;
export const DEFAULT_BROWSER_LOCAL_CDP_READY_TIMEOUT_MS = 8_000;
export const DEFAULT_BROWSER_SCREENSHOT_TIMEOUT_MS = 20_000;
export const DEFAULT_BROWSER_SNAPSHOT_TIMEOUT_MS = 20_000;
export const DEFAULT_BROWSER_TAB_CLEANUP_IDLE_MINUTES = 120;
export const DEFAULT_BROWSER_TAB_CLEANUP_MAX_TABS_PER_SESSION = 8;
export const DEFAULT_BROWSER_TAB_CLEANUP_SWEEP_MINUTES = 5;
@@ -92,6 +92,45 @@ describe("pw-tools-core aria snapshot storage", () => {
});
});
it("races snapshotAriaViaPlaywright against an explicit timeoutMs", async () => {
vi.useFakeTimers();
try {
const page = { id: "page-1" };
getPageForTargetId.mockResolvedValue(page);
withPageScopedCdpClient.mockImplementation(() => new Promise(() => {}));
const mod = await import("./pw-tools-core.snapshot.js");
const promise = mod.snapshotAriaViaPlaywright({
cdpUrl: "http://127.0.0.1:9222",
targetId: "tab-1",
timeoutMs: 750,
});
void promise.catch(() => {});
await vi.advanceTimersByTimeAsync(750);
await expect(promise).rejects.toThrow(/Aria snapshot via Playwright timed out/);
} finally {
vi.useRealTimers();
}
});
it("forwards an explicit timeoutMs into the role-aria Playwright ariaSnapshot call", async () => {
const ariaSnapshotMock = vi.fn().mockResolvedValue("");
const page = { ariaSnapshot: ariaSnapshotMock };
getPageForTargetId.mockResolvedValue(page);
const mod = await import("./pw-tools-core.snapshot.js");
await mod.snapshotRoleViaPlaywright({
cdpUrl: "http://127.0.0.1:9222",
targetId: "tab-1",
refsMode: "aria",
timeoutMs: 8888,
});
expect(ariaSnapshotMock).toHaveBeenCalledWith({ mode: "ai", timeout: 8888 });
});
it("stores role fallback metadata when backend markers are unavailable", async () => {
const page = { id: "page-1" };
const mod = await import("./pw-tools-core.snapshot.js");
@@ -140,6 +140,7 @@ export async function snapshotAriaViaPlaywright(opts: {
cdpUrl: string;
targetId?: string;
limit?: number;
timeoutMs?: number;
ssrfPolicy?: SsrFPolicy;
}): Promise<{ nodes: AriaSnapshotNode[] }> {
const limit = Math.max(1, Math.min(2000, Math.floor(opts.limit ?? 500)));
@@ -157,7 +158,11 @@ export async function snapshotAriaViaPlaywright(opts: {
targetId: opts.targetId,
});
}
const res = (await withPageScopedCdpClient({
const ariaTimeoutMs =
typeof opts.timeoutMs === "number" && Number.isFinite(opts.timeoutMs) && opts.timeoutMs > 0
? Math.max(500, Math.min(60_000, Math.floor(opts.timeoutMs)))
: undefined;
const collectAxTree = withPageScopedCdpClient({
cdpUrl: opts.cdpUrl,
page,
targetId: opts.targetId,
@@ -167,7 +172,23 @@ export async function snapshotAriaViaPlaywright(opts: {
nodes?: RawAXNode[];
};
},
})) as {
});
const res = (await (ariaTimeoutMs === undefined
? collectAxTree
: (() => {
let timer: ReturnType<typeof setTimeout> | undefined;
const timeout = new Promise<never>((_, reject) => {
timer = setTimeout(() => {
reject(new Error(`Aria snapshot via Playwright timed out after ${ariaTimeoutMs}ms.`));
}, ariaTimeoutMs);
timer.unref?.();
});
return Promise.race([collectAxTree, timeout]).finally(() => {
if (timer) {
clearTimeout(timer);
}
});
})())) as {
nodes?: RawAXNode[];
};
const nodes = Array.isArray(res?.nodes) ? res.nodes : [];
@@ -241,6 +262,7 @@ export async function snapshotRoleViaPlaywright(opts: {
refsMode?: "role" | "aria";
options?: RoleSnapshotOptions;
urls?: boolean;
timeoutMs?: number;
ssrfPolicy?: SsrFPolicy;
}): Promise<{
snapshot: string;
@@ -262,13 +284,15 @@ export async function snapshotRoleViaPlaywright(opts: {
});
}
const ariaSnapshotTimeout = Math.max(500, Math.min(60_000, Math.floor(opts.timeoutMs ?? 5000)));
if (opts.refsMode === "aria") {
if (normalizeOptionalString(opts.selector) || normalizeOptionalString(opts.frameSelector)) {
throw new Error("refs=aria does not support selector/frame snapshots yet.");
}
const snapshot = await page.ariaSnapshot({
mode: "ai",
timeout: 5000,
timeout: ariaSnapshotTimeout,
});
const built = buildRoleSnapshotFromAiSnapshot(snapshot, opts.options);
const snapshotWithUrls = opts.urls
@@ -298,7 +322,7 @@ export async function snapshotRoleViaPlaywright(opts: {
? page.locator(selector)
: page.locator(":root");
const ariaSnapshot = await locator.ariaSnapshot();
const ariaSnapshot = await locator.ariaSnapshot({ timeout: ariaSnapshotTimeout });
const built = buildRoleSnapshotFromAriaSnapshot(ariaSnapshot ?? "", opts.options);
const snapshotWithUrls = opts.urls
? appendSnapshotUrls(built.snapshot, await collectSnapshotUrls(page))
@@ -47,4 +47,31 @@ describe("resolveSnapshotPlan", () => {
expect(plan.urls).toBe(true);
expect(plan.wantsRoleSnapshot).toBe(true);
});
it("parses timeoutMs from the snapshot query string", () => {
const plan = resolveSnapshotPlan({
profile: profile("openclaw"),
query: { timeoutMs: "12345" },
hasPlaywright: true,
});
expect(plan.timeoutMs).toBe(12345);
});
it("ignores non-positive timeoutMs values", () => {
expect(
resolveSnapshotPlan({
profile: profile("openclaw"),
query: { timeoutMs: "0" },
hasPlaywright: true,
}).timeoutMs,
).toBeUndefined();
expect(
resolveSnapshotPlan({
profile: profile("openclaw"),
query: { timeoutMs: "not-a-number" },
hasPlaywright: true,
}).timeoutMs,
).toBeUndefined();
});
});
@@ -32,6 +32,7 @@ type BrowserSnapshotPlan = {
refsMode?: "aria" | "role";
selectorValue?: string;
frameSelectorValue?: string;
timeoutMs?: number;
wantsRoleSnapshot: boolean;
};
@@ -79,6 +80,11 @@ export function resolveSnapshotPlan(params: {
depthRaw ?? (mode === "efficient" ? DEFAULT_AI_SNAPSHOT_EFFICIENT_DEPTH : undefined);
const selectorValue = normalizeOptionalString(toStringOrEmpty(params.query.selector));
const frameSelectorValue = normalizeOptionalString(toStringOrEmpty(params.query.frame));
const timeoutMsRaw = toNumber(params.query.timeoutMs);
const timeoutMs =
timeoutMsRaw !== undefined && Number.isFinite(timeoutMsRaw) && timeoutMsRaw > 0
? Math.max(1, Math.floor(timeoutMsRaw))
: undefined;
return {
format,
@@ -93,6 +99,7 @@ export function resolveSnapshotPlan(params: {
refsMode,
selectorValue,
frameSelectorValue,
timeoutMs,
wantsRoleSnapshot:
labels === true ||
urls === true ||
@@ -0,0 +1,125 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createBrowserRouteApp, createBrowserRouteResponse } from "./test-helpers.js";
const cdpMocks = vi.hoisted(() => ({
captureScreenshot: vi.fn(),
snapshotAria: vi.fn(async () => ({ nodes: [] })),
snapshotRoleViaCdp: vi.fn(async () => ({
snapshot: "button Continue",
refs: {},
stats: { lines: 1, chars: 15, refs: 0, interactive: 0 },
})),
}));
const profileContext = vi.hoisted(() => ({
profile: {
name: "openclaw",
driver: "openclaw" as const,
cdpPort: 18_800,
cdpUrl: "http://127.0.0.1:18800",
cdpHost: "127.0.0.1",
cdpIsLoopback: true,
color: "#FF4500",
headless: false,
attachOnly: false,
},
ensureTabAvailable: vi.fn(async () => ({
targetId: "tab-1",
url: "https://example.com",
wsUrl: "ws://127.0.0.1:18800/devtools/page/tab-1",
})),
}));
vi.mock("../cdp.js", () => ({
captureScreenshot: cdpMocks.captureScreenshot,
snapshotAria: cdpMocks.snapshotAria,
snapshotRoleViaCdp: cdpMocks.snapshotRoleViaCdp,
}));
vi.mock("../chrome-mcp.js", () => ({
evaluateChromeMcpScript: vi.fn(),
navigateChromeMcpPage: vi.fn(),
takeChromeMcpScreenshot: vi.fn(),
takeChromeMcpSnapshot: vi.fn(),
}));
vi.mock("../navigation-guard.js", () => ({
assertBrowserNavigationAllowed: vi.fn(async () => {}),
assertBrowserNavigationResultAllowed: vi.fn(async () => {}),
withBrowserNavigationPolicy: vi.fn(() => ({})),
}));
vi.mock("../screenshot.js", () => ({
DEFAULT_BROWSER_SCREENSHOT_MAX_BYTES: 128,
DEFAULT_BROWSER_SCREENSHOT_MAX_SIDE: 64,
normalizeBrowserScreenshot: vi.fn(async (buffer: Buffer) => ({
buffer,
contentType: "image/png",
})),
}));
vi.mock("../../media/store.js", () => ({
ensureMediaDir: vi.fn(async () => {}),
saveMediaBuffer: vi.fn(async () => ({ path: "/tmp/fake.png" })),
}));
vi.mock("./agent.shared.js", () => ({
getPwAiModule: vi.fn(async () => null),
handleRouteError: vi.fn(),
readBody: vi.fn((req: { body?: unknown }) => req.body ?? {}),
requirePwAi: vi.fn(async () => null),
resolveProfileContext: vi.fn(() => profileContext),
withPlaywrightRouteContext: vi.fn(),
withRouteTabContext: vi.fn(),
}));
const { registerBrowserAgentSnapshotRoutes } = await import("./agent.snapshot.js");
function getSnapshotHandler() {
const { app, getHandlers } = createBrowserRouteApp();
registerBrowserAgentSnapshotRoutes(app, {
state: () => ({ resolved: {} }),
} as never);
const handler = getHandlers.get("/snapshot");
expect(handler).toBeTypeOf("function");
return handler;
}
describe("browser agent snapshot timeout routing", () => {
beforeEach(() => {
cdpMocks.captureScreenshot.mockClear();
cdpMocks.snapshotAria.mockClear();
cdpMocks.snapshotRoleViaCdp.mockClear();
profileContext.ensureTabAvailable.mockClear();
});
it("passes timeoutMs to direct CDP aria snapshots", async () => {
const handler = getSnapshotHandler();
const response = createBrowserRouteResponse();
await handler?.({ params: {}, query: { format: "aria", timeoutMs: "4321" } }, response.res);
expect(response.statusCode).toBe(200);
expect(cdpMocks.snapshotAria).toHaveBeenCalledWith(
expect.objectContaining({
wsUrl: "ws://127.0.0.1:18800/devtools/page/tab-1",
timeoutMs: 4321,
}),
);
});
it("passes timeoutMs to direct CDP role snapshots", async () => {
const handler = getSnapshotHandler();
const response = createBrowserRouteResponse();
await handler?.({ params: {}, query: { format: "ai", timeoutMs: "9876" } }, response.res);
expect(response.statusCode).toBe(200);
expect(cdpMocks.snapshotRoleViaCdp).toHaveBeenCalledWith(
expect.objectContaining({
wsUrl: "ws://127.0.0.1:18800/devtools/page/tab-1",
timeoutMs: 9876,
}),
);
});
});
@@ -577,6 +577,7 @@ export function registerBrowserAgentSnapshotRoutes(
profileName: profileCtx.profile.name,
profile: profileCtx.profile,
targetId: tab.targetId,
timeoutMs: plan.timeoutMs,
});
if (plan.format === "aria") {
return res.json({
@@ -623,6 +624,7 @@ export function registerBrowserAgentSnapshotRoutes(
profile: profileCtx.profile,
targetId: tab.targetId,
format: "png",
timeoutMs: plan.timeoutMs,
});
const normalized = await normalizeBrowserScreenshot(labeled, {
maxSide: DEFAULT_BROWSER_SCREENSHOT_MAX_SIDE,
@@ -683,6 +685,7 @@ export function registerBrowserAgentSnapshotRoutes(
refsMode: plan.refsMode,
ssrfPolicy: ctx.state().resolved.ssrfPolicy,
urls: plan.urls,
timeoutMs: plan.timeoutMs,
options: {
interactive: plan.interactive ?? undefined,
compact: plan.compact ?? undefined,
@@ -700,6 +703,7 @@ export function registerBrowserAgentSnapshotRoutes(
return await snapshotRoleViaCdp({
wsUrl: tab.wsUrl,
urls: plan.urls,
timeoutMs: plan.timeoutMs,
options: {
interactive: plan.interactive ?? undefined,
compact: plan.compact ?? undefined,
@@ -725,6 +729,7 @@ export function registerBrowserAgentSnapshotRoutes(
targetId: tab.targetId,
ssrfPolicy: ctx.state().resolved.ssrfPolicy,
urls: plan.urls,
timeoutMs: plan.timeoutMs,
...(typeof plan.resolvedMaxChars === "number"
? { maxChars: plan.resolvedMaxChars }
: {}),
@@ -743,6 +748,7 @@ export function registerBrowserAgentSnapshotRoutes(
targetId: tab.targetId,
refs: "refs" in snap ? snap.refs : {},
type: "png",
timeoutMs: plan.timeoutMs,
});
const normalized = await normalizeBrowserScreenshot(labeled.buffer, {
maxSide: DEFAULT_BROWSER_SCREENSHOT_MAX_SIDE,
@@ -797,11 +803,12 @@ export function registerBrowserAgentSnapshotRoutes(
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
limit: plan.limit,
timeoutMs: plan.timeoutMs,
ssrfPolicy: ctx.state().resolved.ssrfPolicy,
});
});
})()
: snapshotAria({ wsUrl: tab.wsUrl ?? "", limit: plan.limit });
: snapshotAria({ wsUrl: tab.wsUrl ?? "", limit: plan.limit, timeoutMs: plan.timeoutMs });
const resolved = await Promise.resolve(snap);
if (!resolved) {
+1
View File
@@ -242,6 +242,7 @@ function disableShrinkwrappedOverrideConflictSources(lockfile, overrideRules) {
if (!packages || typeof packages !== "object") {
return [];
}
/** @type {Set<string>} */
const disabled = new Set();
for (const violation of collectOverrideViolations(lockfile, overrideRules)) {
const ancestors = violation.packagePath.slice(0, -1).toReversed();