feat: support room-level reviewer and arbiter agent selection

This commit is contained in:
ejclaw
2026-04-15 23:47:46 +09:00
parent 1846d59d2f
commit a59813dee6
15 changed files with 618 additions and 52 deletions

View File

@@ -1,8 +1,13 @@
import { Database } from 'bun:sqlite';
import { OWNER_AGENT_TYPE, REVIEWER_AGENT_TYPE } from '../config.js';
import {
ARBITER_AGENT_TYPE,
OWNER_AGENT_TYPE,
REVIEWER_AGENT_TYPE,
} from '../config.js';
import { isValidGroupFolder } from '../group-folder.js';
import { logger } from '../logger.js';
import { type RoleAgentPlan, resolveRoleAgentPlan } from '../role-agent-plan.js';
import type { AgentType, RegisteredGroup, RoomMode } from '../types.js';
export type RoomModeSource = 'explicit' | 'inferred';
@@ -58,8 +63,12 @@ export interface LegacyRoomMigrationPlan {
export interface SyncRoomRoleOverridesOptions {
ownerAgentConfig?: RegisteredGroup['agentConfig'];
ownerCreatedAt?: string;
reviewerAgentType?: AgentType;
reviewerAgentConfig?: RegisteredGroup['agentConfig'];
reviewerCreatedAt?: string;
arbiterAgentType?: AgentType | null;
arbiterAgentConfig?: RegisteredGroup['agentConfig'];
arbiterCreatedAt?: string;
updatedAt?: string;
}
@@ -207,14 +216,32 @@ export function getStoredRoomRowsFromDatabase(
.filter((row): row is StoredRoomSettings => Boolean(row));
}
export function resolveStoredRoomRoleAgentPlan(
database: Database,
stored: StoredRoomSettings,
): RoleAgentPlan {
const overrides = getStoredRoomRoleOverrideRows(database, stored.chatJid);
const ownerAgentType =
stored.ownerAgentType ?? overrides.get('owner')?.agentType ?? OWNER_AGENT_TYPE;
return resolveRoleAgentPlan({
paired: stored.roomMode === 'tribunal',
groupAgentType: ownerAgentType,
configuredReviewer:
overrides.get('reviewer')?.agentType ?? REVIEWER_AGENT_TYPE,
configuredArbiter: overrides.get('arbiter')?.agentType ?? ARBITER_AGENT_TYPE,
});
}
export function buildRegisteredGroupFromStoredSettings(
database: Database,
stored: StoredRoomSettings,
requestedAgentType?: AgentType,
): (RegisteredGroup & { jid: string }) | undefined {
const capabilityTypes = resolveStoredRoomCapabilityTypes(database, stored);
const rolePlan = resolveStoredRoomRoleAgentPlan(database, stored);
const ownerAgentType =
stored.ownerAgentType ||
rolePlan.ownerAgentType ||
(capabilityTypes.length > 0
? inferOwnerAgentTypeFromRegisteredAgentTypes(capabilityTypes)
: undefined);
@@ -233,8 +260,12 @@ export function buildRegisteredGroupFromStoredSettings(
return undefined;
}
const role: 'owner' | 'reviewer' =
ownerAgentType === resolvedAgentType ? 'owner' : 'reviewer';
const role: 'owner' | 'reviewer' | 'arbiter' =
ownerAgentType === resolvedAgentType
? 'owner'
: rolePlan.reviewerAgentType === resolvedAgentType
? 'reviewer'
: 'arbiter';
const capabilityMetadata = getStoredRoomRoleOverrideRows(
database,
stored.chatJid,
@@ -273,24 +304,17 @@ export function resolveStoredRoomCapabilityTypes(
database: Database,
stored: StoredRoomSettings,
): AgentType[] {
const overrides = getStoredRoomRoleOverrideRows(database, stored.chatJid);
const ownerAgentType =
stored.ownerAgentType ??
overrides.get('owner')?.agentType ??
OWNER_AGENT_TYPE;
if (stored.modeSource !== 'explicit') {
return inferStoredRoomCapabilityTypes(database, stored);
}
const types = new Set<AgentType>([ownerAgentType]);
if (stored.roomMode === 'tribunal') {
const reviewerAgentType =
overrides.get('reviewer')?.agentType ??
(ownerAgentType === REVIEWER_AGENT_TYPE
? OWNER_AGENT_TYPE
: REVIEWER_AGENT_TYPE);
types.add(reviewerAgentType);
const rolePlan = resolveStoredRoomRoleAgentPlan(database, stored);
const types = new Set<AgentType>([rolePlan.ownerAgentType]);
if (stored.roomMode === 'tribunal' && rolePlan.reviewerAgentType) {
types.add(rolePlan.reviewerAgentType);
}
if (stored.roomMode === 'tribunal' && rolePlan.arbiterAgentType) {
types.add(rolePlan.arbiterAgentType);
}
return [...types];
}
@@ -526,11 +550,19 @@ export function syncRoomRoleOverridesForRoom(
});
if (roomMode === 'tribunal') {
const reviewerAgentType =
ownerAgentType === REVIEWER_AGENT_TYPE
const existingReviewerOverride = existingOverrides.get('reviewer');
const previousDefaultReviewerAgentType =
existingOwnerOverride?.agentType === REVIEWER_AGENT_TYPE
? OWNER_AGENT_TYPE
: REVIEWER_AGENT_TYPE;
const existingReviewerOverride = existingOverrides.get('reviewer');
const computedReviewerAgentType =
ownerAgentType === REVIEWER_AGENT_TYPE ? OWNER_AGENT_TYPE : REVIEWER_AGENT_TYPE;
const reviewerAgentType =
options.reviewerAgentType ??
(existingReviewerOverride &&
existingReviewerOverride.agentType !== previousDefaultReviewerAgentType
? existingReviewerOverride.agentType
: computedReviewerAgentType);
upsertRoomRoleOverride(database, chatJid, {
role: 'reviewer',
agentType: reviewerAgentType,
@@ -547,6 +579,40 @@ export function syncRoomRoleOverridesForRoom(
now,
updatedAt: now,
});
const existingArbiterOverride = existingOverrides.get('arbiter');
const arbiterAgentType =
options.arbiterAgentType ??
existingArbiterOverride?.agentType ??
ARBITER_AGENT_TYPE ??
null;
if (arbiterAgentType) {
upsertRoomRoleOverride(database, chatJid, {
role: 'arbiter',
agentType: arbiterAgentType,
agentConfig:
options.arbiterAgentConfig ??
(existingArbiterOverride?.agentType === arbiterAgentType
? existingArbiterOverride.agentConfig
: undefined),
createdAt:
(existingArbiterOverride?.agentType === arbiterAgentType
? existingArbiterOverride.createdAt
: undefined) ??
options.arbiterCreatedAt ??
now,
updatedAt: now,
});
} else {
database
.prepare(
`DELETE FROM room_role_overrides
WHERE chat_jid = ?
AND role = 'arbiter'`,
)
.run(chatJid);
}
} else {
database
.prepare(
@@ -555,6 +621,13 @@ export function syncRoomRoleOverridesForRoom(
AND role = 'reviewer'`,
)
.run(chatJid);
database
.prepare(
`DELETE FROM room_role_overrides
WHERE chat_jid = ?
AND role = 'arbiter'`,
)
.run(chatJid);
}
}

View File

@@ -17,6 +17,7 @@ import {
normalizeStoredAgentType,
resolveAssignedRoomFolder,
resolveStoredRoomCapabilityTypes,
resolveStoredRoomRoleAgentPlan,
syncRoomRoleOverridesForRoom,
updateStoredRoomMetadata,
upsertRoomRoleOverride,
@@ -31,6 +32,8 @@ export interface AssignRoomInput {
name: string;
roomMode?: RoomMode;
ownerAgentType?: AgentType;
reviewerAgentType?: AgentType;
arbiterAgentType?: AgentType | null;
folder?: string;
isMain?: boolean;
workDir?: string;
@@ -233,6 +236,8 @@ export function assignRoomInDatabase(
syncRoomRoleOverridesForRoom(database, chatJid, roomMode, ownerAgentType, {
ownerAgentConfig: input.ownerAgentConfig,
ownerCreatedAt: input.addedAt ?? now,
reviewerAgentType: input.reviewerAgentType,
arbiterAgentType: input.arbiterAgentType,
updatedAt: input.addedAt ?? now,
});
})();
@@ -294,6 +299,14 @@ export function getStoredRoomSettingsFromDatabase(
return getStoredRoomSettingsRowFromDatabase(database, chatJid);
}
export function getStoredRoomRoleAgentPlanFromDatabase(
database: Database,
chatJid: string,
): ReturnType<typeof resolveStoredRoomRoleAgentPlan> | undefined {
const stored = getStoredRoomSettingsRowFromDatabase(database, chatJid);
return stored ? resolveStoredRoomRoleAgentPlan(database, stored) : undefined;
}
function getStoredRoomModeRowFromDatabase(
database: Database,
chatJid: string,

View File

@@ -34,6 +34,7 @@ import {
getExplicitRoomModeFromDatabase,
getRegisteredAgentTypesForJidFromDatabase,
getRegisteredGroupFromDatabase,
getStoredRoomRoleAgentPlanFromDatabase,
getStoredRoomSettingsFromDatabase,
setExplicitRoomModeInDatabase,
setRegisteredGroupForTestsInDatabase,
@@ -354,6 +355,14 @@ export function getStoredRoomSettings(
return getStoredRoomSettingsFromDatabase(db, chatJid);
}
export function getStoredRoomRoleAgentPlan(
chatJid: string,
): ReturnType<typeof getStoredRoomRoleAgentPlanFromDatabase> {
const db = getDatabaseIfInitialized();
if (!db) return undefined;
return getStoredRoomRoleAgentPlanFromDatabase(db, chatJid);
}
export function getExplicitRoomMode(chatJid: string): RoomMode | undefined {
return getExplicitRoomModeFromDatabase(requireDatabase(), chatJid);
}