fix: keep core tools when allowlist is plugin-only
This commit is contained in:
@@ -6,6 +6,7 @@ Docs: https://docs.clawd.bot
|
|||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- Media: preserve PNG alpha when possible; fall back to JPEG when still over size cap. (#1491) Thanks @robbyczgw-cla.
|
- Media: preserve PNG alpha when possible; fall back to JPEG when still over size cap. (#1491) Thanks @robbyczgw-cla.
|
||||||
|
- Agents: treat plugin-only tool allowlists as opt-ins; keep core tools enabled. (#1467)
|
||||||
|
|
||||||
## 2026.1.22
|
## 2026.1.22
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,8 @@ Enable optional tools in `agents.list[].tools.allow` (or global `tools.allow`):
|
|||||||
```
|
```
|
||||||
|
|
||||||
Other config knobs that affect tool availability:
|
Other config knobs that affect tool availability:
|
||||||
|
- Allowlists that only name plugin tools are treated as plugin opt-ins; core tools remain
|
||||||
|
enabled unless you also include core tools or groups in the allowlist.
|
||||||
- `tools.profile` / `agents.list[].tools.profile` (base allowlist)
|
- `tools.profile` / `agents.list[].tools.profile` (base allowlist)
|
||||||
- `tools.byProvider` / `agents.list[].tools.byProvider` (provider‑specific allow/deny)
|
- `tools.byProvider` / `agents.list[].tools.byProvider` (provider‑specific allow/deny)
|
||||||
- `tools.sandbox.tools.*` (sandbox tool policy when sandboxed)
|
- `tools.sandbox.tools.*` (sandbox tool policy when sandboxed)
|
||||||
|
|||||||
@@ -121,6 +121,10 @@ Lobster is an **optional** plugin tool (not enabled by default). Allow it per ag
|
|||||||
|
|
||||||
You can also allow it globally with `tools.allow` if every agent should see it.
|
You can also allow it globally with `tools.allow` if every agent should see it.
|
||||||
|
|
||||||
|
Note: allowlists are opt-in for optional plugins. If your allowlist only names
|
||||||
|
plugin tools (like `lobster`), Clawdbot keeps core tools enabled. To restrict core
|
||||||
|
tools, include the core tools or groups you want in the allowlist too.
|
||||||
|
|
||||||
## Example: Email triage
|
## Example: Email triage
|
||||||
|
|
||||||
Without Lobster:
|
Without Lobster:
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import {
|
|||||||
collectExplicitAllowlist,
|
collectExplicitAllowlist,
|
||||||
expandPolicyWithPluginGroups,
|
expandPolicyWithPluginGroups,
|
||||||
resolveToolProfilePolicy,
|
resolveToolProfilePolicy,
|
||||||
|
stripPluginOnlyAllowlist,
|
||||||
} from "./tool-policy.js";
|
} from "./tool-policy.js";
|
||||||
import { getPluginToolMeta } from "../plugins/tools.js";
|
import { getPluginToolMeta } from "../plugins/tools.js";
|
||||||
|
|
||||||
@@ -298,12 +299,30 @@ export function createClawdbotCodingTools(options?: {
|
|||||||
tools,
|
tools,
|
||||||
toolMeta: (tool) => getPluginToolMeta(tool as AnyAgentTool),
|
toolMeta: (tool) => getPluginToolMeta(tool as AnyAgentTool),
|
||||||
});
|
});
|
||||||
const profilePolicyExpanded = expandPolicyWithPluginGroups(profilePolicy, pluginGroups);
|
const profilePolicyExpanded = expandPolicyWithPluginGroups(
|
||||||
const providerProfileExpanded = expandPolicyWithPluginGroups(providerProfilePolicy, pluginGroups);
|
stripPluginOnlyAllowlist(profilePolicy, pluginGroups),
|
||||||
const globalPolicyExpanded = expandPolicyWithPluginGroups(globalPolicy, pluginGroups);
|
pluginGroups,
|
||||||
const globalProviderExpanded = expandPolicyWithPluginGroups(globalProviderPolicy, pluginGroups);
|
);
|
||||||
const agentPolicyExpanded = expandPolicyWithPluginGroups(agentPolicy, pluginGroups);
|
const providerProfileExpanded = expandPolicyWithPluginGroups(
|
||||||
const agentProviderExpanded = expandPolicyWithPluginGroups(agentProviderPolicy, pluginGroups);
|
stripPluginOnlyAllowlist(providerProfilePolicy, pluginGroups),
|
||||||
|
pluginGroups,
|
||||||
|
);
|
||||||
|
const globalPolicyExpanded = expandPolicyWithPluginGroups(
|
||||||
|
stripPluginOnlyAllowlist(globalPolicy, pluginGroups),
|
||||||
|
pluginGroups,
|
||||||
|
);
|
||||||
|
const globalProviderExpanded = expandPolicyWithPluginGroups(
|
||||||
|
stripPluginOnlyAllowlist(globalProviderPolicy, pluginGroups),
|
||||||
|
pluginGroups,
|
||||||
|
);
|
||||||
|
const agentPolicyExpanded = expandPolicyWithPluginGroups(
|
||||||
|
stripPluginOnlyAllowlist(agentPolicy, pluginGroups),
|
||||||
|
pluginGroups,
|
||||||
|
);
|
||||||
|
const agentProviderExpanded = expandPolicyWithPluginGroups(
|
||||||
|
stripPluginOnlyAllowlist(agentProviderPolicy, pluginGroups),
|
||||||
|
pluginGroups,
|
||||||
|
);
|
||||||
const sandboxPolicyExpanded = expandPolicyWithPluginGroups(sandbox?.tools, pluginGroups);
|
const sandboxPolicyExpanded = expandPolicyWithPluginGroups(sandbox?.tools, pluginGroups);
|
||||||
const subagentPolicyExpanded = expandPolicyWithPluginGroups(subagentPolicy, pluginGroups);
|
const subagentPolicyExpanded = expandPolicyWithPluginGroups(subagentPolicy, pluginGroups);
|
||||||
|
|
||||||
|
|||||||
25
src/agents/tool-policy.plugin-only-allowlist.test.ts
Normal file
25
src/agents/tool-policy.plugin-only-allowlist.test.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import { stripPluginOnlyAllowlist, type PluginToolGroups } from "./tool-policy.js";
|
||||||
|
|
||||||
|
const pluginGroups: PluginToolGroups = {
|
||||||
|
all: ["lobster", "workflow_tool"],
|
||||||
|
byPlugin: new Map([["lobster", ["lobster", "workflow_tool"]]]),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("stripPluginOnlyAllowlist", () => {
|
||||||
|
it("strips allowlist when it only targets plugin tools", () => {
|
||||||
|
const policy = stripPluginOnlyAllowlist({ allow: ["lobster"] }, pluginGroups);
|
||||||
|
expect(policy?.allow).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("strips allowlist when it only targets plugin groups", () => {
|
||||||
|
const policy = stripPluginOnlyAllowlist({ allow: ["group:plugins"] }, pluginGroups);
|
||||||
|
expect(policy?.allow).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps allowlist when it mixes plugin and core entries", () => {
|
||||||
|
const policy = stripPluginOnlyAllowlist({ allow: ["lobster", "read"] }, pluginGroups);
|
||||||
|
expect(policy?.allow).toEqual(["lobster", "read"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -178,6 +178,22 @@ export function expandPolicyWithPluginGroups(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function stripPluginOnlyAllowlist(
|
||||||
|
policy: ToolPolicyLike | undefined,
|
||||||
|
groups: PluginToolGroups,
|
||||||
|
): ToolPolicyLike | undefined {
|
||||||
|
if (!policy?.allow || policy.allow.length === 0) return policy;
|
||||||
|
const normalized = normalizeToolList(policy.allow);
|
||||||
|
if (normalized.length === 0) return policy;
|
||||||
|
const pluginIds = new Set(groups.byPlugin.keys());
|
||||||
|
const pluginTools = new Set(groups.all);
|
||||||
|
const isPluginEntry = (entry: string) =>
|
||||||
|
entry === "group:plugins" || pluginIds.has(entry) || pluginTools.has(entry);
|
||||||
|
const isPluginOnly = normalized.every((entry) => isPluginEntry(entry));
|
||||||
|
if (!isPluginOnly) return policy;
|
||||||
|
return { ...policy, allow: undefined };
|
||||||
|
}
|
||||||
|
|
||||||
export function resolveToolProfilePolicy(profile?: string): ToolProfilePolicy | undefined {
|
export function resolveToolProfilePolicy(profile?: string): ToolProfilePolicy | undefined {
|
||||||
if (!profile) return undefined;
|
if (!profile) return undefined;
|
||||||
const resolved = TOOL_PROFILES[profile as ToolProfileId];
|
const resolved = TOOL_PROFILES[profile as ToolProfileId];
|
||||||
|
|||||||
Reference in New Issue
Block a user