feat: label AI-generated conversation messages

This commit is contained in:
jjbrunton committed 2026-08-08 23:23:26 +01:00
1 parent 3beb8aada2
commit 5ad7f1bf77
10 files changed
+69 -11

No files matched your search

+2
View File
@@ -7,6 +7,7 @@ import type { ChatService } from './chat/service.js';
import {
buildConversationState,
chooseConversationIntent,
conversationMessageFingerprint,
MAX_AUTOMATED_CONVERSATION_TURNS,
mergeConversationState,
remainingAutomationTurns,
@@ -101,6 +102,7 @@ export class ConversationAutomationService {
state.askedForMedia = true;
state.automation.turnsUsed += 1;
state.automation.lastIncomingKey = incomingKey;
state.automation.aiMessageFingerprints.push(conversationMessageFingerprint(reply));
const exhausted = state.automation.turnsUsed >= MAX_AUTOMATED_CONVERSATION_TURNS;
if (exhausted) {
state.automation.status = 'human-required';
+12
View File
@@ -1,3 +1,5 @@
import { createHash } from 'node:crypto';
export interface ConversationMessage {
sender: string;
text: string;
@@ -45,6 +47,8 @@ export interface ConversationAutomationState {
status: ConversationAutomationStatus;
lastIncomingKey: string | null;
handoffReason: string | null;
/** SHA-256 fingerprints avoid storing a second copy of generated message text. */
aiMessageFingerprints: string[];
}
export function createConversationAutomationState(): ConversationAutomationState {
@@ -53,9 +57,14 @@ export function createConversationAutomationState(): ConversationAutomationState
status: 'active',
lastIncomingKey: null,
handoffReason: null,
aiMessageFingerprints: [],
};
}
export function conversationMessageFingerprint(message: string): string {
return createHash('sha256').update(message.trim()).digest('hex');
}
export function remainingAutomationTurns(state: ConversationState): number {
if (state.automation.status !== 'active') return 0;
return Math.max(0, MAX_AUTOMATED_CONVERSATION_TURNS - state.automation.turnsUsed);
@@ -222,6 +231,9 @@ function parseAutomationState(value: unknown): ConversationAutomationState {
status,
lastIncomingKey: typeof parsed.lastIncomingKey === 'string' ? parsed.lastIncomingKey : null,
handoffReason: typeof parsed.handoffReason === 'string' ? parsed.handoffReason : null,
aiMessageFingerprints: Array.isArray(parsed.aiMessageFingerprints)
? parsed.aiMessageFingerprints.filter((item): item is string => typeof item === 'string')
: [],
};
}