feat: add completion_reason to paired_tasks (done/escalated/stopped/arbiter_escalated)
- owner can summon arbiter via BLOCKED/NEEDS_CONTEXT verdict - completion_reason tracks why task ended: done (success), escalated (reviewer gave up), stopped (/stop), arbiter_escalated
This commit is contained in:
@@ -561,6 +561,7 @@ describe('paired task state', () => {
|
||||
status: 'active',
|
||||
arbiter_verdict: null,
|
||||
arbiter_requested_at: null,
|
||||
completion_reason: null,
|
||||
created_at: '2026-03-28T00:00:00.000Z',
|
||||
updated_at: '2026-03-28T00:00:00.000Z',
|
||||
});
|
||||
@@ -602,6 +603,7 @@ describe('paired task state', () => {
|
||||
status: 'active',
|
||||
arbiter_verdict: null,
|
||||
arbiter_requested_at: null,
|
||||
completion_reason: null,
|
||||
created_at: '2026-03-28T00:00:00.000Z',
|
||||
updated_at: '2026-03-28T00:00:00.000Z',
|
||||
});
|
||||
|
||||
16
src/db.ts
16
src/db.ts
@@ -256,6 +256,7 @@ function createSchema(database: Database): void {
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
arbiter_verdict TEXT,
|
||||
arbiter_requested_at TEXT,
|
||||
completion_reason TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
CHECK (status IN ('active', 'review_ready', 'in_review', 'merge_ready', 'completed', 'arbiter_requested', 'in_arbitration'))
|
||||
@@ -633,6 +634,7 @@ function createSchema(database: Database): void {
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
arbiter_verdict TEXT,
|
||||
arbiter_requested_at TEXT,
|
||||
completion_reason TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
CHECK (status IN ('active', 'review_ready', 'in_review', 'merge_ready', 'completed', 'arbiter_requested', 'in_arbitration'))
|
||||
@@ -664,6 +666,15 @@ function createSchema(database: Database): void {
|
||||
/* column already exists */
|
||||
}
|
||||
|
||||
// Migration: add completion_reason column to paired_tasks if it doesn't exist
|
||||
try {
|
||||
database.exec(
|
||||
`ALTER TABLE paired_tasks ADD COLUMN completion_reason TEXT`,
|
||||
);
|
||||
} catch {
|
||||
/* column already exists */
|
||||
}
|
||||
|
||||
// Drop legacy tables that are no longer used
|
||||
for (const table of [
|
||||
'paired_executions',
|
||||
@@ -2035,6 +2046,7 @@ export function updatePairedTask(
|
||||
| 'status'
|
||||
| 'arbiter_verdict'
|
||||
| 'arbiter_requested_at'
|
||||
| 'completion_reason'
|
||||
| 'updated_at'
|
||||
>
|
||||
>,
|
||||
@@ -2074,6 +2086,10 @@ export function updatePairedTask(
|
||||
fields.push('arbiter_requested_at = ?');
|
||||
values.push(updates.arbiter_requested_at);
|
||||
}
|
||||
if (updates.completion_reason !== undefined) {
|
||||
fields.push('completion_reason = ?');
|
||||
values.push(updates.completion_reason);
|
||||
}
|
||||
if (updates.updated_at !== undefined) {
|
||||
fields.push('updated_at = ?');
|
||||
values.push(updates.updated_at);
|
||||
|
||||
@@ -361,6 +361,7 @@ describe('runAgentForGroup room memory', () => {
|
||||
status: 'active',
|
||||
arbiter_verdict: null,
|
||||
arbiter_requested_at: null,
|
||||
completion_reason: null,
|
||||
created_at: '2026-03-29T00:00:00.000Z',
|
||||
updated_at: '2026-03-29T00:00:00.000Z',
|
||||
},
|
||||
@@ -425,6 +426,7 @@ describe('runAgentForGroup room memory', () => {
|
||||
status: 'active',
|
||||
arbiter_verdict: null,
|
||||
arbiter_requested_at: null,
|
||||
completion_reason: null,
|
||||
created_at: '2026-03-28T00:00:00.000Z',
|
||||
updated_at: '2026-03-28T00:00:00.000Z',
|
||||
},
|
||||
@@ -514,6 +516,7 @@ describe('runAgentForGroup room memory', () => {
|
||||
status: 'active',
|
||||
arbiter_verdict: null,
|
||||
arbiter_requested_at: null,
|
||||
completion_reason: null,
|
||||
created_at: '2026-03-28T00:00:00.000Z',
|
||||
updated_at: '2026-03-28T00:00:00.000Z',
|
||||
},
|
||||
|
||||
@@ -565,6 +565,7 @@ describe('createMessageRuntime', () => {
|
||||
status: 'review_ready',
|
||||
arbiter_verdict: null,
|
||||
arbiter_requested_at: null,
|
||||
completion_reason: null,
|
||||
created_at: '2026-03-30T00:00:00.000Z',
|
||||
updated_at: '2026-03-30T00:00:00.000Z',
|
||||
});
|
||||
|
||||
@@ -846,6 +846,7 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
|
||||
if (task) {
|
||||
updatePairedTask(task.id, {
|
||||
status: 'completed',
|
||||
completion_reason: 'stopped',
|
||||
updated_at: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -112,6 +112,7 @@ function buildPairedTask(overrides: Partial<PairedTask> = {}): PairedTask {
|
||||
status: 'active',
|
||||
arbiter_verdict: null,
|
||||
arbiter_requested_at: null,
|
||||
completion_reason: null,
|
||||
created_at: '2026-03-28T00:00:00.000Z',
|
||||
updated_at: '2026-03-28T00:00:00.000Z',
|
||||
...overrides,
|
||||
@@ -202,6 +203,7 @@ describe('paired execution context', () => {
|
||||
status: 'review_ready',
|
||||
arbiter_verdict: null,
|
||||
arbiter_requested_at: null,
|
||||
completion_reason: null,
|
||||
created_at: '2026-03-28T00:00:00.000Z',
|
||||
updated_at: '2026-03-28T00:00:00.000Z',
|
||||
});
|
||||
@@ -236,6 +238,7 @@ describe('paired execution context', () => {
|
||||
status: 'review_ready',
|
||||
arbiter_verdict: null,
|
||||
arbiter_requested_at: null,
|
||||
completion_reason: null,
|
||||
created_at: '2026-03-28T00:00:00.000Z',
|
||||
updated_at: '2026-03-28T00:00:00.000Z',
|
||||
});
|
||||
@@ -273,6 +276,7 @@ describe('paired execution context', () => {
|
||||
status: 'active',
|
||||
arbiter_verdict: null,
|
||||
arbiter_requested_at: null,
|
||||
completion_reason: null,
|
||||
created_at: '2026-03-28T00:00:00.000Z',
|
||||
updated_at: '2026-03-28T00:00:00.000Z',
|
||||
});
|
||||
@@ -316,6 +320,7 @@ describe('paired execution context', () => {
|
||||
status: 'in_review',
|
||||
arbiter_verdict: null,
|
||||
arbiter_requested_at: null,
|
||||
completion_reason: null,
|
||||
created_at: '2026-03-28T00:00:00.000Z',
|
||||
updated_at: '2026-03-28T00:00:00.000Z',
|
||||
});
|
||||
|
||||
@@ -189,6 +189,7 @@ function ensureActiveTask(
|
||||
status: 'active',
|
||||
arbiter_verdict: null,
|
||||
arbiter_requested_at: null,
|
||||
completion_reason: null,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
};
|
||||
@@ -384,6 +385,7 @@ export function completePairedExecutionContext(args: {
|
||||
updatePairedTask(taskId, {
|
||||
status: verdict === 'done' ? 'merge_ready' : 'completed',
|
||||
...(verdict === 'done' ? { source_ref: approvedSourceRef } : {}),
|
||||
...(verdict !== 'done' ? { completion_reason: 'escalated' } : {}),
|
||||
updated_at: now,
|
||||
});
|
||||
logger.info(
|
||||
@@ -442,7 +444,7 @@ export function completePairedExecutionContext(args: {
|
||||
|
||||
if (hasNewChanges === false) {
|
||||
// No code changes since reviewer approved → finalize complete
|
||||
updatePairedTask(taskId, { status: 'completed', updated_at: now });
|
||||
updatePairedTask(taskId, { status: 'completed', completion_reason: 'done', updated_at: now });
|
||||
logger.info(
|
||||
{ taskId, summary: args.summary?.slice(0, 100) },
|
||||
'Owner finalized after reviewer approval — task completed',
|
||||
@@ -573,7 +575,7 @@ export function completePairedExecutionContext(args: {
|
||||
'Reviewer blocked/needs_context — requesting arbiter before escalating',
|
||||
);
|
||||
} else {
|
||||
updatePairedTask(taskId, { status: 'completed', updated_at: now });
|
||||
updatePairedTask(taskId, { status: 'completed', completion_reason: 'escalated', updated_at: now });
|
||||
logger.info(
|
||||
{ taskId, verdict, summary: args.summary?.slice(0, 100) },
|
||||
'Reviewer escalated to user — ping-pong stopped',
|
||||
@@ -598,7 +600,7 @@ export function completePairedExecutionContext(args: {
|
||||
'Deadlock detected — requesting arbiter intervention',
|
||||
);
|
||||
} else {
|
||||
updatePairedTask(taskId, { status: 'completed', updated_at: now });
|
||||
updatePairedTask(taskId, { status: 'completed', completion_reason: 'escalated', updated_at: now });
|
||||
logger.info(
|
||||
{ taskId, verdict, roundTrips: task.round_trip_count },
|
||||
'Stopped ping-pong — escalating to user (arbiter not configured)',
|
||||
@@ -646,6 +648,7 @@ export function completePairedExecutionContext(args: {
|
||||
updatePairedTask(taskId, {
|
||||
status: 'completed',
|
||||
arbiter_verdict: 'escalate',
|
||||
completion_reason: 'arbiter_escalated',
|
||||
updated_at: now,
|
||||
});
|
||||
logger.info({ taskId }, 'Arbiter escalated to user — task completed');
|
||||
@@ -655,6 +658,7 @@ export function completePairedExecutionContext(args: {
|
||||
updatePairedTask(taskId, {
|
||||
status: 'completed',
|
||||
arbiter_verdict: 'unknown',
|
||||
completion_reason: 'arbiter_escalated',
|
||||
updated_at: now,
|
||||
});
|
||||
break;
|
||||
|
||||
@@ -80,6 +80,7 @@ describe('paired workspace manager', () => {
|
||||
status: 'active',
|
||||
arbiter_verdict: null,
|
||||
arbiter_requested_at: null,
|
||||
completion_reason: null,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
});
|
||||
@@ -171,6 +172,7 @@ describe('paired workspace manager', () => {
|
||||
status: 'active',
|
||||
arbiter_verdict: null,
|
||||
arbiter_requested_at: null,
|
||||
completion_reason: null,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
});
|
||||
@@ -249,6 +251,7 @@ describe('paired workspace manager', () => {
|
||||
status: 'active',
|
||||
arbiter_verdict: null,
|
||||
arbiter_requested_at: null,
|
||||
completion_reason: null,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
});
|
||||
@@ -326,6 +329,7 @@ describe('paired workspace manager', () => {
|
||||
status: 'active',
|
||||
arbiter_verdict: null,
|
||||
arbiter_requested_at: null,
|
||||
completion_reason: null,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
});
|
||||
@@ -469,6 +473,7 @@ describe('paired workspace manager', () => {
|
||||
status: 'active',
|
||||
arbiter_verdict: null,
|
||||
arbiter_requested_at: null,
|
||||
completion_reason: null,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
});
|
||||
@@ -533,6 +538,7 @@ describe('paired workspace manager', () => {
|
||||
status: 'review_ready',
|
||||
arbiter_verdict: null,
|
||||
arbiter_requested_at: null,
|
||||
completion_reason: null,
|
||||
created_at: now,
|
||||
updated_at: '2026-03-28T00:01:00.000Z',
|
||||
});
|
||||
@@ -601,6 +607,7 @@ describe('paired workspace manager', () => {
|
||||
status: 'active',
|
||||
arbiter_verdict: null,
|
||||
arbiter_requested_at: null,
|
||||
completion_reason: null,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
});
|
||||
|
||||
@@ -71,6 +71,7 @@ export interface PairedTask {
|
||||
status: PairedTaskStatus;
|
||||
arbiter_verdict: string | null;
|
||||
arbiter_requested_at: string | null;
|
||||
completion_reason: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user