Unify paired status transitions and retry handling

This commit is contained in:
ejclaw
2026-04-07 02:49:02 +09:00
parent 4511ca5692
commit b8e8c9dabf
13 changed files with 574 additions and 280 deletions

View File

@@ -1,116 +1,14 @@
import {
classifyRotationTrigger,
type AgentTriggerReason,
type CodexRotationReason,
isCodexRotationReason,
} from './agent-error-detection.js';
import { detectCodexRotationTrigger } from './codex-token-rotation.js';
import { resolveNextTurnAction } from './message-runtime-rules.js';
import type { PairedRoomRole, PairedTaskStatus } from './types.js';
import { getErrorMessage } from './utils.js';
export interface ExecutorStreamedTrigger {
reason: AgentTriggerReason;
retryAfterMs?: number;
}
export interface ExecutorAttemptState {
sawOutput: boolean;
retryableSessionFailureDetected?: boolean;
streamedTriggerReason?: ExecutorStreamedTrigger;
error?: unknown;
}
export function isRetryableClaudeSessionFailureAttempt(args: {
attempt: ExecutorAttemptState;
isClaudeCodeAgent: boolean;
provider: 'claude' | 'codex';
shouldRetryFreshSessionOnAgentFailure: (args: {
result: null;
error: string;
}) => boolean;
}): boolean {
if (
!args.isClaudeCodeAgent ||
args.provider !== 'claude' ||
args.attempt.sawOutput
) {
return false;
}
if (args.attempt.retryableSessionFailureDetected === true) {
return true;
}
if (args.attempt.error == null) {
return false;
}
return args.shouldRetryFreshSessionOnAgentFailure({
result: null,
error: getErrorMessage(args.attempt.error),
});
}
export function resolveClaudeRetryTrigger(args: {
canRetryClaudeCredentials: boolean;
provider: 'claude' | 'codex';
attempt: Pick<ExecutorAttemptState, 'sawOutput' | 'streamedTriggerReason'>;
fallbackMessage?: string | null;
}): { reason: AgentTriggerReason; retryAfterMs?: number } | null {
if (
!(
args.canRetryClaudeCredentials &&
args.provider === 'claude' &&
!args.attempt.sawOutput
)
) {
return null;
}
if (args.attempt.streamedTriggerReason) {
return {
reason: args.attempt.streamedTriggerReason.reason,
retryAfterMs: args.attempt.streamedTriggerReason.retryAfterMs,
};
}
const trigger = classifyRotationTrigger(args.fallbackMessage);
if (!trigger.shouldRetry) {
return null;
}
return {
reason: trigger.reason,
retryAfterMs: trigger.retryAfterMs,
};
}
export function resolveCodexRetryTrigger(args: {
canRetryCodex: boolean;
attempt: Pick<ExecutorAttemptState, 'streamedTriggerReason'>;
rotationMessage?: string | null;
}): { reason: CodexRotationReason } | null {
if (!args.canRetryCodex) {
return null;
}
if (args.attempt.streamedTriggerReason) {
if (!isCodexRotationReason(args.attempt.streamedTriggerReason.reason)) {
return null;
}
return {
reason: args.attempt.streamedTriggerReason.reason,
};
}
const trigger = detectCodexRotationTrigger(args.rotationMessage);
if (!trigger.shouldRotate) {
return null;
}
return { reason: trigger.reason };
}
export {
isRetryableClaudeSessionFailureAttempt,
resolveAttemptRetryAction,
resolveClaudeRetryTrigger,
resolveCodexRetryTrigger,
type AttemptRetryAction,
type AttemptRetryState as ExecutorAttemptState,
type AttemptStreamedTrigger as ExecutorStreamedTrigger,
} from './agent-attempt-retry.js';
export type PairedFollowUpQueueAction =
| 'generic'