fix: route BLOCKED/NEEDS_CONTEXT through arbiter before escalating

BLOCKED and NEEDS_CONTEXT verdicts went straight to completed,
bypassing the arbiter entirely. Now when arbiter is enabled, these
verdicts trigger arbiter intervention first — the arbiter may
resolve the block or decide to escalate to the user (ESCALATE).
This commit is contained in:
Eyejoker
2026-03-30 22:26:51 +09:00
parent a043f2b79a
commit 45fe80f5f5

View File

@@ -67,9 +67,16 @@ function classifyReviewerVerdict(
// Arbiter verdict detection // Arbiter verdict detection
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
type ArbiterVerdictResult = 'proceed' | 'revise' | 'reset' | 'escalate' | 'unknown'; type ArbiterVerdictResult =
| 'proceed'
| 'revise'
| 'reset'
| 'escalate'
| 'unknown';
function classifyArbiterVerdict(summary: string | null | undefined): ArbiterVerdictResult { function classifyArbiterVerdict(
summary: string | null | undefined,
): ArbiterVerdictResult {
if (!summary) return 'unknown'; if (!summary) return 'unknown';
const cleaned = summary.replace(/<internal>[\s\S]*?<\/internal>/g, '').trim(); const cleaned = summary.replace(/<internal>[\s\S]*?<\/internal>/g, '').trim();
if (!cleaned) return 'unknown'; if (!cleaned) return 'unknown';
@@ -522,12 +529,25 @@ export function completePairedExecutionContext(args: {
case 'blocked': case 'blocked':
case 'needs_context': case 'needs_context':
// Needs user decision — stop ping-pong, leave task active // If arbiter is enabled, let arbiter judge before escalating to user.
updatePairedTask(taskId, { status: 'completed', updated_at: now }); // Arbiter may resolve the block (e.g. owner can fix it, info exists in context).
logger.info( if (isArbiterEnabled()) {
{ taskId, verdict, summary: args.summary?.slice(0, 100) }, updatePairedTask(taskId, {
'Reviewer escalated to user — ping-pong stopped', status: 'arbiter_requested',
); arbiter_requested_at: now,
updated_at: now,
});
logger.info(
{ taskId, verdict, summary: args.summary?.slice(0, 100) },
'Reviewer blocked/needs_context — requesting arbiter before escalating',
);
} else {
updatePairedTask(taskId, { status: 'completed', updated_at: now });
logger.info(
{ taskId, verdict, summary: args.summary?.slice(0, 100) },
'Reviewer escalated to user — ping-pong stopped',
);
}
break; break;
case 'done_with_concerns': case 'done_with_concerns':
@@ -597,10 +617,7 @@ export function completePairedExecutionContext(args: {
arbiter_verdict: 'escalate', arbiter_verdict: 'escalate',
updated_at: now, updated_at: now,
}); });
logger.info( logger.info({ taskId }, 'Arbiter escalated to user — task completed');
{ taskId },
'Arbiter escalated to user — task completed',
);
break; break;
default: default:
// Unknown verdict — treat as escalate // Unknown verdict — treat as escalate