mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:20:43 +00:00
fix(gateway): keep requested plugin tools invokable (#76285) thanks @BunsDev
Keep directly requested plugin tools invokable under restrictive profiles, with the changelog update included on the verified branch.
This commit is contained in:
@@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Gateway: keep directly requested plugin tools invokable under restrictive tool profiles while preserving explicit deny lists and the HTTP safety deny list, preventing catalog/invoke mismatches that surface as "Tool not available". Thanks @BunsDev.
|
||||
- Channels: keep Matrix and Mattermost bundled in the core package instead of advertising external npm installs before those channels are cut over. Thanks @vincentkoc.
|
||||
- Bonjour: disable LAN mDNS advertising after a repeated stuck-announcing recovery instead of repeatedly restarting ciao and saturating the Gateway event loop.
|
||||
- CLI/plugins: stop treating the non-plugin `auth` command root as a bundled plugin id, so restrictive `plugins.allow` configs no longer tell users to add stale `auth` plugin entries.
|
||||
|
||||
@@ -39,6 +39,7 @@ export function resolveGatewayScopedTools(params: {
|
||||
excludeToolNames?: Iterable<string>;
|
||||
disablePluginTools?: boolean;
|
||||
senderIsOwner?: boolean;
|
||||
gatewayRequestedTools?: string[];
|
||||
}) {
|
||||
const {
|
||||
agentId,
|
||||
@@ -53,11 +54,15 @@ export function resolveGatewayScopedTools(params: {
|
||||
} = resolveEffectiveToolPolicy({ config: params.cfg, sessionKey: params.sessionKey });
|
||||
const profilePolicy = resolveToolProfilePolicy(profile);
|
||||
const providerProfilePolicy = resolveToolProfilePolicy(providerProfile);
|
||||
const profilePolicyWithAlsoAllow = mergeAlsoAllowPolicy(profilePolicy, profileAlsoAllow);
|
||||
const providerProfilePolicyWithAlsoAllow = mergeAlsoAllowPolicy(
|
||||
providerProfilePolicy,
|
||||
providerProfileAlsoAllow,
|
||||
);
|
||||
const gatewayRequestedTools = params.gatewayRequestedTools ?? [];
|
||||
const profilePolicyWithAlsoAllow = mergeAlsoAllowPolicy(profilePolicy, [
|
||||
...(profileAlsoAllow ?? []),
|
||||
...gatewayRequestedTools,
|
||||
]);
|
||||
const providerProfilePolicyWithAlsoAllow = mergeAlsoAllowPolicy(providerProfilePolicy, [
|
||||
...(providerProfileAlsoAllow ?? []),
|
||||
...gatewayRequestedTools,
|
||||
]);
|
||||
const groupPolicy = resolveGroupToolPolicy({
|
||||
config: params.cfg,
|
||||
sessionKey: params.sessionKey,
|
||||
@@ -101,6 +106,7 @@ export function resolveGatewayScopedTools(params: {
|
||||
agentProviderPolicy,
|
||||
groupPolicy,
|
||||
subagentPolicy,
|
||||
gatewayRequestedTools.length > 0 ? { allow: gatewayRequestedTools } : undefined,
|
||||
]),
|
||||
});
|
||||
|
||||
|
||||
@@ -7,6 +7,10 @@ type RunBeforeToolCallHook = typeof runBeforeToolCallHookType;
|
||||
type RunBeforeToolCallHookArgs = Parameters<RunBeforeToolCallHook>[0];
|
||||
type RunBeforeToolCallHookResult = Awaited<ReturnType<RunBeforeToolCallHook>>;
|
||||
|
||||
const pluginToolMetaState = vi.hoisted(
|
||||
() => new Map<string, { pluginId: string; optional: boolean }>(),
|
||||
);
|
||||
|
||||
const hookMocks = vi.hoisted(() => ({
|
||||
resolveToolLoopDetectionConfig: vi.fn(() => ({ warnAt: 3 })),
|
||||
runBeforeToolCallHook: vi.fn(
|
||||
@@ -63,7 +67,8 @@ vi.mock("../plugins/config-state.js", async (importOriginal) => {
|
||||
});
|
||||
|
||||
vi.mock("../plugins/tools.js", () => ({
|
||||
getPluginToolMeta: () => undefined,
|
||||
getPluginToolMeta: (tool: { name?: string }) =>
|
||||
typeof tool?.name === "string" ? pluginToolMetaState.get(tool.name) : undefined,
|
||||
}));
|
||||
|
||||
// Perf: the real tool factory instantiates many tools per request; for these HTTP
|
||||
@@ -136,6 +141,11 @@ vi.mock("../agents/openclaw-tools.js", () => {
|
||||
parameters: { type: "object", properties: {} },
|
||||
execute: async () => ({ ok: true, result: "browser" }),
|
||||
},
|
||||
{
|
||||
name: "plugin_doctor",
|
||||
parameters: { type: "object", properties: {} },
|
||||
execute: async () => ({ ok: true, permissionFlow: true }),
|
||||
},
|
||||
{
|
||||
name: "owner_only_test",
|
||||
ownerOnly: true,
|
||||
@@ -259,6 +269,8 @@ beforeEach(() => {
|
||||
pluginHttpHandlers = [];
|
||||
cfg = {};
|
||||
lastCreateOpenClawToolsContext = undefined;
|
||||
pluginToolMetaState.clear();
|
||||
pluginToolMetaState.set("plugin_doctor", { pluginId: "test-plugin", optional: true });
|
||||
hookMocks.resolveToolLoopDetectionConfig.mockClear();
|
||||
hookMocks.resolveToolLoopDetectionConfig.mockImplementation(() => ({ warnAt: 3 }));
|
||||
hookMocks.runBeforeToolCallHook.mockClear();
|
||||
@@ -463,6 +475,25 @@ describe("POST /tools/invoke", () => {
|
||||
expect(lastCreateOpenClawToolsContext?.disablePluginTools).toBe(false);
|
||||
});
|
||||
|
||||
it("allows the requested plugin tool through Gateway profile filtering", async () => {
|
||||
cfg = {
|
||||
...cfg,
|
||||
agents: { list: [{ id: "main", default: true }] },
|
||||
tools: { profile: "minimal" },
|
||||
};
|
||||
|
||||
const res = await invokeToolAuthed({
|
||||
tool: "plugin_doctor",
|
||||
sessionKey: "main",
|
||||
});
|
||||
|
||||
const body = await expectOkInvokeResponse(res);
|
||||
expect(body.result).toMatchObject({ ok: true, permissionFlow: true });
|
||||
expect(lastCreateOpenClawToolsContext?.pluginToolAllowlist).toEqual(
|
||||
expect.arrayContaining(["plugin_doctor"]),
|
||||
);
|
||||
});
|
||||
|
||||
it("blocks tool execution when before_tool_call rejects the invoke", async () => {
|
||||
setMainAllowedTools({ allow: ["tools_invoke_test"] });
|
||||
hookMocks.runBeforeToolCallHook.mockResolvedValueOnce({
|
||||
|
||||
@@ -183,6 +183,9 @@ export async function invokeGatewayTool(params: {
|
||||
}
|
||||
}
|
||||
|
||||
const knownCoreTool = isKnownCoreToolId(toolName);
|
||||
const gatewayRequestedTools = knownCoreTool ? [] : [toolName];
|
||||
|
||||
const action = normalizeOptionalString(params.input.action);
|
||||
const argsRaw = params.input.args;
|
||||
const args =
|
||||
@@ -203,9 +206,9 @@ export async function invokeGatewayTool(params: {
|
||||
surface: "http",
|
||||
disablePluginTools,
|
||||
senderIsOwner: params.senderIsOwner,
|
||||
gatewayRequestedTools,
|
||||
});
|
||||
|
||||
const knownCoreTool = isKnownCoreToolId(toolName);
|
||||
let { agentId, tools } = resolveTools(knownCoreTool);
|
||||
if (knownCoreTool && !tools.some((candidate) => candidate.name === toolName)) {
|
||||
({ agentId, tools } = resolveTools(false));
|
||||
|
||||
Reference in New Issue
Block a user