142 lines
6.2 KiB
TypeScript
142 lines
6.2 KiB
TypeScript
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<string, unknown>, event: Record<string, unknown>) => Record<string, unknown>;
|
|
ensureTurnOrder: (api: OpenClawPluginApi, channelId: string) => Promise<void> | void;
|
|
getModeratorUserId: (cfg: DirigentConfig) => string | undefined;
|
|
recordChannelAccount: (api: OpenClawPluginApi, channelId: string, accountId: string) => boolean;
|
|
extractMentionedUserIds: (content: string) => string[];
|
|
buildUserIdToAccountIdMap: (api: OpenClawPluginApi) => Map<string, string>;
|
|
enterMultiMessageMode: (channelId: string) => void;
|
|
exitMultiMessageMode: (channelId: string) => void;
|
|
discussionService?: {
|
|
maybeReplyClosedChannel: (channelId: string, senderId?: string) => Promise<boolean>;
|
|
};
|
|
};
|
|
|
|
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<string, unknown>;
|
|
const e = (event || {}) as Record<string, unknown>;
|
|
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<string, unknown>).metadata as Record<string, unknown> | undefined;
|
|
const from =
|
|
(typeof metadata?.senderId === "string" && metadata.senderId) ||
|
|
(typeof (e as Record<string, unknown>).from === "string" ? ((e as Record<string, unknown>).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<string, unknown>).content as string) || ((e as Record<string, unknown>).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)}`);
|
|
}
|
|
});
|
|
}
|