Skip to content

Commit 197510f

Browse files
committed
refactor: add browser plugin runtime package
1 parent 1619090 commit 197510f

67 files changed

Lines changed: 5069 additions & 2 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

extensions/browser/index.test.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { describe, expect, it, vi } from "vitest";
2+
import { createTestPluginApi } from "../../test/helpers/extensions/plugin-api.js";
3+
import type { OpenClawPluginApi } from "./runtime-api.js";
4+
5+
const runtimeApiMocks = vi.hoisted(() => ({
6+
createBrowserPluginService: vi.fn(() => ({ id: "browser-control", start: vi.fn() })),
7+
createBrowserTool: vi.fn(() => ({
8+
name: "browser",
9+
description: "browser",
10+
parameters: { type: "object", properties: {} },
11+
execute: vi.fn(),
12+
})),
13+
handleBrowserGatewayRequest: vi.fn(),
14+
registerBrowserCli: vi.fn(),
15+
}));
16+
17+
vi.mock("./runtime-api.js", async (importOriginal) => {
18+
const actual = await importOriginal<typeof import("./runtime-api.js")>();
19+
return {
20+
...actual,
21+
createBrowserPluginService: runtimeApiMocks.createBrowserPluginService,
22+
createBrowserTool: runtimeApiMocks.createBrowserTool,
23+
handleBrowserGatewayRequest: runtimeApiMocks.handleBrowserGatewayRequest,
24+
registerBrowserCli: runtimeApiMocks.registerBrowserCli,
25+
};
26+
});
27+
28+
import browserPlugin from "./index.js";
29+
30+
function createApi() {
31+
const registerCli = vi.fn();
32+
const registerGatewayMethod = vi.fn();
33+
const registerService = vi.fn();
34+
const registerTool = vi.fn();
35+
const api = createTestPluginApi({
36+
id: "browser",
37+
name: "Browser",
38+
source: "test",
39+
config: {},
40+
runtime: {} as OpenClawPluginApi["runtime"],
41+
registerCli,
42+
registerGatewayMethod,
43+
registerService,
44+
registerTool,
45+
}) as OpenClawPluginApi;
46+
return { api, registerCli, registerGatewayMethod, registerService, registerTool };
47+
}
48+
49+
describe("browser plugin", () => {
50+
it("registers browser tool, cli, gateway method, and service ownership", () => {
51+
const { api, registerCli, registerGatewayMethod, registerService, registerTool } = createApi();
52+
browserPlugin.register(api);
53+
54+
expect(registerTool).toHaveBeenCalledTimes(1);
55+
expect(registerCli).toHaveBeenCalledWith(expect.any(Function), { commands: ["browser"] });
56+
expect(registerGatewayMethod).toHaveBeenCalledWith(
57+
"browser.request",
58+
runtimeApiMocks.handleBrowserGatewayRequest,
59+
{ scope: "operator.write" },
60+
);
61+
expect(runtimeApiMocks.createBrowserPluginService).toHaveBeenCalledTimes(1);
62+
expect(registerService).toHaveBeenCalledWith(
63+
runtimeApiMocks.createBrowserPluginService.mock.results[0]?.value,
64+
);
65+
});
66+
67+
it("forwards per-session browser options into the tool factory", () => {
68+
const { api, registerTool } = createApi();
69+
browserPlugin.register(api);
70+
71+
const tool = registerTool.mock.calls[0]?.[0];
72+
if (typeof tool !== "function") {
73+
throw new Error("expected browser plugin to register a tool factory");
74+
}
75+
76+
tool({
77+
sessionKey: "agent:main:webchat:direct:123",
78+
browser: {
79+
sandboxBridgeUrl: "http://127.0.0.1:9999",
80+
allowHostControl: true,
81+
},
82+
});
83+
84+
expect(runtimeApiMocks.createBrowserTool).toHaveBeenCalledWith({
85+
sandboxBridgeUrl: "http://127.0.0.1:9999",
86+
allowHostControl: true,
87+
agentSessionKey: "agent:main:webchat:direct:123",
88+
});
89+
});
90+
});

extensions/browser/index.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import {
2+
createBrowserPluginService,
3+
createBrowserTool,
4+
definePluginEntry,
5+
handleBrowserGatewayRequest,
6+
registerBrowserCli,
7+
type OpenClawPluginToolContext,
8+
type OpenClawPluginToolFactory,
9+
} from "./runtime-api.js";
10+
11+
export default definePluginEntry({
12+
id: "browser",
13+
name: "Browser",
14+
description: "Default browser tool plugin",
15+
register(api) {
16+
api.registerTool(((ctx: OpenClawPluginToolContext) =>
17+
createBrowserTool({
18+
sandboxBridgeUrl: ctx.browser?.sandboxBridgeUrl,
19+
allowHostControl: ctx.browser?.allowHostControl,
20+
agentSessionKey: ctx.sessionKey,
21+
})) as OpenClawPluginToolFactory);
22+
api.registerCli(({ program }) => registerBrowserCli(program), { commands: ["browser"] });
23+
api.registerGatewayMethod("browser.request", handleBrowserGatewayRequest, {
24+
scope: "operator.write",
25+
});
26+
api.registerService(createBrowserPluginService());
27+
},
28+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"id": "browser",
3+
"enabledByDefault": true,
4+
"configSchema": {
5+
"type": "object",
6+
"additionalProperties": false,
7+
"properties": {}
8+
}
9+
}

extensions/browser/package.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "@openclaw/browser-plugin",
3+
"version": "2026.3.25",
4+
"private": true,
5+
"description": "OpenClaw browser tool plugin",
6+
"type": "module",
7+
"openclaw": {
8+
"extensions": [
9+
"./index.ts"
10+
]
11+
}
12+
}

extensions/browser/runtime-api.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export { createBrowserTool } from "./src/browser-tool.js";
2+
export { registerBrowserCli } from "./src/cli/browser-cli.js";
3+
export { createBrowserPluginService } from "./src/plugin-service.js";
4+
export { handleBrowserGatewayRequest } from "./src/gateway/browser-request.js";
5+
export {
6+
definePluginEntry,
7+
type OpenClawPluginApi,
8+
type OpenClawPluginToolContext,
9+
type OpenClawPluginToolFactory,
10+
} from "openclaw/plugin-sdk/plugin-entry";
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
export {
2+
browserAct,
3+
browserArmDialog,
4+
browserArmFileChooser,
5+
browserConsoleMessages,
6+
browserNavigate,
7+
browserPdfSave,
8+
browserScreenshotAction,
9+
} from "./browser/client-actions.js";
10+
export {
11+
browserCloseTab,
12+
browserFocusTab,
13+
browserOpenTab,
14+
browserCreateProfile,
15+
browserDeleteProfile,
16+
browserProfiles,
17+
browserResetProfile,
18+
browserSnapshot,
19+
browserStart,
20+
browserStatus,
21+
browserStop,
22+
browserTabAction,
23+
browserTabs,
24+
} from "./browser/client.js";
25+
export type {
26+
BrowserCreateProfileResult,
27+
BrowserDeleteProfileResult,
28+
BrowserResetProfileResult,
29+
BrowserStatus,
30+
BrowserTab,
31+
BrowserTransport,
32+
ProfileStatus,
33+
SnapshotResult,
34+
} from "./browser/client.js";
35+
export { resolveBrowserConfig, resolveProfile } from "./browser/config.js";
36+
export { DEFAULT_AI_SNAPSHOT_MAX_CHARS } from "./browser/constants.js";
37+
export { redactCdpUrl } from "./browser/cdp.helpers.js";
38+
export { DEFAULT_UPLOAD_DIR, resolveExistingPathsWithinRoot } from "./browser/paths.js";
39+
export { getBrowserProfileCapabilities } from "./browser/profile-capabilities.js";
40+
export { applyBrowserProxyPaths, persistBrowserProxyFiles } from "./browser/proxy-files.js";
41+
export {
42+
isPersistentBrowserProfileMutation,
43+
normalizeBrowserRequestPath,
44+
resolveRequestedBrowserProfile,
45+
} from "./browser/request-policy.js";
46+
export {
47+
trackSessionBrowserTab,
48+
untrackSessionBrowserTab,
49+
} from "./browser/session-tab-registry.js";
50+
export { ensureBrowserControlAuth, resolveBrowserControlAuth } from "./browser/control-auth.js";
51+
export {
52+
createBrowserControlContext,
53+
getBrowserControlState,
54+
startBrowserControlServiceFromConfig,
55+
stopBrowserControlService,
56+
} from "./control-service.js";
57+
export { createBrowserRuntimeState, stopBrowserRuntime } from "./browser/runtime-lifecycle.js";
58+
export { type BrowserServerState, createBrowserRouteContext } from "./browser/server-context.js";
59+
export { registerBrowserRoutes } from "./browser/routes/index.js";
60+
export { createBrowserRouteDispatcher } from "./browser/routes/dispatcher.js";
61+
export type { BrowserRouteRegistrar } from "./browser/routes/types.js";
62+
export {
63+
installBrowserAuthMiddleware,
64+
installBrowserCommonMiddleware,
65+
} from "./browser/server-middleware.js";
66+
export type { BrowserFormField } from "./browser/client-actions-core.js";
67+
export {
68+
normalizeBrowserFormField,
69+
normalizeBrowserFormFieldValue,
70+
} from "./browser/form-fields.js";

0 commit comments

Comments
 (0)