Files
oh-my-opencode/src/hooks/ralph-loop/verification-failure-handler.ts
T
YeonGyu-Kim 3caae14192 fix(ralph-loop): abort stale Oracle sessions before ulw verification restart
When Oracle verification fails in ulw-loop mode, the previous Oracle
session was never aborted before restarting. Each retry created a new
descendant session, causing unbounded session accumulation and 500
errors from server overload.

Now abort the old verification session before:
- restarting the loop after failed verification
- re-entering verification phase on subsequent DONE detection
2026-03-18 12:49:27 +09:00

104 lines
2.5 KiB
TypeScript

import type { PluginInput } from "@opencode-ai/plugin"
import { log } from "../../shared/logger"
import { buildVerificationFailurePrompt } from "./continuation-prompt-builder"
import { HOOK_NAME } from "./constants"
import { injectContinuationPrompt } from "./continuation-prompt-injector"
import type { RalphLoopState } from "./types"
type LoopStateController = {
restartAfterFailedVerification: (
sessionID: string,
messageCountAtStart?: number,
) => RalphLoopState | null
}
function getMessageCountFromResponse(messagesResponse: unknown): number {
if (Array.isArray(messagesResponse)) {
return messagesResponse.length
}
if (
typeof messagesResponse === "object"
&& messagesResponse !== null
&& "data" in messagesResponse
) {
const data = (messagesResponse as { data?: unknown }).data
return Array.isArray(data) ? data.length : 0
}
return 0
}
async function getSessionMessageCount(
ctx: PluginInput,
sessionID: string,
directory: string,
): Promise<number> {
const messagesResponse = await ctx.client.session.messages({
path: { id: sessionID },
query: { directory },
})
return getMessageCountFromResponse(messagesResponse)
}
export async function handleFailedVerification(
ctx: PluginInput,
input: {
state: RalphLoopState
directory: string
apiTimeoutMs: number
loopState: LoopStateController
},
): Promise<boolean> {
const { state, directory, apiTimeoutMs, loopState } = input
const parentSessionID = state.session_id
if (!parentSessionID) {
return false
}
let messageCountAtStart: number
try {
messageCountAtStart = await getSessionMessageCount(ctx, parentSessionID, directory)
} catch (error) {
log(`[${HOOK_NAME}] Failed to read parent session before verification retry`, {
parentSessionID,
error: String(error),
})
return false
}
if (state.verification_session_id) {
ctx.client.session.abort({ path: { id: state.verification_session_id } }).catch(() => {})
}
const resumedState = loopState.restartAfterFailedVerification(
parentSessionID,
messageCountAtStart,
)
if (!resumedState) {
log(`[${HOOK_NAME}] Failed to restart loop after verification failure`, {
parentSessionID,
})
return false
}
await injectContinuationPrompt(ctx, {
sessionID: parentSessionID,
prompt: buildVerificationFailurePrompt(resumedState),
directory,
apiTimeoutMs,
})
await ctx.client.tui?.showToast?.({
body: {
title: "ULTRAWORK LOOP",
message: "Oracle verification failed. Continuing ULTRAWORK loop.",
variant: "warning",
duration: 5000,
},
}).catch(() => {})
return true
}