feat: add agent API and operational analytics

This commit is contained in:
jjbrunton committed 2026-08-08 22:08:14 +01:00
1 parent 720ea0652a
commit e8b730b592
65 files changed
+2650 -1214

No files matched your search

@@ -0,0 +1,46 @@
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');
}
if (outcome === 'engaged' && !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.`);
}
return outcome;
}