import type { ConversationOutcomesRepository, MessagesRepository, SentMessageRecord, SuccessfulMessagesRepository, } from '../database/repositories.js'; import type { Logger } from '../utils/logger.js'; import { classifyConversationOutcome, type OutcomeContext, type ConversationOutcome } from './conversation-outcome.js'; export interface ConversationOutcomeDependencies { messages: MessagesRepository; successful: SuccessfulMessagesRepository; outcomes: ConversationOutcomesRepository; logger: Logger; } /** Canonical reply/outcome path shared by inbox and Matrix ingestion. */ export function recordConversationReply( message: SentMessageRecord, responseSnippet: string, context: OutcomeContext, deps: ConversationOutcomeDependencies, ): ConversationOutcome { const outcome = classifyConversationOutcome(responseSnippet, context); deps.messages.markGotResponse(message.id); deps.outcomes.record(message.id, outcome, responseSnippet || null); if (outcome === 'boundary') { deps.outcomes.suppressRecipient(message.recipient, 'boundary'); } const terminalNegative = outcome === 'boundary' || outcome === 'declined' || outcome === 'upsell'; if (terminalNegative || (context.authoritativeTranscript && (outcome !== 'engaged' || !context.learningEligible))) { deps.successful.deleteBySentMessageId(message.id); } if (outcome === 'engaged' && context.learningEligible && !deps.successful.existsByMessage(message.message)) { deps.successful.insert({ sentMessageId: message.id, originalMessage: message.message, imageAnalysis: message.postImageAnalysis ?? null, postTitle: message.postTitle ?? null, responseSnippet: responseSnippet || null, subreddit: message.subreddit ?? null, }); deps.logger.info(`Engaged response from u/${message.recipient}; stored for learning.`); } else if (outcome !== 'engaged') { deps.logger.info(`Response from u/${message.recipient} classified as ${outcome}; excluded from learning examples.`); } else if (!context.learningEligible) { deps.logger.info(`Response from u/${message.recipient} is not terminal; deferred from learning examples.`); } return outcome; }