Harden conversation replies and prioritize newest candidates

This commit is contained in:
jjbrunton committed 2026-08-09 18:16:57 +01:00
1 parent f98c5c732d
commit b8e4a1bb88
23 files changed
+598 -113

No files matched your search

+6 -17
View File
@@ -1,5 +1,6 @@
import { createHash } from 'node:crypto';
import { isSafeOutboundMessage } from './outbound-message-safety.js';
import { containsMediaRequest, detectConversationBoundaries } from './conversation-boundaries.js';
export interface ConversationMessage {
sender: string;
@@ -38,7 +39,7 @@ export interface ConversationState {
export const LEGACY_AUTOMATED_CONVERSATION_TURNS = 3;
export const DEFAULT_HYBRID_RAPPORT_TURNS = 3;
export const CONVERSATION_POLICY_VERSION = 2;
export const CONVERSATION_POLICY_VERSION = 3;
export type ConversationPolicyMode = 'legacy' | 'r4r' | 'r4r-media' | 'manual';
export type HybridConversationPhase = 'rapport' | 'media-ask' | 'post-media-ask';
@@ -103,11 +104,6 @@ export function buildConversationState(messages: ConversationMessage[]): Convers
.filter((message) => !message.isSelf)
.map((message) => message.text)
.join('\n');
const ownText = messages
.filter((message) => message.isSelf)
.map((message) => message.text)
.join('\n');
const ageMatch = firstSelfMessage.match(/\b(3[0-5])\s*[mM]\b/);
const persona: ConversationPersona = {
age: ageMatch ? Number(ageMatch[1]) : EMPTY_PERSONA.age,
@@ -115,20 +111,14 @@ export function buildConversationState(messages: ConversationMessage[]): Convers
relationshipStatus: /\bmarried\b|\bwife\b/i.test(firstSelfMessage) ? 'married' : EMPTY_PERSONA.relationshipStatus,
};
const boundaries: string[] = [];
if (/\b(no|not)\b[^.\n]{0,50}\b(pic|pics|photo|photos|selfie|selfies|video|videos|nudes?)\b|\bdon't send\b[^.\n]{0,30}\b(pic|photo|video|nudes?)\b/i.test(theirText)) {
boundaries.push('does not want to send photos or videos');
}
if (/\bstop\b|\bleave me alone\b|\bnot interested\b/i.test(theirText)) {
boundaries.push('does not want further sexual escalation');
}
const boundaries = detectConversationBoundaries(messages);
const topics = extractTopics(theirText);
return {
persona,
boundaries,
sellerDetected: false,
askedForMedia: /\b(send|show|share)\b[^.\n]{0,40}\b(pic|pics|photo|photos|selfie|selfies|video|videos|nudes?)\b/i.test(ownText),
askedForMedia: messages.some((message) => message.isSelf && containsMediaRequest(message.text)),
topics,
automation: createConversationAutomationState(),
};
@@ -214,14 +204,13 @@ export function isSafeConversationReply(message: string): boolean {
/** R4R automation may converse and flirt, but it must never solicit sexual media. */
export function isSafeR4RReply(message: string): boolean {
if (!isSafeConversationReply(message)) return false;
return !/\b(send|show|share|swap|trade|drop|give|dm)\b[^.!?\n]{0,60}\b(pic|pics|photo|photos|selfie|selfies|video|videos|nude|nudes|content)\b/i.test(message)
&& !/\b(can|could|would|will) you\b[^.!?\n]{0,60}\b(pic|pics|photo|photos|selfie|selfies|video|videos|nude|nudes)\b/i.test(message);
return !containsMediaRequest(message);
}
/** The hybrid policy gets one optional, non-explicit image request after rapport. */
export function isSafeHybridMediaAsk(message: string): boolean {
if (!isSafeConversationReply(message)) return false;
if (!/\b(pic|pics|photo|photos|selfie|selfies|video|videos)\b/i.test(message)) return false;
if (!containsMediaRequest(message)) return false;
if (/\b(nude|nudes|naked|explicit|nsfw|sexy pic|sexual)\b/i.test(message)) return false;
return !/\b(must|prove|dare|owe|have to|need to)\b/i.test(message);
}