From e876a509a5b0c6c5affe855b508614d899837548 Mon Sep 17 00:00:00 2001 From: Eyejoker Date: Fri, 29 May 2026 19:52:20 +0900 Subject: [PATCH] refactor: split message executor target preparation (#190) --- quality/code-quality-budgets.json | 5 - src/message-agent-executor-target.ts | 724 +++++++++++++++++---------- 2 files changed, 466 insertions(+), 263 deletions(-) diff --git a/quality/code-quality-budgets.json b/quality/code-quality-budgets.json index 82902a0..7c7a1f3 100644 --- a/quality/code-quality-budgets.json +++ b/quality/code-quality-budgets.json @@ -109,11 +109,6 @@ "maxFunctionLines": 314, "owner": "main service entrypoint hotspot" }, - "src/message-agent-executor-target.ts": { - "maxFunctionLines": 274, - "maxComplexity": 48, - "owner": "message executor target hotspot" - }, "src/message-agent-executor.test.ts": { "maxLines": 3847, "maxFunctionLines": 2500, diff --git a/src/message-agent-executor-target.ts b/src/message-agent-executor-target.ts index e8fe08c..80ad79c 100644 --- a/src/message-agent-executor-target.ts +++ b/src/message-agent-executor-target.ts @@ -16,24 +16,24 @@ import { markPairedTurnRunning, } from './db.js'; import { createScopedLogger } from './logger.js'; -import { buildRoomMemoryBriefing } from './sqlite-memory-store.js'; +import { resolveExecutionTarget } from './message-runtime-rules.js'; import type { PreparedPairedExecutionContext } from './paired-execution-context.js'; import { preparePairedExecutionContext } from './paired-execution-context.js'; import { resolveRuntimePairedTurnIdentity, type PairedTurnIdentity, } from './paired-turn-identity.js'; -import { resolveExecutionTarget } from './message-runtime-rules.js'; import { buildRoomRoleContext } from './room-role-context.js'; import { getEffectiveChannelLease } from './service-routing.js'; +import { buildRoomMemoryBriefing } from './sqlite-memory-store.js'; import { getTokenCount } from './token-rotation.js'; -import { writeGroupsSnapshot, writeTasksSnapshot } from './agent-runner.js'; import type { AgentType, PairedRoomRole, RegisteredGroup, RoomRoleContext, } from './types.js'; +import { writeGroupsSnapshot, writeTasksSnapshot } from './agent-runner.js'; interface ExecutionTargetDeps { assistantName: string; @@ -79,277 +79,485 @@ export interface PreparedMessageAgentExecution { clearRoleSdkSessions: () => void; } +type CurrentChannelLease = ReturnType; +type LatestOpenPairedTask = ReturnType; +type ResolvedExecutionTarget = ReturnType; + +interface ResolvedRoleExecutionState { + reviewerMode: boolean; + arbiterMode: boolean; + effectiveGroup: RegisteredGroup; + isClaudeCodeAgent: boolean; + forceFreshClaudeReviewerSession: boolean; + shouldPersistSession: boolean; + currentSessionId: string | undefined; + provider: 'claude' | 'codex'; +} + +interface RuntimePairedTurnResolution { + preparedTurnTaskUpdatedAt: string | undefined; + runtimePairedTurnIdentity: PairedTurnIdentity | undefined; +} + export async function prepareMessageAgentExecutionTarget( deps: ExecutionTargetDeps, args: ExecutionTargetArgs, ): Promise { - const { group, chatJid, runId, startSeq, endSeq } = args; - const isMain = group.isMain === true; - const sessions = deps.getSessions(); + return new MessageAgentExecutionTargetPreparer(deps, args).run(); +} - const currentLease = getEffectiveChannelLease(chatJid); - const pairedTask = currentLease.reviewer_agent_type - ? getLatestOpenPairedTaskForChat(chatJid) - : null; - const executionTarget = resolveExecutionTarget({ - lease: currentLease, - pairedTaskStatus: pairedTask?.status, - groupFolder: group.folder, - groupAgentType: group.agentType, - forcedRole: args.forcedRole, - forcedAgentType: args.forcedAgentType, - }); - const { - inferredRole, - canHonorForcedRole, - activeRole, - effectiveServiceId, - reviewerServiceId, - arbiterServiceId, - roleAgentPlan, - configuredAgentType, - effectiveAgentType, - sessionFolder, - } = executionTarget; - const reviewerMode = activeRole === 'reviewer'; - const arbiterMode = activeRole === 'arbiter'; - const effectiveGroup = - effectiveAgentType !== roleAgentPlan.ownerAgentType - ? { ...group, agentType: effectiveAgentType } - : group; - const isClaudeCodeAgent = effectiveAgentType === 'claude-code'; - const unsafeHostPairedMode = isUnsafeHostPairedModeEnabled(); - const forceFreshClaudeReviewerSession = - reviewerMode && - isClaudeCodeAgent && - unsafeHostPairedMode && - shouldForceFreshClaudeReviewerSessionInUnsafeHostMode(); - const shouldPersistSession = - activeRole !== 'arbiter' && - !args.forcedAgentType && - !forceFreshClaudeReviewerSession; - const storedSessionId = sessions[sessionFolder]; - if (forceFreshClaudeReviewerSession && storedSessionId) { - deps.clearSession(sessionFolder); - } - const currentSessionId = - activeRole === 'arbiter' || - args.forcedAgentType || - forceFreshClaudeReviewerSession - ? undefined - : storedSessionId; - const provider = isClaudeCodeAgent ? 'claude' : 'codex'; - const memoryBriefing = currentSessionId - ? undefined - : await buildRoomMemoryBriefing({ - groupFolder: group.folder, - groupName: group.name, - }).catch(() => undefined); +class MessageAgentExecutionTargetPreparer { + constructor( + private readonly deps: ExecutionTargetDeps, + private readonly args: ExecutionTargetArgs, + ) {} - const tasks = getAllTasks(roleAgentPlan.ownerAgentType); - writeTasksSnapshot( - group.folder, - isMain, - tasks.map((task) => ({ - id: task.id, - groupFolder: task.group_folder, - prompt: task.prompt, - schedule_type: task.schedule_type, - schedule_value: task.schedule_value, - status: task.status, - next_run: task.next_run, - })), - ); + async run(): Promise { + const currentLease = getEffectiveChannelLease(this.args.chatJid); + const pairedTask = this.resolveLatestPairedTask(currentLease); + const executionTarget = this.resolveTarget(currentLease, pairedTask); + const roleState = this.resolveRoleExecutionState(executionTarget); + const memoryBriefing = await this.resolveMemoryBriefing( + roleState.currentSessionId, + ); + this.writeAgentSnapshots(executionTarget.roleAgentPlan.ownerAgentType); + const canRetryClaudeCredentials = this.canRetryClaudeCredentials( + roleState.isClaudeCodeAgent, + ); + const roomRoleContext = buildRoomRoleContext( + currentLease, + executionTarget.effectiveServiceId, + executionTarget.activeRole, + ); + const pairedExecutionContext = this.preparePairedContext(roomRoleContext); + const runtimeTurn = this.resolveRuntimePairedTurnIdentity({ + pairedTask, + pairedExecutionContext, + activeRole: executionTarget.activeRole, + }); + this.validateRuntimePairedTurnIdentity({ + pairedTask, + pairedExecutionContext, + activeRole: executionTarget.activeRole, + runtimeTurn, + }); + this.markRuntimePairedTurnRunning( + runtimeTurn.runtimePairedTurnIdentity, + executionTarget, + ); + this.applyPairedEnvOverrides({ + pairedExecutionContext, + runtimePairedTurnIdentity: runtimeTurn.runtimePairedTurnIdentity, + activeRole: executionTarget.activeRole, + isClaudeCodeAgent: roleState.isClaudeCodeAgent, + }); + const log = this.createExecutionLogger({ + currentLease, + pairedTask, + executionTarget, + roleState, + runtimePairedTurnIdentity: runtimeTurn.runtimePairedTurnIdentity, + }); - writeGroupsSnapshot( - group.folder, - isMain, - listAvailableGroups(deps.getRoomBindings()), - ); - - const claudeTokenCount = isClaudeCodeAgent ? getTokenCount() : 0; - const canRetryClaudeCredentials = isClaudeCodeAgent && claudeTokenCount > 0; - const roomRoleContext = buildRoomRoleContext( - currentLease, - effectiveServiceId, - activeRole, - ); - const pairedExecutionContext = preparePairedExecutionContext({ - group, - chatJid, - runId, - roomRoleContext, - hasHumanMessage: args.hasHumanMessage, - pairedTurnIdentity: args.pairedTurnIdentity, - }); - const preparedTurnTaskUpdatedAt = pairedExecutionContext - ? (pairedExecutionContext.claimedTaskUpdatedAt ?? - pairedExecutionContext.task.updated_at) - : undefined; - const runtimePairedTurnIdentity = - args.pairedTurnIdentity ?? - (pairedExecutionContext - ? resolveRuntimePairedTurnIdentity({ - taskId: pairedExecutionContext.task.id, - taskUpdatedAt: - preparedTurnTaskUpdatedAt ?? pairedExecutionContext.task.updated_at, - role: activeRole, - taskStatus: pairedExecutionContext.task.status, - hasHumanMessage: args.hasHumanMessage, - }) - : pairedTask - ? resolveRuntimePairedTurnIdentity({ - taskId: pairedTask.id, - taskUpdatedAt: pairedTask.updated_at, - role: activeRole, - taskStatus: pairedTask.status, - hasHumanMessage: args.hasHumanMessage, - }) - : undefined); - - if (runtimePairedTurnIdentity) { - if (runtimePairedTurnIdentity.role !== activeRole) { - throw new Error( - `Paired turn ${runtimePairedTurnIdentity.turnId} cannot execute as ${activeRole}`, - ); - } - if ( - pairedExecutionContext && - runtimePairedTurnIdentity.taskId !== pairedExecutionContext.task.id - ) { - throw new Error( - `Paired turn ${runtimePairedTurnIdentity.turnId} task_id does not match the prepared execution context`, - ); - } - if ( - pairedExecutionContext && - runtimePairedTurnIdentity.taskUpdatedAt !== preparedTurnTaskUpdatedAt - ) { - throw new Error( - `Paired turn ${runtimePairedTurnIdentity.turnId} task_updated_at does not match the prepared execution context`, - ); - } - if ( - !pairedExecutionContext && - pairedTask && - runtimePairedTurnIdentity.taskId !== pairedTask.id - ) { - throw new Error( - `Paired turn ${runtimePairedTurnIdentity.turnId} task_id does not match the latest paired task`, - ); - } - if ( - !pairedExecutionContext && - pairedTask && - runtimePairedTurnIdentity.taskUpdatedAt !== pairedTask.updated_at - ) { - throw new Error( - `Paired turn ${runtimePairedTurnIdentity.turnId} task_updated_at does not match the latest paired task`, - ); - } + return { + currentLease, + pairedTask, + activeRole: executionTarget.activeRole, + effectiveServiceId: executionTarget.effectiveServiceId, + effectiveAgentType: executionTarget.effectiveAgentType, + sessionFolder: executionTarget.sessionFolder, + reviewerMode: roleState.reviewerMode, + arbiterMode: roleState.arbiterMode, + effectiveGroup: roleState.effectiveGroup, + isClaudeCodeAgent: roleState.isClaudeCodeAgent, + forceFreshClaudeReviewerSession: + roleState.forceFreshClaudeReviewerSession, + shouldPersistSession: roleState.shouldPersistSession, + currentSessionId: roleState.currentSessionId, + provider: roleState.provider, + memoryBriefing, + canRetryClaudeCredentials, + roomRoleContext, + pairedExecutionContext, + runtimePairedTurnIdentity: runtimeTurn.runtimePairedTurnIdentity, + log, + clearRoleSdkSessions: this.createClearRoleSdkSessions( + pairedExecutionContext, + log, + ), + }; } - if (runtimePairedTurnIdentity) { - markPairedTurnRunning({ - turnIdentity: runtimePairedTurnIdentity, - executorServiceId: effectiveServiceId, - executorAgentType: effectiveAgentType, - runId, + private resolveLatestPairedTask( + currentLease: CurrentChannelLease, + ): LatestOpenPairedTask | null { + return currentLease.reviewer_agent_type + ? getLatestOpenPairedTaskForChat(this.args.chatJid) + : null; + } + + private resolveTarget( + currentLease: CurrentChannelLease, + pairedTask: LatestOpenPairedTask | null, + ): ResolvedExecutionTarget { + return resolveExecutionTarget({ + lease: currentLease, + pairedTaskStatus: pairedTask?.status, + groupFolder: this.args.group.folder, + groupAgentType: this.args.group.agentType, + forcedRole: this.args.forcedRole, + forcedAgentType: this.args.forcedAgentType, }); } - if (pairedExecutionContext && !args.forcedAgentType) { - const roleConfig = getRoleModelConfig(activeRole); - if (roleConfig.model) { - const modelKey = isClaudeCodeAgent ? 'CLAUDE_MODEL' : 'CODEX_MODEL'; - pairedExecutionContext.envOverrides[modelKey] = roleConfig.model; + private resolveRoleExecutionState( + executionTarget: ResolvedExecutionTarget, + ): ResolvedRoleExecutionState { + const reviewerMode = executionTarget.activeRole === 'reviewer'; + const arbiterMode = executionTarget.activeRole === 'arbiter'; + const effectiveGroup = + executionTarget.effectiveAgentType !== + executionTarget.roleAgentPlan.ownerAgentType + ? { + ...this.args.group, + agentType: executionTarget.effectiveAgentType, + } + : this.args.group; + const isClaudeCodeAgent = + executionTarget.effectiveAgentType === 'claude-code'; + const forceFreshClaudeReviewerSession = + reviewerMode && + isClaudeCodeAgent && + isUnsafeHostPairedModeEnabled() && + shouldForceFreshClaudeReviewerSessionInUnsafeHostMode(); + const shouldPersistSession = + executionTarget.activeRole !== 'arbiter' && + !this.args.forcedAgentType && + !forceFreshClaudeReviewerSession; + const storedSessionId = + this.deps.getSessions()[executionTarget.sessionFolder]; + if (forceFreshClaudeReviewerSession && storedSessionId) { + this.deps.clearSession(executionTarget.sessionFolder); } - if (roleConfig.effort) { - const effortKey = isClaudeCodeAgent ? 'CLAUDE_EFFORT' : 'CODEX_EFFORT'; - pairedExecutionContext.envOverrides[effortKey] = roleConfig.effort; - } - } - if (pairedExecutionContext && runtimePairedTurnIdentity) { - pairedExecutionContext.envOverrides.EJCLAW_PAIRED_TURN_ID = - runtimePairedTurnIdentity.turnId; - pairedExecutionContext.envOverrides.EJCLAW_PAIRED_TURN_ROLE = - runtimePairedTurnIdentity.role; - pairedExecutionContext.envOverrides.EJCLAW_PAIRED_TURN_INTENT = - runtimePairedTurnIdentity.intentKind; - pairedExecutionContext.envOverrides.EJCLAW_PAIRED_TASK_UPDATED_AT = - runtimePairedTurnIdentity.taskUpdatedAt; - } - - const log = createScopedLogger({ - chatJid, - groupName: group.name, - groupFolder: group.folder, - runId, - messageSeqStart: startSeq ?? undefined, - messageSeqEnd: endSeq ?? undefined, - role: activeRole, - serviceId: effectiveServiceId, - turnId: runtimePairedTurnIdentity?.turnId, - }); - log.info( - { - forcedRole: args.forcedRole, - forcedAgentType: args.forcedAgentType ?? null, - inferredRole, - canHonorForcedRole, - pairedTaskId: pairedTask?.id, - pairedTaskStatus: pairedTask?.status, - configuredAgentType, - effectiveServiceId, - effectiveAgentType, - groupAgentType: group.agentType, - configuredReviewerAgentType: REVIEWER_AGENT_TYPE, - configuredArbiterAgentType: ARBITER_AGENT_TYPE, - reviewerServiceId, - arbiterServiceId, - reviewerAgentType: currentLease.reviewer_agent_type, - arbiterAgentType: currentLease.arbiter_agent_type, + const currentSessionId = + executionTarget.activeRole === 'arbiter' || + this.args.forcedAgentType || + forceFreshClaudeReviewerSession + ? undefined + : storedSessionId; + return { reviewerMode, arbiterMode, - sessionFolder, - resumedSession: currentSessionId ?? null, - }, - 'Resolved execution target for agent turn', - ); + effectiveGroup, + isClaudeCodeAgent, + forceFreshClaudeReviewerSession, + shouldPersistSession, + currentSessionId, + provider: isClaudeCodeAgent ? 'claude' : 'codex', + }; + } - const clearRoleSdkSessions = (): void => { - const configDir = pairedExecutionContext?.envOverrides?.CLAUDE_CONFIG_DIR; - if (!configDir) return; - for (const sdkDir of ['.claude', '.codex']) { - const sessionsDir = path.join(configDir, sdkDir, 'sessions'); - if (fs.existsSync(sessionsDir)) { - fs.rmSync(sessionsDir, { recursive: true, force: true }); - log.info({ sessionsDir }, 'Cleared SDK sessions for fresh Claude turn'); - } + private async resolveMemoryBriefing( + currentSessionId: string | undefined, + ): Promise { + if (currentSessionId) { + return undefined; } - }; + return buildRoomMemoryBriefing({ + groupFolder: this.args.group.folder, + groupName: this.args.group.name, + }).catch(() => undefined); + } - return { - currentLease, - pairedTask, - activeRole, - effectiveServiceId, - effectiveAgentType, - sessionFolder, - reviewerMode, - arbiterMode, - effectiveGroup, - isClaudeCodeAgent, - forceFreshClaudeReviewerSession, - shouldPersistSession, - currentSessionId, - provider, - memoryBriefing, - canRetryClaudeCredentials, - roomRoleContext, - pairedExecutionContext, - runtimePairedTurnIdentity, - log, - clearRoleSdkSessions, - }; + private writeAgentSnapshots(ownerAgentType: AgentType): void { + const tasks = getAllTasks(ownerAgentType); + writeTasksSnapshot( + this.args.group.folder, + this.args.group.isMain === true, + tasks.map((task) => ({ + id: task.id, + groupFolder: task.group_folder, + prompt: task.prompt, + schedule_type: task.schedule_type, + schedule_value: task.schedule_value, + status: task.status, + next_run: task.next_run, + })), + ); + writeGroupsSnapshot( + this.args.group.folder, + this.args.group.isMain === true, + listAvailableGroups(this.deps.getRoomBindings()), + ); + } + + private canRetryClaudeCredentials(isClaudeCodeAgent: boolean): boolean { + return isClaudeCodeAgent && getTokenCount() > 0; + } + + private preparePairedContext( + roomRoleContext: RoomRoleContext | undefined, + ): PreparedPairedExecutionContext | undefined { + return preparePairedExecutionContext({ + group: this.args.group, + chatJid: this.args.chatJid, + runId: this.args.runId, + roomRoleContext, + hasHumanMessage: this.args.hasHumanMessage, + pairedTurnIdentity: this.args.pairedTurnIdentity, + }); + } + + private resolveRuntimePairedTurnIdentity(args: { + pairedTask: LatestOpenPairedTask | null; + pairedExecutionContext: PreparedPairedExecutionContext | undefined; + activeRole: PairedRoomRole; + }): RuntimePairedTurnResolution { + const preparedTurnTaskUpdatedAt = args.pairedExecutionContext + ? (args.pairedExecutionContext.claimedTaskUpdatedAt ?? + args.pairedExecutionContext.task.updated_at) + : undefined; + const runtimePairedTurnIdentity = + this.args.pairedTurnIdentity ?? + this.resolveCurrentRuntimePairedTurnIdentity({ + ...args, + preparedTurnTaskUpdatedAt, + }); + return { preparedTurnTaskUpdatedAt, runtimePairedTurnIdentity }; + } + + private resolveCurrentRuntimePairedTurnIdentity(args: { + pairedTask: LatestOpenPairedTask | null; + pairedExecutionContext: PreparedPairedExecutionContext | undefined; + activeRole: PairedRoomRole; + preparedTurnTaskUpdatedAt: string | undefined; + }): PairedTurnIdentity | undefined { + if (args.pairedExecutionContext) { + return resolveRuntimePairedTurnIdentity({ + taskId: args.pairedExecutionContext.task.id, + taskUpdatedAt: + args.preparedTurnTaskUpdatedAt ?? + args.pairedExecutionContext.task.updated_at, + role: args.activeRole, + taskStatus: args.pairedExecutionContext.task.status, + hasHumanMessage: this.args.hasHumanMessage, + }); + } + if (!args.pairedTask) { + return undefined; + } + return resolveRuntimePairedTurnIdentity({ + taskId: args.pairedTask.id, + taskUpdatedAt: args.pairedTask.updated_at, + role: args.activeRole, + taskStatus: args.pairedTask.status, + hasHumanMessage: this.args.hasHumanMessage, + }); + } + + private validateRuntimePairedTurnIdentity(args: { + pairedTask: LatestOpenPairedTask | null; + pairedExecutionContext: PreparedPairedExecutionContext | undefined; + activeRole: PairedRoomRole; + runtimeTurn: RuntimePairedTurnResolution; + }): void { + const { runtimePairedTurnIdentity, preparedTurnTaskUpdatedAt } = + args.runtimeTurn; + if (!runtimePairedTurnIdentity) { + return; + } + if (runtimePairedTurnIdentity.role !== args.activeRole) { + throw new Error( + `Paired turn ${runtimePairedTurnIdentity.turnId} cannot execute as ${args.activeRole}`, + ); + } + this.validatePreparedContextTurnIdentity({ + pairedExecutionContext: args.pairedExecutionContext, + runtimePairedTurnIdentity, + preparedTurnTaskUpdatedAt, + }); + this.validateLatestTaskTurnIdentity({ + pairedTask: args.pairedTask, + pairedExecutionContext: args.pairedExecutionContext, + runtimePairedTurnIdentity, + }); + } + + private validatePreparedContextTurnIdentity(args: { + pairedExecutionContext: PreparedPairedExecutionContext | undefined; + runtimePairedTurnIdentity: PairedTurnIdentity; + preparedTurnTaskUpdatedAt: string | undefined; + }): void { + if (!args.pairedExecutionContext) { + return; + } + if ( + args.runtimePairedTurnIdentity.taskId !== + args.pairedExecutionContext.task.id + ) { + throw new Error( + `Paired turn ${args.runtimePairedTurnIdentity.turnId} task_id does not match the prepared execution context`, + ); + } + if ( + args.runtimePairedTurnIdentity.taskUpdatedAt !== + args.preparedTurnTaskUpdatedAt + ) { + throw new Error( + `Paired turn ${args.runtimePairedTurnIdentity.turnId} task_updated_at does not match the prepared execution context`, + ); + } + } + + private validateLatestTaskTurnIdentity(args: { + pairedTask: LatestOpenPairedTask | null; + pairedExecutionContext: PreparedPairedExecutionContext | undefined; + runtimePairedTurnIdentity: PairedTurnIdentity; + }): void { + if (args.pairedExecutionContext || !args.pairedTask) { + return; + } + if (args.runtimePairedTurnIdentity.taskId !== args.pairedTask.id) { + throw new Error( + `Paired turn ${args.runtimePairedTurnIdentity.turnId} task_id does not match the latest paired task`, + ); + } + if ( + args.runtimePairedTurnIdentity.taskUpdatedAt !== + args.pairedTask.updated_at + ) { + throw new Error( + `Paired turn ${args.runtimePairedTurnIdentity.turnId} task_updated_at does not match the latest paired task`, + ); + } + } + + private markRuntimePairedTurnRunning( + runtimePairedTurnIdentity: PairedTurnIdentity | undefined, + executionTarget: ResolvedExecutionTarget, + ): void { + if (!runtimePairedTurnIdentity) { + return; + } + markPairedTurnRunning({ + turnIdentity: runtimePairedTurnIdentity, + executorServiceId: executionTarget.effectiveServiceId, + executorAgentType: executionTarget.effectiveAgentType, + runId: this.args.runId, + }); + } + + private applyPairedEnvOverrides(args: { + pairedExecutionContext: PreparedPairedExecutionContext | undefined; + runtimePairedTurnIdentity: PairedTurnIdentity | undefined; + activeRole: PairedRoomRole; + isClaudeCodeAgent: boolean; + }): void { + this.applyRoleModelEnvOverrides(args); + this.applyRuntimePairedTurnEnvOverrides(args); + } + + private applyRoleModelEnvOverrides(args: { + pairedExecutionContext: PreparedPairedExecutionContext | undefined; + activeRole: PairedRoomRole; + isClaudeCodeAgent: boolean; + }): void { + if (!args.pairedExecutionContext || this.args.forcedAgentType) { + return; + } + const roleConfig = getRoleModelConfig(args.activeRole); + if (roleConfig.model) { + const modelKey = args.isClaudeCodeAgent ? 'CLAUDE_MODEL' : 'CODEX_MODEL'; + args.pairedExecutionContext.envOverrides[modelKey] = roleConfig.model; + } + if (roleConfig.effort) { + const effortKey = args.isClaudeCodeAgent + ? 'CLAUDE_EFFORT' + : 'CODEX_EFFORT'; + args.pairedExecutionContext.envOverrides[effortKey] = roleConfig.effort; + } + } + + private applyRuntimePairedTurnEnvOverrides(args: { + pairedExecutionContext: PreparedPairedExecutionContext | undefined; + runtimePairedTurnIdentity: PairedTurnIdentity | undefined; + }): void { + if (!args.pairedExecutionContext || !args.runtimePairedTurnIdentity) { + return; + } + args.pairedExecutionContext.envOverrides.EJCLAW_PAIRED_TURN_ID = + args.runtimePairedTurnIdentity.turnId; + args.pairedExecutionContext.envOverrides.EJCLAW_PAIRED_TURN_ROLE = + args.runtimePairedTurnIdentity.role; + args.pairedExecutionContext.envOverrides.EJCLAW_PAIRED_TURN_INTENT = + args.runtimePairedTurnIdentity.intentKind; + args.pairedExecutionContext.envOverrides.EJCLAW_PAIRED_TASK_UPDATED_AT = + args.runtimePairedTurnIdentity.taskUpdatedAt; + } + + private createExecutionLogger(args: { + currentLease: CurrentChannelLease; + pairedTask: LatestOpenPairedTask | null; + executionTarget: ResolvedExecutionTarget; + roleState: ResolvedRoleExecutionState; + runtimePairedTurnIdentity: PairedTurnIdentity | undefined; + }): ReturnType { + const log = createScopedLogger({ + chatJid: this.args.chatJid, + groupName: this.args.group.name, + groupFolder: this.args.group.folder, + runId: this.args.runId, + messageSeqStart: this.args.startSeq ?? undefined, + messageSeqEnd: this.args.endSeq ?? undefined, + role: args.executionTarget.activeRole, + serviceId: args.executionTarget.effectiveServiceId, + turnId: args.runtimePairedTurnIdentity?.turnId, + }); + log.info( + { + forcedRole: this.args.forcedRole, + forcedAgentType: this.args.forcedAgentType ?? null, + inferredRole: args.executionTarget.inferredRole, + canHonorForcedRole: args.executionTarget.canHonorForcedRole, + pairedTaskId: args.pairedTask?.id, + pairedTaskStatus: args.pairedTask?.status, + configuredAgentType: args.executionTarget.configuredAgentType, + effectiveServiceId: args.executionTarget.effectiveServiceId, + effectiveAgentType: args.executionTarget.effectiveAgentType, + groupAgentType: this.args.group.agentType, + configuredReviewerAgentType: REVIEWER_AGENT_TYPE, + configuredArbiterAgentType: ARBITER_AGENT_TYPE, + reviewerServiceId: args.executionTarget.reviewerServiceId, + arbiterServiceId: args.executionTarget.arbiterServiceId, + reviewerAgentType: args.currentLease.reviewer_agent_type, + arbiterAgentType: args.currentLease.arbiter_agent_type, + reviewerMode: args.roleState.reviewerMode, + arbiterMode: args.roleState.arbiterMode, + sessionFolder: args.executionTarget.sessionFolder, + resumedSession: args.roleState.currentSessionId ?? null, + }, + 'Resolved execution target for agent turn', + ); + return log; + } + + private createClearRoleSdkSessions( + pairedExecutionContext: PreparedPairedExecutionContext | undefined, + log: ReturnType, + ): () => void { + return () => { + const configDir = pairedExecutionContext?.envOverrides?.CLAUDE_CONFIG_DIR; + if (!configDir) return; + for (const sdkDir of ['.claude', '.codex']) { + const sessionsDir = path.join(configDir, sdkDir, 'sessions'); + if (fs.existsSync(sessionsDir)) { + fs.rmSync(sessionsDir, { recursive: true, force: true }); + log.info( + { sessionsDir }, + 'Cleared SDK sessions for fresh Claude turn', + ); + } + } + }; + } }