refactor: split message executor attempt lifecycle (#188)
This commit is contained in:
@@ -114,10 +114,6 @@
|
|||||||
"maxComplexity": 47,
|
"maxComplexity": 47,
|
||||||
"owner": "message executor attempt runner hotspot"
|
"owner": "message executor attempt runner hotspot"
|
||||||
},
|
},
|
||||||
"src/message-agent-executor-lifecycle.ts": {
|
|
||||||
"maxFunctionLines": 417,
|
|
||||||
"owner": "message executor lifecycle hotspot"
|
|
||||||
},
|
|
||||||
"src/message-agent-executor-target.ts": {
|
"src/message-agent-executor-target.ts": {
|
||||||
"maxFunctionLines": 274,
|
"maxFunctionLines": 274,
|
||||||
"maxComplexity": 48,
|
"maxComplexity": 48,
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ function isRetryableCodexSessionFailureAttempt(args: {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function executeMessageAgentAttemptLifecycle(args: {
|
interface ExecuteMessageAgentAttemptLifecycleArgs {
|
||||||
provider: 'claude' | 'codex';
|
provider: 'claude' | 'codex';
|
||||||
runAttempt: (provider: 'claude' | 'codex') => Promise<MessageAgentAttempt>;
|
runAttempt: (provider: 'claude' | 'codex') => Promise<MessageAgentAttempt>;
|
||||||
isClaudeCodeAgent: boolean;
|
isClaudeCodeAgent: boolean;
|
||||||
@@ -69,143 +69,166 @@ export async function executeMessageAgentAttemptLifecycle(args: {
|
|||||||
warn: (obj: Record<string, unknown> | string, msg?: string) => void;
|
warn: (obj: Record<string, unknown> | string, msg?: string) => void;
|
||||||
error: (obj: Record<string, unknown> | string, msg?: string) => void;
|
error: (obj: Record<string, unknown> | string, msg?: string) => void;
|
||||||
};
|
};
|
||||||
}): Promise<AttemptResult> {
|
}
|
||||||
const {
|
|
||||||
provider,
|
|
||||||
runAttempt,
|
|
||||||
isClaudeCodeAgent,
|
|
||||||
canRetryClaudeCredentials,
|
|
||||||
clearStoredSession,
|
|
||||||
clearRoleSdkSessions,
|
|
||||||
sessionFolder,
|
|
||||||
maybeHandoffToCodex,
|
|
||||||
hasDirectTerminalDelivery,
|
|
||||||
pairedExecutionLifecycle,
|
|
||||||
shouldRetryFreshSessionOnAgentFailure,
|
|
||||||
rotationLogContext,
|
|
||||||
log,
|
|
||||||
} = args;
|
|
||||||
|
|
||||||
let resetSessionRequested = false;
|
interface RecoveryResult {
|
||||||
const rememberAttempt = (
|
attempt: MessageAgentAttempt;
|
||||||
attempt: MessageAgentAttempt,
|
resolved: AttemptResult | null;
|
||||||
): MessageAgentAttempt => {
|
}
|
||||||
|
|
||||||
|
class MessageAgentAttemptLifecycleRunner {
|
||||||
|
private resetSessionRequested = false;
|
||||||
|
|
||||||
|
constructor(private readonly args: ExecuteMessageAgentAttemptLifecycleArgs) {}
|
||||||
|
|
||||||
|
async execute(): Promise<AttemptResult> {
|
||||||
|
let primaryAttempt = await this.runTrackedAttempt(this.args.provider);
|
||||||
|
const recoveredSessionAttempt =
|
||||||
|
await this.recoverRetryableClaudeSessionFailure(primaryAttempt);
|
||||||
|
if (recoveredSessionAttempt.resolved) {
|
||||||
|
return recoveredSessionAttempt.resolved;
|
||||||
|
}
|
||||||
|
primaryAttempt = recoveredSessionAttempt.attempt;
|
||||||
|
|
||||||
|
const recoveredCodexSessionAttempt =
|
||||||
|
await this.recoverRetryableCodexSessionFailure(primaryAttempt);
|
||||||
|
if (recoveredCodexSessionAttempt.resolved) {
|
||||||
|
return recoveredCodexSessionAttempt.resolved;
|
||||||
|
}
|
||||||
|
primaryAttempt = recoveredCodexSessionAttempt.attempt;
|
||||||
|
|
||||||
|
if (primaryAttempt.error) {
|
||||||
|
return this.handlePrimaryAttemptFailure(
|
||||||
|
primaryAttempt,
|
||||||
|
getErrorMessage(primaryAttempt.error),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.finalizePrimaryAttempt(primaryAttempt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private rememberAttempt(attempt: MessageAgentAttempt): MessageAgentAttempt {
|
||||||
if (attempt.resetSessionRequested) {
|
if (attempt.resetSessionRequested) {
|
||||||
resetSessionRequested = true;
|
this.resetSessionRequested = true;
|
||||||
}
|
}
|
||||||
return attempt;
|
return attempt;
|
||||||
};
|
}
|
||||||
|
|
||||||
const runTrackedAttempt = async (
|
private async runTrackedAttempt(
|
||||||
currentProvider: 'claude' | 'codex',
|
currentProvider: 'claude' | 'codex',
|
||||||
): Promise<MessageAgentAttempt> =>
|
): Promise<MessageAgentAttempt> {
|
||||||
rememberAttempt(await runAttempt(currentProvider));
|
return this.rememberAttempt(await this.args.runAttempt(currentProvider));
|
||||||
|
}
|
||||||
|
|
||||||
const retryCodexWithRotation = async (
|
private async retryCodexWithRotation(
|
||||||
initialTrigger: { reason: CodexRotationReason },
|
initialTrigger: { reason: CodexRotationReason },
|
||||||
rotationMessage?: string,
|
rotationMessage?: string,
|
||||||
): Promise<AttemptResult> => {
|
): Promise<AttemptResult> {
|
||||||
return runCodexAttemptWithRotation({
|
return runCodexAttemptWithRotation({
|
||||||
initialTrigger,
|
initialTrigger,
|
||||||
runAttempt: () => runTrackedAttempt('codex'),
|
runAttempt: () => this.runTrackedAttempt('codex'),
|
||||||
logContext: rotationLogContext,
|
logContext: this.args.rotationLogContext,
|
||||||
rotationMessage,
|
rotationMessage,
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
const retryClaudeWithRotation = async (
|
private async retryClaudeWithRotation(
|
||||||
initialTrigger: {
|
initialTrigger: {
|
||||||
reason: AgentTriggerReason;
|
reason: AgentTriggerReason;
|
||||||
retryAfterMs?: number;
|
retryAfterMs?: number;
|
||||||
},
|
},
|
||||||
rotationMessage?: string,
|
rotationMessage?: string,
|
||||||
): Promise<AttemptResult> => {
|
): Promise<AttemptResult> {
|
||||||
return runClaudeAttemptWithRotation({
|
return runClaudeAttemptWithRotation({
|
||||||
initialTrigger,
|
initialTrigger,
|
||||||
runAttempt: () => runTrackedAttempt('claude'),
|
runAttempt: () => this.runTrackedAttempt('claude'),
|
||||||
logContext: rotationLogContext,
|
logContext: this.args.rotationLogContext,
|
||||||
rotationMessage,
|
rotationMessage,
|
||||||
onSuccess: ({ sawOutput }) => {
|
onSuccess: ({ sawOutput }) => {
|
||||||
pairedExecutionLifecycle.markSawOutput(sawOutput);
|
this.args.pairedExecutionLifecycle.markSawOutput(sawOutput);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
const maybeHandoffAfterError = (
|
private maybeHandoffAfterError(
|
||||||
reason: AgentTriggerReason,
|
reason: AgentTriggerReason,
|
||||||
attempt: MessageAgentAttempt,
|
attempt: MessageAgentAttempt,
|
||||||
): AttemptResult => {
|
): AttemptResult {
|
||||||
if (maybeHandoffToCodex(reason, attempt.sawVisibleOutput)) {
|
if (this.args.maybeHandoffToCodex(reason, attempt.sawVisibleOutput)) {
|
||||||
return 'success';
|
return 'success';
|
||||||
}
|
}
|
||||||
return 'error';
|
return 'error';
|
||||||
};
|
}
|
||||||
|
|
||||||
const retryClaudeAttemptIfNeeded = async (
|
private async retryClaudeAttemptIfNeeded(
|
||||||
attempt: MessageAgentAttempt,
|
attempt: MessageAgentAttempt,
|
||||||
rotationMessage?: string | null,
|
rotationMessage?: string | null,
|
||||||
): Promise<AttemptResult | null> => {
|
): Promise<AttemptResult | null> {
|
||||||
const retryAction = await executeAttemptRetryAction({
|
const retryAction = await executeAttemptRetryAction({
|
||||||
provider,
|
provider: this.args.provider,
|
||||||
canRetryClaudeCredentials,
|
canRetryClaudeCredentials: this.args.canRetryClaudeCredentials,
|
||||||
canRetryCodex: false,
|
canRetryCodex: false,
|
||||||
attempt,
|
attempt,
|
||||||
rotationMessage,
|
rotationMessage,
|
||||||
runClaude: retryClaudeWithRotation,
|
runClaude: (trigger, message) =>
|
||||||
runCodex: retryCodexWithRotation,
|
this.retryClaudeWithRotation(trigger, message),
|
||||||
|
runCodex: (trigger, message) =>
|
||||||
|
this.retryCodexWithRotation(trigger, message),
|
||||||
});
|
});
|
||||||
if (retryAction.kind !== 'claude') {
|
if (retryAction.kind !== 'claude') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (retryAction.result === 'error') {
|
if (retryAction.result === 'error') {
|
||||||
return maybeHandoffAfterError(retryAction.trigger.reason, attempt);
|
return this.maybeHandoffAfterError(retryAction.trigger.reason, attempt);
|
||||||
}
|
}
|
||||||
|
|
||||||
pairedExecutionLifecycle.markStatus('succeeded');
|
this.args.pairedExecutionLifecycle.markStatus('succeeded');
|
||||||
return retryAction.result;
|
return retryAction.result;
|
||||||
};
|
}
|
||||||
|
|
||||||
const retryCodexAttemptIfNeeded = async (
|
private async retryCodexAttemptIfNeeded(
|
||||||
attempt: MessageAgentAttempt,
|
attempt: MessageAgentAttempt,
|
||||||
rotationMessage?: string | null,
|
rotationMessage?: string | null,
|
||||||
): Promise<AttemptResult | null> => {
|
): Promise<AttemptResult | null> {
|
||||||
const retryAction = await executeAttemptRetryAction({
|
const retryAction = await executeAttemptRetryAction({
|
||||||
provider,
|
provider: this.args.provider,
|
||||||
canRetryClaudeCredentials: false,
|
canRetryClaudeCredentials: false,
|
||||||
canRetryCodex: !isClaudeCodeAgent && getCodexAccountCount() > 1,
|
canRetryCodex: !this.args.isClaudeCodeAgent && getCodexAccountCount() > 1,
|
||||||
attempt,
|
attempt,
|
||||||
rotationMessage,
|
rotationMessage,
|
||||||
runClaude: retryClaudeWithRotation,
|
runClaude: (trigger, message) =>
|
||||||
runCodex: retryCodexWithRotation,
|
this.retryClaudeWithRotation(trigger, message),
|
||||||
|
runCodex: (trigger, message) =>
|
||||||
|
this.retryCodexWithRotation(trigger, message),
|
||||||
});
|
});
|
||||||
if (retryAction.kind !== 'codex') {
|
if (retryAction.kind !== 'codex') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (retryAction.result === 'success') {
|
if (retryAction.result === 'success') {
|
||||||
pairedExecutionLifecycle.markStatus('succeeded');
|
this.args.pairedExecutionLifecycle.markStatus('succeeded');
|
||||||
}
|
}
|
||||||
return retryAction.result;
|
return retryAction.result;
|
||||||
};
|
}
|
||||||
|
|
||||||
const isRetryableClaudeSessionFailure = (
|
private isRetryableClaudeSessionFailure(
|
||||||
attempt: MessageAgentAttempt,
|
attempt: MessageAgentAttempt,
|
||||||
): boolean =>
|
): boolean {
|
||||||
isRetryableClaudeSessionFailureAttempt({
|
return isRetryableClaudeSessionFailureAttempt({
|
||||||
attempt,
|
attempt,
|
||||||
isClaudeCodeAgent,
|
isClaudeCodeAgent: this.args.isClaudeCodeAgent,
|
||||||
provider,
|
provider: this.args.provider,
|
||||||
shouldRetryFreshSessionOnAgentFailure,
|
shouldRetryFreshSessionOnAgentFailure:
|
||||||
|
this.args.shouldRetryFreshSessionOnAgentFailure,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const recoverRetryableClaudeSessionFailure = async (
|
private async recoverRetryableClaudeSessionFailure(
|
||||||
attempt: MessageAgentAttempt,
|
attempt: MessageAgentAttempt,
|
||||||
): Promise<{
|
): Promise<RecoveryResult> {
|
||||||
attempt: MessageAgentAttempt;
|
const { clearRoleSdkSessions, clearStoredSession, log } = this.args;
|
||||||
resolved: AttemptResult | null;
|
if (!this.isRetryableClaudeSessionFailure(attempt)) {
|
||||||
}> => {
|
|
||||||
if (!isRetryableClaudeSessionFailure(attempt)) {
|
|
||||||
return { attempt, resolved: null };
|
return { attempt, resolved: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,8 +238,8 @@ export async function executeMessageAgentAttemptLifecycle(args: {
|
|||||||
'Cleared poisoned Claude session before visible output, retrying fresh session',
|
'Cleared poisoned Claude session before visible output, retrying fresh session',
|
||||||
);
|
);
|
||||||
|
|
||||||
const freshAttempt = await runTrackedAttempt('claude');
|
const freshAttempt = await this.runTrackedAttempt('claude');
|
||||||
if (!isRetryableClaudeSessionFailure(freshAttempt)) {
|
if (!this.isRetryableClaudeSessionFailure(freshAttempt)) {
|
||||||
return { attempt: freshAttempt, resolved: null };
|
return { attempt: freshAttempt, resolved: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,16 +248,15 @@ export async function executeMessageAgentAttemptLifecycle(args: {
|
|||||||
log.error('Retryable Claude session failure persisted after fresh retry');
|
log.error('Retryable Claude session failure persisted after fresh retry');
|
||||||
return {
|
return {
|
||||||
attempt: freshAttempt,
|
attempt: freshAttempt,
|
||||||
resolved: maybeHandoffAfterError('session-failure', freshAttempt),
|
resolved: this.maybeHandoffAfterError('session-failure', freshAttempt),
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
const recoverRetryableCodexSessionFailure = async (
|
private async recoverRetryableCodexSessionFailure(
|
||||||
attempt: MessageAgentAttempt,
|
attempt: MessageAgentAttempt,
|
||||||
): Promise<{
|
): Promise<RecoveryResult> {
|
||||||
attempt: MessageAgentAttempt;
|
const { clearRoleSdkSessions, clearStoredSession, log, provider } =
|
||||||
resolved: AttemptResult | null;
|
this.args;
|
||||||
}> => {
|
|
||||||
if (!isRetryableCodexSessionFailureAttempt({ provider, attempt })) {
|
if (!isRetryableCodexSessionFailureAttempt({ provider, attempt })) {
|
||||||
return { attempt, resolved: null };
|
return { attempt, resolved: null };
|
||||||
}
|
}
|
||||||
@@ -245,7 +267,7 @@ export async function executeMessageAgentAttemptLifecycle(args: {
|
|||||||
'Cleared poisoned Codex session before visible output, retrying fresh session',
|
'Cleared poisoned Codex session before visible output, retrying fresh session',
|
||||||
);
|
);
|
||||||
|
|
||||||
const freshAttempt = await runTrackedAttempt('codex');
|
const freshAttempt = await this.runTrackedAttempt('codex');
|
||||||
if (
|
if (
|
||||||
!isRetryableCodexSessionFailureAttempt({
|
!isRetryableCodexSessionFailureAttempt({
|
||||||
provider,
|
provider,
|
||||||
@@ -262,13 +284,14 @@ export async function executeMessageAgentAttemptLifecycle(args: {
|
|||||||
attempt: freshAttempt,
|
attempt: freshAttempt,
|
||||||
resolved: 'error',
|
resolved: 'error',
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
const handlePrimaryAttemptFailure = async (
|
private async handlePrimaryAttemptFailure(
|
||||||
attempt: MessageAgentAttempt,
|
attempt: MessageAgentAttempt,
|
||||||
rotationMessage: string,
|
rotationMessage: string,
|
||||||
): Promise<AttemptResult> => {
|
): Promise<AttemptResult> {
|
||||||
const claudeRetryResult = await retryClaudeAttemptIfNeeded(
|
const { log, provider } = this.args;
|
||||||
|
const claudeRetryResult = await this.retryClaudeAttemptIfNeeded(
|
||||||
attempt,
|
attempt,
|
||||||
rotationMessage,
|
rotationMessage,
|
||||||
);
|
);
|
||||||
@@ -276,7 +299,7 @@ export async function executeMessageAgentAttemptLifecycle(args: {
|
|||||||
return claudeRetryResult;
|
return claudeRetryResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
const codexRetryResult = await retryCodexAttemptIfNeeded(
|
const codexRetryResult = await this.retryCodexAttemptIfNeeded(
|
||||||
attempt,
|
attempt,
|
||||||
rotationMessage,
|
rotationMessage,
|
||||||
);
|
);
|
||||||
@@ -303,74 +326,41 @@ export async function executeMessageAgentAttemptLifecycle(args: {
|
|||||||
'Agent process error',
|
'Agent process error',
|
||||||
);
|
);
|
||||||
return 'error';
|
return 'error';
|
||||||
};
|
}
|
||||||
|
|
||||||
const finalizePrimaryAttempt = async (
|
private async finalizePrimaryAttempt(
|
||||||
attempt: MessageAgentAttempt,
|
attempt: MessageAgentAttempt,
|
||||||
): Promise<AttemptResult> => {
|
): Promise<AttemptResult> {
|
||||||
|
const { log, provider } = this.args;
|
||||||
const output = attempt.output;
|
const output = attempt.output;
|
||||||
if (!output) {
|
if (!output) {
|
||||||
log.error({ provider }, 'Agent produced no output object');
|
log.error({ provider }, 'Agent produced no output object');
|
||||||
return 'error';
|
return 'error';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pairedExecutionLifecycle.getSummary()) {
|
this.updateSummaryFromOutputIfNeeded(output);
|
||||||
const finalOutputText = getAgentOutputText(output);
|
|
||||||
pairedExecutionLifecycle.updateSummary({
|
|
||||||
outputText:
|
|
||||||
typeof finalOutputText === 'string' && finalOutputText.length > 0
|
|
||||||
? finalOutputText
|
|
||||||
: null,
|
|
||||||
errorText:
|
|
||||||
typeof output.error === 'string' && output.error.length > 0
|
|
||||||
? output.error
|
|
||||||
: null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!attempt.sawOutput &&
|
!attempt.sawOutput &&
|
||||||
!hasDirectTerminalDelivery() &&
|
!this.args.hasDirectTerminalDelivery() &&
|
||||||
output.status !== 'error'
|
output.status !== 'error'
|
||||||
) {
|
) {
|
||||||
const claudeRetryResult = await retryClaudeAttemptIfNeeded(attempt);
|
const claudeRetryResult = await this.retryClaudeAttemptIfNeeded(attempt);
|
||||||
if (claudeRetryResult) {
|
if (claudeRetryResult) {
|
||||||
return claudeRetryResult;
|
return claudeRetryResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
this.clearPoisonedSessionAfterFailure(output);
|
||||||
isClaudeCodeAgent &&
|
|
||||||
(resetSessionRequested || shouldResetSessionOnAgentFailure(output))
|
|
||||||
) {
|
|
||||||
clearStoredSession();
|
|
||||||
log.warn(
|
|
||||||
{ sessionFolder },
|
|
||||||
'Cleared poisoned agent session after unrecoverable error',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!isClaudeCodeAgent &&
|
|
||||||
provider === 'codex' &&
|
|
||||||
(resetSessionRequested || shouldResetCodexSessionOnAgentFailure(output))
|
|
||||||
) {
|
|
||||||
clearStoredSession();
|
|
||||||
clearRoleSdkSessions();
|
|
||||||
log.warn(
|
|
||||||
{ sessionFolder },
|
|
||||||
'Cleared poisoned Codex session after unrecoverable error',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (output.status === 'error') {
|
if (output.status === 'error') {
|
||||||
return handlePrimaryAttemptFailure(
|
return this.handlePrimaryAttemptFailure(
|
||||||
attempt,
|
attempt,
|
||||||
output.error ?? 'Agent process error',
|
output.error ?? 'Agent process error',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const codexRetryResult = await retryCodexAttemptIfNeeded(
|
const codexRetryResult = await this.retryCodexAttemptIfNeeded(
|
||||||
attempt,
|
attempt,
|
||||||
output.error ?? output.result,
|
output.error ?? output.result,
|
||||||
);
|
);
|
||||||
@@ -379,13 +369,7 @@ export async function executeMessageAgentAttemptLifecycle(args: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (attempt.streamedTriggerReason) {
|
if (attempt.streamedTriggerReason) {
|
||||||
if (
|
if (this.resolveStreamedTriggerHandoff(attempt)) {
|
||||||
isClaudeCodeAgent &&
|
|
||||||
maybeHandoffToCodex(
|
|
||||||
attempt.streamedTriggerReason.reason,
|
|
||||||
attempt.sawVisibleOutput,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return 'success';
|
return 'success';
|
||||||
}
|
}
|
||||||
log.error(
|
log.error(
|
||||||
@@ -397,45 +381,98 @@ export async function executeMessageAgentAttemptLifecycle(args: {
|
|||||||
return 'error';
|
return 'error';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (this.isSuccessWithoutVisibleOutput(attempt)) {
|
||||||
attempt.sawSuccessNullResultWithoutOutput &&
|
|
||||||
!attempt.sawOutput &&
|
|
||||||
!hasDirectTerminalDelivery()
|
|
||||||
) {
|
|
||||||
log.error(
|
log.error(
|
||||||
'Agent returned success with null result and no visible output',
|
'Agent returned success with null result and no visible output',
|
||||||
);
|
);
|
||||||
return 'error';
|
return 'error';
|
||||||
}
|
}
|
||||||
|
|
||||||
pairedExecutionLifecycle.markStatus('succeeded');
|
this.args.pairedExecutionLifecycle.markStatus('succeeded');
|
||||||
pairedExecutionLifecycle.markSawOutput(
|
this.args.pairedExecutionLifecycle.markSawOutput(
|
||||||
attempt.sawOutput || hasDirectTerminalDelivery(),
|
attempt.sawOutput || this.args.hasDirectTerminalDelivery(),
|
||||||
);
|
);
|
||||||
return 'success';
|
return 'success';
|
||||||
};
|
|
||||||
|
|
||||||
let primaryAttempt = await runTrackedAttempt(provider);
|
|
||||||
const recoveredSessionAttempt =
|
|
||||||
await recoverRetryableClaudeSessionFailure(primaryAttempt);
|
|
||||||
if (recoveredSessionAttempt.resolved) {
|
|
||||||
return recoveredSessionAttempt.resolved;
|
|
||||||
}
|
}
|
||||||
primaryAttempt = recoveredSessionAttempt.attempt;
|
|
||||||
|
|
||||||
const recoveredCodexSessionAttempt =
|
private updateSummaryFromOutputIfNeeded(
|
||||||
await recoverRetryableCodexSessionFailure(primaryAttempt);
|
output: NonNullable<MessageAgentAttempt['output']>,
|
||||||
if (recoveredCodexSessionAttempt.resolved) {
|
): void {
|
||||||
return recoveredCodexSessionAttempt.resolved;
|
const { pairedExecutionLifecycle } = this.args;
|
||||||
|
if (pairedExecutionLifecycle.getSummary()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const finalOutputText = getAgentOutputText(output);
|
||||||
|
pairedExecutionLifecycle.updateSummary({
|
||||||
|
outputText:
|
||||||
|
typeof finalOutputText === 'string' && finalOutputText.length > 0
|
||||||
|
? finalOutputText
|
||||||
|
: null,
|
||||||
|
errorText:
|
||||||
|
typeof output.error === 'string' && output.error.length > 0
|
||||||
|
? output.error
|
||||||
|
: null,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
primaryAttempt = recoveredCodexSessionAttempt.attempt;
|
|
||||||
|
|
||||||
if (primaryAttempt.error) {
|
private clearPoisonedSessionAfterFailure(
|
||||||
return handlePrimaryAttemptFailure(
|
output: NonNullable<MessageAgentAttempt['output']>,
|
||||||
primaryAttempt,
|
): void {
|
||||||
getErrorMessage(primaryAttempt.error),
|
const {
|
||||||
|
clearRoleSdkSessions,
|
||||||
|
clearStoredSession,
|
||||||
|
isClaudeCodeAgent,
|
||||||
|
log,
|
||||||
|
provider,
|
||||||
|
sessionFolder,
|
||||||
|
} = this.args;
|
||||||
|
if (
|
||||||
|
isClaudeCodeAgent &&
|
||||||
|
(this.resetSessionRequested || shouldResetSessionOnAgentFailure(output))
|
||||||
|
) {
|
||||||
|
clearStoredSession();
|
||||||
|
log.warn(
|
||||||
|
{ sessionFolder },
|
||||||
|
'Cleared poisoned agent session after unrecoverable error',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!isClaudeCodeAgent &&
|
||||||
|
provider === 'codex' &&
|
||||||
|
(this.resetSessionRequested ||
|
||||||
|
shouldResetCodexSessionOnAgentFailure(output))
|
||||||
|
) {
|
||||||
|
clearStoredSession();
|
||||||
|
clearRoleSdkSessions();
|
||||||
|
log.warn(
|
||||||
|
{ sessionFolder },
|
||||||
|
'Cleared poisoned Codex session after unrecoverable error',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private resolveStreamedTriggerHandoff(attempt: MessageAgentAttempt): boolean {
|
||||||
|
if (!attempt.streamedTriggerReason || !this.args.isClaudeCodeAgent) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.args.maybeHandoffToCodex(
|
||||||
|
attempt.streamedTriggerReason.reason,
|
||||||
|
attempt.sawVisibleOutput,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return finalizePrimaryAttempt(primaryAttempt);
|
private isSuccessWithoutVisibleOutput(attempt: MessageAgentAttempt): boolean {
|
||||||
|
return (
|
||||||
|
attempt.sawSuccessNullResultWithoutOutput &&
|
||||||
|
!attempt.sawOutput &&
|
||||||
|
!this.args.hasDirectTerminalDelivery()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function executeMessageAgentAttemptLifecycle(
|
||||||
|
args: ExecuteMessageAgentAttemptLifecycleArgs,
|
||||||
|
): Promise<AttemptResult> {
|
||||||
|
return new MessageAgentAttemptLifecycleRunner(args).execute();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user