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>
This commit is contained in:
ironbyte-rgb
2026-02-05 18:29:07 -06:00
committed by GitHub
parent 57326f72e6
commit 02842bef91
3 changed files with 105 additions and 0 deletions

View File

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

View File

@@ -255,6 +255,107 @@ describe("initSessionState reset triggers in WhatsApp groups", () => {
});
});
describe("initSessionState reset triggers in Slack channels", () => {
async function createStorePath(prefix: string): Promise<string> {
const root = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
return path.join(root, "sessions.json");
}
async function seedSessionStore(params: {
storePath: string;
sessionKey: string;
sessionId: string;
}): Promise<void> {
const { saveSessionStore } = await import("../../config/sessions.js");
await saveSessionStore(params.storePath, {
[params.sessionKey]: {
sessionId: params.sessionId,
updatedAt: Date.now(),
},
});
}
it("Reset trigger /reset works when Slack message has a leading <@...> mention token", async () => {
const storePath = await createStorePath("openclaw-slack-channel-reset-");
const sessionKey = "agent:main:slack:channel:c1";
const existingSessionId = "existing-session-123";
await seedSessionStore({
storePath,
sessionKey,
sessionId: existingSessionId,
});
const cfg = {
session: { store: storePath, idleMinutes: 999 },
} as OpenClawConfig;
const channelMessageCtx = {
Body: "<@U123> /reset",
RawBody: "<@U123> /reset",
CommandBody: "<@U123> /reset",
From: "slack:channel:C1",
To: "channel:C1",
ChatType: "channel",
SessionKey: sessionKey,
Provider: "slack",
Surface: "slack",
SenderId: "U123",
SenderName: "Owner",
};
const result = await initSessionState({
ctx: channelMessageCtx,
cfg,
commandAuthorized: true,
});
expect(result.isNewSession).toBe(true);
expect(result.resetTriggered).toBe(true);
expect(result.sessionId).not.toBe(existingSessionId);
expect(result.bodyStripped).toBe("");
});
it("Reset trigger /new preserves args when Slack message has a leading <@...> mention token", async () => {
const storePath = await createStorePath("openclaw-slack-channel-new-");
const sessionKey = "agent:main:slack:channel:c2";
const existingSessionId = "existing-session-123";
await seedSessionStore({
storePath,
sessionKey,
sessionId: existingSessionId,
});
const cfg = {
session: { store: storePath, idleMinutes: 999 },
} as OpenClawConfig;
const channelMessageCtx = {
Body: "<@U123> /new take notes",
RawBody: "<@U123> /new take notes",
CommandBody: "<@U123> /new take notes",
From: "slack:channel:C2",
To: "channel:C2",
ChatType: "channel",
SessionKey: sessionKey,
Provider: "slack",
Surface: "slack",
SenderId: "U123",
SenderName: "Owner",
};
const result = await initSessionState({
ctx: channelMessageCtx,
cfg,
commandAuthorized: true,
});
expect(result.isNewSession).toBe(true);
expect(result.resetTriggered).toBe(true);
expect(result.sessionId).not.toBe(existingSessionId);
expect(result.bodyStripped).toBe("take notes");
});
});
describe("applyResetModelOverride", () => {
it("selects a model hint and strips it from the body", async () => {
const cfg = {} as OpenClawConfig;

View File

@@ -295,6 +295,9 @@ const DOCKS: Record<ChatChannelId, ChannelDock> = {
resolveRequireMention: resolveSlackGroupRequireMention,
resolveToolPolicy: resolveSlackGroupToolPolicy,
},
mentions: {
stripPatterns: () => ["<@[^>]+>"],
},
threading: {
resolveReplyToMode: ({ cfg, accountId, chatType }) =>
resolveSlackReplyToMode(resolveSlackAccount({ cfg, accountId }), chatType),