mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
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:
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user