fix(browser): allow page extraction in tab-bound runs (#116779)

* fix(browser): allow extraction in tab-bound runs

* test(browser): tighten tab-bound extraction coverage

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
Vincent Koc
2026-08-03 12:25:59 +08:00
committed by GitHub
parent d68d94b5d4
commit 9689d55c12
3 changed files with 82 additions and 7 deletions
@@ -29,13 +29,36 @@ describe("browser tab tool binding", () => {
});
});
it("rejects route, tab, and browser-wide action escapes", () => {
expect(() =>
applyBrowserTabToolBinding({ action: "snapshot", targetId: "target-b" }, binding),
).toThrow("cannot override its run-bound tab target");
expect(() =>
applyBrowserTabToolBinding({ action: "snapshot", node: "other" }, binding),
).toThrow("cannot override its run-bound node");
it("pins page extraction to the trusted tab and browser route", () => {
expect(
applyBrowserTabToolBinding(
{ action: "extract", query: "When does the release ship?" },
binding,
),
).toEqual({
action: "extract",
query: "When does the release ship?",
target: "node",
node: "desktop",
profile: "chrome",
targetId: "target-a",
});
});
it("rejects page extraction route escapes and browser-wide actions", () => {
for (const [input, error] of [
[{ targetId: "target-b" }, "cannot override its run-bound tab target"],
[{ profile: "other" }, "cannot override its run-bound profile"],
[{ node: "other" }, "cannot override its run-bound node"],
[{ target: "host" }, "cannot override its run-bound target"],
] as const) {
expect(() =>
applyBrowserTabToolBinding(
{ action: "extract", query: "When does the release ship?", ...input },
binding,
),
).toThrow(error);
}
expect(() => applyBrowserTabToolBinding({ action: "open" }, binding)).toThrow(
"unavailable in a tab-bound run",
);
@@ -52,6 +52,7 @@ const TAB_BOUND_ACTIONS = new Set([
"console",
"dialog",
"download",
"extract",
"focus",
"navigate",
"pdf",
@@ -3318,6 +3318,57 @@ describe("browser tool extract", () => {
beforeEach(resetBrowserToolMocks);
afterEach(() => vi.restoreAllMocks());
const runToolBinding = {
kind: "tab" as const,
tabId: 17,
target: "host" as const,
profile: "openclaw",
targetId: "target-a",
};
it("extracts from the trusted tab in a tab-bound run", async () => {
const tool = createBrowserTool({ agentId: "work", runToolBinding });
const result = await tool.execute?.("call-bound-extract", {
action: "extract",
query: "When does the release ship?",
});
expect(browserActionsMocks.browserPageContent).toHaveBeenCalledWith(undefined, {
targetId: "target-a",
profile: "openclaw",
timeoutMs: 60_000,
signal: undefined,
});
expect(toolCommonMocks.prepareSimpleCompletionModelForAgent).toHaveBeenCalledWith(
expect.objectContaining({
agentId: "work",
useUtilityModel: true,
allowMissingApiKeyModes: ["aws-sdk"],
}),
);
expect(result?.content[0]).toMatchObject({
type: "text",
text: expect.stringContaining("Friday."),
});
});
it("rejects extraction from a foreign tab before browser or model access", async () => {
const tool = createBrowserTool({ agentId: "work", runToolBinding });
await expect(
tool.execute?.("call-bound-extract-escape", {
action: "extract",
query: "When does the release ship?",
targetId: "target-b",
}),
).rejects.toThrow("cannot override its run-bound tab target");
expect(browserActionsMocks.browserPageContent).not.toHaveBeenCalled();
expect(toolCommonMocks.prepareSimpleCompletionModelForAgent).not.toHaveBeenCalled();
expect(toolCommonMocks.completeWithPreparedSimpleCompletionModel).not.toHaveBeenCalled();
});
it("captures, converts, and answers with the configured agent model", async () => {
toolCommonMocks.sanitizeHtml.mockResolvedValueOnce("<main>Ships Friday.</main>");
toolCommonMocks.htmlToMarkdown.mockReturnValueOnce({ text: "Ships **Friday**." });