Skip to content

Commit 06e85d5

Browse files
authored
fix: honor explicit message tool allowlists (#82889)
1 parent 2c549ae commit 06e85d5

6 files changed

Lines changed: 108 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai
1313
- Telegram: keep the top-level default account in the account list when named accounts or bindings are added alongside top-level credentials, preserving default polling while still letting named-only configs resolve to a single account. Fixes #82794. (#82794) Thanks @giodl73-repo.
1414
- CLI/channels: show configured official external channels such as Discord in `openclaw channels list` when their plugin package is missing, including the install and doctor repair command instead of reporting no configured channels. Fixes #82813.
1515
- Signal: preserve mixed-case group IDs through routing and session persistence so group auto-replies keep delivering after updates. Fixes #82827.
16+
- Agents/tools: keep the `message` tool available in embedded runs when it is explicitly allowed through `tools.alsoAllow` or runtime tool allowlists, so channel plugins with custom reply delivery can still use configured message sends. Fixes #82833. Thanks @cn1313113.
1617
- WhatsApp: honor forced document delivery for outbound image, GIF, and video media so `forceDocument`/`asDocument` sends preserve original media bytes instead of using compressed media payloads. (#79272) Thanks @itsuzef.
1718
- WhatsApp: name outbound document attachments from their MIME type when no filename is provided, so PDF and CSV sends arrive as `file.pdf` and `file.csv` instead of an extensionless `file`. Thanks @mcaxtr.
1819

src/agents/openclaw-tools.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -328,15 +328,32 @@ export function createOpenClawTools(
328328
});
329329
options?.recordToolPrepStage?.("openclaw-tools:nodes-tool");
330330
const embedded = isEmbeddedMode();
331-
const includeMessageTool = !embedded || options?.sourceReplyDeliveryMode === "message_tool_only";
331+
const explicitFactoryAllowlist = mergeFactoryPolicyList(
332+
resolvedConfig?.tools?.allow,
333+
resolvedConfig?.tools?.alsoAllow,
334+
options?.pluginToolAllowlist,
335+
);
336+
const explicitFactoryDenylist = mergeFactoryPolicyList(
337+
resolvedConfig?.tools?.deny,
338+
options?.pluginToolDenylist,
339+
);
340+
const messageExplicitlyAllowed = isToolExplicitlyAllowedByFactoryPolicy({
341+
toolName: "message",
342+
allowlist: explicitFactoryAllowlist,
343+
denylist: explicitFactoryDenylist,
344+
});
345+
const includeMessageTool =
346+
!embedded ||
347+
options?.sourceReplyDeliveryMode === "message_tool_only" ||
348+
messageExplicitlyAllowed;
332349
const effectiveCallGateway = embedded
333350
? createEmbeddedCallGateway()
334351
: openClawToolsDeps.callGateway;
335352
const includeUpdatePlanTool =
336353
isToolExplicitlyAllowedByFactoryPolicy({
337354
toolName: "update_plan",
338-
allowlist: mergeFactoryPolicyList(resolvedConfig?.tools?.allow, options?.pluginToolAllowlist),
339-
denylist: mergeFactoryPolicyList(resolvedConfig?.tools?.deny, options?.pluginToolDenylist),
355+
allowlist: explicitFactoryAllowlist,
356+
denylist: explicitFactoryDenylist,
340357
}) ||
341358
isUpdatePlanToolEnabledForOpenClawTools({
342359
config: resolvedConfig,

src/agents/openclaw-tools.update-plan.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,29 @@ describe("openclaw-tools update_plan gating", () => {
100100
expect(toolNames(tools)).toContain("message");
101101
});
102102

103+
it("keeps explicitly allowed message tool in embedded completions", () => {
104+
setEmbeddedMode(true);
105+
const fromRuntimeAllowlist = createOpenClawTools({
106+
config: {} as OpenClawConfig,
107+
disablePluginTools: true,
108+
pluginToolAllowlist: ["message"],
109+
});
110+
const fromGlobalAlsoAllow = createOpenClawTools({
111+
config: { tools: { profile: "minimal", alsoAllow: ["message"] } } as OpenClawConfig,
112+
disablePluginTools: true,
113+
});
114+
const denied = createOpenClawTools({
115+
config: {} as OpenClawConfig,
116+
disablePluginTools: true,
117+
pluginToolAllowlist: ["message"],
118+
pluginToolDenylist: ["message"],
119+
});
120+
121+
expect(toolNames(fromRuntimeAllowlist)).toContain("message");
122+
expect(toolNames(fromGlobalAlsoAllow)).toContain("message");
123+
expect(toolNames(denied)).not.toContain("message");
124+
});
125+
103126
it("registers update_plan when explicitly enabled", () => {
104127
const config = {
105128
tools: {

src/agents/pi-tools.create-openclaw-coding-tools.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,40 @@ describe("createOpenClawCodingTools", () => {
346346
expectListIncludes(options.pluginToolAllowlist, ["memory_search", "memory_get"]);
347347
});
348348

349+
it("preserves runtime-allowed message through restrictive profiles", () => {
350+
const tools = createOpenClawCodingTools({
351+
config: { tools: { profile: "minimal" } },
352+
runtimeToolAllowlist: ["message"],
353+
toolConstructionPlan: {
354+
includeBaseCodingTools: false,
355+
includeShellTools: false,
356+
includeChannelTools: false,
357+
includeOpenClawTools: true,
358+
includePluginTools: false,
359+
},
360+
});
361+
362+
expect(toolNameList(tools)).toContain("message");
363+
});
364+
365+
it("preserves runtime allowlist groups containing message through restrictive profiles", () => {
366+
for (const runtimeToolAllowlist of [["group:messaging"], ["group:openclaw"], ["*"]]) {
367+
const tools = createOpenClawCodingTools({
368+
config: { tools: { profile: "minimal" } },
369+
runtimeToolAllowlist,
370+
toolConstructionPlan: {
371+
includeBaseCodingTools: false,
372+
includeShellTools: false,
373+
includeChannelTools: false,
374+
includeOpenClawTools: true,
375+
includePluginTools: false,
376+
},
377+
});
378+
379+
expect(toolNameList(tools)).toContain("message");
380+
}
381+
});
382+
349383
it("passes source reply delivery mode to OpenClaw tool construction", () => {
350384
const createOpenClawToolsMock = vi.mocked(createOpenClawTools);
351385
createOpenClawToolsMock.mockClear();

src/agents/pi-tools.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ import {
8181
applyOwnerOnlyToolPolicy,
8282
collectExplicitAllowlist,
8383
collectExplicitDenylist,
84+
expandToolGroups,
8485
hasRestrictiveAllowPolicy,
8586
mergeAlsoAllowPolicy,
8687
normalizeToolName,
@@ -545,10 +546,17 @@ export function createOpenClawCodingTools(options?: {
545546
const mergeToolSearchControlAllowlist = <TPolicy extends { allow?: string[] }>(
546547
policy: TPolicy | undefined,
547548
) => mergeAlsoAllowPolicy(policy, toolSearchControlAllowlist);
549+
const runtimeToolAllowlistIncludesMessage = expandToolGroups(
550+
options?.runtimeToolAllowlist ?? [],
551+
).some((toolName) => {
552+
const normalized = normalizeToolName(toolName);
553+
return normalized === "*" || normalized === "message";
554+
});
548555
const runtimeProfileAlsoAllow = [
549556
...(options?.forceMessageTool || options?.sourceReplyDeliveryMode === "message_tool_only"
550557
? ["message"]
551558
: []),
559+
...(runtimeToolAllowlistIncludesMessage ? ["message"] : []),
552560
...(forceHeartbeatTool ? [HEARTBEAT_RESPONSE_TOOL_NAME] : []),
553561
...toolSearchControlAllowlist,
554562
];

src/plugins/runtime/types-channel.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,32 @@ export type PluginRuntimeChannel = {
8585
};
8686
reply: {
8787
dispatchReplyWithBufferedBlockDispatcher: DispatchReplyWithBufferedBlockDispatcher;
88+
/**
89+
* @deprecated Prefer `openclaw/plugin-sdk/channel-message` adapters plus
90+
* `dispatchReplyWithBufferedBlockDispatcher` or channel turn helpers.
91+
* This is a low-level legacy dispatcher escape hatch.
92+
*/
8893
createReplyDispatcherWithTyping: CreateReplyDispatcherWithTyping;
8994
resolveEffectiveMessagesConfig: typeof import("../../agents/identity.js").resolveEffectiveMessagesConfig;
95+
/**
96+
* @deprecated Prefer the channel-message reply pipeline helpers. This is
97+
* tied to the low-level legacy dispatcher path.
98+
*/
9099
resolveHumanDelayConfig: typeof import("../../agents/identity.js").resolveHumanDelayConfig;
100+
/**
101+
* @deprecated Prefer `dispatchReplyWithBufferedBlockDispatcher` with a
102+
* channel-message adapter or the channel turn helpers. Direct use must
103+
* manually preserve source reply delivery metadata such as
104+
* `sourceReplyDeliveryMode`.
105+
*/
91106
dispatchReplyFromConfig: import("../../auto-reply/reply/dispatch-from-config.types.js").DispatchReplyFromConfig;
92107
withReplyDispatcher: typeof import("../../auto-reply/dispatch-dispatcher.js").withReplyDispatcher;
93108
settleReplyDispatcher: typeof import("../../auto-reply/dispatch-dispatcher.js").settleReplyDispatcher;
109+
/**
110+
* @deprecated Prefer `buildChannelInboundEventContext` /
111+
* `buildChannelTurnContext` from `openclaw/plugin-sdk/channel-inbound` so
112+
* inbound event metadata is carried into reply dispatch.
113+
*/
94114
finalizeInboundContext: typeof import("../../auto-reply/reply/inbound-context.js").finalizeInboundContext;
95115
formatAgentEnvelope: typeof import("../../auto-reply/envelope.js").formatAgentEnvelope;
96116
/** @deprecated Prefer `BodyForAgent` + structured user-context blocks (do not build plaintext envelopes for prompts). */
@@ -119,9 +139,11 @@ export type PluginRuntimeChannel = {
119139
get: typeof import("../../infra/channel-activity.js").getChannelActivity;
120140
};
121141
session: {
142+
/** @deprecated Prefer channel turn helpers that record inbound sessions as part of dispatch. */
122143
resolveStorePath: typeof import("../../config/sessions/paths.js").resolveStorePath;
123144
readSessionUpdatedAt: ReadSessionUpdatedAt;
124145
recordSessionMetaFromInbound: RecordSessionMetaFromInbound;
146+
/** @deprecated Prefer channel turn helpers that record inbound sessions as part of dispatch. */
125147
recordInboundSession: RecordInboundSession;
126148
updateLastRoute: UpdateLastRoute;
127149
};

0 commit comments

Comments
 (0)