feat: paired review system overhaul — unified service, configurable agents, silent output removal
- Remove UNIFIED_MODE legacy: single service manages all 3 Discord bots - Add OWNER_AGENT_TYPE / REVIEWER_AGENT_TYPE env vars for configurable agent selection - Fix Discord channel routing: reviewer output goes through correct bot (discord/discord-review) - Fix channel name assignment: explicit names prevent agentTypeFilter collision - Remove silent output suppression system: harness-level protections replace prompt workarounds - Add reviewer approval detection: DONE marker on first line stops ping-pong - Include user's original message in reviewer prompt for context - Fix round_trip_count auto-reset on new user message - Fix session ID conflict: reviewer doesn't reuse owner's session - Fix pending progress text flush in runner on close sentinel - Promote buffered intermediate text to final result when result event has no text - Remove legacy files: .env.codex.example, .env.codex-review.example, migrate-unify.cjs - Update CLAUDE.md: server-side build deployment, unified architecture docs
This commit is contained in:
@@ -28,6 +28,20 @@ import type {
|
||||
RoomRoleContext,
|
||||
} from './types.js';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Reviewer approval detection
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Only check the first line for explicit status markers from the prompt's
|
||||
// completion status protocol (DONE, DONE_WITH_CONCERNS, BLOCKED, NEEDS_CONTEXT).
|
||||
// DONE = approved → stop ping-pong. Everything else = continue.
|
||||
function isReviewerApproval(summary: string | null | undefined): boolean {
|
||||
if (!summary) return false;
|
||||
const firstLine = summary.trimStart().split('\n')[0].trim();
|
||||
// Match DONE but not DONE_WITH_CONCERNS
|
||||
return /\bDONE\b/.test(firstLine) && !/\bDONE_WITH_CONCERNS\b/.test(firstLine);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -168,6 +182,14 @@ export function preparePairedExecutionContext(args: {
|
||||
const now = new Date().toISOString();
|
||||
|
||||
if (roomRoleContext.role === 'owner') {
|
||||
// New human message → new ping-pong cycle. Reset the round trip counter
|
||||
// so previous interactions don't block the auto-review trigger.
|
||||
if (latestTask.round_trip_count > 0) {
|
||||
updatePairedTask(latestTask.id, {
|
||||
round_trip_count: 0,
|
||||
updated_at: now,
|
||||
});
|
||||
}
|
||||
workspace = provisionOwnerWorkspaceForPairedTask(latestTask.id);
|
||||
} else {
|
||||
const reviewerWorkspace = prepareReviewerWorkspaceForExecution(latestTask);
|
||||
@@ -268,17 +290,29 @@ export function completePairedExecutionContext(args: {
|
||||
}
|
||||
}
|
||||
|
||||
// Reviewer finished → set task back to active so owner can respond
|
||||
// Reviewer finished → check if approved or needs more work
|
||||
if (role === 'reviewer') {
|
||||
const now = new Date().toISOString();
|
||||
updatePairedTask(taskId, {
|
||||
status: 'active',
|
||||
updated_at: now,
|
||||
});
|
||||
logger.info(
|
||||
{ taskId },
|
||||
'Reviewer completed, task set back to active for owner',
|
||||
);
|
||||
const approved = isReviewerApproval(args.summary);
|
||||
if (approved) {
|
||||
updatePairedTask(taskId, {
|
||||
status: 'completed',
|
||||
updated_at: now,
|
||||
});
|
||||
logger.info(
|
||||
{ taskId, summary: args.summary?.slice(0, 100) },
|
||||
'Reviewer approved, task completed — ping-pong stopped',
|
||||
);
|
||||
} else {
|
||||
updatePairedTask(taskId, {
|
||||
status: 'active',
|
||||
updated_at: now,
|
||||
});
|
||||
logger.info(
|
||||
{ taskId },
|
||||
'Reviewer requested changes, task set back to active for owner',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user