Add human-led conversation learning loop
This commit is contained in:
1 parent
bc42c55555
commit
687abab82b
52 files changed
+2112
-53
No files matched your search
@@ -23,6 +23,7 @@ export type { InvitationCooldown } from './types.js';
|
||||
import { fetchWithTimeout } from '../../utils/fetch-with-timeout.js';
|
||||
import type { AccountAuthGate } from '../account-auth-context.js';
|
||||
import { containsMediaRequest } from '../conversation-boundaries.js';
|
||||
import type { ConversationLearningObserver } from '../conversation-learning-observer.js';
|
||||
|
||||
const ACTIVE_WINDOW_MS = 96 * 60 * 60 * 1000;
|
||||
const ROOM_URL_PREFIX = 'https://chat.reddit.com/room/';
|
||||
@@ -60,6 +61,11 @@ export type ConversationStartResult =
|
||||
| { status: 'terminal'; reason: string }
|
||||
| { status: 'retryable'; reason: string; retryAt: number };
|
||||
|
||||
export interface ChatSendReceipt {
|
||||
eventId: string;
|
||||
sentAt: number;
|
||||
}
|
||||
|
||||
interface BuildChatOpts {
|
||||
knownUsernames: Map<string, string>;
|
||||
includeTimelineMessages?: boolean;
|
||||
@@ -170,6 +176,7 @@ export class ChatService {
|
||||
private accountAuth?: AccountAuthGate,
|
||||
private outcomesRepo?: ConversationOutcomesRepository,
|
||||
private conversationStateRepo?: ConversationStateRepository,
|
||||
private conversationLearningObserver?: ConversationLearningObserver,
|
||||
) {
|
||||
this.cache = cache ?? new ChatCache(logger);
|
||||
if (this.redditUsersRepo) {
|
||||
@@ -608,38 +615,48 @@ export class ChatService {
|
||||
* a raw Matrix room ID like `!abc:reddit.com`.
|
||||
*/
|
||||
async sendChatMessage(chatUrlOrRoomId: string, message: string, txnId?: string): Promise<boolean> {
|
||||
if (!this.accountAuth?.isFullyAuthenticated()) return false;
|
||||
return (await this.sendChatMessageDetailed(chatUrlOrRoomId, message, txnId)) !== null;
|
||||
}
|
||||
|
||||
/** Send a reply and retain the authoritative Matrix event identity. */
|
||||
async sendChatMessageDetailed(
|
||||
chatUrlOrRoomId: string,
|
||||
message: string,
|
||||
txnId?: string,
|
||||
): Promise<ChatSendReceipt | null> {
|
||||
if (!this.accountAuth?.isFullyAuthenticated()) return null;
|
||||
const active = this.getInvitationCooldown();
|
||||
if (active && this.blocksExistingRoomSends(active)) {
|
||||
const secondsLeft = Math.ceil((active.until - Date.now()) / 1000);
|
||||
this.logger.warn(
|
||||
`[chat] skipping sendChatMessage — send cooldown active for ${secondsLeft}s (${active.redditCode ?? 'rate-limited'})`,
|
||||
);
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
return this.enqueue(async () => {
|
||||
try {
|
||||
const roomId = ChatService.extractRoomId(chatUrlOrRoomId);
|
||||
if (!roomId) {
|
||||
this.logger.error(`[chat] cannot extract room ID from "${chatUrlOrRoomId}"`);
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
const eventId = await this.withAuth(() => this.matrix.sendTextMessage(roomId, message, txnId));
|
||||
this.logger.info(`[chat] sent message to ${roomId} (event_id=${eventId})`);
|
||||
const chat = this.cache.get().find((c) => c.roomId === roomId);
|
||||
const sentAt = Date.now();
|
||||
if (chat) {
|
||||
chat.lastMessageSnippet = message.slice(0, 200);
|
||||
chat.lastMessageTime = Date.now();
|
||||
chat.lastMessageTime = sentAt;
|
||||
chat.isFromUs = true;
|
||||
}
|
||||
return true;
|
||||
return { eventId, sentAt };
|
||||
} catch (error) {
|
||||
if (error instanceof MatrixRateLimitError) {
|
||||
this.setInvitationCooldown(error, 'all-sends');
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
this.logger.error('[chat] sendChatMessage failed:', error);
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1252,11 +1269,17 @@ export class ChatService {
|
||||
this.logger.warn('[chat] failed to record conversation outcome:', err);
|
||||
return false;
|
||||
}
|
||||
this.conversationLearningObserver?.observe(chat, performanceMessage, conversationState);
|
||||
return wasMissingOutcome;
|
||||
} else if (hasReply && !performanceMessage.gotResponse) {
|
||||
this.messagesRepo.markGotResponse(performanceMessage.id);
|
||||
this.logger.warn('[chat] response recorded without canonical outcome repository; excluded from learning');
|
||||
}
|
||||
this.conversationLearningObserver?.observe(
|
||||
chat,
|
||||
performanceMessage,
|
||||
this.conversationStateRepo?.get(chat.roomId),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user