import { getPairedTurnOutputs } from './db.js'; import { resolveStoredVisibleVerdict, type VisibleVerdict, } from './paired-verdict.js'; import { matchesExpectedPairedFollowUpIntent, resolveFollowUpDispatch, resolveNextTurnAction, shouldRetrySilentOwnerExecution, type FollowUpDispatch, type NextTurnAction, } from './message-runtime-rules.js'; import { schedulePairedFollowUpOnce, type ScheduledPairedFollowUpIntentKind, } from './paired-follow-up-scheduler.js'; import type { PairedRoomRole, PairedTask, PairedTaskStatus } from './types.js'; export type PairedFollowUpSource = Parameters< typeof resolveFollowUpDispatch >[0]['source']; type PairedTurnOutputContext = ReturnType[number]; type PairedFollowUpTaskContext = Pick< PairedTask, 'id' | 'status' | 'round_trip_count' | 'updated_at' | 'owner_failure_count' >; function timestampMs(value: string | null | undefined): number | null { if (!value) { return null; } const parsed = Date.parse(value); return Number.isFinite(parsed) ? parsed : null; } function isPersistedOutputOlderThanTaskRevision(args: { output: PairedTurnOutputContext; task: Partial> | null | undefined; }): boolean { const outputCreatedAt = timestampMs(args.output.created_at); const taskUpdatedAt = timestampMs(args.task?.updated_at); if (outputCreatedAt == null || taskUpdatedAt == null) { return false; } return outputCreatedAt < taskUpdatedAt; } function shouldPreferFallbackTurnOutputContext(args: { output: PairedTurnOutputContext | null | undefined; task: Partial> | null | undefined; fallbackRole: PairedRoomRole | null | undefined; }): boolean { if (!args.output || !args.fallbackRole) { return false; } if (args.output.role === args.fallbackRole) { return false; } return isPersistedOutputOlderThanTaskRevision({ output: args.output, task: args.task, }); } export interface PairedFollowUpDecision { taskId: string | null; taskStatus: PairedTaskStatus | null; lastTurnOutputRole: PairedRoomRole | null; lastTurnOutputVerdict: VisibleVerdict | null; nextTurnAction: NextTurnAction; dispatch: FollowUpDispatch; } export type PairedFollowUpDispatchResult = | (PairedFollowUpDecision & { kind: 'none'; }) | (PairedFollowUpDecision & { kind: 'message-check'; }) | (PairedFollowUpDecision & { kind: 'paired-follow-up'; intentKind: ScheduledPairedFollowUpIntentKind; scheduled: boolean; }); export function resolveLatestPairedTurnOutputContext(args: { task: | (Pick & Partial>) | null | undefined; fallbackLastTurnOutputRole?: PairedRoomRole | null; fallbackLastTurnOutputVerdict?: VisibleVerdict | null; }): { role: PairedRoomRole | null; verdict: VisibleVerdict | null; } { const latestOutput = args.task ? getPairedTurnOutputs(args.task.id).at(-1) : null; const output = shouldPreferFallbackTurnOutputContext({ output: latestOutput, task: args.task, fallbackRole: args.fallbackLastTurnOutputRole, }) ? null : latestOutput; return { role: output?.role ?? args.fallbackLastTurnOutputRole ?? null, verdict: resolveStoredVisibleVerdict({ verdict: output?.verdict ?? null, outputText: output?.output_text ?? null, }) ?? args.fallbackLastTurnOutputVerdict ?? null, }; } export function resolvePairedFollowUpDecision(args: { task: | Pick< PairedFollowUpTaskContext, 'id' | 'status' | 'updated_at' | 'owner_failure_count' > | null | undefined; source: PairedFollowUpSource; completedRole?: PairedRoomRole; executionStatus?: 'succeeded' | 'failed'; sawOutput?: boolean; fallbackLastTurnOutputRole?: PairedRoomRole | null; fallbackLastTurnOutputVerdict?: VisibleVerdict | null; }): PairedFollowUpDecision { const { role: lastTurnOutputRole, verdict: lastTurnOutputVerdict } = resolveLatestPairedTurnOutputContext({ task: args.task, fallbackLastTurnOutputRole: args.fallbackLastTurnOutputRole, fallbackLastTurnOutputVerdict: args.fallbackLastTurnOutputVerdict, }); const silentOwnerRetry = shouldRetrySilentOwnerExecution({ completedRole: args.completedRole, executionStatus: args.executionStatus, sawOutput: args.sawOutput, taskStatus: args.task?.status ?? null, }); const nextTurnAction = silentOwnerRetry ? ({ kind: 'owner-follow-up' } as const) : resolveNextTurnAction({ taskStatus: args.task?.status ?? null, lastTurnOutputRole, lastTurnOutputVerdict, ownerFailureCount: args.task?.owner_failure_count ?? null, }); const dispatch = resolveFollowUpDispatch({ source: args.source, nextTurnAction, completedRole: args.completedRole, executionStatus: args.executionStatus, sawOutput: args.sawOutput, }); return { taskId: args.task?.id ?? null, taskStatus: args.task?.status ?? null, lastTurnOutputRole, lastTurnOutputVerdict, nextTurnAction, dispatch, }; } export function schedulePairedFollowUpIntent(args: { chatJid: string; runId: string; task: PairedFollowUpTaskContext | null | undefined; intentKind: ScheduledPairedFollowUpIntentKind; enqueue: () => void; fallbackLastTurnOutputRole?: PairedRoomRole | null; fallbackLastTurnOutputVerdict?: VisibleVerdict | null; lastTurnOutputRole?: PairedRoomRole | null; lastTurnOutputVerdict?: VisibleVerdict | null; allowSilentOwnerRetry?: boolean; }): boolean { if (!args.task) { return false; } const latestOutputContext = args.lastTurnOutputRole != null || args.lastTurnOutputVerdict != null ? { role: args.lastTurnOutputRole ?? null, verdict: args.lastTurnOutputVerdict ?? null, } : resolveLatestPairedTurnOutputContext({ task: args.task, fallbackLastTurnOutputRole: args.fallbackLastTurnOutputRole, fallbackLastTurnOutputVerdict: args.fallbackLastTurnOutputVerdict, }); if ( !matchesExpectedPairedFollowUpIntent({ taskStatus: args.task.status, lastTurnOutputRole: latestOutputContext.role, lastTurnOutputVerdict: latestOutputContext.verdict, ownerFailureCount: args.task.owner_failure_count ?? null, intentKind: args.intentKind, }) && !( args.allowSilentOwnerRetry === true && args.task.status === 'active' && args.intentKind === 'owner-follow-up' ) ) { return false; } return schedulePairedFollowUpOnce({ chatJid: args.chatJid, runId: args.runId, task: args.task, intentKind: args.intentKind, enqueue: args.enqueue, }); } export function schedulePairedFollowUpWithMessageCheck(args: { chatJid: string; runId: string; task: PairedFollowUpTaskContext | null | undefined; intentKind: ScheduledPairedFollowUpIntentKind; enqueueMessageCheck: () => void; fallbackLastTurnOutputRole?: PairedRoomRole | null; fallbackLastTurnOutputVerdict?: VisibleVerdict | null; lastTurnOutputRole?: PairedRoomRole | null; lastTurnOutputVerdict?: VisibleVerdict | null; }): boolean { return schedulePairedFollowUpIntent({ chatJid: args.chatJid, runId: args.runId, task: args.task, intentKind: args.intentKind, enqueue: args.enqueueMessageCheck, fallbackLastTurnOutputRole: args.fallbackLastTurnOutputRole, fallbackLastTurnOutputVerdict: args.fallbackLastTurnOutputVerdict, lastTurnOutputRole: args.lastTurnOutputRole, lastTurnOutputVerdict: args.lastTurnOutputVerdict, }); } export function dispatchPairedFollowUpForEvent(args: { chatJid: string; runId: string; task: PairedFollowUpTaskContext | null | undefined; source: PairedFollowUpSource; completedRole?: PairedRoomRole; executionStatus?: 'succeeded' | 'failed'; sawOutput?: boolean; fallbackLastTurnOutputRole?: PairedRoomRole | null; fallbackLastTurnOutputVerdict?: VisibleVerdict | null; enqueue: () => void; enqueueMessageCheck?: () => void; }): PairedFollowUpDispatchResult { const decision = resolvePairedFollowUpDecision({ task: args.task, source: args.source, completedRole: args.completedRole, executionStatus: args.executionStatus, sawOutput: args.sawOutput, fallbackLastTurnOutputRole: args.fallbackLastTurnOutputRole, fallbackLastTurnOutputVerdict: args.fallbackLastTurnOutputVerdict, }); if ( decision.dispatch.kind === 'enqueue' && decision.dispatch.queueKind === 'message-check' ) { args.enqueueMessageCheck?.(); return { kind: 'message-check', ...decision, }; } if ( decision.dispatch.kind === 'enqueue' && decision.dispatch.queueKind === 'paired-follow-up' && decision.nextTurnAction.kind !== 'none' ) { const scheduled = schedulePairedFollowUpIntent({ chatJid: args.chatJid, runId: args.runId, task: args.task, intentKind: decision.nextTurnAction.kind, enqueue: args.enqueue, lastTurnOutputRole: decision.lastTurnOutputRole, lastTurnOutputVerdict: decision.lastTurnOutputVerdict, allowSilentOwnerRetry: shouldRetrySilentOwnerExecution({ completedRole: args.completedRole, executionStatus: args.executionStatus, sawOutput: args.sawOutput, taskStatus: args.task?.status ?? null, }), }); return { kind: 'paired-follow-up', ...decision, intentKind: decision.nextTurnAction.kind, scheduled, }; } return { kind: 'none', ...decision, }; } export function enqueuePairedFollowUpAfterEvent(args: { chatJid: string; runId: string; task: PairedFollowUpTaskContext | null | undefined; source: PairedFollowUpSource; completedRole?: PairedRoomRole; executionStatus?: 'succeeded' | 'failed'; sawOutput?: boolean; fallbackLastTurnOutputRole?: PairedRoomRole | null; fallbackLastTurnOutputVerdict?: VisibleVerdict | null; enqueueMessageCheck: () => void; }): PairedFollowUpDispatchResult { return dispatchPairedFollowUpForEvent({ chatJid: args.chatJid, runId: args.runId, task: args.task, source: args.source, completedRole: args.completedRole, executionStatus: args.executionStatus, sawOutput: args.sawOutput, fallbackLastTurnOutputRole: args.fallbackLastTurnOutputRole, fallbackLastTurnOutputVerdict: args.fallbackLastTurnOutputVerdict, enqueue: args.enqueueMessageCheck, enqueueMessageCheck: args.enqueueMessageCheck, }); } export function requeuePendingPairedTurn(args: { schedulePairedFollowUp: () => boolean; closeStdin: () => void; }): boolean { const scheduled = args.schedulePairedFollowUp(); if (scheduled) { args.closeStdin(); } return scheduled; }