import { onRecoverySessionCompact, onRecoverySuccessfulTurn, onRecoveryUserInput, planRecoveryForAssistantError, planRecoveryForSilentContextOverflow, setRecoveryPendingAttention, type GoalRecoveryMachineState, type RecoveryAction, } from "./recovery-machine.js"; import type { AssistantErrorMessage } from "./recovery.js"; import type { ThreadGoal } from "./types.js"; interface RecoveryRuntimeDeps { getGoal: () => ThreadGoal | null; getRecoveryState: () => GoalRecoveryMachineState; clearContinuationState: () => void; pauseGoalForRecovery: (ctx: TContext, recoveryReason: string) => void; refreshUi: (ctx: TContext) => void; maybeContinue: (ctx: TContext) => void; } export function createGoalRecoveryRuntime(deps: RecoveryRuntimeDeps) { const pauseForRecoveryAttention = (ctx: TContext, reason: string): void => { const goal = deps.getGoal(); if (!goal || goal.status !== "active") { return; } deps.pauseGoalForRecovery(ctx, reason); }; const applyRecoveryAction = (action: RecoveryAction, ctx: TContext): void => { switch (action.type) { case "noop": return; case "pending": { const goal = deps.getGoal(); if (!goal || goal.status !== "active") { return; } deps.clearContinuationState(); setRecoveryPendingAttention(deps.getRecoveryState(), action.reason); deps.refreshUi(ctx); return; } case "pause": pauseForRecoveryAttention(ctx, action.reason); return; } }; const handlePersistentAssistantError = (message: AssistantErrorMessage, ctx: TContext): void => { const goal = deps.getGoal(); if (!goal || goal.status !== "active") { return; } applyRecoveryAction(planRecoveryForAssistantError(deps.getRecoveryState(), message), ctx); }; const handleSilentContextOverflow = (ctx: TContext): void => { const goal = deps.getGoal(); if (!goal || goal.status !== "active") { return; } applyRecoveryAction(planRecoveryForSilentContextOverflow(deps.getRecoveryState()), ctx); }; const finishSuccessfulAssistantTurn = ( message: AssistantErrorMessage, ctx: TContext, options?: { continueGoal?: boolean }, ): void => { if (onRecoverySuccessfulTurn(deps.getRecoveryState(), message)) { deps.refreshUi(ctx); if (options?.continueGoal !== false) { deps.maybeContinue(ctx); } } }; return { onUserInput: () => { onRecoveryUserInput(deps.getRecoveryState()); }, onSessionCompact: () => { onRecoverySessionCompact(deps.getRecoveryState()); }, handlePersistentAssistantError, handleSilentContextOverflow, finishSuccessfulAssistantTurn, }; }