Extract paired turn fallback planning

This commit is contained in:
ejclaw
2026-04-06 19:53:26 +09:00
parent 5b832042da
commit a3dac2b983
3 changed files with 267 additions and 94 deletions

116
src/paired-turn-fallback.ts Normal file
View File

@@ -0,0 +1,116 @@
import type { AgentTriggerReason } from './agent-error-detection.js';
import type { AgentType, PairedRoomRole } from './types.js';
const CODEX_HANDOFF_REASONS = new Set<AgentTriggerReason>([
'429',
'usage-exhausted',
'auth-expired',
'org-access-denied',
'session-failure',
]);
export interface CodexFallbackHandoffRecord {
source_role: PairedRoomRole;
target_role: PairedRoomRole;
source_agent_type: AgentType;
target_agent_type: 'codex';
prompt: string;
start_seq: number | null;
end_seq: number | null;
reason: string;
intended_role: PairedRoomRole;
}
export interface CodexFallbackHandoffPlan {
handoff: CodexFallbackHandoffRecord;
activateOwnerFailoverReason?: string;
logMessage: string;
}
export type CodexFallbackResolution =
| { type: 'none' }
| { type: 'skip'; logMessage: string }
| { type: 'handoff'; plan: CodexFallbackHandoffPlan };
export function resolveCodexFallbackHandoff(args: {
activeRole: PairedRoomRole;
effectiveAgentType: AgentType;
hasReviewer: boolean;
fallbackEnabled: boolean;
reason: AgentTriggerReason;
sawVisibleOutput: boolean;
prompt: string;
startSeq?: number | null;
endSeq?: number | null;
}): CodexFallbackResolution {
if (args.sawVisibleOutput || !CODEX_HANDOFF_REASONS.has(args.reason)) {
return { type: 'none' };
}
if (!args.hasReviewer) {
return { type: 'none' };
}
if (!args.fallbackEnabled) {
return {
type: 'skip',
logMessage: 'Fallback disabled for role, skipping handoff',
};
}
const baseHandoff = {
source_role: args.activeRole,
source_agent_type: args.effectiveAgentType,
target_agent_type: 'codex' as const,
prompt: args.prompt,
start_seq: args.startSeq ?? null,
end_seq: args.endSeq ?? null,
};
if (args.activeRole === 'arbiter') {
return {
type: 'handoff',
plan: {
handoff: {
...baseHandoff,
target_role: 'arbiter',
intended_role: 'arbiter',
reason: `arbiter-claude-${args.reason}`,
},
logMessage:
'Claude arbiter unavailable, handed off arbiter turn to codex',
},
};
}
if (args.activeRole === 'reviewer') {
return {
type: 'handoff',
plan: {
handoff: {
...baseHandoff,
target_role: 'reviewer',
intended_role: 'reviewer',
reason: `reviewer-claude-${args.reason}`,
},
logMessage:
'Claude reviewer unavailable, handed off review turn to codex-review',
},
};
}
return {
type: 'handoff',
plan: {
handoff: {
...baseHandoff,
target_role: args.activeRole,
intended_role: args.activeRole,
reason: `claude-${args.reason}`,
},
activateOwnerFailoverReason: `claude-${args.reason}`,
logMessage:
'Claude unavailable, handed off current owner turn to codex fallback',
},
};
}