import test from 'node:test'; import assert from 'node:assert/strict'; import { registerBeforeMessageWriteHook } from '../plugin/hooks/before-message-write.ts'; import { registerMessageReceivedHook } from '../plugin/hooks/message-received.ts'; import { registerMessageSentHook } from '../plugin/hooks/message-sent.ts'; import { buildDiscussionOriginCallbackMessage } from '../plugin/core/discussion-messages.ts'; import { initTurnOrder, onNewMessage, getTurnDebugInfo, resetTurn } from '../plugin/turn-manager.ts'; type Handler = (event: Record, ctx: Record) => unknown; function makeApi() { const handlers = new Map(); return { handlers, logger: { info: (_msg: string) => {}, warn: (_msg: string) => {}, }, on(name: string, handler: Handler) { handlers.set(name, handler); }, }; } test('before_message_write leaves ordinary channels dormant without sending a discussion idle reminder', async () => { const channelId = 'normal-channel'; resetTurn(channelId); initTurnOrder(channelId, ['agent-a', 'agent-b']); onNewMessage(channelId, 'human-user', true); const state = getTurnDebugInfo(channelId); const [firstSpeaker, secondSpeaker] = state.turnOrder as string[]; assert.ok(firstSpeaker); assert.ok(secondSpeaker); const sessionChannelId = new Map([ ['sess-a', channelId], ['sess-b', channelId], ]); const sessionAccountId = new Map([ ['sess-a', firstSpeaker], ['sess-b', secondSpeaker], ]); const sessionAllowed = new Map([ ['sess-a', true], ['sess-b', true], ]); const sessionTurnHandled = new Set(); const idleReminderCalls: string[] = []; const moderatorMessages: string[] = []; const api = makeApi(); registerBeforeMessageWriteHook({ api: api as any, baseConfig: { endSymbols: ['🔚'], moderatorBotToken: 'bot-token' } as any, policyState: { channelPolicies: {} }, sessionAllowed, sessionChannelId, sessionAccountId, sessionTurnHandled, ensurePolicyStateLoaded: () => {}, shouldDebugLog: () => false, ensureTurnOrder: () => {}, resolveDiscordUserId: () => undefined, isMultiMessageMode: () => false, sendModeratorMessage: async (_token, _channelId, content) => { moderatorMessages.push(content); return { ok: true }; }, discussionService: { maybeSendIdleReminder: async (id) => { idleReminderCalls.push(id); }, getDiscussion: () => undefined, }, }); const beforeMessageWrite = api.handlers.get('before_message_write'); assert.ok(beforeMessageWrite); await beforeMessageWrite?.({ message: { role: 'assistant', content: 'NO_REPLY' } }, { sessionKey: 'sess-a' }); await beforeMessageWrite?.({ message: { role: 'assistant', content: 'NO_REPLY' } }, { sessionKey: 'sess-b' }); assert.deepEqual(idleReminderCalls, []); assert.deepEqual(moderatorMessages, []); assert.equal(getTurnDebugInfo(channelId).dormant, true); }); test('message_received lets moderator discussion callback notifications wake the origin channel workflow', async () => { const channelId = '1474327736242798612'; resetTurn(channelId); initTurnOrder(channelId, ['agent-a', 'agent-b']); assert.equal(getTurnDebugInfo(channelId).currentSpeaker, null); const api = makeApi(); registerMessageReceivedHook({ api: api as any, baseConfig: { moderatorUserId: 'moderator-user', humanList: ['human-user'], } as any, shouldDebugLog: () => false, debugCtxSummary: () => ({}), ensureTurnOrder: () => {}, getModeratorUserId: (cfg) => (cfg as any).moderatorUserId, recordChannelAccount: () => false, extractMentionedUserIds: () => [], buildUserIdToAccountIdMap: () => new Map(), enterMultiMessageMode: () => {}, exitMultiMessageMode: () => {}, discussionService: { maybeReplyClosedChannel: async () => false, }, }); const messageReceived = api.handlers.get('message_received'); assert.ok(messageReceived); await messageReceived?.({ content: buildDiscussionOriginCallbackMessage('/workspace/plans/discussion-summary.md', 'discussion-42'), from: 'moderator-user', }, { conversationId: channelId, }); const state = getTurnDebugInfo(channelId); assert.equal(state.currentSpeaker, state.turnOrder[0]); assert.equal(state.dormant, false); }); test('message_sent skips handoff after discuss-callback has closed the discussion channel', async () => { const channelId = 'discussion-closed-channel'; resetTurn(channelId); initTurnOrder(channelId, ['agent-a', 'agent-b']); onNewMessage(channelId, 'human-user', true); const state = getTurnDebugInfo(channelId); const currentSpeaker = state.currentSpeaker as string; assert.ok(currentSpeaker); const moderatorMessages: string[] = []; const api = makeApi(); registerMessageSentHook({ api: api as any, baseConfig: { endSymbols: ['🔚'], moderatorBotToken: 'bot-token', schedulingIdentifier: '➡️', } as any, policyState: { channelPolicies: {} }, sessionChannelId: new Map([['sess-closed', channelId]]), sessionAccountId: new Map([['sess-closed', currentSpeaker]]), sessionTurnHandled: new Set(), ensurePolicyStateLoaded: () => {}, resolveDiscordUserId: () => 'discord-user-next', sendModeratorMessage: async (_token, _channelId, content) => { moderatorMessages.push(content); return { ok: true }; }, discussionService: { isClosedDiscussion: (id) => id === channelId, }, }); const messageSent = api.handlers.get('message_sent'); assert.ok(messageSent); await messageSent?.({ content: 'NO_REPLY' }, { sessionKey: 'sess-closed', accountId: currentSpeaker, channelId }); assert.deepEqual(moderatorMessages, []); assert.equal(getTurnDebugInfo(channelId).currentSpeaker, currentSpeaker); });