Introduces a third agent role (arbiter) that is summoned on-demand when owner and reviewer reach a deadlock (same verdict 3+ rounds). Architecture: - 3 Discord bots: owner (codex), reviewer (claude), arbiter (claude/codex) - Arbiter is NOT always-on — only invoked when deadlock detected - Arbiter renders binding verdict: PROCEED/REVISE/RESET/ESCALATE - Non-escalate verdicts reset round_trip_count and resume ping-pong - Backward compatible: ARBITER_AGENT_TYPE unset = existing 2-agent mode Changes across 13 source files + 7 test files: - types.ts: PairedRoomRole += 'arbiter', new statuses, ArbiterVerdict type - config.ts: ARBITER_AGENT_TYPE, ARBITER_SERVICE_ID, ARBITER_DEADLOCK_THRESHOLD - db.ts: schema migration (arbiter columns in channel_owner + paired_tasks) - service-routing.ts: arbiter_service_id in lease - room-role-context.ts: arbiter role detection - paired-execution-context.ts: deadlock->arbiter, verdict handling - message-runtime.ts: arbiter turn routing, cursor, sender labeling - message-agent-executor.ts: arbiter mode, failover - agent-runner.ts + environment.ts: arbiter container mode - platform-prompts.ts: arbiter prompt loading New files: - prompts/arbiter-paired-room.md: arbiter system prompt - src/arbiter-context.ts: builds conversation context for arbiter judgment
53 lines
1.4 KiB
TypeScript
53 lines
1.4 KiB
TypeScript
import {
|
|
CODEX_MAIN_SERVICE_ID,
|
|
CODEX_REVIEW_SERVICE_ID,
|
|
normalizeServiceId,
|
|
} from './config.js';
|
|
import type { RoomRoleContext } from './types.js';
|
|
import type { EffectiveChannelLease } from './service-routing.js';
|
|
|
|
export function buildRoomRoleContext(
|
|
lease: EffectiveChannelLease,
|
|
serviceId: string,
|
|
): RoomRoleContext | undefined {
|
|
const normalizedServiceId = normalizeServiceId(serviceId);
|
|
const reviewerServiceId = lease.reviewer_service_id
|
|
? normalizeServiceId(lease.reviewer_service_id)
|
|
: null;
|
|
|
|
if (!reviewerServiceId) {
|
|
return undefined;
|
|
}
|
|
|
|
const ownerServiceId = normalizeServiceId(lease.owner_service_id);
|
|
const arbiterServiceId = lease.arbiter_service_id
|
|
? normalizeServiceId(lease.arbiter_service_id)
|
|
: undefined;
|
|
|
|
// Check arbiter role first: if this service matches the arbiter_service_id,
|
|
// it takes the arbiter role (even if it also matches owner or reviewer)
|
|
const role =
|
|
arbiterServiceId && arbiterServiceId === normalizedServiceId
|
|
? 'arbiter'
|
|
: ownerServiceId === normalizedServiceId
|
|
? 'owner'
|
|
: reviewerServiceId === normalizedServiceId
|
|
? 'reviewer'
|
|
: null;
|
|
|
|
if (!role) {
|
|
return undefined;
|
|
}
|
|
|
|
return {
|
|
serviceId: normalizedServiceId,
|
|
role,
|
|
ownerServiceId,
|
|
reviewerServiceId,
|
|
failoverOwner:
|
|
ownerServiceId === CODEX_REVIEW_SERVICE_ID &&
|
|
reviewerServiceId === CODEX_MAIN_SERVICE_ID,
|
|
arbiterServiceId,
|
|
};
|
|
}
|