diff --git a/src/message-agent-executor.ts b/src/message-agent-executor.ts index 7998d1c..1c0f22d 100644 --- a/src/message-agent-executor.ts +++ b/src/message-agent-executor.ts @@ -78,6 +78,7 @@ export async function runAgentForGroup( runId: string; startSeq?: number | null; endSeq?: number | null; + hasHumanMessage?: boolean; onOutput?: (output: AgentOutput) => Promise; }, ): Promise<'success' | 'error'> { @@ -159,6 +160,7 @@ export async function runAgentForGroup( chatJid, runId, roomRoleContext, + hasHumanMessage: args.hasHumanMessage, }); const effectivePrompt = prompt; let pairedExecutionStatus: 'succeeded' | 'failed' = 'failed'; diff --git a/src/message-runtime.ts b/src/message-runtime.ts index d95bc6c..5f113a3 100644 --- a/src/message-runtime.ts +++ b/src/message-runtime.ts @@ -272,6 +272,7 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): { options?: { startSeq?: number | null; endSeq?: number | null; + hasHumanMessage?: boolean; }, ): Promise<'success' | 'error'> => runAgentForGroup( @@ -290,6 +291,7 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): { runId, startSeq: options?.startSeq, endSeq: options?.endSeq, + hasHumanMessage: options?.hasHumanMessage, onOutput, }, ); @@ -302,6 +304,7 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): { channel: Channel; startSeq: number | null; endSeq: number | null; + hasHumanMessage?: boolean; }): Promise<{ outputStatus: 'success' | 'error'; deliverySucceeded: boolean; @@ -358,7 +361,7 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): { chatJid, runId, (result) => turnController.handleOutput(result), - { startSeq, endSeq }, + { startSeq, endSeq, hasHumanMessage: args.hasHumanMessage }, ); const { deliverySucceeded, visiblePhase } = @@ -755,6 +758,7 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): { pendingTaskForChannel.status === 'in_review'); const turnChannel = useReviewerChannel ? reviewerChannel : channel; + const hasHumanMsg = !isBotOnlyPairedRoomTurn(chatJid, missedMessages); const { deliverySucceeded, visiblePhase } = await executeTurn({ group, prompt, @@ -763,6 +767,7 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): { channel: turnChannel, startSeq, endSeq, + hasHumanMessage: hasHumanMsg, }); if (!deliverySucceeded) { diff --git a/src/paired-execution-context.ts b/src/paired-execution-context.ts index c85044a..e311cb1 100644 --- a/src/paired-execution-context.ts +++ b/src/paired-execution-context.ts @@ -198,6 +198,7 @@ export function preparePairedExecutionContext(args: { chatJid: string; runId: string; roomRoleContext?: RoomRoleContext; + hasHumanMessage?: boolean; }): PreparedPairedExecutionContext | undefined { const { group, chatJid, roomRoleContext } = args; if (!roomRoleContext || !group.workDir) { @@ -217,9 +218,13 @@ export function preparePairedExecutionContext(args: { if (roomRoleContext.role === 'owner') { // New human message → new ping-pong cycle. Reset round trip counter // AND status so the owner turn is not treated as a finalize turn. + // Reset status on new human message so the owner gets a fresh working + // turn. merge_ready is only reset when a human message is present — + // without it, this is a finalize turn after reviewer approval and + // resetting would prevent task completion. const needsReset = latestTask.round_trip_count > 0 || - latestTask.status === 'merge_ready' || + (latestTask.status === 'merge_ready' && args.hasHumanMessage === true) || latestTask.status === 'review_ready' || latestTask.status === 'in_review'; if (needsReset) {