Skip to content

Commit 02842be

Browse files
ironbyte-rgbGrimvoltAIgrp06
authored
fix(slack): add mention stripPatterns for /new and /reset commands (#9971)
* fix(slack): add mention stripPatterns for /new and /reset commands Fixes #9937 The Slack dock was missing mentions.stripPatterns that Discord has. This caused /new and /reset to fail when sent with a mention (e.g. @bot /reset) because <@userid> wasn't stripped before matching. * fix(slack): strip mentions for /new and /reset (#9971) (thanks @ironbyte-rgb) --------- Co-authored-by: ironbyte-rgb <amontaboi76@gmail.com> Co-authored-by: George Pickett <gpickett00@gmail.com>
1 parent 57326f7 commit 02842be

File tree

3 files changed

+105
-0
lines changed

3 files changed

+105
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ Docs: https://docs.openclaw.ai
5151
- Onboarding: infer auth choice from non-interactive API key flags. (#8484) Thanks @f-trycua.
5252
- Security: keep untrusted channel metadata out of system prompts (Slack/Discord). Thanks @KonstantinMirin.
5353
- Discord: treat allowlisted senders as owner for system-prompt identity hints while keeping channel topics untrusted.
54+
- Slack: strip `<@...>` mention tokens before command matching so `/new` and `/reset` work when prefixed with a mention. (#9971) Thanks @ironbyte-rgb.
5455
- Security: enforce sandboxed media paths for message tool attachments. (#9182) Thanks @victormier.
5556
- Security: require explicit credentials for gateway URL overrides to prevent credential leakage. (#8113) Thanks @victormier.
5657
- Security: gate `whatsapp_login` tool to owner senders and default-deny non-owner contexts. (#8768) Thanks @victormier.

src/auto-reply/reply/session-resets.test.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,107 @@ describe("initSessionState reset triggers in WhatsApp groups", () => {
255255
});
256256
});
257257

258+
describe("initSessionState reset triggers in Slack channels", () => {
259+
async function createStorePath(prefix: string): Promise<string> {
260+
const root = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
261+
return path.join(root, "sessions.json");
262+
}
263+
264+
async function seedSessionStore(params: {
265+
storePath: string;
266+
sessionKey: string;
267+
sessionId: string;
268+
}): Promise<void> {
269+
const { saveSessionStore } = await import("../../config/sessions.js");
270+
await saveSessionStore(params.storePath, {
271+
[params.sessionKey]: {
272+
sessionId: params.sessionId,
273+
updatedAt: Date.now(),
274+
},
275+
});
276+
}
277+
278+
it("Reset trigger /reset works when Slack message has a leading <@...> mention token", async () => {
279+
const storePath = await createStorePath("openclaw-slack-channel-reset-");
280+
const sessionKey = "agent:main:slack:channel:c1";
281+
const existingSessionId = "existing-session-123";
282+
await seedSessionStore({
283+
storePath,
284+
sessionKey,
285+
sessionId: existingSessionId,
286+
});
287+
288+
const cfg = {
289+
session: { store: storePath, idleMinutes: 999 },
290+
} as OpenClawConfig;
291+
292+
const channelMessageCtx = {
293+
Body: "<@U123> /reset",
294+
RawBody: "<@U123> /reset",
295+
CommandBody: "<@U123> /reset",
296+
From: "slack:channel:C1",
297+
To: "channel:C1",
298+
ChatType: "channel",
299+
SessionKey: sessionKey,
300+
Provider: "slack",
301+
Surface: "slack",
302+
SenderId: "U123",
303+
SenderName: "Owner",
304+
};
305+
306+
const result = await initSessionState({
307+
ctx: channelMessageCtx,
308+
cfg,
309+
commandAuthorized: true,
310+
});
311+
312+
expect(result.isNewSession).toBe(true);
313+
expect(result.resetTriggered).toBe(true);
314+
expect(result.sessionId).not.toBe(existingSessionId);
315+
expect(result.bodyStripped).toBe("");
316+
});
317+
318+
it("Reset trigger /new preserves args when Slack message has a leading <@...> mention token", async () => {
319+
const storePath = await createStorePath("openclaw-slack-channel-new-");
320+
const sessionKey = "agent:main:slack:channel:c2";
321+
const existingSessionId = "existing-session-123";
322+
await seedSessionStore({
323+
storePath,
324+
sessionKey,
325+
sessionId: existingSessionId,
326+
});
327+
328+
const cfg = {
329+
session: { store: storePath, idleMinutes: 999 },
330+
} as OpenClawConfig;
331+
332+
const channelMessageCtx = {
333+
Body: "<@U123> /new take notes",
334+
RawBody: "<@U123> /new take notes",
335+
CommandBody: "<@U123> /new take notes",
336+
From: "slack:channel:C2",
337+
To: "channel:C2",
338+
ChatType: "channel",
339+
SessionKey: sessionKey,
340+
Provider: "slack",
341+
Surface: "slack",
342+
SenderId: "U123",
343+
SenderName: "Owner",
344+
};
345+
346+
const result = await initSessionState({
347+
ctx: channelMessageCtx,
348+
cfg,
349+
commandAuthorized: true,
350+
});
351+
352+
expect(result.isNewSession).toBe(true);
353+
expect(result.resetTriggered).toBe(true);
354+
expect(result.sessionId).not.toBe(existingSessionId);
355+
expect(result.bodyStripped).toBe("take notes");
356+
});
357+
});
358+
258359
describe("applyResetModelOverride", () => {
259360
it("selects a model hint and strips it from the body", async () => {
260361
const cfg = {} as OpenClawConfig;

src/channels/dock.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,9 @@ const DOCKS: Record<ChatChannelId, ChannelDock> = {
295295
resolveRequireMention: resolveSlackGroupRequireMention,
296296
resolveToolPolicy: resolveSlackGroupToolPolicy,
297297
},
298+
mentions: {
299+
stripPatterns: () => ["<@[^>]+>"],
300+
},
298301
threading: {
299302
resolveReplyToMode: ({ cfg, accountId, chatType }) =>
300303
resolveSlackReplyToMode(resolveSlackAccount({ cfg, accountId }), chatType),

0 commit comments

Comments
 (0)