feat(hooks): wire up message_sent plugin hook

Fire the message_sent hook after successful/failed message sends.
This enables plugins to implement retry logic, logging, or other
post-send behaviors.

The hook is called in executeSendAction for both:
- Plugin-handled sends (via dispatchChannelMessageAction)
- Core sends (via sendMessage)

Hook payload includes:
- to: recipient
- content: message text
- success: boolean
- error: error message (when success=false)
This commit is contained in:
Mariusz Krawczyk 2026-01-28 15:44:53 +00:00
parent 01e0d3a320
commit ca1802015a

View File

@ -3,6 +3,7 @@ import { dispatchChannelMessageAction } from "../../channels/plugins/message-act
import type { ChannelId, ChannelThreadingToolContext } from "../../channels/plugins/types.js"; import type { ChannelId, ChannelThreadingToolContext } from "../../channels/plugins/types.js";
import type { MoltbotConfig } from "../../config/config.js"; import type { MoltbotConfig } from "../../config/config.js";
import { appendAssistantMessageToSessionTranscript } from "../../config/sessions.js"; import { appendAssistantMessageToSessionTranscript } from "../../config/sessions.js";
import { getGlobalHookRunner } from "../../plugins/hook-runner-global.js";
import type { GatewayClientMode, GatewayClientName } from "../../utils/message-channel.js"; import type { GatewayClientMode, GatewayClientName } from "../../utils/message-channel.js";
import type { OutboundSendDeps } from "./deliver.js"; import type { OutboundSendDeps } from "./deliver.js";
import type { MessagePollResult, MessageSendResult } from "./message.js"; import type { MessagePollResult, MessageSendResult } from "./message.js";
@ -81,7 +82,11 @@ export async function executeSendAction(params: {
}> { }> {
throwIfAborted(params.ctx.abortSignal); throwIfAborted(params.ctx.abortSignal);
if (!params.ctx.dryRun) { if (!params.ctx.dryRun) {
const handled = await dispatchChannelMessageAction({ let handled: AgentToolResult<unknown> | null = null;
let pluginError: string | undefined;
try {
handled = await dispatchChannelMessageAction({
channel: params.ctx.channel, channel: params.ctx.channel,
action: "send", action: "send",
cfg: params.ctx.cfg, cfg: params.ctx.cfg,
@ -91,7 +96,44 @@ export async function executeSendAction(params: {
toolContext: params.ctx.toolContext, toolContext: params.ctx.toolContext,
dryRun: params.ctx.dryRun, dryRun: params.ctx.dryRun,
}); });
} catch (err) {
pluginError = err instanceof Error ? err.message : String(err);
// Fire hook for failed plugin send
const hookRunner = getGlobalHookRunner();
if (hookRunner) {
hookRunner.runMessageSent(
{
to: params.to,
content: params.message,
success: false,
error: pluginError,
},
{
channelId: params.ctx.channel,
accountId: params.ctx.accountId ?? undefined,
},
);
}
throw err;
}
if (handled) { if (handled) {
// Fire hook for successful plugin send
const hookRunner = getGlobalHookRunner();
if (hookRunner) {
hookRunner.runMessageSent(
{
to: params.to,
content: params.message,
success: true,
},
{
channelId: params.ctx.channel,
accountId: params.ctx.accountId ?? undefined,
},
);
}
if (params.ctx.mirror) { if (params.ctx.mirror) {
const mirrorText = params.ctx.mirror.text ?? params.message; const mirrorText = params.ctx.mirror.text ?? params.message;
const mirrorMediaUrls = const mirrorMediaUrls =
@ -114,7 +156,12 @@ export async function executeSendAction(params: {
} }
throwIfAborted(params.ctx.abortSignal); throwIfAborted(params.ctx.abortSignal);
const result: MessageSendResult = await sendMessage({
let result: MessageSendResult;
let sendError: string | undefined;
try {
result = await sendMessage({
cfg: params.ctx.cfg, cfg: params.ctx.cfg,
to: params.to, to: params.to,
content: params.message, content: params.message,
@ -130,6 +177,42 @@ export async function executeSendAction(params: {
mirror: params.ctx.mirror, mirror: params.ctx.mirror,
abortSignal: params.ctx.abortSignal, abortSignal: params.ctx.abortSignal,
}); });
} catch (err) {
sendError = err instanceof Error ? err.message : String(err);
// Fire hook for failed send
const hookRunner = getGlobalHookRunner();
if (hookRunner) {
hookRunner.runMessageSent(
{
to: params.to,
content: params.message,
success: false,
error: sendError,
},
{
channelId: params.ctx.channel,
accountId: params.ctx.accountId ?? undefined,
},
);
}
throw err;
}
// Fire hook for successful send
const hookRunner = getGlobalHookRunner();
if (hookRunner) {
hookRunner.runMessageSent(
{
to: params.to,
content: params.message,
success: true,
},
{
channelId: params.ctx.channel,
accountId: params.ctx.accountId ?? undefined,
},
);
}
return { return {
handledBy: "core", handledBy: "core",