feat: recover missing conversations, fix media attachment, add manual inbox tab
Missing conversations: recipient Reddit accounts that are deleted or suspended left conversations unaccounted for. A new RecipientAccountStatusService polls Reddit for the users behind cached conversations and records their account status durably, with progressive backfill and delta-media fixes in ChatService so history and images are no longer dropped mid-sync. Media: attachMedia now surfaces download failures as MediaAttachmentError so the sync cursor can be retried instead of silently losing an image, recognizes image bytes when Matrix returns a generic content type, and keeps separate gallery occurrences when the same asset appears in more than one event. Manual inbox tab: the conversations list now carries the reply-loop status, so the inbox can separate conversations the operator is driving by hand from the ones automation is still handling. A Manual filter pill and a per-row chip explain why each conversation was handed over. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
725b5fc4e6
commit
ac12a3146a
35 files changed
+1164
-129
No files matched your search
@@ -13,7 +13,11 @@ import {
|
||||
ActivityLogRepository,
|
||||
RecipientHistoryRepository,
|
||||
} from '../database/repositories.js';
|
||||
import { AccountContentRepository, RedditAccountsRepository } from '../database/account-repositories.js';
|
||||
import {
|
||||
AccountContentRepository,
|
||||
RedditAccountsRepository,
|
||||
RedditRecipientAccountStatusRepository,
|
||||
} from '../database/account-repositories.js';
|
||||
import { RedditApiClient, RedditRateLimitError, type RedditPost } from '../reddit/api.js';
|
||||
import { RedditRequestCoordinator } from '../reddit/request-coordinator.js';
|
||||
import { OpenRouterClient } from '../ai/openrouter.js';
|
||||
@@ -48,6 +52,7 @@ import { ConversationAutomationService } from './conversation-automation.js';
|
||||
import { AiUsageRepository } from '../database/ai-usage-repository.js';
|
||||
import { MediaLibraryRepository } from '../database/media-library-repository.js';
|
||||
import { MediaLibraryExchangeService } from './media-library-exchange.js';
|
||||
import { RecipientAccountStatusService } from './recipient-account-status.js';
|
||||
|
||||
export interface ConcreteRuntimeFactoryOptions {
|
||||
config: Config;
|
||||
@@ -212,6 +217,14 @@ async function buildRuntime(
|
||||
);
|
||||
await chatService.loadCache();
|
||||
const mediaSyncService = new MediaSyncService(chatService, logger, accountConfig.media.syncIntervalMs, accountAuth);
|
||||
const recipientAccountStatusRepo = new RedditRecipientAccountStatusRepository(db);
|
||||
const recipientAccountStatusService = new RecipientAccountStatusService(
|
||||
chatService,
|
||||
redditApi,
|
||||
recipientAccountStatusRepo,
|
||||
logger,
|
||||
accountAuth,
|
||||
);
|
||||
const responseChecker = new ResponseCheckerService(
|
||||
chatPage, messagesRepo, successfulMessagesRepo, postsRepo, logger,
|
||||
accountAuth, conversationOutcomesRepo,
|
||||
@@ -250,10 +263,11 @@ async function buildRuntime(
|
||||
browserProfileDir, browserClient, accountAuth, matrixClient, chatCache, redditApi,
|
||||
postsRepo, messagesRepo, successfulMessagesRepo, deliveryOutboxRepo,
|
||||
conversationStateRepo, conversationReadStateRepo, conversationOutcomesRepo,
|
||||
redditUsersRepo, accountContentRepo,
|
||||
redditUsersRepo, accountContentRepo, recipientAccountStatusRepo,
|
||||
activityLogRepo,
|
||||
monitorService, processorService, responseChecker, conversationAutomation, chatService, mediaService,
|
||||
mediaSyncService, mediaExchange, rateLimiter,
|
||||
recipientAccountStatusService,
|
||||
};
|
||||
const lifecycle = new AccountRuntimeLoop(
|
||||
resources, accountConfig, logger, isBotRunning, recipientDeduplicator,
|
||||
@@ -293,6 +307,7 @@ class AccountRuntimeLoop {
|
||||
if (this.loop) return;
|
||||
this.controller = new AbortController();
|
||||
this.resources.mediaSyncService.start();
|
||||
this.resources.recipientAccountStatusService.start();
|
||||
void this.resources.chatService.runStartupSync()
|
||||
.then(() => this.resources.chatService.enrichUsernames())
|
||||
.catch((error) => this.logger.error(`[account ${this.resources.accountAuth.getSnapshot().redditUsername}] startup sync failed:`, error));
|
||||
@@ -302,6 +317,7 @@ class AccountRuntimeLoop {
|
||||
async stop(): Promise<void> {
|
||||
this.controller?.abort();
|
||||
this.resources.mediaSyncService.stop();
|
||||
this.resources.recipientAccountStatusService.stop();
|
||||
await this.loop?.catch((error) => {
|
||||
if (!(error instanceof DOMException && error.name === 'AbortError')) throw error;
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user