refactor: split message executor attempt runner (#189)

This commit is contained in:
Eyejoker
2026-05-29 19:49:57 +09:00
committed by GitHub
parent 6f82cea91e
commit 87eb2f0488
2 changed files with 272 additions and 225 deletions

View File

@@ -109,11 +109,6 @@
"maxFunctionLines": 314, "maxFunctionLines": 314,
"owner": "main service entrypoint hotspot" "owner": "main service entrypoint hotspot"
}, },
"src/message-agent-executor-attempt-runner.ts": {
"maxFunctionLines": 275,
"maxComplexity": 47,
"owner": "message executor attempt runner hotspot"
},
"src/message-agent-executor-target.ts": { "src/message-agent-executor-target.ts": {
"maxFunctionLines": 274, "maxFunctionLines": 274,
"maxComplexity": 48, "maxComplexity": 48,

View File

@@ -1,6 +1,9 @@
import type { Logger } from 'pino'; import type { Logger } from 'pino';
import { createEvaluatedOutputHandler } from './agent-attempt.js'; import {
createEvaluatedOutputHandler,
type EvaluatedAgentOutput,
} from './agent-attempt.js';
import type { AttemptStreamedTrigger } from './agent-attempt-retry.js'; import type { AttemptStreamedTrigger } from './agent-attempt-retry.js';
import { runAgentProcess, type AgentOutput } from './agent-runner.js'; import { runAgentProcess, type AgentOutput } from './agent-runner.js';
import { markCompactRefreshNeeded } from './compact-refresh.js'; import { markCompactRefreshNeeded } from './compact-refresh.js';
@@ -64,7 +67,7 @@ function createProviderLog(
return providerLog; return providerLog;
} }
export async function runMessageAgentAttempt(args: { interface RunMessageAgentAttemptArgs {
provider: 'claude' | 'codex'; provider: 'claude' | 'codex';
currentSessionId: string | undefined; currentSessionId: string | undefined;
isClaudeCodeAgent: boolean; isClaudeCodeAgent: boolean;
@@ -100,240 +103,289 @@ export async function runMessageAgentAttempt(args: {
recordFinalOutputBeforeDelivery(outputText: string): boolean; recordFinalOutputBeforeDelivery(outputText: string): boolean;
}; };
log: Logger; log: Logger;
}): Promise<MessageAgentAttempt> { }
const {
provider, type StreamedOutputHandler = ReturnType<typeof createEvaluatedOutputHandler>;
currentSessionId,
isClaudeCodeAgent, class MessageAgentAttemptRunner {
canRetryClaudeCredentials, private resetSessionRequested = false;
shouldPersistSession, private readonly attemptSessionId: string | undefined;
effectiveGroup, private readonly streamedOutputHandler: StreamedOutputHandler;
agentInput,
activeRole, constructor(private readonly args: RunMessageAgentAttemptArgs) {
effectiveServiceId, this.attemptSessionId = args.currentSessionId;
effectiveAgentType, this.streamedOutputHandler = createEvaluatedOutputHandler({
sessionFolder, agentType: args.isClaudeCodeAgent ? 'claude-code' : 'codex',
roomRoleContext, provider: args.provider,
pairedExecutionContext,
fallbackWorkspaceDir,
onPersistSession,
registerProcess,
onOutput,
pairedExecutionLifecycle,
log,
} = args;
const attemptSessionId = currentSessionId;
let resetSessionRequested = false;
const streamedOutputHandler = createEvaluatedOutputHandler({
agentType: isClaudeCodeAgent ? 'claude-code' : 'codex',
provider,
evaluationOptions: { evaluationOptions: {
suppressClaudeAuthErrorOutput: provider === 'claude', suppressClaudeAuthErrorOutput: args.provider === 'claude',
trackSuccessNullResult: true, trackSuccessNullResult: true,
shortCircuitTriggeredErrors: shortCircuitTriggeredErrors:
provider === 'claude' args.provider === 'claude'
? canRetryClaudeCredentials ? args.canRetryClaudeCredentials
: getCodexAccountCount() > 1, : getCodexAccountCount() > 1,
}, },
onEvaluatedOutput: async ({ onEvaluatedOutput: (event) => this.handleEvaluatedOutput(event),
});
}
async run(): Promise<MessageAgentAttempt> {
const providerLog = createProviderLog(
this.args.log,
this.args.provider,
this.args.effectiveAgentType,
);
try {
const output = await this.runAgentProcessWithStreaming();
this.persistReturnedSession(output);
maybeMarkCompactRefreshForOutput({
output, output,
outputText, activeRole: this.args.activeRole,
structuredOutput, sessionFolder: this.args.sessionFolder,
evaluation, });
}) => { providerLog.info(
maybeMarkCompactRefreshForOutput({ output, activeRole, sessionFolder });
const outputPhase = output.phase ?? 'final';
if (outputPhase !== 'final') {
log.info(
{ {
provider, status: output.status,
sawOutput: this.streamedOutputHandler.getState().sawOutput,
},
`Provider response completed (provider: ${this.args.provider})`,
);
return this.buildAttempt({ output });
} catch (error) {
return this.buildAttempt({ error });
}
}
private async runAgentProcessWithStreaming(): Promise<AgentOutput> {
return runAgentProcess(
this.args.effectiveGroup,
{
...this.args.agentInput,
sessionId: this.attemptSessionId,
},
this.args.registerProcess,
(output) => this.streamedOutputHandler.handleOutput(output),
this.args.pairedExecutionContext?.envOverrides,
);
}
private async handleEvaluatedOutput(
event: EvaluatedAgentOutput,
): Promise<void> {
maybeMarkCompactRefreshForOutput({
output: event.output,
activeRole: this.args.activeRole,
sessionFolder: this.args.sessionFolder,
});
this.logNonFinalOutput(event);
this.trackSessionResetRequest(event.output);
this.persistStreamedSession(event.output);
this.updatePairedSummary(event);
this.logEvaluationTrigger(event);
if (this.suppressedOutputWasHandled(event)) {
return;
}
if (!event.evaluation.shouldForwardOutput) {
return;
}
await this.forwardOutputIfAccepted(event);
}
private logNonFinalOutput(event: EvaluatedAgentOutput): void {
const outputPhase = event.output.phase ?? 'final';
if (outputPhase === 'final') {
return;
}
this.args.log.info(
{
provider: this.args.provider,
outputPhase, outputPhase,
outputStatus: output.status, outputStatus: event.output.status,
visibility: structuredOutput?.visibility ?? null, visibility: event.structuredOutput?.visibility ?? null,
preview: preview:
outputText && outputText.length > 0 event.outputText && event.outputText.length > 0
? outputText.slice(0, 160) ? event.outputText.slice(0, 160)
: null, : null,
errorPreview: errorPreview:
typeof output.error === 'string' && output.error.length > 0 typeof event.output.error === 'string' &&
? output.error.slice(0, 160) event.output.error.length > 0
? event.output.error.slice(0, 160)
: null, : null,
activeRole, activeRole: this.args.activeRole,
effectiveServiceId, effectiveServiceId: this.args.effectiveServiceId,
effectiveAgentType, effectiveAgentType: this.args.effectiveAgentType,
sessionFolder, sessionFolder: this.args.sessionFolder,
resumedSession: attemptSessionId ?? null, resumedSession: this.attemptSessionId ?? null,
streamedSessionId: output.newSessionId ?? null, streamedSessionId: event.output.newSessionId ?? null,
roomRoleServiceId: roomRoleContext?.serviceId ?? null, roomRoleServiceId: this.args.roomRoleContext?.serviceId ?? null,
roomRole: roomRoleContext?.role ?? null, roomRole: this.args.roomRoleContext?.role ?? null,
pairedTaskId: pairedExecutionContext?.task.id ?? null, pairedTaskId: this.args.pairedExecutionContext?.task.id ?? null,
workspaceDir: workspaceDir:
pairedExecutionContext?.workspace?.workspace_dir ?? this.args.pairedExecutionContext?.workspace?.workspace_dir ??
fallbackWorkspaceDir ?? this.args.fallbackWorkspaceDir ??
null, null,
}, },
'Observed streamed agent activity', 'Observed streamed agent activity',
); );
} }
private trackSessionResetRequest(output: AgentOutput): void {
if ( if (
isClaudeCodeAgent && this.args.isClaudeCodeAgent &&
provider === 'claude' && this.args.provider === 'claude' &&
shouldResetSessionOnAgentFailure(output) shouldResetSessionOnAgentFailure(output)
) { ) {
resetSessionRequested = true; this.resetSessionRequested = true;
} }
if ( if (
!isClaudeCodeAgent && !this.args.isClaudeCodeAgent &&
provider === 'codex' && this.args.provider === 'codex' &&
shouldResetCodexSessionOnAgentFailure(output) shouldResetCodexSessionOnAgentFailure(output)
) { ) {
resetSessionRequested = true; this.resetSessionRequested = true;
} }
if (
output.newSessionId &&
!resetSessionRequested &&
shouldPersistSession
) {
onPersistSession(output.newSessionId);
} }
pairedExecutionLifecycle.updateSummary({ private persistStreamedSession(output: AgentOutput): void {
outputText, if (
errorText: typeof output.error === 'string' ? output.error : null, output.newSessionId &&
!this.resetSessionRequested &&
this.args.shouldPersistSession
) {
this.args.onPersistSession(output.newSessionId);
}
}
private persistReturnedSession(output: AgentOutput): void {
if (output.newSessionId && this.args.shouldPersistSession) {
this.args.onPersistSession(output.newSessionId);
}
}
private updatePairedSummary(event: EvaluatedAgentOutput): void {
this.args.pairedExecutionLifecycle.updateSummary({
outputText: event.outputText,
errorText:
typeof event.output.error === 'string' ? event.output.error : null,
}); });
if (evaluation.newTrigger && outputText && output.status === 'success') { }
log.warn(
private logEvaluationTrigger(event: EvaluatedAgentOutput): void {
if (
event.evaluation.newTrigger &&
event.outputText &&
event.output.status === 'success'
) {
this.args.log.warn(
{ {
reason: evaluation.newTrigger.reason, reason: event.evaluation.newTrigger.reason,
resultPreview: outputText.slice(0, 120), resultPreview: event.outputText.slice(0, 120),
}, },
'Detected Claude rotation trigger in successful output', 'Detected Claude rotation trigger in successful output',
); );
} else if (evaluation.newTrigger && typeof output.error === 'string') { return;
log.warn( }
if (event.evaluation.newTrigger && typeof event.output.error === 'string') {
this.args.log.warn(
{ {
reason: evaluation.newTrigger.reason, reason: event.evaluation.newTrigger.reason,
errorPreview: output.error.slice(0, 120), errorPreview: event.output.error.slice(0, 120),
}, },
provider === 'claude' this.args.provider === 'claude'
? 'Detected Claude rotation trigger in streamed error output' ? 'Detected Claude rotation trigger in streamed error output'
: 'Detected Codex rotation trigger in streamed error output', : 'Detected Codex rotation trigger in streamed error output',
); );
} }
}
if (evaluation.suppressedAuthError) { private suppressedOutputWasHandled(event: EvaluatedAgentOutput): boolean {
log.warn( if (event.evaluation.suppressedAuthError) {
this.args.log.warn(
{ {
resultPreview: outputText ? outputText.slice(0, 120) : undefined, resultPreview: event.outputText
? event.outputText.slice(0, 120)
: undefined,
}, },
'Suppressed Claude 401 auth error from chat output', 'Suppressed Claude 401 auth error from chat output',
); );
return; return true;
} }
if (event.evaluation.suppressedRetryableSessionFailure) {
if (evaluation.suppressedRetryableSessionFailure) { this.args.log.warn(
log.warn(
{ {
resultPreview: outputText resultPreview: event.outputText
? outputText.slice(0, 160) ? event.outputText.slice(0, 160)
: output.error?.slice(0, 160), : event.output.error?.slice(0, 160),
}, },
provider === 'claude' this.args.provider === 'claude'
? 'Suppressed retryable Claude session failure from chat output' ? 'Suppressed retryable Claude session failure from chat output'
: 'Suppressed retryable Codex session failure from chat output', : 'Suppressed retryable Codex session failure from chat output',
); );
return; return true;
}
return false;
} }
if (!evaluation.shouldForwardOutput) { private async forwardOutputIfAccepted(
event: EvaluatedAgentOutput,
): Promise<void> {
if (event.outputText && event.outputText.length > 0) {
this.streamedOutputHandler.markVisibleOutput();
}
if (!this.finalOutputWasAccepted(event)) {
return; return;
} }
if (outputText && outputText.length > 0) { await this.args.onOutput?.(event.output);
streamedOutputHandler.markVisibleOutput();
} }
private finalOutputWasAccepted(event: EvaluatedAgentOutput): boolean {
const outputPhase = event.output.phase ?? 'final';
if ( if (
outputPhase === 'final' && outputPhase !== 'final' ||
output.status === 'success' && event.output.status !== 'success' ||
outputText && !event.outputText ||
outputText.length > 0 event.outputText.length === 0
) { ) {
let finalOutputAccepted = true; return true;
}
try { try {
finalOutputAccepted = return this.args.pairedExecutionLifecycle.recordFinalOutputBeforeDelivery(
pairedExecutionLifecycle.recordFinalOutputBeforeDelivery( event.outputText,
outputText,
); );
} catch (err) { } catch (err) {
log.warn( this.args.log.warn(
{ pairedTaskId: pairedExecutionContext?.task.id ?? null, err }, {
pairedTaskId: this.args.pairedExecutionContext?.task.id ?? null,
err,
},
'Failed to persist paired turn output and status before delivery', 'Failed to persist paired turn output and status before delivery',
); );
} return true;
if (!finalOutputAccepted) {
return;
} }
} }
if (onOutput) {
await onOutput(output);
}
},
});
const wrappedOnOutput = async (output: AgentOutput) => { private buildAttempt(args: {
await streamedOutputHandler.handleOutput(output); output?: AgentOutput;
}; error?: unknown;
}): MessageAgentAttempt {
const providerLog = createProviderLog(log, provider, effectiveAgentType); const streamedState = this.streamedOutputHandler.getState();
try {
const output = await runAgentProcess(
effectiveGroup,
{
...agentInput,
sessionId: attemptSessionId,
},
registerProcess,
wrappedOnOutput,
pairedExecutionContext?.envOverrides,
);
if (output.newSessionId && shouldPersistSession) {
onPersistSession(output.newSessionId);
}
maybeMarkCompactRefreshForOutput({ output, activeRole, sessionFolder });
providerLog.info(
{
status: output.status,
sawOutput: streamedOutputHandler.getState().sawOutput,
},
`Provider response completed (provider: ${provider})`,
);
const streamedState = streamedOutputHandler.getState();
return { return {
output, ...args,
sawOutput: streamedState.sawOutput, sawOutput: streamedState.sawOutput,
sawVisibleOutput: streamedState.sawVisibleOutput, sawVisibleOutput: streamedState.sawVisibleOutput,
sawSuccessNullResultWithoutOutput: sawSuccessNullResultWithoutOutput:
streamedState.sawSuccessNullResultWithoutOutput, streamedState.sawSuccessNullResultWithoutOutput,
retryableSessionFailureDetected: retryableSessionFailureDetected:
streamedState.retryableSessionFailureDetected === true, streamedState.retryableSessionFailureDetected === true,
resetSessionRequested, resetSessionRequested: this.resetSessionRequested,
streamedTriggerReason: streamedState.streamedTriggerReason,
};
} catch (error) {
const streamedState = streamedOutputHandler.getState();
return {
error,
sawOutput: streamedState.sawOutput,
sawVisibleOutput: streamedState.sawVisibleOutput,
sawSuccessNullResultWithoutOutput:
streamedState.sawSuccessNullResultWithoutOutput,
retryableSessionFailureDetected:
streamedState.retryableSessionFailureDetected === true,
resetSessionRequested,
streamedTriggerReason: streamedState.streamedTriggerReason, streamedTriggerReason: streamedState.streamedTriggerReason,
}; };
} }
} }
export async function runMessageAgentAttempt(
args: RunMessageAgentAttemptArgs,
): Promise<MessageAgentAttempt> {
return new MessageAgentAttemptRunner(args).run();
}