mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:30:42 +00:00
fix: enable browser tools with full profile (#76557)
Summary: - The PR makes `tools.profile: "full"` resolve to a wildcard allowlist, teaches plugin optional-tool allowlist checks to honor `*`, and updates regression tests, docs, and the changelog for browser tool availability. - Reproducibility: yes. source-level reproduction is high confidence: current main makes `full` resolve to no ... plugin allowlist helpers do not accept `*`. I did not run a live browser session in this read-only review. Automerge notes: - PR branch already contained follow-up commit before automerge: docs: update full profile description and add changelog for #76507 Validation: - ClawSweeper review passed for headb5329de33c. - Required merge gates passed before the squash merge. Prepared head SHA:b5329de33cReview: https://github.com/openclaw/openclaw/pull/76557#issuecomment-4365736091 Co-authored-by: Alex Knight <aknight@atlassian.com>
This commit is contained in:
@@ -95,6 +95,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Active Memory: preserve the target agent context when building embedded recall plugin tools so `memory_search` and `memory_get` stay available for explicit recall sessions. Fixes #76343. Thanks @Countermarch.
|
||||
- Plugins/externalization: repair missing configured plugin installs from npm by default, reserve ClawHub downloads for explicit `clawhubSpec` metadata, and cover agent-runtime/env-selected plugin repair. Thanks @vincentkoc.
|
||||
- Plugins/install: allow official catalog-matched npm channel plugins such as Feishu to pass the trusted install scanner path while keeping spoofed package names blocked. Thanks @vincentkoc.
|
||||
- Tools/profiles: make `tools.profile: "full"` grant all tools including optional plugin tools such as browser, so the full profile no longer silently drops plugin-provided tools that require an explicit allowlist entry. Fixes #76507. Thanks @amknight.
|
||||
- Feishu: keep timeout env parsing separate from the HTTP client wrapper so package security scans no longer report a false env-harvesting hit during install. Thanks @vincentkoc.
|
||||
- Upgrade/config: validate configured web-search providers and statically suppressed model/provider pairs against the active plugin set at config load, so stale plugin state fails loud before runtime fallback.
|
||||
- Status/update: resolve beta update-channel checks from the installed version when config still says `stable`, and let `status --deep` reuse live gateway channel credential state instead of warning on command-path-only token misses.
|
||||
|
||||
@@ -146,7 +146,7 @@ Per-agent override: `agents.list[].tools.profile`.
|
||||
|
||||
| Profile | What it includes |
|
||||
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `full` | Unrestricted baseline for broader command/control access; same as leaving `tools.profile` unset |
|
||||
| `full` | All core and optional plugin tools; unrestricted baseline for broader command/control access |
|
||||
| `coding` | `group:fs`, `group:runtime`, `group:web`, `group:sessions`, `group:memory`, `cron`, `image`, `image_generate`, `music_generate`, `video_generate` |
|
||||
| `messaging` | `group:messaging`, `sessions_list`, `sessions_history`, `sessions_send`, `session_status` |
|
||||
| `minimal` | `session_status` only |
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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("*");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -331,7 +331,9 @@ const CORE_TOOL_PROFILES: Record<ToolProfileId, ToolProfilePolicy> = {
|
||||
messaging: {
|
||||
allow: [...listCoreToolIdsForProfile("messaging"), "bundle-mcp"],
|
||||
},
|
||||
full: {},
|
||||
full: {
|
||||
allow: ["*"],
|
||||
},
|
||||
};
|
||||
|
||||
function buildCoreToolGroupMap() {
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user