import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; import { onNewMessage, setMentionOverride, getTurnDebugInfo } from "../turn-manager.js"; import { extractDiscordChannelId } from "../channel-resolver.js"; import type { DirigentConfig } from "../rules.js"; type DebugConfig = { enableDebugLogs?: boolean; debugLogChannelIds?: string[]; }; type MessageReceivedDeps = { api: OpenClawPluginApi; baseConfig: DirigentConfig; shouldDebugLog: (config: DirigentConfig & DebugConfig, channelId?: string) => boolean; debugCtxSummary: (ctx: Record, event: Record) => Record; ensureTurnOrder: (api: OpenClawPluginApi, channelId: string) => Promise | void; getModeratorUserId: (cfg: DirigentConfig) => string | undefined; recordChannelAccount: (api: OpenClawPluginApi, channelId: string, accountId: string) => boolean; extractMentionedUserIds: (content: string) => string[]; buildUserIdToAccountIdMap: (api: OpenClawPluginApi) => Map; enterMultiMessageMode: (channelId: string) => void; exitMultiMessageMode: (channelId: string) => void; discussionService?: { maybeReplyClosedChannel: (channelId: string, senderId?: string) => Promise; }; }; export function registerMessageReceivedHook(deps: MessageReceivedDeps): void { const { api, baseConfig, shouldDebugLog, debugCtxSummary, ensureTurnOrder, getModeratorUserId, recordChannelAccount, extractMentionedUserIds, buildUserIdToAccountIdMap, enterMultiMessageMode, exitMultiMessageMode, discussionService, } = deps; api.on("message_received", async (event, ctx) => { try { const c = (ctx || {}) as Record; const e = (event || {}) as Record; const preChannelId = extractDiscordChannelId(c, e); const livePre = baseConfig as DirigentConfig & DebugConfig; if (shouldDebugLog(livePre, preChannelId)) { api.logger.info(`dirigent: debug message_received preflight ctx=${JSON.stringify(debugCtxSummary(c, e))}`); } if (preChannelId) { await ensureTurnOrder(api, preChannelId); const metadata = (e as Record).metadata as Record | undefined; const from = (typeof metadata?.senderId === "string" && metadata.senderId) || (typeof (e as Record).from === "string" ? ((e as Record).from as string) : ""); const moderatorUserId = getModeratorUserId(livePre); if (discussionService) { const closedHandled = await discussionService.maybeReplyClosedChannel(preChannelId, from); if (closedHandled) return; } if (moderatorUserId && from === moderatorUserId) { if (shouldDebugLog(livePre, preChannelId)) { api.logger.info(`dirigent: ignoring moderator message in channel=${preChannelId}`); } } else { const humanList = livePre.humanList || livePre.bypassUserIds || []; const isHuman = humanList.includes(from); const senderAccountId = typeof c.accountId === "string" ? c.accountId : undefined; if (senderAccountId && senderAccountId !== "default") { const isNew = recordChannelAccount(api, preChannelId, senderAccountId); if (isNew) { await ensureTurnOrder(api, preChannelId); api.logger.info(`dirigent: new account ${senderAccountId} seen in channel=${preChannelId}, turn order updated`); } } if (isHuman) { const messageContent = ((e as Record).content as string) || ((e as Record).text as string) || ""; // Handle multi-message mode markers const startMarker = livePre.multiMessageStartMarker || "↗️"; const endMarker = livePre.multiMessageEndMarker || "↙️"; if (messageContent.includes(startMarker)) { enterMultiMessageMode(preChannelId); api.logger.info(`dirigent: entered multi-message mode channel=${preChannelId}`); } else if (messageContent.includes(endMarker)) { exitMultiMessageMode(preChannelId); api.logger.info(`dirigent: exited multi-message mode channel=${preChannelId}`); // After exiting multi-message mode, activate the turn system onNewMessage(preChannelId, senderAccountId, isHuman); } else { const mentionedUserIds = extractMentionedUserIds(messageContent); if (mentionedUserIds.length > 0) { const userIdMap = buildUserIdToAccountIdMap(api); const mentionedAccountIds = mentionedUserIds.map((uid) => userIdMap.get(uid)).filter((aid): aid is string => !!aid); if (mentionedAccountIds.length > 0) { await ensureTurnOrder(api, preChannelId); const overrideSet = setMentionOverride(preChannelId, mentionedAccountIds); if (overrideSet) { api.logger.info( `dirigent: mention override set channel=${preChannelId} mentionedAgents=${JSON.stringify(mentionedAccountIds)}`, ); if (shouldDebugLog(livePre, preChannelId)) { api.logger.info(`dirigent: turn state after override: ${JSON.stringify(getTurnDebugInfo(preChannelId))}`); } } else { onNewMessage(preChannelId, senderAccountId, isHuman); } } else { onNewMessage(preChannelId, senderAccountId, isHuman); } } else { onNewMessage(preChannelId, senderAccountId, isHuman); } } } else { onNewMessage(preChannelId, senderAccountId, isHuman); } if (shouldDebugLog(livePre, preChannelId)) { api.logger.info( `dirigent: turn onNewMessage channel=${preChannelId} from=${from} isHuman=${isHuman} accountId=${senderAccountId ?? "unknown"}`, ); } } } } catch (err) { api.logger.warn(`dirigent: message hook failed: ${String(err)}`); } }); }