refactor(slack): dedupe outbound hook handling

This commit is contained in:
Peter Steinberger
2026-02-15 06:49:48 +00:00
parent 57d0130336
commit a39a5a35b0
+58 -42
View File
@@ -17,6 +17,35 @@ function resolveSlackSendIdentity(identity?: OutboundIdentity): SlackSendIdentit
return { username, iconUrl, iconEmoji }; return { username, iconUrl, iconEmoji };
} }
async function applySlackMessageSendingHooks(params: {
to: string;
text: string;
threadTs?: string;
accountId?: string;
mediaUrl?: string;
}): Promise<{ cancelled: boolean; text: string }> {
const hookRunner = getGlobalHookRunner();
if (!hookRunner?.hasHooks("message_sending")) {
return { cancelled: false, text: params.text };
}
const hookResult = await hookRunner.runMessageSending(
{
to: params.to,
content: params.text,
metadata: {
threadTs: params.threadTs,
channelId: params.to,
...(params.mediaUrl ? { mediaUrl: params.mediaUrl } : {}),
},
},
{ channelId: "slack", accountId: params.accountId ?? undefined },
);
if (hookResult?.cancel) {
return { cancelled: true, text: params.text };
}
return { cancelled: false, text: hookResult?.content ?? params.text };
}
export const slackOutbound: ChannelOutboundAdapter = { export const slackOutbound: ChannelOutboundAdapter = {
deliveryMode: "direct", deliveryMode: "direct",
chunker: null, chunker: null,
@@ -25,30 +54,23 @@ export const slackOutbound: ChannelOutboundAdapter = {
const send = deps?.sendSlack ?? sendMessageSlack; const send = deps?.sendSlack ?? sendMessageSlack;
// Use threadId fallback so routed tool notifications stay in the Slack thread. // Use threadId fallback so routed tool notifications stay in the Slack thread.
const threadTs = replyToId ?? (threadId != null ? String(threadId) : undefined); const threadTs = replyToId ?? (threadId != null ? String(threadId) : undefined);
let finalText = text; const hookResult = await applySlackMessageSendingHooks({
to,
// Run message_sending hooks (e.g. thread-ownership can cancel the send). text,
const hookRunner = getGlobalHookRunner(); threadTs,
if (hookRunner?.hasHooks("message_sending")) { accountId: accountId ?? undefined,
const hookResult = await hookRunner.runMessageSending( });
{ to, content: text, metadata: { threadTs, channelId: to } }, if (hookResult.cancelled) {
{ channelId: "slack", accountId: accountId ?? undefined }, return {
); channel: "slack",
if (hookResult?.cancel) { messageId: "cancelled-by-hook",
return { channelId: to,
channel: "slack", meta: { cancelled: true },
messageId: "cancelled-by-hook", };
channelId: to,
meta: { cancelled: true },
};
}
if (hookResult?.content) {
finalText = hookResult.content;
}
} }
const slackIdentity = resolveSlackSendIdentity(identity); const slackIdentity = resolveSlackSendIdentity(identity);
const result = await send(to, finalText, { const result = await send(to, hookResult.text, {
threadTs, threadTs,
accountId: accountId ?? undefined, accountId: accountId ?? undefined,
...(slackIdentity ? { identity: slackIdentity } : {}), ...(slackIdentity ? { identity: slackIdentity } : {}),
@@ -59,30 +81,24 @@ export const slackOutbound: ChannelOutboundAdapter = {
const send = deps?.sendSlack ?? sendMessageSlack; const send = deps?.sendSlack ?? sendMessageSlack;
// Use threadId fallback so routed tool notifications stay in the Slack thread. // Use threadId fallback so routed tool notifications stay in the Slack thread.
const threadTs = replyToId ?? (threadId != null ? String(threadId) : undefined); const threadTs = replyToId ?? (threadId != null ? String(threadId) : undefined);
let finalText = text; const hookResult = await applySlackMessageSendingHooks({
to,
// Run message_sending hooks (e.g. thread-ownership can cancel the send). text,
const hookRunner = getGlobalHookRunner(); threadTs,
if (hookRunner?.hasHooks("message_sending")) { mediaUrl,
const hookResult = await hookRunner.runMessageSending( accountId: accountId ?? undefined,
{ to, content: text, metadata: { threadTs, channelId: to, mediaUrl } }, });
{ channelId: "slack", accountId: accountId ?? undefined }, if (hookResult.cancelled) {
); return {
if (hookResult?.cancel) { channel: "slack",
return { messageId: "cancelled-by-hook",
channel: "slack", channelId: to,
messageId: "cancelled-by-hook", meta: { cancelled: true },
channelId: to, };
meta: { cancelled: true },
};
}
if (hookResult?.content) {
finalText = hookResult.content;
}
} }
const slackIdentity = resolveSlackSendIdentity(identity); const slackIdentity = resolveSlackSendIdentity(identity);
const result = await send(to, finalText, { const result = await send(to, hookResult.text, {
mediaUrl, mediaUrl,
threadTs, threadTs,
accountId: accountId ?? undefined, accountId: accountId ?? undefined,