refactor: extract resolveEffectiveAgentType/resolveSessionFolder as SSOT, deduplicate CONTAINER_IMAGE

This commit is contained in:
Eyejoker
2026-03-30 23:44:49 +09:00
parent e649432a40
commit 3e2954ebcf
3 changed files with 57 additions and 36 deletions

View File

@@ -40,8 +40,7 @@ import type { RegisteredGroup } from './types.js';
// ── Config ──────────────────────────────────────────────────────── // ── Config ────────────────────────────────────────────────────────
const CONTAINER_IMAGE = import { REVIEWER_CONTAINER_IMAGE as CONTAINER_IMAGE } from './config.js';
process.env.REVIEWER_CONTAINER_IMAGE || 'ejclaw-reviewer:latest';
// ── Types ───────────────────────────────────────────────────────── // ── Types ─────────────────────────────────────────────────────────

View File

@@ -23,7 +23,11 @@ import {
completePairedExecutionContext, completePairedExecutionContext,
preparePairedExecutionContext, preparePairedExecutionContext,
} from './paired-execution-context.js'; } from './paired-execution-context.js';
import { resolveActiveRole } from './message-runtime-rules.js'; import {
resolveActiveRole,
resolveEffectiveAgentType,
resolveSessionFolder,
} from './message-runtime-rules.js';
import { buildRoomRoleContext } from './room-role-context.js'; import { buildRoomRoleContext } from './room-role-context.js';
import { import {
classifyRotationTrigger, classifyRotationTrigger,
@@ -35,11 +39,8 @@ import {
shouldRetryFreshSessionOnAgentFailure, shouldRetryFreshSessionOnAgentFailure,
} from './session-recovery.js'; } from './session-recovery.js';
import { import {
ARBITER_AGENT_TYPE,
CODEX_MAIN_SERVICE_ID,
CODEX_REVIEW_SERVICE_ID, CODEX_REVIEW_SERVICE_ID,
SERVICE_SESSION_SCOPE, SERVICE_SESSION_SCOPE,
REVIEWER_AGENT_TYPE,
isClaudeService, isClaudeService,
getRoleModelConfig, getRoleModelConfig,
} from './config.js'; } from './config.js';
@@ -107,34 +108,20 @@ export async function runAgentForGroup(
const reviewerMode = activeRole === 'reviewer'; const reviewerMode = activeRole === 'reviewer';
const arbiterMode = activeRole === 'arbiter'; const arbiterMode = activeRole === 'arbiter';
// Override agent type based on role config (OWNER_AGENT_TYPE / REVIEWER_AGENT_TYPE). const effectiveAgentType = resolveEffectiveAgentType(
// When the reviewer uses a different agent type than the group default, activeRole,
// swap in the configured type for the duration of this execution. group.agentType,
const reviewerAgentTypeOverride = );
reviewerMode && REVIEWER_AGENT_TYPE !== (group.agentType || 'claude-code'); const effectiveGroup =
const arbiterAgentTypeOverride = effectiveAgentType !== (group.agentType || 'claude-code')
arbiterMode && ? { ...group, agentType: effectiveAgentType }
ARBITER_AGENT_TYPE != null &&
ARBITER_AGENT_TYPE !== (group.agentType || 'claude-code');
const effectiveAgentType = arbiterAgentTypeOverride
? ARBITER_AGENT_TYPE!
: reviewerAgentTypeOverride
? REVIEWER_AGENT_TYPE
: group.agentType || 'claude-code';
const effectiveGroup = arbiterAgentTypeOverride
? { ...group, agentType: ARBITER_AGENT_TYPE! }
: reviewerAgentTypeOverride
? { ...group, agentType: REVIEWER_AGENT_TYPE }
: group; : group;
const isClaudeCodeAgent = effectiveAgentType === 'claude-code'; const isClaudeCodeAgent = effectiveAgentType === 'claude-code';
const sessionFolder = resolveSessionFolder(
// When the reviewer/arbiter uses a different agent type than the group default, group.folder,
// use a separate session key so owner, reviewer, and arbiter sessions don't collide. activeRole,
const sessionFolder = arbiterAgentTypeOverride group.agentType,
? `${group.folder}:arbiter` );
: reviewerAgentTypeOverride
? `${group.folder}:reviewer`
: group.folder;
const sessionId = sessions[sessionFolder]; const sessionId = sessions[sessionFolder];
const memoryBriefing = sessionId const memoryBriefing = sessionId
? undefined ? undefined
@@ -478,10 +465,7 @@ export async function runAgentForGroup(
groupFolder: group.folder, groupFolder: group.folder,
runId, runId,
provider: effectiveAgentType, provider: effectiveAgentType,
reviewerMode, role: activeRole,
arbiterMode,
reviewerAgentTypeOverride,
arbiterAgentTypeOverride,
}, },
`Using provider: ${effectiveAgentType}`, `Using provider: ${effectiveAgentType}`,
); );

View File

@@ -4,6 +4,12 @@ import { normalizeStoredSeqCursor } from './message-cursor.js';
import { isTriggerAllowed, loadSenderAllowlist } from './sender-allowlist.js'; import { isTriggerAllowed, loadSenderAllowlist } from './sender-allowlist.js';
import { isTaskStatusControlMessage } from './task-watch-status.js'; import { isTaskStatusControlMessage } from './task-watch-status.js';
import { import {
ARBITER_AGENT_TYPE,
OWNER_AGENT_TYPE,
REVIEWER_AGENT_TYPE,
} from './config.js';
import {
type AgentType,
type Channel, type Channel,
type NewMessage, type NewMessage,
type RegisteredGroup, type RegisteredGroup,
@@ -57,6 +63,38 @@ export function resolveCursorKey(
return role === 'owner' ? chatJid : `${chatJid}:${role}`; return role === 'owner' ? chatJid : `${chatJid}:${role}`;
} }
/** Resolve the effective agent type for a role, considering per-role overrides. */
export function resolveEffectiveAgentType(
role: 'owner' | 'reviewer' | 'arbiter',
groupAgentType: AgentType | undefined,
): AgentType {
const groupDefault: AgentType = groupAgentType || 'claude-code';
switch (role) {
case 'reviewer':
return REVIEWER_AGENT_TYPE !== groupDefault
? REVIEWER_AGENT_TYPE
: groupDefault;
case 'arbiter':
return ARBITER_AGENT_TYPE != null && ARBITER_AGENT_TYPE !== groupDefault
? ARBITER_AGENT_TYPE
: groupDefault;
default:
return groupDefault;
}
}
/** Session folder key for a role. Owner uses groupFolder, others use groupFolder:role. */
export function resolveSessionFolder(
groupFolder: string,
role: 'owner' | 'reviewer' | 'arbiter',
groupAgentType: AgentType | undefined,
): string {
const effectiveType = resolveEffectiveAgentType(role, groupAgentType);
const groupDefault: AgentType = groupAgentType || 'claude-code';
if (effectiveType === groupDefault) return groupFolder;
return `${groupFolder}:${role}`;
}
export function createImplicitContinuationTracker(idleTimeout: number) { export function createImplicitContinuationTracker(idleTimeout: number) {
const implicitContinuationUntil = new Map<string, number>(); const implicitContinuationUntil = new Map<string, number>();