feat: add removeAckAfterReply option for Discord, Slack, and Telegram
Add `messages.removeAckAfterReply` config option to automatically remove acknowledgment reactions after the bot sends a reply, reducing visual clutter while still providing immediate feedback. Platforms: Discord, Slack, Telegram Implementation: - Added removeAckAfterReply boolean field to MessagesConfig (default: false) - Track ack reaction state in all three platform handlers - Remove ack reaction after successful reply delivery - Graceful error handling with verbose logging Platform-specific: - Discord: uses removeReactionDiscord() - Slack: uses removeSlackReaction() - Telegram: uses setMessageReaction() with empty array Closes #627
This commit is contained in:
committed by
Peter Steinberger
parent
a29f5dda2e
commit
b5858c0148
@@ -999,6 +999,8 @@ export type MessagesConfig = {
|
|||||||
ackReaction?: string;
|
ackReaction?: string;
|
||||||
/** When to send ack reactions. Default: "group-mentions". */
|
/** When to send ack reactions. Default: "group-mentions". */
|
||||||
ackReactionScope?: "group-mentions" | "group-all" | "direct" | "all";
|
ackReactionScope?: "group-mentions" | "group-all" | "direct" | "all";
|
||||||
|
/** Remove ack reaction after reply is sent (default: false). */
|
||||||
|
removeAckAfterReply?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CommandsConfig = {
|
export type CommandsConfig = {
|
||||||
|
|||||||
@@ -603,6 +603,7 @@ const MessagesSchema = z
|
|||||||
ackReactionScope: z
|
ackReactionScope: z
|
||||||
.enum(["group-mentions", "group-all", "direct", "all"])
|
.enum(["group-mentions", "group-all", "direct", "all"])
|
||||||
.optional(),
|
.optional(),
|
||||||
|
removeAckAfterReply: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
.optional();
|
.optional();
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,11 @@ import {
|
|||||||
waitForDiscordGatewayStop,
|
waitForDiscordGatewayStop,
|
||||||
} from "./monitor.gateway.js";
|
} from "./monitor.gateway.js";
|
||||||
import { fetchDiscordApplicationId } from "./probe.js";
|
import { fetchDiscordApplicationId } from "./probe.js";
|
||||||
import { reactMessageDiscord, sendMessageDiscord } from "./send.js";
|
import {
|
||||||
|
reactMessageDiscord,
|
||||||
|
removeReactionDiscord,
|
||||||
|
sendMessageDiscord,
|
||||||
|
} from "./send.js";
|
||||||
import { normalizeDiscordToken } from "./token.js";
|
import { normalizeDiscordToken } from "./token.js";
|
||||||
|
|
||||||
export type MonitorDiscordOpts = {
|
export type MonitorDiscordOpts = {
|
||||||
@@ -958,6 +962,7 @@ export function createDiscordMessageHandler(params: {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ackReaction = resolveAckReaction(cfg, route.agentId);
|
const ackReaction = resolveAckReaction(cfg, route.agentId);
|
||||||
|
const removeAckAfterReply = cfg.messages?.removeAckAfterReply ?? false;
|
||||||
const shouldAckReaction = () => {
|
const shouldAckReaction = () => {
|
||||||
if (!ackReaction) return false;
|
if (!ackReaction) return false;
|
||||||
if (ackReactionScope === "all") return true;
|
if (ackReactionScope === "all") return true;
|
||||||
@@ -972,6 +977,7 @@ export function createDiscordMessageHandler(params: {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
let didAddAckReaction = false;
|
||||||
if (shouldAckReaction()) {
|
if (shouldAckReaction()) {
|
||||||
reactMessageDiscord(message.channelId, message.id, ackReaction, {
|
reactMessageDiscord(message.channelId, message.id, ackReaction, {
|
||||||
rest: client.rest,
|
rest: client.rest,
|
||||||
@@ -980,6 +986,7 @@ export function createDiscordMessageHandler(params: {
|
|||||||
`discord react failed for channel ${message.channelId}: ${String(err)}`,
|
`discord react failed for channel ${message.channelId}: ${String(err)}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
didAddAckReaction = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fromLabel = isDirectMessage
|
const fromLabel = isDirectMessage
|
||||||
@@ -1201,6 +1208,15 @@ export function createDiscordMessageHandler(params: {
|
|||||||
`discord: delivered ${finalCount} reply${finalCount === 1 ? "" : "ies"} to ${replyTarget}`,
|
`discord: delivered ${finalCount} reply${finalCount === 1 ? "" : "ies"} to ${replyTarget}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (removeAckAfterReply && didAddAckReaction && ackReaction) {
|
||||||
|
removeReactionDiscord(message.channelId, message.id, ackReaction, {
|
||||||
|
rest: client.rest,
|
||||||
|
}).catch((err) => {
|
||||||
|
logVerbose(
|
||||||
|
`discord: failed to remove ack reaction from ${message.channelId}/${message.id}: ${String(err)}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
isGuildMessage &&
|
isGuildMessage &&
|
||||||
shouldClearHistory &&
|
shouldClearHistory &&
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ import {
|
|||||||
} from "../routing/session-key.js";
|
} from "../routing/session-key.js";
|
||||||
import type { RuntimeEnv } from "../runtime.js";
|
import type { RuntimeEnv } from "../runtime.js";
|
||||||
import { resolveSlackAccount } from "./accounts.js";
|
import { resolveSlackAccount } from "./accounts.js";
|
||||||
import { reactSlackMessage } from "./actions.js";
|
import { reactSlackMessage, removeSlackReaction } from "./actions.js";
|
||||||
import { sendMessageSlack } from "./send.js";
|
import { sendMessageSlack } from "./send.js";
|
||||||
import { resolveSlackThreadTargets } from "./threading.js";
|
import { resolveSlackThreadTargets } from "./threading.js";
|
||||||
import { resolveSlackAppToken, resolveSlackBotToken } from "./token.js";
|
import { resolveSlackAppToken, resolveSlackBotToken } from "./token.js";
|
||||||
@@ -913,6 +913,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
|||||||
const rawBody = (message.text ?? "").trim() || media?.placeholder || "";
|
const rawBody = (message.text ?? "").trim() || media?.placeholder || "";
|
||||||
if (!rawBody) return;
|
if (!rawBody) return;
|
||||||
const ackReaction = resolveAckReaction(cfg, route.agentId);
|
const ackReaction = resolveAckReaction(cfg, route.agentId);
|
||||||
|
const removeAckAfterReply = cfg.messages?.removeAckAfterReply ?? false;
|
||||||
const shouldAckReaction = () => {
|
const shouldAckReaction = () => {
|
||||||
if (!ackReaction) return false;
|
if (!ackReaction) return false;
|
||||||
if (ackReactionScope === "all") return true;
|
if (ackReactionScope === "all") return true;
|
||||||
@@ -927,6 +928,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
let didAddAckReaction = false;
|
||||||
if (shouldAckReaction() && message.ts) {
|
if (shouldAckReaction() && message.ts) {
|
||||||
reactSlackMessage(message.channel, message.ts, ackReaction, {
|
reactSlackMessage(message.channel, message.ts, ackReaction, {
|
||||||
token: botToken,
|
token: botToken,
|
||||||
@@ -936,6 +938,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
|||||||
`slack react failed for channel ${message.channel}: ${String(err)}`,
|
`slack react failed for channel ${message.channel}: ${String(err)}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
didAddAckReaction = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const roomLabel = channelName ? `#${channelName}` : `#${message.channel}`;
|
const roomLabel = channelName ? `#${channelName}` : `#${message.channel}`;
|
||||||
@@ -1157,6 +1160,16 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
|||||||
`slack: delivered ${finalCount} reply${finalCount === 1 ? "" : "ies"} to ${replyTarget}`,
|
`slack: delivered ${finalCount} reply${finalCount === 1 ? "" : "ies"} to ${replyTarget}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (removeAckAfterReply && didAddAckReaction && ackReaction && message.ts) {
|
||||||
|
removeSlackReaction(message.channel, message.ts, ackReaction, {
|
||||||
|
token: botToken,
|
||||||
|
client: app.client,
|
||||||
|
}).catch((err) => {
|
||||||
|
logVerbose(
|
||||||
|
`slack: failed to remove ack reaction from ${message.channel}/${message.ts}: ${String(err)}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
app.event(
|
app.event(
|
||||||
|
|||||||
@@ -577,6 +577,7 @@ export function createTelegramBot(opts: TelegramBotOptions) {
|
|||||||
|
|
||||||
// ACK reactions
|
// ACK reactions
|
||||||
const ackReaction = resolveAckReaction(cfg, route.agentId);
|
const ackReaction = resolveAckReaction(cfg, route.agentId);
|
||||||
|
const removeAckAfterReply = cfg.messages?.removeAckAfterReply ?? false;
|
||||||
const shouldAckReaction = () => {
|
const shouldAckReaction = () => {
|
||||||
if (!ackReaction) return false;
|
if (!ackReaction) return false;
|
||||||
if (ackReactionScope === "all") return true;
|
if (ackReactionScope === "all") return true;
|
||||||
@@ -590,6 +591,7 @@ export function createTelegramBot(opts: TelegramBotOptions) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
let didAddAckReaction = false;
|
||||||
if (shouldAckReaction() && msg.message_id) {
|
if (shouldAckReaction() && msg.message_id) {
|
||||||
const api = bot.api as unknown as {
|
const api = bot.api as unknown as {
|
||||||
setMessageReaction?: (
|
setMessageReaction?: (
|
||||||
@@ -608,6 +610,7 @@ export function createTelegramBot(opts: TelegramBotOptions) {
|
|||||||
`telegram react failed for chat ${chatId}: ${String(err)}`,
|
`telegram react failed for chat ${chatId}: ${String(err)}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
didAddAckReaction = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -854,6 +857,22 @@ export function createTelegramBot(opts: TelegramBotOptions) {
|
|||||||
markDispatchIdle();
|
markDispatchIdle();
|
||||||
draftStream?.stop();
|
draftStream?.stop();
|
||||||
if (!queuedFinal) return;
|
if (!queuedFinal) return;
|
||||||
|
if (removeAckAfterReply && didAddAckReaction && msg.message_id) {
|
||||||
|
const api = bot.api as unknown as {
|
||||||
|
setMessageReaction?: (
|
||||||
|
chatId: number | string,
|
||||||
|
messageId: number,
|
||||||
|
reactions: Array<{ type: "emoji"; emoji: string }>,
|
||||||
|
) => Promise<void>;
|
||||||
|
};
|
||||||
|
if (typeof api.setMessageReaction === "function") {
|
||||||
|
api.setMessageReaction(chatId, msg.message_id, []).catch((err) => {
|
||||||
|
logVerbose(
|
||||||
|
`telegram: failed to remove ack reaction from ${chatId}/${msg.message_id}: ${String(err)}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const nativeCommands = nativeEnabled ? listNativeCommandSpecs() : [];
|
const nativeCommands = nativeEnabled ? listNativeCommandSpecs() : [];
|
||||||
|
|||||||
Reference in New Issue
Block a user