refactor: split message executor target preparation (#190)
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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,77 +79,201 @@ export interface PreparedMessageAgentExecution {
|
||||
clearRoleSdkSessions: () => void;
|
||||
}
|
||||
|
||||
type CurrentChannelLease = ReturnType<typeof getEffectiveChannelLease>;
|
||||
type LatestOpenPairedTask = ReturnType<typeof getLatestOpenPairedTaskForChat>;
|
||||
type ResolvedExecutionTarget = ReturnType<typeof resolveExecutionTarget>;
|
||||
|
||||
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<PreparedMessageAgentExecution> {
|
||||
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)
|
||||
class MessageAgentExecutionTargetPreparer {
|
||||
constructor(
|
||||
private readonly deps: ExecutionTargetDeps,
|
||||
private readonly args: ExecutionTargetArgs,
|
||||
) {}
|
||||
|
||||
async run(): Promise<PreparedMessageAgentExecution> {
|
||||
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,
|
||||
});
|
||||
|
||||
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,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
private resolveLatestPairedTask(
|
||||
currentLease: CurrentChannelLease,
|
||||
): LatestOpenPairedTask | null {
|
||||
return currentLease.reviewer_agent_type
|
||||
? getLatestOpenPairedTaskForChat(this.args.chatJid)
|
||||
: null;
|
||||
const executionTarget = resolveExecutionTarget({
|
||||
}
|
||||
|
||||
private resolveTarget(
|
||||
currentLease: CurrentChannelLease,
|
||||
pairedTask: LatestOpenPairedTask | null,
|
||||
): ResolvedExecutionTarget {
|
||||
return resolveExecutionTarget({
|
||||
lease: currentLease,
|
||||
pairedTaskStatus: pairedTask?.status,
|
||||
groupFolder: group.folder,
|
||||
groupAgentType: group.agentType,
|
||||
forcedRole: args.forcedRole,
|
||||
forcedAgentType: args.forcedAgentType,
|
||||
groupFolder: this.args.group.folder,
|
||||
groupAgentType: this.args.group.agentType,
|
||||
forcedRole: this.args.forcedRole,
|
||||
forcedAgentType: this.args.forcedAgentType,
|
||||
});
|
||||
const {
|
||||
inferredRole,
|
||||
canHonorForcedRole,
|
||||
activeRole,
|
||||
effectiveServiceId,
|
||||
reviewerServiceId,
|
||||
arbiterServiceId,
|
||||
roleAgentPlan,
|
||||
configuredAgentType,
|
||||
effectiveAgentType,
|
||||
sessionFolder,
|
||||
} = executionTarget;
|
||||
const reviewerMode = activeRole === 'reviewer';
|
||||
const arbiterMode = activeRole === 'arbiter';
|
||||
}
|
||||
|
||||
private resolveRoleExecutionState(
|
||||
executionTarget: ResolvedExecutionTarget,
|
||||
): ResolvedRoleExecutionState {
|
||||
const reviewerMode = executionTarget.activeRole === 'reviewer';
|
||||
const arbiterMode = executionTarget.activeRole === 'arbiter';
|
||||
const effectiveGroup =
|
||||
effectiveAgentType !== roleAgentPlan.ownerAgentType
|
||||
? { ...group, agentType: effectiveAgentType }
|
||||
: group;
|
||||
const isClaudeCodeAgent = effectiveAgentType === 'claude-code';
|
||||
const unsafeHostPairedMode = isUnsafeHostPairedModeEnabled();
|
||||
executionTarget.effectiveAgentType !==
|
||||
executionTarget.roleAgentPlan.ownerAgentType
|
||||
? {
|
||||
...this.args.group,
|
||||
agentType: executionTarget.effectiveAgentType,
|
||||
}
|
||||
: this.args.group;
|
||||
const isClaudeCodeAgent =
|
||||
executionTarget.effectiveAgentType === 'claude-code';
|
||||
const forceFreshClaudeReviewerSession =
|
||||
reviewerMode &&
|
||||
isClaudeCodeAgent &&
|
||||
unsafeHostPairedMode &&
|
||||
isUnsafeHostPairedModeEnabled() &&
|
||||
shouldForceFreshClaudeReviewerSessionInUnsafeHostMode();
|
||||
const shouldPersistSession =
|
||||
activeRole !== 'arbiter' &&
|
||||
!args.forcedAgentType &&
|
||||
executionTarget.activeRole !== 'arbiter' &&
|
||||
!this.args.forcedAgentType &&
|
||||
!forceFreshClaudeReviewerSession;
|
||||
const storedSessionId = sessions[sessionFolder];
|
||||
const storedSessionId =
|
||||
this.deps.getSessions()[executionTarget.sessionFolder];
|
||||
if (forceFreshClaudeReviewerSession && storedSessionId) {
|
||||
deps.clearSession(sessionFolder);
|
||||
this.deps.clearSession(executionTarget.sessionFolder);
|
||||
}
|
||||
const currentSessionId =
|
||||
activeRole === 'arbiter' ||
|
||||
args.forcedAgentType ||
|
||||
executionTarget.activeRole === 'arbiter' ||
|
||||
this.args.forcedAgentType ||
|
||||
forceFreshClaudeReviewerSession
|
||||
? undefined
|
||||
: storedSessionId;
|
||||
const provider = isClaudeCodeAgent ? 'claude' : 'codex';
|
||||
const memoryBriefing = currentSessionId
|
||||
? undefined
|
||||
: await buildRoomMemoryBriefing({
|
||||
groupFolder: group.folder,
|
||||
groupName: group.name,
|
||||
}).catch(() => undefined);
|
||||
return {
|
||||
reviewerMode,
|
||||
arbiterMode,
|
||||
effectiveGroup,
|
||||
isClaudeCodeAgent,
|
||||
forceFreshClaudeReviewerSession,
|
||||
shouldPersistSession,
|
||||
currentSessionId,
|
||||
provider: isClaudeCodeAgent ? 'claude' : 'codex',
|
||||
};
|
||||
}
|
||||
|
||||
const tasks = getAllTasks(roleAgentPlan.ownerAgentType);
|
||||
private async resolveMemoryBriefing(
|
||||
currentSessionId: string | undefined,
|
||||
): Promise<string | undefined> {
|
||||
if (currentSessionId) {
|
||||
return undefined;
|
||||
}
|
||||
return buildRoomMemoryBriefing({
|
||||
groupFolder: this.args.group.folder,
|
||||
groupName: this.args.group.name,
|
||||
}).catch(() => undefined);
|
||||
}
|
||||
|
||||
private writeAgentSnapshots(ownerAgentType: AgentType): void {
|
||||
const tasks = getAllTasks(ownerAgentType);
|
||||
writeTasksSnapshot(
|
||||
group.folder,
|
||||
isMain,
|
||||
this.args.group.folder,
|
||||
this.args.group.isMain === true,
|
||||
tasks.map((task) => ({
|
||||
id: task.id,
|
||||
groupFolder: task.group_folder,
|
||||
@@ -160,196 +284,280 @@ export async function prepareMessageAgentExecutionTarget(
|
||||
next_run: task.next_run,
|
||||
})),
|
||||
);
|
||||
|
||||
writeGroupsSnapshot(
|
||||
group.folder,
|
||||
isMain,
|
||||
listAvailableGroups(deps.getRoomBindings()),
|
||||
this.args.group.folder,
|
||||
this.args.group.isMain === true,
|
||||
listAvailableGroups(this.deps.getRoomBindings()),
|
||||
);
|
||||
}
|
||||
|
||||
const claudeTokenCount = isClaudeCodeAgent ? getTokenCount() : 0;
|
||||
const canRetryClaudeCredentials = isClaudeCodeAgent && claudeTokenCount > 0;
|
||||
const roomRoleContext = buildRoomRoleContext(
|
||||
currentLease,
|
||||
effectiveServiceId,
|
||||
activeRole,
|
||||
);
|
||||
const pairedExecutionContext = preparePairedExecutionContext({
|
||||
group,
|
||||
chatJid,
|
||||
runId,
|
||||
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: args.hasHumanMessage,
|
||||
pairedTurnIdentity: args.pairedTurnIdentity,
|
||||
hasHumanMessage: this.args.hasHumanMessage,
|
||||
pairedTurnIdentity: this.args.pairedTurnIdentity,
|
||||
});
|
||||
const preparedTurnTaskUpdatedAt = pairedExecutionContext
|
||||
? (pairedExecutionContext.claimedTaskUpdatedAt ??
|
||||
pairedExecutionContext.task.updated_at)
|
||||
}
|
||||
|
||||
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 =
|
||||
args.pairedTurnIdentity ??
|
||||
(pairedExecutionContext
|
||||
? resolveRuntimePairedTurnIdentity({
|
||||
taskId: pairedExecutionContext.task.id,
|
||||
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:
|
||||
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}`,
|
||||
);
|
||||
args.preparedTurnTaskUpdatedAt ??
|
||||
args.pairedExecutionContext.task.updated_at,
|
||||
role: args.activeRole,
|
||||
taskStatus: args.pairedExecutionContext.task.status,
|
||||
hasHumanMessage: this.args.hasHumanMessage,
|
||||
});
|
||||
}
|
||||
if (
|
||||
pairedExecutionContext &&
|
||||
runtimePairedTurnIdentity.taskId !== pairedExecutionContext.task.id
|
||||
) {
|
||||
throw new Error(
|
||||
`Paired turn ${runtimePairedTurnIdentity.turnId} task_id does not match the prepared execution context`,
|
||||
);
|
||||
if (!args.pairedTask) {
|
||||
return undefined;
|
||||
}
|
||||
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`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (runtimePairedTurnIdentity) {
|
||||
markPairedTurnRunning({
|
||||
turnIdentity: runtimePairedTurnIdentity,
|
||||
executorServiceId: effectiveServiceId,
|
||||
executorAgentType: effectiveAgentType,
|
||||
runId,
|
||||
return resolveRuntimePairedTurnIdentity({
|
||||
taskId: args.pairedTask.id,
|
||||
taskUpdatedAt: args.pairedTask.updated_at,
|
||||
role: args.activeRole,
|
||||
taskStatus: args.pairedTask.status,
|
||||
hasHumanMessage: this.args.hasHumanMessage,
|
||||
});
|
||||
}
|
||||
|
||||
if (pairedExecutionContext && !args.forcedAgentType) {
|
||||
const roleConfig = getRoleModelConfig(activeRole);
|
||||
if (roleConfig.model) {
|
||||
const modelKey = isClaudeCodeAgent ? 'CLAUDE_MODEL' : 'CODEX_MODEL';
|
||||
pairedExecutionContext.envOverrides[modelKey] = roleConfig.model;
|
||||
private validateRuntimePairedTurnIdentity(args: {
|
||||
pairedTask: LatestOpenPairedTask | null;
|
||||
pairedExecutionContext: PreparedPairedExecutionContext | undefined;
|
||||
activeRole: PairedRoomRole;
|
||||
runtimeTurn: RuntimePairedTurnResolution;
|
||||
}): void {
|
||||
const { runtimePairedTurnIdentity, preparedTurnTaskUpdatedAt } =
|
||||
args.runtimeTurn;
|
||||
if (!runtimePairedTurnIdentity) {
|
||||
return;
|
||||
}
|
||||
if (roleConfig.effort) {
|
||||
const effortKey = isClaudeCodeAgent ? 'CLAUDE_EFFORT' : 'CODEX_EFFORT';
|
||||
pairedExecutionContext.envOverrides[effortKey] = roleConfig.effort;
|
||||
if (runtimePairedTurnIdentity.role !== args.activeRole) {
|
||||
throw new Error(
|
||||
`Paired turn ${runtimePairedTurnIdentity.turnId} cannot execute as ${args.activeRole}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
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;
|
||||
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<typeof createScopedLogger> {
|
||||
const log = createScopedLogger({
|
||||
chatJid,
|
||||
groupName: group.name,
|
||||
groupFolder: group.folder,
|
||||
runId,
|
||||
messageSeqStart: startSeq ?? undefined,
|
||||
messageSeqEnd: endSeq ?? undefined,
|
||||
role: activeRole,
|
||||
serviceId: effectiveServiceId,
|
||||
turnId: runtimePairedTurnIdentity?.turnId,
|
||||
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: args.forcedRole,
|
||||
forcedAgentType: args.forcedAgentType ?? null,
|
||||
inferredRole,
|
||||
canHonorForcedRole,
|
||||
pairedTaskId: pairedTask?.id,
|
||||
pairedTaskStatus: pairedTask?.status,
|
||||
configuredAgentType,
|
||||
effectiveServiceId,
|
||||
effectiveAgentType,
|
||||
groupAgentType: group.agentType,
|
||||
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,
|
||||
arbiterServiceId,
|
||||
reviewerAgentType: currentLease.reviewer_agent_type,
|
||||
arbiterAgentType: currentLease.arbiter_agent_type,
|
||||
reviewerMode,
|
||||
arbiterMode,
|
||||
sessionFolder,
|
||||
resumedSession: currentSessionId ?? null,
|
||||
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;
|
||||
}
|
||||
|
||||
const clearRoleSdkSessions = (): void => {
|
||||
private createClearRoleSdkSessions(
|
||||
pairedExecutionContext: PreparedPairedExecutionContext | undefined,
|
||||
log: ReturnType<typeof createScopedLogger>,
|
||||
): () => 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');
|
||||
log.info(
|
||||
{ sessionsDir },
|
||||
'Cleared SDK sessions for fresh Claude turn',
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
currentLease,
|
||||
pairedTask,
|
||||
activeRole,
|
||||
effectiveServiceId,
|
||||
effectiveAgentType,
|
||||
sessionFolder,
|
||||
reviewerMode,
|
||||
arbiterMode,
|
||||
effectiveGroup,
|
||||
isClaudeCodeAgent,
|
||||
forceFreshClaudeReviewerSession,
|
||||
shouldPersistSession,
|
||||
currentSessionId,
|
||||
provider,
|
||||
memoryBriefing,
|
||||
canRetryClaudeCredentials,
|
||||
roomRoleContext,
|
||||
pairedExecutionContext,
|
||||
runtimePairedTurnIdentity,
|
||||
log,
|
||||
clearRoleSdkSessions,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user