






























@@ -15,6 +15,7 @@ import {
1515resolveProfile,
1616wrapExternalContent,
1717} from "./browser-tool.runtime.js";
18+import { DEFAULT_BROWSER_ACTION_TIMEOUT_MS } from "./browser/constants.js";
18191920const browserToolActionDeps = {
2021 browserAct,
@@ -25,6 +26,94 @@ const browserToolActionDeps = {
2526 loadConfig,
2627};
272829+const BROWSER_ACT_REQUEST_TIMEOUT_SLACK_MS = 5_000;
30+31+type BrowserActRequest = Parameters<typeof browserAct>[1];
32+type BrowserActRequestWithTimeout = BrowserActRequest & { timeoutMs?: number };
33+34+function normalizePositiveTimeoutMs(value: unknown): number | undefined {
35+return typeof value === "number" && Number.isFinite(value) && value > 0
36+ ? Math.floor(value)
37+ : undefined;
38+}
39+40+function supportsBrowserActTimeout(request: BrowserActRequest): boolean {
41+switch (request.kind) {
42+case "click":
43+case "type":
44+case "hover":
45+case "scrollIntoView":
46+case "drag":
47+case "select":
48+case "fill":
49+case "evaluate":
50+case "wait":
51+return true;
52+default:
53+return false;
54+}
55+}
56+57+function existingSessionRejectsActTimeout(request: BrowserActRequest): boolean {
58+switch (request.kind) {
59+case "type":
60+case "hover":
61+case "scrollIntoView":
62+case "drag":
63+case "select":
64+case "fill":
65+case "evaluate":
66+return true;
67+default:
68+return false;
69+}
70+}
71+72+function usesExistingSessionProfile(profileName: string | undefined): boolean {
73+const cfg = browserToolActionDeps.loadConfig();
74+const resolved = resolveBrowserConfig(cfg.browser, cfg);
75+const profile = resolveProfile(resolved, profileName ?? resolved.defaultProfile);
76+return profile ? getBrowserProfileCapabilities(profile).usesChromeMcp : false;
77+}
78+79+function withConfiguredActTimeout(
80+request: BrowserActRequest,
81+profileName: string | undefined,
82+): BrowserActRequest {
83+const typedRequest = request as BrowserActRequestWithTimeout;
84+if (normalizePositiveTimeoutMs(typedRequest.timeoutMs) !== undefined) {
85+return request;
86+}
87+if (!supportsBrowserActTimeout(request)) {
88+return request;
89+}
90+if (existingSessionRejectsActTimeout(request) && usesExistingSessionProfile(profileName)) {
91+return request;
92+}
93+94+const cfg = browserToolActionDeps.loadConfig();
95+const configuredTimeout =
96+normalizePositiveTimeoutMs(cfg.browser?.actionTimeoutMs) ?? DEFAULT_BROWSER_ACTION_TIMEOUT_MS;
97+return { ...typedRequest, timeoutMs: configuredTimeout } as BrowserActRequest;
98+}
99+100+function resolveActProxyTimeoutMs(request: BrowserActRequest): number | undefined {
101+const candidateTimeouts: number[] = [];
102+const explicitTimeout = normalizePositiveTimeoutMs(
103+(request as BrowserActRequestWithTimeout).timeoutMs,
104+);
105+if (explicitTimeout !== undefined) {
106+candidateTimeouts.push(explicitTimeout + BROWSER_ACT_REQUEST_TIMEOUT_SLACK_MS);
107+}
108+if (request.kind === "wait") {
109+const waitDuration = normalizePositiveTimeoutMs(request.timeMs);
110+if (waitDuration !== undefined) {
111+candidateTimeouts.push(waitDuration + BROWSER_ACT_REQUEST_TIMEOUT_SLACK_MS);
112+}
113+}
114+return candidateTimeouts.length ? Math.max(...candidateTimeouts) : undefined;
115+}
116+28117export const __testing = {
29118setDepsForTest(
30119overrides: Partial<{
@@ -408,32 +497,34 @@ export async function executeConsoleAction(params: {
408497}
409498410499export async function executeActAction(params: {
411-request: Parameters<typeof browserAct>[1];
500+request: BrowserActRequest;
412501baseUrl?: string;
413502profile?: string;
414503proxyRequest: BrowserProxyRequest | null;
415504onTabActivity?: (targetId: string | undefined) => void;
416505}): Promise<AgentToolResult<unknown>> {
417506const { request, baseUrl, profile, proxyRequest } = params;
507+const effectiveRequest = withConfiguredActTimeout(request, profile);
418508try {
419509const result = proxyRequest
420510 ? await proxyRequest({
421511method: "POST",
422512path: "/act",
423513 profile,
424-body: request,
514+body: effectiveRequest,
515+timeoutMs: resolveActProxyTimeoutMs(effectiveRequest),
425516})
426- : await browserToolActionDeps.browserAct(baseUrl, request, {
517+ : await browserToolActionDeps.browserAct(baseUrl, effectiveRequest, {
427518 profile,
428519});
429520params.onTabActivity?.(
430521readStringValue((result as { targetId?: unknown }).targetId) ??
431-readStringValue(request.targetId),
522+readStringValue(effectiveRequest.targetId),
432523);
433524return jsonResult(result);
434525} catch (err) {
435526if (isChromeStaleTargetError(profile, err)) {
436-const retryRequest = stripTargetIdFromActRequest(request);
527+const retryRequest = stripTargetIdFromActRequest(effectiveRequest);
437528const tabs = proxyRequest
438529 ? ((
439530(await proxyRequest({
@@ -445,14 +536,15 @@ export async function executeActAction(params: {
445536 : await browserToolActionDeps.browserTabs(baseUrl, { profile }).catch(() => []);
446537// Some user-browser targetIds can go stale between snapshots and actions.
447538// Only retry safe read-only actions, and only when exactly one tab remains attached.
448-if (retryRequest && canRetryChromeActWithoutTargetId(request) && tabs.length === 1) {
539+if (retryRequest && canRetryChromeActWithoutTargetId(effectiveRequest) && tabs.length === 1) {
449540try {
450541const retryResult = proxyRequest
451542 ? await proxyRequest({
452543method: "POST",
453544path: "/act",
454545 profile,
455546body: retryRequest,
547+timeoutMs: resolveActProxyTimeoutMs(retryRequest),
456548})
457549 : await browserToolActionDeps.browserAct(baseUrl, retryRequest, {
458550 profile,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。