feat: verdict-based ping-pong control with merge_ready finalization

- DONE → merge_ready → owner final turn (commit/push) → completed
- DONE_WITH_CONCERNS → active → owner addresses → ping-pong continues
- BLOCKED/NEEDS_CONTEXT → completed (escalate to user, stop ping-pong)
- Regular feedback → active → ping-pong continues
- Owner detects merge_ready status and finalizes autonomously
This commit is contained in:
Eyejoker
2026-03-29 20:59:08 +09:00
parent 16d20fe627
commit d8cb4b691e
5 changed files with 125 additions and 69 deletions

View File

@@ -491,7 +491,10 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
(pendingTask.status === 'review_ready' ||
pendingTask.status === 'in_review');
const deliveryChannel = isReviewerWorkItem ? reviewerChannel : channel;
const delivered = await deliverOpenWorkItem(deliveryChannel, openWorkItem);
const delivered = await deliverOpenWorkItem(
deliveryChannel,
openWorkItem,
);
if (!delivered) return false;
}
@@ -532,9 +535,10 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
if (ownerContent) {
parts.push(`Owner response:\n---\n${ownerContent}\n---`);
}
const reviewPrompt = parts.length > 0
? `${parts.join('\n\n')}\n\nReview the owner's response above. Provide feedback or approve.`
: 'Review the latest owner changes in the workspace. Provide feedback or approve.';
const reviewPrompt =
parts.length > 0
? `${parts.join('\n\n')}\n\nReview the owner's response above. Provide feedback or approve.`
: 'Review the latest owner changes in the workspace. Provide feedback or approve.';
// Advance cursor past the owner's messages so they aren't re-processed
const lastRaw = rawMissedMessages[rawMissedMessages.length - 1];
@@ -559,6 +563,31 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
return deliverySucceeded;
}
// merge_ready: reviewer approved, owner gets final turn to finalize
if (pendingReviewTask && pendingReviewTask.status === 'merge_ready') {
const lastRaw = rawMissedMessages[rawMissedMessages.length - 1];
if (lastRaw?.seq != null) {
advanceLastAgentCursor(
deps.getLastAgentTimestamps(),
deps.saveState,
chatJid,
lastRaw.seq,
);
}
const finalizePrompt =
'The reviewer approved your work (DONE). Finalize: commit changes, push if appropriate, and report the result.';
const { deliverySucceeded } = await executeTurn({
group,
prompt: finalizePrompt,
chatJid,
runId,
channel,
startSeq: null,
endSeq: null,
});
return deliverySucceeded;
}
const lastIgnored = rawMissedMessages[rawMissedMessages.length - 1];
if (lastIgnored) {
advanceLastAgentCursor(