diff --git a/src/message-runtime-rules.ts b/src/message-runtime-rules.ts index cac11a2..3ff1dc6 100644 --- a/src/message-runtime-rules.ts +++ b/src/message-runtime-rules.ts @@ -39,6 +39,30 @@ export function pairedCursorKey( return isReviewerTurn ? `${chatJid}:reviewer` : chatJid; } +/** + * Auto-resolve the cursor key based on current paired task status. + * Non-paired rooms always return chatJid. + * Paired rooms return role-specific keys so owner/reviewer/arbiter + * cursors are independent. + */ +export function resolveCursorKey( + chatJid: string, + taskStatus?: string | null, +): string { + if (!isPairedRoomJid(chatJid)) return chatJid; + if (!taskStatus) return chatJid; + switch (taskStatus) { + case 'review_ready': + case 'in_review': + return `${chatJid}:reviewer`; + case 'arbiter_requested': + case 'in_arbitration': + return `${chatJid}:arbiter`; + default: + return chatJid; + } +} + export function createImplicitContinuationTracker(idleTimeout: number) { const implicitContinuationUntil = new Map(); diff --git a/src/message-runtime.ts b/src/message-runtime.ts index e8cc4c2..52d64d5 100644 --- a/src/message-runtime.ts +++ b/src/message-runtime.ts @@ -41,7 +41,7 @@ import { isTriggerAllowed, loadSenderAllowlist } from './sender-allowlist.js'; import { advanceLastAgentCursor, createImplicitContinuationTracker, - pairedCursorKey, + resolveCursorKey, filterLoopingPairedBotMessages, getProcessableMessages, hasAllowedTrigger, @@ -634,7 +634,7 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): { deps.saveState, chatJid, cursor, - `${chatJid}:reviewer`, + resolveCursorKey(chatJid, pendingReviewTask?.status), ); } @@ -665,7 +665,7 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): { deps.saveState, chatJid, cursor, - `${chatJid}:arbiter`, + resolveCursorKey(chatJid, pendingReviewTask?.status), ); } @@ -839,9 +839,7 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): { : useReviewerChannel ? reviewerChannel : channel; - const cursorKey = useArbiterChannel - ? `${chatJid}:arbiter` - : pairedCursorKey(chatJid, !!useReviewerChannel); + const cursorKey = resolveCursorKey(chatJid, pendingTaskForChannel?.status); // Arbiter turns use a dedicated context prompt; regular turns use formatted messages. let prompt: string; @@ -1049,17 +1047,7 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): { const loopPendingTask = isPairedRoomJid(chatJid) ? getLatestOpenPairedTaskForChat(chatJid) : null; - const loopIsReviewerTurn = - !!loopPendingTask && - (loopPendingTask.status === 'review_ready' || - loopPendingTask.status === 'in_review'); - const loopIsArbiterTurn = - !!loopPendingTask && - (loopPendingTask.status === 'arbiter_requested' || - loopPendingTask.status === 'in_arbitration'); - const loopCursorKey = loopIsArbiterTurn - ? `${chatJid}:arbiter` - : pairedCursorKey(chatJid, loopIsReviewerTurn); + const loopCursorKey = resolveCursorKey(chatJid, loopPendingTask?.status); const rawPendingMessages = getMessagesSinceSeq( chatJid,