fix(browser): close tracked tabs on session cleanup (#36666)

This commit is contained in:
Vignesh Natarajan
2026-03-05 16:36:29 -08:00
parent 6dfd39c32f
commit 06a229f98f
8 changed files with 412 additions and 1 deletions

View File

@@ -129,6 +129,7 @@ export function createOpenClawTools(options?: {
createBrowserTool({
sandboxBridgeUrl: options?.sandboxBrowserBridgeUrl,
allowHostControl: options?.allowHostBrowserControl,
agentSessionKey: options?.agentSessionKey,
}),
createCanvasTool({ config: options?.config }),
createNodesTool({

View File

@@ -82,6 +82,12 @@ const configMocks = vi.hoisted(() => ({
}));
vi.mock("../../config/config.js", () => configMocks);
const sessionTabRegistryMocks = vi.hoisted(() => ({
trackSessionBrowserTab: vi.fn(),
untrackSessionBrowserTab: vi.fn(),
}));
vi.mock("../../browser/session-tab-registry.js", () => sessionTabRegistryMocks);
const toolCommonMocks = vi.hoisted(() => ({
imageResultFromFile: vi.fn(),
}));
@@ -292,6 +298,23 @@ describe("browser tool url alias support", () => {
);
});
it("tracks opened tabs when session context is available", async () => {
browserClientMocks.browserOpenTab.mockResolvedValueOnce({
targetId: "tab-123",
title: "Example",
url: "https://example.com",
});
const tool = createBrowserTool({ agentSessionKey: "agent:main:main" });
await tool.execute?.("call-1", { action: "open", url: "https://example.com" });
expect(sessionTabRegistryMocks.trackSessionBrowserTab).toHaveBeenCalledWith({
sessionKey: "agent:main:main",
targetId: "tab-123",
baseUrl: undefined,
profile: undefined,
});
});
it("accepts url alias for navigate", async () => {
const tool = createBrowserTool();
await tool.execute?.("call-1", {
@@ -317,6 +340,26 @@ describe("browser tool url alias support", () => {
"targetUrl required",
);
});
it("untracks explicit tab close for tracked sessions", async () => {
const tool = createBrowserTool({ agentSessionKey: "agent:main:main" });
await tool.execute?.("call-1", {
action: "close",
targetId: "tab-xyz",
});
expect(browserClientMocks.browserCloseTab).toHaveBeenCalledWith(
undefined,
"tab-xyz",
expect.objectContaining({ profile: undefined }),
);
expect(sessionTabRegistryMocks.untrackSessionBrowserTab).toHaveBeenCalledWith({
sessionKey: "agent:main:main",
targetId: "tab-xyz",
baseUrl: undefined,
profile: undefined,
});
});
});
describe("browser tool act compatibility", () => {

View File

@@ -19,6 +19,10 @@ import {
import { resolveBrowserConfig } from "../../browser/config.js";
import { DEFAULT_UPLOAD_DIR, resolveExistingPathsWithinRoot } from "../../browser/paths.js";
import { applyBrowserProxyPaths, persistBrowserProxyFiles } from "../../browser/proxy-files.js";
import {
trackSessionBrowserTab,
untrackSessionBrowserTab,
} from "../../browser/session-tab-registry.js";
import { loadConfig } from "../../config/config.js";
import {
executeActAction,
@@ -275,6 +279,7 @@ function resolveBrowserBaseUrl(params: {
export function createBrowserTool(opts?: {
sandboxBridgeUrl?: string;
allowHostControl?: boolean;
agentSessionKey?: string;
}): AnyAgentTool {
const targetDefault = opts?.sandboxBridgeUrl ? "sandbox" : "host";
const hostHint =
@@ -418,7 +423,14 @@ export function createBrowserTool(opts?: {
});
return jsonResult(result);
}
return jsonResult(await browserOpenTab(baseUrl, targetUrl, { profile }));
const opened = await browserOpenTab(baseUrl, targetUrl, { profile });
trackSessionBrowserTab({
sessionKey: opts?.agentSessionKey,
targetId: opened.targetId,
baseUrl,
profile,
});
return jsonResult(opened);
}
case "focus": {
const targetId = readStringParam(params, "targetId", {
@@ -455,6 +467,12 @@ export function createBrowserTool(opts?: {
}
if (targetId) {
await browserCloseTab(baseUrl, targetId, { profile });
untrackSessionBrowserTab({
sessionKey: opts?.agentSessionKey,
targetId,
baseUrl,
profile,
});
} else {
await browserAct(baseUrl, { kind: "close" }, { profile });
}