Add durable candidate funnel

This commit is contained in:
jjbrunton committed 2026-08-09 15:08:06 +01:00
1 parent 5abac31c28
commit df35648920
20 files changed
+1128 -142

No files matched your search

+28
View File
@@ -31,6 +31,7 @@ import type { ProcessorService } from './processor.js';
import type { ResponseCheckerService } from './response-checker.js';
import type { RedditPost } from '../reddit/api.js';
import type { ConversationAutomationService } from './conversation-automation.js';
import type { ProcessedPost, ProcessResult } from './processor.js';
/** Credentials passed to a factory. They must never be exposed by list/snapshot APIs. */
export interface VerifiedAccountSession {
@@ -85,8 +86,15 @@ export interface RedditAccountRuntimeLifecycle {
stop(): void | Promise<void>;
/** Process a post assigned by the shared discovery scheduler. */
dispatchPost?(post: RedditPost, signal?: AbortSignal): Promise<void>;
preparePost?(post: RedditPost, signal?: AbortSignal): Promise<ProcessResult>;
dispatchPreparedPost?(post: ProcessedPost, signal?: AbortSignal): Promise<PreparedDeliveryResult>;
}
export type PreparedDeliveryResult =
| { status: 'sent'; conversationId: string }
| { status: 'terminal'; reason: string }
| { status: 'retryable'; reason: string; retryAt: number };
export type RedditAccountRuntimeState =
| 'created'
| 'starting'
@@ -185,6 +193,26 @@ export class RedditAccountRuntime {
return this.resources.rateLimiter.getTimers().nextMessageAvailableAt === null;
}
async canScan(): Promise<boolean> {
if (this.state !== 'running') return false;
return this.resources.accountAuth.isFullyAuthenticated()
|| await this.resources.accountAuth.ensureFullyAuthenticated();
}
preparePost(post: RedditPost, signal?: AbortSignal): Promise<ProcessResult> {
if (this.state !== 'running' || !this.lifecycle.preparePost) {
throw new Error(`Runtime ${this.session.redditUsername} cannot prepare candidates`);
}
return this.lifecycle.preparePost(post, signal);
}
dispatchPreparedPost(post: ProcessedPost, signal?: AbortSignal): Promise<PreparedDeliveryResult> {
if (this.state !== 'running' || !this.lifecycle.dispatchPreparedPost) {
throw new Error(`Runtime ${this.session.redditUsername} cannot dispatch prepared candidates`);
}
return this.lifecycle.dispatchPreparedPost(post, signal);
}
dispatchPost(post: RedditPost, signal?: AbortSignal): Promise<void> {
if (this.state !== 'running') {
throw new Error(`Cannot assign a post to ${this.session.redditUsername}: runtime is not running`);