fix(browser): accept top-level act fields with nested requests (#93674)

* fix(browser): accept top-level act fields with nested requests

Co-authored-by: Capivariano <11271294+angelusbr@users.noreply.github.com>

* fix(clownfish): address review for gitcrawl-416-autonomous-terminal-gap (1)

Co-authored-by: Capivariano <11271294+angelusbr@users.noreply.github.com>

---------

Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
Co-authored-by: Capivariano <11271294+angelusbr@users.noreply.github.com>
This commit is contained in:
Vincent Koc
2026-06-16 22:50:53 +08:00
committed by GitHub
parent acc375ff75
commit 75cdf22152
2 changed files with 91 additions and 2 deletions

View File

@@ -1435,6 +1435,73 @@ describe("browser tool act compatibility", () => {
expect(opts.profile).toBeUndefined();
});
it("backfills missing flattened fields into nested act requests", async () => {
const tool = createBrowserTool();
await tool.execute?.("call-1", {
action: "act",
kind: "click",
ref: "f1e3",
selector: "#title",
targetId: "tab-1",
timeoutMs: 5000,
request: {
kind: "click",
doubleClick: true,
},
});
const request = lastMockCallArg<{
kind?: string;
ref?: string;
selector?: string;
targetId?: string;
timeoutMs?: number;
doubleClick?: boolean;
}>(browserActionsMocks.browserAct, 1);
expect(request).toEqual({
kind: "click",
ref: "f1e3",
selector: "#title",
targetId: "tab-1",
timeoutMs: 5000,
doubleClick: true,
});
});
it("keeps nested act request fields authoritative when flattened fields differ", async () => {
const tool = createBrowserTool();
await tool.execute?.("call-1", {
action: "act",
kind: "click",
ref: "legacy-ref",
selector: "#legacy",
targetId: "legacy-tab",
timeoutMs: 5000,
request: {
kind: "click",
ref: "nested-ref",
selector: "#nested",
targetId: "nested-tab",
timeoutMs: 7000,
},
});
const request = lastMockCallArg<{
kind?: string;
ref?: string;
selector?: string;
targetId?: string;
timeoutMs?: number;
}>(browserActionsMocks.browserAct, 1);
expect(request).toEqual({
kind: "click",
ref: "nested-ref",
selector: "#nested",
targetId: "nested-tab",
timeoutMs: 7000,
});
});
it("applies configured browser action timeout when act timeout is omitted", async () => {
configMocks.loadConfig.mockReturnValue({ browser: { actionTimeoutMs: 45_000 } });

View File

@@ -163,6 +163,7 @@ function readTargetUrlParam(params: Record<string, unknown>) {
}
const LEGACY_BROWSER_ACT_REQUEST_KEYS = [
"kind",
"targetId",
"ref",
"doubleClick",
@@ -190,10 +191,31 @@ const LEGACY_BROWSER_ACT_REQUEST_KEYS = [
"timeoutMs",
] as const;
const LEGACY_BROWSER_ACT_SHARED_REQUEST_KEYS = new Set<
(typeof LEGACY_BROWSER_ACT_REQUEST_KEYS)[number]
>(["targetId"]);
function readActRequestParam(params: Record<string, unknown>) {
const requestParam = params.request;
if (requestParam && typeof requestParam === "object") {
return requestParam as Parameters<typeof browserAct>[1];
const request = { ...(requestParam as Record<string, unknown>) };
const hasMismatchedKind =
typeof request.kind === "string" &&
typeof params.kind === "string" &&
request.kind !== params.kind;
for (const key of LEGACY_BROWSER_ACT_REQUEST_KEYS) {
if (Object.hasOwn(request, key) || !Object.hasOwn(params, key)) {
continue;
}
// Flattened act fields are legacy shape repair. Only the tab scope is
// safe across kind mismatches; action-specific fields can corrupt the
// explicit nested request.
if (hasMismatchedKind && !LEGACY_BROWSER_ACT_SHARED_REQUEST_KEYS.has(key)) {
continue;
}
request[key] = params[key];
}
return request as Parameters<typeof browserAct>[1];
}
const kind = readStringParam(params, "kind");
@@ -201,7 +223,7 @@ function readActRequestParam(params: Record<string, unknown>) {
return undefined;
}
const request: Record<string, unknown> = { kind };
const request: Record<string, unknown> = {};
for (const key of LEGACY_BROWSER_ACT_REQUEST_KEYS) {
if (!Object.hasOwn(params, key)) {
continue;