Files
EJClaw/src/room-role-context.ts
Eyejoker a043f2b79a feat: implement MAGI 3-agent arbiter system for deadlock resolution
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
2026-03-30 22:20:42 +09:00

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,
};
}