refactor: split paired executor lifecycle (#187)

This commit is contained in:
Eyejoker
2026-05-29 19:45:08 +09:00
committed by GitHub
parent b2cfe385cf
commit 41f0460848
2 changed files with 402 additions and 298 deletions

View File

@@ -118,10 +118,6 @@
"maxFunctionLines": 417,
"owner": "message executor lifecycle hotspot"
},
"src/message-agent-executor-paired.ts": {
"maxFunctionLines": 440,
"owner": "message executor paired hotspot"
},
"src/message-agent-executor-target.ts": {
"maxFunctionLines": 274,
"maxComplexity": 48,

View File

@@ -98,7 +98,7 @@ export interface PairedExecutionLifecycle {
asyncFinalize(): Promise<void>;
}
export function createPairedExecutionLifecycle(args: {
interface CreatePairedExecutionLifecycleArgs {
pairedExecutionContext?: PreparedPairedExecutionContext;
pairedTurnIdentity?: PairedTurnIdentity;
completedRole: PairedRoomRole;
@@ -109,38 +109,138 @@ export function createPairedExecutionLifecycle(args: {
getCloseReason?: () => string | null;
onOutput?: (output: AgentOutput) => Promise<void>;
log: ExecutorLog;
}): PairedExecutionLifecycle {
const {
pairedExecutionContext,
pairedTurnIdentity,
completedRole,
chatJid,
}
type PairedExecutionStatus = 'succeeded' | 'failed';
interface FinalizeState {
directTerminalOutput: string | null;
effectiveStatus: PairedExecutionStatus;
sawOutputForFollowUp: boolean;
interruptedByHumanMessage: boolean;
}
class PairedExecutionLifecycleController implements PairedExecutionLifecycle {
private pairedExecutionStatus: PairedExecutionStatus = 'failed';
private pairedExecutionSummary: string | null = null;
private pairedFinalOutput: string | null = null;
private pairedSummaryLocked = false;
private pairedExecutionCompleted = false;
private pairedExecutionDelegated = false;
private pairedSawOutput = false;
private pairedTurnOutputPersisted = false;
private pairedTurnStateFinalized = false;
private leaseHeartbeatTimer: ReturnType<typeof setInterval> | null = null;
constructor(private readonly args: CreatePairedExecutionLifecycleArgs) {
if (!this.args.pairedExecutionContext) {
return;
}
this.leaseHeartbeatTimer = setInterval(
() => this.heartbeatLeaseIfNeeded(),
PAIRED_TASK_EXECUTION_LEASE_HEARTBEAT_MS,
);
this.leaseHeartbeatTimer.unref?.();
}
updateSummary({
outputText,
errorText,
}: {
outputText?: string | null;
errorText?: string | null;
}): void {
if (this.pairedSummaryLocked) {
return;
}
if (outputText && outputText.length > 0) {
this.pairedExecutionSummary = outputText.slice(0, 500);
return;
}
if (errorText && errorText.length > 0) {
this.pairedExecutionSummary = errorText.slice(0, 500);
}
}
recordFinalOutputBeforeDelivery(outputText: string): boolean {
if (this.wasInterruptedByHumanMessage()) return false;
if (!this.currentRunOwnsActiveAttempt('streamed-final-output')) {
return false;
}
this.lockVisibleVerdict(outputText);
this.completeSuccessfulOwnerTurnBeforeDeliveryIfNeeded();
this.persistPairedTurnOutputIfNeeded();
return true;
}
completeImmediately({ status }: { status: PairedExecutionStatus }): void {
const { completedRole, pairedExecutionContext, runId } = this.args;
if (!pairedExecutionContext || this.pairedExecutionCompleted) {
return;
}
this.pairedExecutionStatus = status;
if (status === 'succeeded') {
this.persistPairedTurnOutputIfNeeded();
}
this.clearLeaseHeartbeat();
completePairedExecutionContext({
taskId: pairedExecutionContext.task.id,
role: completedRole,
status,
runId,
enqueueMessageCheck,
getDirectTerminalDeliveryText,
getCloseReason,
onOutput,
log,
} = args;
summary: this.pairedExecutionSummary,
});
this.pairedExecutionCompleted = true;
}
let pairedExecutionStatus: 'succeeded' | 'failed' = 'failed';
let pairedExecutionSummary: string | null = null;
let pairedFinalOutput: string | null = null;
let pairedSummaryLocked = false;
let pairedExecutionCompleted = false;
let pairedExecutionDelegated = false;
let pairedSawOutput = false;
let pairedTurnOutputPersisted = false;
let pairedTurnStateFinalized = false;
let leaseHeartbeatTimer: ReturnType<typeof setInterval> | null = null;
const requiresVisibleVerdict =
pairedExecutionContext?.requiresVisibleVerdict === true;
const missingVisibleVerdictSummary =
'Execution completed without a visible terminal verdict.';
const wasInterruptedByHumanMessage = (): boolean =>
isHumanMessageCloseReason(getCloseReason?.() ?? null);
markDelegated(): void {
this.pairedExecutionDelegated = true;
}
const currentRunOwnsActiveAttempt = (reason: string): boolean => {
markStatus(status: PairedExecutionStatus): void {
this.pairedExecutionStatus = status;
}
markSawOutput(sawOutput: boolean): void {
this.pairedSawOutput = sawOutput;
}
getSummary(): string | null {
return this.pairedExecutionSummary;
}
async asyncFinalize(): Promise<void> {
this.clearLeaseHeartbeat();
if (!this.currentRunOwnsActiveAttempt('async-finalize')) {
return;
}
if (this.releaseDelegatedExecutionIfNeeded()) {
return;
}
const state = this.resolveFinalizeState();
this.completeStoredExecutionIfNeeded(state);
this.finalizePairedTurnState(
state.effectiveStatus,
state.effectiveStatus === 'failed' ? this.pairedExecutionSummary : null,
);
await this.notifyCompletionAndQueueFollowUp(state);
}
private wasInterruptedByHumanMessage(): boolean {
return isHumanMessageCloseReason(this.args.getCloseReason?.() ?? null);
}
private currentRunOwnsActiveAttempt(reason: string): boolean {
const { log, pairedExecutionContext, pairedTurnIdentity, runId } =
this.args;
if (!pairedTurnIdentity) {
return true;
}
@@ -176,13 +276,14 @@ export function createPairedExecutionLifecycle(args: {
'Skipping paired final side effects because this run no longer owns the active attempt',
);
return false;
};
}
const finalizePairedTurnState = (
private finalizePairedTurnState(
status: 'succeeded' | 'failed',
errorText?: string | null,
) => {
if (!pairedTurnIdentity || pairedTurnStateFinalized) {
): void {
const { pairedTurnIdentity } = this.args;
if (!pairedTurnIdentity || this.pairedTurnStateFinalized) {
return;
}
if (status === 'succeeded') {
@@ -190,20 +291,22 @@ export function createPairedExecutionLifecycle(args: {
} else {
failPairedTurn({
turnIdentity: pairedTurnIdentity,
error: errorText ?? pairedExecutionSummary,
error: errorText ?? this.pairedExecutionSummary,
});
}
pairedTurnStateFinalized = true;
};
this.pairedTurnStateFinalized = true;
}
const clearLeaseHeartbeat = () => {
if (!leaseHeartbeatTimer) {
private clearLeaseHeartbeat(): void {
if (!this.leaseHeartbeatTimer) {
return;
}
clearInterval(leaseHeartbeatTimer);
leaseHeartbeatTimer = null;
};
const heartbeatLeaseIfNeeded = () => {
clearInterval(this.leaseHeartbeatTimer);
this.leaseHeartbeatTimer = null;
}
private heartbeatLeaseIfNeeded(): void {
const { pairedExecutionContext, runId, log } = this.args;
if (!pairedExecutionContext) {
return;
}
@@ -231,22 +334,15 @@ export function createPairedExecutionLifecycle(args: {
'Failed to refresh paired execution lease heartbeat',
);
}
};
if (pairedExecutionContext) {
leaseHeartbeatTimer = setInterval(
heartbeatLeaseIfNeeded,
PAIRED_TASK_EXECUTION_LEASE_HEARTBEAT_MS,
);
leaseHeartbeatTimer.unref?.();
}
const persistPairedTurnOutputIfNeeded = () => {
private persistPairedTurnOutputIfNeeded(): void {
const { completedRole, pairedExecutionContext } = this.args;
if (
!pairedExecutionContext ||
pairedTurnOutputPersisted ||
!pairedFinalOutput ||
pairedFinalOutput.length === 0
this.pairedTurnOutputPersisted ||
!this.pairedFinalOutput ||
this.pairedFinalOutput.length === 0
) {
return;
}
@@ -256,57 +352,65 @@ export function createPairedExecutionLifecycle(args: {
pairedExecutionContext.task.id,
turnNumber,
completedRole,
pairedFinalOutput,
this.pairedFinalOutput,
);
pairedTurnOutputPersisted = true;
};
this.pairedTurnOutputPersisted = true;
}
const completeSuccessfulOwnerTurnBeforeDeliveryIfNeeded = () => {
private completeSuccessfulOwnerTurnBeforeDeliveryIfNeeded(): void {
const { completedRole, pairedExecutionContext, runId } = this.args;
if (
completedRole !== 'owner' ||
!pairedExecutionContext ||
pairedExecutionCompleted ||
!pairedFinalOutput ||
pairedFinalOutput.length === 0
this.pairedExecutionCompleted ||
!this.pairedFinalOutput ||
this.pairedFinalOutput.length === 0
) {
return;
}
pairedExecutionStatus = 'succeeded';
pairedSawOutput = true;
persistPairedTurnOutputIfNeeded();
clearLeaseHeartbeat();
this.pairedExecutionStatus = 'succeeded';
this.pairedSawOutput = true;
this.persistPairedTurnOutputIfNeeded();
this.clearLeaseHeartbeat();
completePairedExecutionContext({
taskId: pairedExecutionContext.task.id,
role: completedRole,
status: 'succeeded',
runId,
summary: pairedExecutionSummary,
summary: this.pairedExecutionSummary,
});
pairedExecutionCompleted = true;
};
this.pairedExecutionCompleted = true;
}
const lockVisibleVerdict = (outputText: string) => {
private lockVisibleVerdict(outputText: string): void {
if (outputText.length === 0) {
return;
}
if (!pairedFinalOutput || pairedFinalOutput.length === 0) {
pairedFinalOutput = outputText;
if (!this.pairedFinalOutput || this.pairedFinalOutput.length === 0) {
this.pairedFinalOutput = outputText;
}
if (!pairedSummaryLocked) {
pairedExecutionSummary = outputText.slice(0, 500);
pairedSummaryLocked = true;
if (!this.pairedSummaryLocked) {
this.pairedExecutionSummary = outputText.slice(0, 500);
this.pairedSummaryLocked = true;
}
this.pairedSawOutput = true;
}
pairedSawOutput = true;
};
const adoptDirectTerminalDeliveryIfNeeded = () => {
private adoptDirectTerminalDeliveryIfNeeded(): string | null {
const {
completedRole,
getDirectTerminalDeliveryText,
log,
pairedExecutionContext,
runId,
} = this.args;
const outputText = getDirectTerminalDeliveryText?.();
if (!outputText || outputText.length === 0) {
return null;
}
if (!pairedFinalOutput || pairedFinalOutput.length === 0) {
lockVisibleVerdict(outputText);
if (!this.pairedFinalOutput || this.pairedFinalOutput.length === 0) {
this.lockVisibleVerdict(outputText);
log.info(
{
pairedTaskId: pairedExecutionContext?.task.id ?? null,
@@ -315,85 +419,18 @@ export function createPairedExecutionLifecycle(args: {
},
'Adopted direct terminal delivery as paired final output',
);
} else if (!pairedSummaryLocked) {
pairedExecutionSummary = pairedFinalOutput.slice(0, 500);
pairedSummaryLocked = true;
} else if (!this.pairedSummaryLocked) {
this.pairedExecutionSummary = this.pairedFinalOutput.slice(0, 500);
this.pairedSummaryLocked = true;
}
return outputText;
};
return {
updateSummary({ outputText, errorText }) {
if (pairedSummaryLocked) {
return;
}
if (outputText && outputText.length > 0) {
pairedExecutionSummary = outputText.slice(0, 500);
return;
}
if (errorText && errorText.length > 0) {
pairedExecutionSummary = errorText.slice(0, 500);
}
},
recordFinalOutputBeforeDelivery(outputText) {
if (wasInterruptedByHumanMessage()) return false;
if (!currentRunOwnsActiveAttempt('streamed-final-output')) {
private releaseDelegatedExecutionIfNeeded(): boolean {
const { log, pairedExecutionContext, runId } = this.args;
if (!pairedExecutionContext || !this.pairedExecutionDelegated) {
return false;
}
lockVisibleVerdict(outputText);
completeSuccessfulOwnerTurnBeforeDeliveryIfNeeded();
persistPairedTurnOutputIfNeeded();
return true;
},
completeImmediately({ status }) {
if (!pairedExecutionContext || pairedExecutionCompleted) {
return;
}
pairedExecutionStatus = status;
if (status === 'succeeded') {
persistPairedTurnOutputIfNeeded();
}
clearLeaseHeartbeat();
completePairedExecutionContext({
taskId: pairedExecutionContext.task.id,
role: completedRole,
status,
runId,
summary: pairedExecutionSummary,
});
pairedExecutionCompleted = true;
},
markDelegated() {
pairedExecutionDelegated = true;
},
markStatus(status) {
pairedExecutionStatus = status;
},
markSawOutput(sawOutput) {
pairedSawOutput = sawOutput;
},
getSummary() {
return pairedExecutionSummary;
},
async asyncFinalize() {
clearLeaseHeartbeat();
if (!currentRunOwnsActiveAttempt('async-finalize')) {
return;
}
if (pairedExecutionContext && pairedExecutionDelegated) {
try {
releasePairedTaskExecutionLease({
taskId: pairedExecutionContext.task.id,
@@ -409,17 +446,36 @@ export function createPairedExecutionLifecycle(args: {
'Failed to release paired execution lease for delegated fallback handoff',
);
}
pairedExecutionCompleted = true;
return;
this.pairedExecutionCompleted = true;
return true;
}
const directTerminalOutput = adoptDirectTerminalDeliveryIfNeeded();
private resolveFinalizeState(): FinalizeState {
const directTerminalOutput = this.adoptDirectTerminalDeliveryIfNeeded();
const missingVisibleVerdict = this.resolveMissingVisibleVerdict();
const effectiveStatus = this.resolveEffectiveStatus(missingVisibleVerdict);
const sawOutputForFollowUp = missingVisibleVerdict
? false
: this.pairedSawOutput;
return {
directTerminalOutput,
effectiveStatus,
sawOutputForFollowUp,
interruptedByHumanMessage: this.wasInterruptedByHumanMessage(),
};
}
private resolveMissingVisibleVerdict(): boolean {
const { completedRole, log, pairedExecutionContext, runId } = this.args;
const missingVisibleVerdict =
requiresVisibleVerdict &&
(!pairedFinalOutput || pairedFinalOutput.length === 0);
if (missingVisibleVerdict) {
pairedExecutionSummary = missingVisibleVerdictSummary;
pairedExecutionContext?.requiresVisibleVerdict === true &&
(!this.pairedFinalOutput || this.pairedFinalOutput.length === 0);
if (!missingVisibleVerdict) {
return false;
}
this.pairedExecutionSummary =
'Execution completed without a visible terminal verdict.';
log.warn(
{
pairedTaskId: pairedExecutionContext?.task.id ?? null,
@@ -428,58 +484,71 @@ export function createPairedExecutionLifecycle(args: {
},
'Treating paired execution as failed because it ended without a visible terminal verdict',
);
return true;
}
const effectiveStatus =
completedRole === 'owner' &&
pairedExecutionStatus === 'succeeded' &&
!pairedSawOutput
? 'failed'
: missingVisibleVerdict && pairedExecutionStatus === 'succeeded'
? 'failed'
: pairedExecutionStatus;
const sawOutputForFollowUp = missingVisibleVerdict
? false
: pairedSawOutput;
const interruptedByHumanMessage = wasInterruptedByHumanMessage();
if (pairedExecutionContext && !pairedExecutionCompleted) {
if (interruptedByHumanMessage) {
private resolveEffectiveStatus(
missingVisibleVerdict: boolean,
): PairedExecutionStatus {
if (
this.args.completedRole === 'owner' &&
this.pairedExecutionStatus === 'succeeded' &&
!this.pairedSawOutput
) {
return 'failed';
}
if (missingVisibleVerdict && this.pairedExecutionStatus === 'succeeded') {
return 'failed';
}
return this.pairedExecutionStatus;
}
private completeStoredExecutionIfNeeded(state: FinalizeState): void {
const { completedRole, log, pairedExecutionContext, runId } = this.args;
if (!pairedExecutionContext || this.pairedExecutionCompleted) {
return;
}
if (state.interruptedByHumanMessage) {
releaseInterruptedPairedExecution(
pairedExecutionContext.task.id,
runId,
log,
);
} else {
if (effectiveStatus === 'succeeded') {
this.pairedExecutionCompleted = true;
return;
}
this.persistSuccessfulOutputBeforeCompletion(state.effectiveStatus);
completeStoredExecution(
pairedExecutionContext.task.id,
completedRole,
state.effectiveStatus,
runId,
this.pairedExecutionSummary,
);
this.pairedExecutionCompleted = true;
}
private persistSuccessfulOutputBeforeCompletion(
effectiveStatus: PairedExecutionStatus,
): void {
if (effectiveStatus !== 'succeeded') {
return;
}
try {
persistPairedTurnOutputIfNeeded();
this.persistPairedTurnOutputIfNeeded();
} catch (err) {
log.warn(
{ pairedTaskId: pairedExecutionContext.task.id, err },
this.args.log.warn(
{ pairedTaskId: this.args.pairedExecutionContext?.task.id, err },
'Failed to store paired turn output',
);
}
}
completeStoredExecution(
pairedExecutionContext.task.id,
completedRole,
effectiveStatus,
runId,
pairedExecutionSummary,
);
}
pairedExecutionCompleted = true;
}
finalizePairedTurnState(
effectiveStatus,
effectiveStatus === 'failed' ? pairedExecutionSummary : null,
);
if (!pairedExecutionContext) {
return;
}
if (interruptedByHumanMessage) {
private async notifyCompletionAndQueueFollowUp(
state: FinalizeState,
): Promise<void> {
const { chatJid, onOutput, pairedExecutionContext } = this.args;
if (!pairedExecutionContext || state.interruptedByHumanMessage) {
return;
}
const finishedTask = getPairedTaskById(pairedExecutionContext.task.id);
@@ -488,45 +557,79 @@ export function createPairedExecutionLifecycle(args: {
chatJid,
onOutput,
});
this.queueFollowUpIfNeeded(state, finishedTask);
}
const queueAction =
directTerminalOutput &&
(completedRole === 'reviewer' || completedRole === 'arbiter')
? 'none'
: resolvePairedFollowUpQueueAction({
completedRole,
executionStatus: effectiveStatus,
sawOutput: sawOutputForFollowUp,
taskStatus: finishedTask?.status ?? null,
outputSummary: pairedExecutionSummary,
});
private queueFollowUpIfNeeded(
state: FinalizeState,
finishedTask: PairedTaskRecord | null | undefined,
): void {
const queueAction = this.resolveQueueAction(state, finishedTask);
if (queueAction !== 'pending' || !finishedTask) {
return;
}
const followUpResult = this.enqueueFollowUp(state, finishedTask);
this.logFollowUpResult(followUpResult, state, finishedTask);
}
const followUpResult = enqueuePairedFollowUpAfterEvent({
private resolveQueueAction(
state: FinalizeState,
finishedTask: PairedTaskRecord | null | undefined,
): ReturnType<typeof resolvePairedFollowUpQueueAction> | 'none' {
const { completedRole } = this.args;
if (
state.directTerminalOutput &&
(completedRole === 'reviewer' || completedRole === 'arbiter')
) {
return 'none';
}
return resolvePairedFollowUpQueueAction({
completedRole,
executionStatus: state.effectiveStatus,
sawOutput: state.sawOutputForFollowUp,
taskStatus: finishedTask?.status ?? null,
outputSummary: this.pairedExecutionSummary,
});
}
private enqueueFollowUp(
state: FinalizeState,
finishedTask: PairedTaskRecord,
): ReturnType<typeof enqueuePairedFollowUpAfterEvent> {
const { chatJid, completedRole, enqueueMessageCheck, runId } = this.args;
return enqueuePairedFollowUpAfterEvent({
chatJid,
runId,
task: finishedTask,
source: 'executor-recovery',
completedRole,
executionStatus: effectiveStatus,
sawOutput: sawOutputForFollowUp,
fallbackLastTurnOutputRole: sawOutputForFollowUp ? completedRole : null,
executionStatus: state.effectiveStatus,
sawOutput: state.sawOutputForFollowUp,
fallbackLastTurnOutputRole: state.sawOutputForFollowUp
? completedRole
: null,
fallbackLastTurnOutputVerdict:
sawOutputForFollowUp && pairedExecutionSummary
? parseVisibleVerdict(pairedExecutionSummary)
state.sawOutputForFollowUp && this.pairedExecutionSummary
? parseVisibleVerdict(this.pairedExecutionSummary)
: null,
enqueueMessageCheck,
});
}
private logFollowUpResult(
followUpResult: ReturnType<typeof enqueuePairedFollowUpAfterEvent>,
state: FinalizeState,
finishedTask: PairedTaskRecord,
): void {
const { completedRole, log, pairedExecutionContext } = this.args;
if (followUpResult.kind !== 'paired-follow-up') {
return;
}
log.info(
{
taskId: pairedExecutionContext.task.id,
taskId: pairedExecutionContext?.task.id,
role: completedRole,
pairedExecutionStatus: effectiveStatus,
pairedExecutionStatus: state.effectiveStatus,
taskStatus: finishedTask.status,
intentKind: followUpResult.intentKind,
scheduled: followUpResult.scheduled,
@@ -535,6 +638,11 @@ export function createPairedExecutionLifecycle(args: {
? 'Queued paired follow-up after failed reviewer/arbiter execution left a pending task state'
: 'Skipped duplicate paired follow-up after failed reviewer/arbiter execution while task state was unchanged',
);
},
};
}
}
export function createPairedExecutionLifecycle(
args: CreatePairedExecutionLifecycleArgs,
): PairedExecutionLifecycle {
return new PairedExecutionLifecycleController(args);
}