Skip to content

Commit 98a9976

Browse files
steipetechat2way
andcommitted
fix(gateway): invoke plugin-backed catalog tools
Co-authored-by: chat2way <[email protected]>
1 parent 6602092 commit 98a9976

3 files changed

Lines changed: 39 additions & 14 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ Docs: https://docs.openclaw.ai
7070
### Fixes
7171

7272
- Subagents: stop stale unended runs from counting as active or pending forever, while preserving restart-aborted recovery for recoverable child sessions. Fixes #71252. Thanks @hclsys.
73+
- Gateway/tools: allow `POST /tools/invoke` to reach plugin-backed catalog tools such as `browser` when no core implementation exists, while still preferring built-in tools for real core names. Thanks @chat2way.
7374
- Browser/security: require `operator.admin` for the `browser.request` gateway method, matching the host/browser-node control authority exposed by that route. Thanks @RichardCao.
7475
- Reply media: allow sandboxed replies to deliver OpenClaw-managed `media/outbound` and `media/tool-*` attachments without treating them as sandbox escapes, while keeping alias-escape checks on the managed media root. Fixes #71138. Thanks @mayor686, @truffle-dev, and @neeravmakwana.
7576
- CLI/agent: keep `openclaw agent --json` stdout reserved for the JSON response by routing gateway, plugin, and embedded-fallback diagnostics to stderr before execution starts. Fixes #71319.

src/gateway/tools-invoke-http.test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@ vi.mock("../agents/openclaw-tools.js", () => {
127127
parameters: { type: "object", properties: {} },
128128
execute: async () => ({ ok: true, result: "nodes" }),
129129
},
130+
{
131+
name: "browser",
132+
parameters: { type: "object", properties: {} },
133+
execute: async () => ({ ok: true, result: "browser" }),
134+
},
130135
{
131136
name: "owner_only_test",
132137
ownerOnly: true,
@@ -181,7 +186,7 @@ vi.mock("../agents/openclaw-tools.js", () => {
181186
return {
182187
createOpenClawTools: (ctx: Record<string, unknown>) => {
183188
lastCreateOpenClawToolsContext = ctx;
184-
return tools;
189+
return ctx.disablePluginTools ? tools.filter((tool) => tool.name !== "browser") : tools;
185190
},
186191
};
187192
});
@@ -878,4 +883,17 @@ describe("POST /tools/invoke", () => {
878883
expect(nodesRes.status).toBe(404);
879884
expect(nodesAdminRes.status).toBe(404);
880885
});
886+
887+
it("falls back to plugin-backed tools when a cataloged core tool has no core implementation", async () => {
888+
setMainAllowedTools({ allow: ["browser"] });
889+
890+
const res = await invokeToolAuthed({
891+
tool: "browser",
892+
sessionKey: "main",
893+
});
894+
895+
const body = await expectOkInvokeResponse(res);
896+
expect(body.result).toEqual({ ok: true, result: "browser" });
897+
expect(lastCreateOpenClawToolsContext?.disablePluginTools).toBe(false);
898+
});
881899
});

src/gateway/tools-invoke-http.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -226,19 +226,25 @@ export async function handleToolsInvokeHttpRequest(
226226
// with the correct owner context and channel-action gates (e.g. Matrix set-profile)
227227
// work correctly for both owner and non-owner callers.
228228
const senderIsOwner = resolveOpenAiCompatibleHttpSenderIsOwner(req, requestAuth);
229-
const { agentId, tools } = resolveGatewayScopedTools({
230-
cfg,
231-
sessionKey,
232-
messageProvider: messageChannel ?? undefined,
233-
accountId,
234-
agentTo,
235-
agentThreadId,
236-
allowGatewaySubagentBinding: true,
237-
allowMediaInvokeCommands: true,
238-
surface: "http",
239-
disablePluginTools: isKnownCoreToolId(toolName),
240-
senderIsOwner,
241-
});
229+
const resolveTools = (disablePluginTools: boolean) =>
230+
resolveGatewayScopedTools({
231+
cfg,
232+
sessionKey,
233+
messageProvider: messageChannel ?? undefined,
234+
accountId,
235+
agentTo,
236+
agentThreadId,
237+
allowGatewaySubagentBinding: true,
238+
allowMediaInvokeCommands: true,
239+
surface: "http",
240+
disablePluginTools,
241+
senderIsOwner,
242+
});
243+
const knownCoreTool = isKnownCoreToolId(toolName);
244+
let { agentId, tools } = resolveTools(knownCoreTool);
245+
if (knownCoreTool && !tools.some((candidate) => candidate.name === toolName)) {
246+
({ agentId, tools } = resolveTools(false));
247+
}
242248
const gatewayFiltered = applyOwnerOnlyToolPolicy(tools, senderIsOwner);
243249

244250
const tool = gatewayFiltered.find((t) => t.name === toolName);

0 commit comments

Comments
 (0)