fix: enable browser tools with full profile (#76507)

This commit is contained in:
Alex Knight
2026-05-03 18:16:57 +10:00
parent 74f243a0d0
commit ba5cd16ed2
5 changed files with 124 additions and 1 deletions

View File

@@ -481,6 +481,54 @@ describe("createOpenClawCodingTools", () => {
expect(names.has("browser")).toBe(false);
});
it("includes browser tool with full profile when browser is configured (#76507)", () => {
const tools = createOpenClawCodingTools({
config: {
tools: { profile: "full" },
browser: { enabled: true },
plugins: { entries: { browser: { enabled: true } } },
} as OpenClawConfig,
senderIsOwner: true,
});
const names = new Set(tools.map((tool) => tool.name));
// full profile must not filter any tools — browser, canvas, etc. must be present.
expect(names.has("browser")).toBe(true);
expect(names.has("canvas")).toBe(true);
expect(names.has("exec")).toBe(true);
expect(names.has("message")).toBe(true);
});
it("includes browser tool with full profile for non-owner senders (#76507)", () => {
const tools = createOpenClawCodingTools({
config: {
tools: { profile: "full" },
browser: { enabled: true },
plugins: { entries: { browser: { enabled: true } } },
} as OpenClawConfig,
senderIsOwner: false,
});
const names = new Set(tools.map((tool) => tool.name));
// browser is NOT owner-only; it must be available to non-owner senders.
expect(names.has("browser")).toBe(true);
expect(names.has("canvas")).toBe(true);
// owner-only tools should be filtered for non-owners
expect(names.has("gateway")).toBe(false);
expect(names.has("cron")).toBe(false);
expect(names.has("nodes")).toBe(false);
});
it("includes browser tool without explicit profile (defaults to no filtering) (#76507)", () => {
const tools = createOpenClawCodingTools({
config: {
browser: { enabled: true },
plugins: { entries: { browser: { enabled: true } } },
} as OpenClawConfig,
});
const names = new Set(tools.map((tool) => tool.name));
// No profile means no profile filtering — all tools pass.
expect(names.has("browser")).toBe(true);
});
it("keeps browser out of coding-profile subagents unless profile-stage alsoAllow adds it", () => {
const baseConfig = {
browser: { enabled: true },

View File

@@ -21,4 +21,10 @@ describe("tool-catalog", () => {
expect(resolveCoreToolProfilePolicy("messaging")?.allow).toContain("bundle-mcp");
expect(resolveCoreToolProfilePolicy("minimal")?.allow).not.toContain("bundle-mcp");
});
it("full profile uses wildcard to grant all tools (#76507)", () => {
const policy = resolveCoreToolProfilePolicy("full");
expect(policy).toBeDefined();
expect(policy!.allow).toContain("*");
});
});

View File

@@ -331,7 +331,9 @@ const CORE_TOOL_PROFILES: Record<ToolProfileId, ToolProfilePolicy> = {
messaging: {
allow: [...listCoreToolIdsForProfile("messaging"), "bundle-mcp"],
},
full: {},
full: {
allow: ["*"],
},
};
function buildCoreToolGroupMap() {

View File

@@ -1313,6 +1313,67 @@ describe("resolvePluginTools optional tools", () => {
expect(loadOpenClawPluginsMock).not.toHaveBeenCalled();
});
it("includes non-optional browser tool when toolAllowlist is empty (full profile)", () => {
const browserEntry: MockRegistryToolEntry = {
pluginId: "browser",
optional: false,
source: "/tmp/browser.js",
names: ["browser"],
declaredNames: ["browser"],
factory: () => makeTool("browser"),
};
setRegistry([browserEntry]);
// Empty toolAllowlist simulates tools.profile: "full" where no explicit
// allow list exists. Non-optional plugin tools must still be resolved.
const tools = resolvePluginTools(createResolveToolsParams({ toolAllowlist: [] }));
expectResolvedToolNames(tools, ["browser"]);
});
it("includes non-optional browser tool when toolAllowlist is undefined (full profile)", () => {
const browserEntry: MockRegistryToolEntry = {
pluginId: "browser",
optional: false,
source: "/tmp/browser.js",
names: ["browser"],
declaredNames: ["browser"],
factory: () => makeTool("browser"),
};
setRegistry([browserEntry]);
// Undefined toolAllowlist is the other variant of "no explicit allowlist".
const tools = resolvePluginTools(createResolveToolsParams());
expectResolvedToolNames(tools, ["browser"]);
});
it("includes non-optional browser tool when toolAllowlist has wildcard (#76507)", () => {
const browserEntry: MockRegistryToolEntry = {
pluginId: "browser",
optional: false,
source: "/tmp/browser.js",
names: ["browser"],
declaredNames: ["browser"],
factory: () => makeTool("browser"),
};
setRegistry([browserEntry]);
// Wildcard allowlist from tools.profile: "full" explicitly grants all tools.
const tools = resolvePluginTools(createResolveToolsParams({ toolAllowlist: ["*"] }));
expectResolvedToolNames(tools, ["browser"]);
});
it("includes optional tools when wildcard allowlist is active (#76507)", () => {
setOptionalDemoRegistry();
// Wildcard must grant optional tools too.
const tools = resolvePluginTools(createResolveToolsParams({ toolAllowlist: ["*"] }));
expectResolvedToolNames(tools, ["optional_tool"]);
});
});
describe("buildPluginToolMetadataKey", () => {

View File

@@ -94,6 +94,9 @@ function isOptionalToolAllowed(params: {
if (params.allowlist.size === 0) {
return false;
}
if (params.allowlist.has("*")) {
return true;
}
const toolName = normalizeToolName(params.toolName);
if (params.allowlist.has(toolName)) {
return true;
@@ -113,6 +116,9 @@ function isOptionalToolEntryPotentiallyAllowed(params: {
if (params.allowlist.size === 0) {
return false;
}
if (params.allowlist.has("*")) {
return true;
}
const pluginKey = normalizeToolName(params.pluginId);
if (params.allowlist.has(pluginKey) || params.allowlist.has("group:plugins")) {
return true;