mirror of
https://github.com/clawdbot/clawdbot.git
synced 2026-02-01 03:47:45 +01:00
fix: honor mention-bypass for group commands
This commit is contained in:
@@ -56,6 +56,7 @@
|
|||||||
- Docs: expand parameter descriptions for agent/wake hooks. (#532) — thanks @mcinteerj
|
- Docs: expand parameter descriptions for agent/wake hooks. (#532) — thanks @mcinteerj
|
||||||
- Docs: add community showcase entries from Discord. (#476) — thanks @gupsammy
|
- Docs: add community showcase entries from Discord. (#476) — thanks @gupsammy
|
||||||
- TUI: refresh status bar after think/verbose/reasoning changes. (#519) — thanks @jdrhyne
|
- TUI: refresh status bar after think/verbose/reasoning changes. (#519) — thanks @jdrhyne
|
||||||
|
- Commands: treat mention-bypassed group command messages as mentioned so elevated directives respond.
|
||||||
|
|
||||||
## 2026.1.8
|
## 2026.1.8
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ read_when:
|
|||||||
- **Global availability gate**: `agent.elevated` is global (not per-agent). If disabled or sender not allowlisted, elevated is unavailable everywhere.
|
- **Global availability gate**: `agent.elevated` is global (not per-agent). If disabled or sender not allowlisted, elevated is unavailable everywhere.
|
||||||
- **Per-session state**: `/elevated on|off` sets the elevated level for the current session key.
|
- **Per-session state**: `/elevated on|off` sets the elevated level for the current session key.
|
||||||
- **Inline directive**: `/elevated on` inside a message applies to that message only.
|
- **Inline directive**: `/elevated on` inside a message applies to that message only.
|
||||||
- **Groups**: In group chats, elevated directives are only honored when the agent is mentioned.
|
- **Groups**: In group chats, elevated directives are only honored when the agent is mentioned. Command-only messages that bypass mention requirements are treated as mentioned.
|
||||||
- **Host execution**: elevated runs `bash` on the host (bypasses sandbox).
|
- **Host execution**: elevated runs `bash` on the host (bypasses sandbox).
|
||||||
- **Unsandboxed agents**: when there is no sandbox to bypass, elevated does not change where `bash` runs.
|
- **Unsandboxed agents**: when there is no sandbox to bypass, elevated does not change where `bash` runs.
|
||||||
- **Tool policy still applies**: if `bash` is denied by tool policy, elevated cannot be used.
|
- **Tool policy still applies**: if `bash` is denied by tool policy, elevated cannot be used.
|
||||||
|
|||||||
@@ -785,6 +785,7 @@ export function createDiscordMessageHandler(params: {
|
|||||||
!hasAnyMention &&
|
!hasAnyMention &&
|
||||||
commandAuthorized &&
|
commandAuthorized &&
|
||||||
hasControlCommand(baseText);
|
hasControlCommand(baseText);
|
||||||
|
const effectiveWasMentioned = wasMentioned || shouldBypassMention;
|
||||||
const canDetectMention = Boolean(botId) || mentionRegexes.length > 0;
|
const canDetectMention = Boolean(botId) || mentionRegexes.length > 0;
|
||||||
if (isGuildMessage && shouldRequireMention) {
|
if (isGuildMessage && shouldRequireMention) {
|
||||||
if (botId && !wasMentioned && !shouldBypassMention) {
|
if (botId && !wasMentioned && !shouldBypassMention) {
|
||||||
@@ -981,7 +982,7 @@ export function createDiscordMessageHandler(params: {
|
|||||||
: undefined,
|
: undefined,
|
||||||
Provider: "discord" as const,
|
Provider: "discord" as const,
|
||||||
Surface: "discord" as const,
|
Surface: "discord" as const,
|
||||||
WasMentioned: wasMentioned,
|
WasMentioned: effectiveWasMentioned,
|
||||||
MessageSid: message.id,
|
MessageSid: message.id,
|
||||||
ParentSessionKey: threadKeys.parentSessionKey,
|
ParentSessionKey: threadKeys.parentSessionKey,
|
||||||
ThreadStarterBody: threadStarterBody,
|
ThreadStarterBody: threadStarterBody,
|
||||||
|
|||||||
@@ -326,6 +326,7 @@ export async function monitorIMessageProvider(
|
|||||||
!mentioned &&
|
!mentioned &&
|
||||||
commandAuthorized &&
|
commandAuthorized &&
|
||||||
hasControlCommand(messageText);
|
hasControlCommand(messageText);
|
||||||
|
const effectiveWasMentioned = mentioned || shouldBypassMention;
|
||||||
if (
|
if (
|
||||||
isGroup &&
|
isGroup &&
|
||||||
requireMention &&
|
requireMention &&
|
||||||
@@ -387,7 +388,7 @@ export async function monitorIMessageProvider(
|
|||||||
MediaPath: mediaPath,
|
MediaPath: mediaPath,
|
||||||
MediaType: mediaType,
|
MediaType: mediaType,
|
||||||
MediaUrl: mediaPath,
|
MediaUrl: mediaPath,
|
||||||
WasMentioned: mentioned,
|
WasMentioned: effectiveWasMentioned,
|
||||||
CommandAuthorized: commandAuthorized,
|
CommandAuthorized: commandAuthorized,
|
||||||
// Originating channel for reply routing.
|
// Originating channel for reply routing.
|
||||||
OriginatingChannel: "imessage" as const,
|
OriginatingChannel: "imessage" as const,
|
||||||
|
|||||||
@@ -250,6 +250,39 @@ describe("monitorSlackProvider tool results", () => {
|
|||||||
expect(replyMock.mock.calls[0][0].WasMentioned).toBe(true);
|
expect(replyMock.mock.calls[0][0].WasMentioned).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("treats control commands as mentions for group bypass", async () => {
|
||||||
|
replyMock.mockResolvedValue({ text: "ok" });
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
const run = monitorSlackProvider({
|
||||||
|
botToken: "bot-token",
|
||||||
|
appToken: "app-token",
|
||||||
|
abortSignal: controller.signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitForEvent("message");
|
||||||
|
const handler = getSlackHandlers()?.get("message");
|
||||||
|
if (!handler) throw new Error("Slack message handler not registered");
|
||||||
|
|
||||||
|
await handler({
|
||||||
|
event: {
|
||||||
|
type: "message",
|
||||||
|
user: "U1",
|
||||||
|
text: "/elevated off",
|
||||||
|
ts: "123",
|
||||||
|
channel: "C1",
|
||||||
|
channel_type: "channel",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await flush();
|
||||||
|
controller.abort();
|
||||||
|
await run;
|
||||||
|
|
||||||
|
expect(replyMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(replyMock.mock.calls[0][0].WasMentioned).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it("threads replies when incoming message is in a thread", async () => {
|
it("threads replies when incoming message is in a thread", async () => {
|
||||||
replyMock.mockResolvedValue({ text: "thread reply" });
|
replyMock.mockResolvedValue({ text: "thread reply" });
|
||||||
|
|
||||||
|
|||||||
@@ -913,6 +913,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
|||||||
!hasAnyMention &&
|
!hasAnyMention &&
|
||||||
commandAuthorized &&
|
commandAuthorized &&
|
||||||
hasControlCommand(message.text ?? "");
|
hasControlCommand(message.text ?? "");
|
||||||
|
const effectiveWasMentioned = wasMentioned || shouldBypassMention;
|
||||||
const canDetectMention = Boolean(botUserId) || mentionRegexes.length > 0;
|
const canDetectMention = Boolean(botUserId) || mentionRegexes.length > 0;
|
||||||
if (
|
if (
|
||||||
isRoom &&
|
isRoom &&
|
||||||
@@ -1058,7 +1059,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
|||||||
ThreadStarterBody: threadStarterBody,
|
ThreadStarterBody: threadStarterBody,
|
||||||
ThreadLabel: threadLabel,
|
ThreadLabel: threadLabel,
|
||||||
Timestamp: message.ts ? Math.round(Number(message.ts) * 1000) : undefined,
|
Timestamp: message.ts ? Math.round(Number(message.ts) * 1000) : undefined,
|
||||||
WasMentioned: isRoomish ? wasMentioned : undefined,
|
WasMentioned: isRoomish ? effectiveWasMentioned : undefined,
|
||||||
MediaPath: media?.path,
|
MediaPath: media?.path,
|
||||||
MediaType: media?.contentType,
|
MediaType: media?.contentType,
|
||||||
MediaUrl: media?.path,
|
MediaUrl: media?.path,
|
||||||
|
|||||||
@@ -486,6 +486,7 @@ export function createTelegramBot(opts: TelegramBotOptions) {
|
|||||||
!hasAnyMention &&
|
!hasAnyMention &&
|
||||||
commandAuthorized &&
|
commandAuthorized &&
|
||||||
hasControlCommand(msg.text ?? msg.caption ?? "");
|
hasControlCommand(msg.text ?? msg.caption ?? "");
|
||||||
|
const effectiveWasMentioned = wasMentioned || shouldBypassMention;
|
||||||
const canDetectMention = Boolean(botUsername) || mentionRegexes.length > 0;
|
const canDetectMention = Boolean(botUsername) || mentionRegexes.length > 0;
|
||||||
if (isGroup && requireMention && canDetectMention) {
|
if (isGroup && requireMention && canDetectMention) {
|
||||||
if (!wasMentioned && !shouldBypassMention) {
|
if (!wasMentioned && !shouldBypassMention) {
|
||||||
@@ -592,7 +593,7 @@ export function createTelegramBot(opts: TelegramBotOptions) {
|
|||||||
ReplyToBody: replyTarget?.body,
|
ReplyToBody: replyTarget?.body,
|
||||||
ReplyToSender: replyTarget?.sender,
|
ReplyToSender: replyTarget?.sender,
|
||||||
Timestamp: msg.date ? msg.date * 1000 : undefined,
|
Timestamp: msg.date ? msg.date * 1000 : undefined,
|
||||||
WasMentioned: isGroup ? wasMentioned : undefined,
|
WasMentioned: isGroup ? effectiveWasMentioned : undefined,
|
||||||
MediaPath: allMedia[0]?.path,
|
MediaPath: allMedia[0]?.path,
|
||||||
MediaType: allMedia[0]?.contentType,
|
MediaType: allMedia[0]?.contentType,
|
||||||
MediaUrl: allMedia[0]?.path,
|
MediaUrl: allMedia[0]?.path,
|
||||||
|
|||||||
Reference in New Issue
Block a user