d490b4dc20
Replace fragile regex text matching with structured detection for Oracle verification evidence. - Add oracle-verification-detector.ts with parseOracleVerificationEvidence() - Use structured parsing instead of multiple regex patterns - Add comprehensive test coverage for edge cases - Update completion-promise-detector.ts to use isOracleVerified() - Update pending-verification-handler.ts to use structured extraction Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
153 lines
3.9 KiB
TypeScript
153 lines
3.9 KiB
TypeScript
import type { PluginInput } from "@opencode-ai/plugin"
|
|
import { log } from "../../shared/logger"
|
|
import { HOOK_NAME } from "./constants"
|
|
import { extractOracleSessionID, isOracleVerified } from "./oracle-verification-detector"
|
|
import type { RalphLoopState } from "./types"
|
|
import { handleFailedVerification } from "./verification-failure-handler"
|
|
import { withTimeout } from "./with-timeout"
|
|
|
|
type OpenCodeSessionMessage = {
|
|
info?: { role?: string }
|
|
parts?: Array<{ type?: string; text?: string }>
|
|
}
|
|
|
|
function collectAssistantText(message: OpenCodeSessionMessage): string {
|
|
if (!Array.isArray(message.parts)) {
|
|
return ""
|
|
}
|
|
|
|
let text = ""
|
|
for (const part of message.parts) {
|
|
if (part.type !== "text" && part.type !== "tool_result") {
|
|
continue
|
|
}
|
|
text += `${text ? "\n" : ""}${part.text ?? ""}`
|
|
}
|
|
|
|
return text
|
|
}
|
|
|
|
async function detectOracleVerificationFromParentSession(
|
|
ctx: PluginInput,
|
|
parentSessionID: string,
|
|
directory: string,
|
|
apiTimeoutMs: number,
|
|
): Promise<string | undefined> {
|
|
try {
|
|
const response = await withTimeout(
|
|
ctx.client.session.messages({
|
|
path: { id: parentSessionID },
|
|
query: { directory },
|
|
}),
|
|
apiTimeoutMs,
|
|
)
|
|
|
|
const messagesResponse: unknown = response
|
|
const responseData =
|
|
typeof messagesResponse === "object" && messagesResponse !== null && "data" in messagesResponse
|
|
? (messagesResponse as { data?: unknown }).data
|
|
: undefined
|
|
const messageArray: unknown[] = Array.isArray(messagesResponse)
|
|
? messagesResponse
|
|
: Array.isArray(responseData)
|
|
? responseData
|
|
: []
|
|
|
|
for (let index = messageArray.length - 1; index >= 0; index -= 1) {
|
|
const message = messageArray[index] as OpenCodeSessionMessage
|
|
if (message.info?.role !== "assistant") {
|
|
continue
|
|
}
|
|
|
|
const assistantText = collectAssistantText(message)
|
|
if (!isOracleVerified(assistantText)) {
|
|
continue
|
|
}
|
|
|
|
const detectedOracleSessionID = extractOracleSessionID(assistantText)
|
|
if (detectedOracleSessionID) {
|
|
return detectedOracleSessionID
|
|
}
|
|
}
|
|
|
|
return undefined
|
|
} catch (error) {
|
|
log(`[${HOOK_NAME}] Failed to scan parent session for oracle verification evidence`, {
|
|
parentSessionID,
|
|
error: String(error),
|
|
})
|
|
return undefined
|
|
}
|
|
}
|
|
|
|
type LoopStateController = {
|
|
restartAfterFailedVerification: (sessionID: string, messageCountAtStart?: number) => RalphLoopState | null
|
|
setVerificationSessionID: (sessionID: string, verificationSessionID: string) => RalphLoopState | null
|
|
}
|
|
|
|
export async function handlePendingVerification(
|
|
ctx: PluginInput,
|
|
input: {
|
|
sessionID: string
|
|
state: RalphLoopState
|
|
verificationSessionID?: string
|
|
matchesParentSession: boolean
|
|
matchesVerificationSession: boolean
|
|
loopState: LoopStateController
|
|
directory: string
|
|
apiTimeoutMs: number
|
|
},
|
|
): Promise<void> {
|
|
const {
|
|
sessionID,
|
|
state,
|
|
verificationSessionID,
|
|
matchesParentSession,
|
|
matchesVerificationSession,
|
|
loopState,
|
|
directory,
|
|
apiTimeoutMs,
|
|
} = input
|
|
|
|
if (matchesParentSession || (verificationSessionID && matchesVerificationSession)) {
|
|
if (!verificationSessionID && state.session_id) {
|
|
const recoveredVerificationSessionID = await detectOracleVerificationFromParentSession(
|
|
ctx,
|
|
state.session_id,
|
|
directory,
|
|
apiTimeoutMs,
|
|
)
|
|
|
|
if (recoveredVerificationSessionID) {
|
|
const updatedState = loopState.setVerificationSessionID(
|
|
state.session_id,
|
|
recoveredVerificationSessionID,
|
|
)
|
|
if (updatedState) {
|
|
log(`[${HOOK_NAME}] Recovered missing verification session from parent evidence`, {
|
|
parentSessionID: state.session_id,
|
|
recoveredVerificationSessionID,
|
|
})
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
const restarted = await handleFailedVerification(ctx, {
|
|
state,
|
|
loopState,
|
|
directory,
|
|
apiTimeoutMs,
|
|
})
|
|
if (restarted) {
|
|
return
|
|
}
|
|
}
|
|
|
|
log(`[${HOOK_NAME}] Waiting for oracle verification`, {
|
|
sessionID,
|
|
verificationSessionID,
|
|
iteration: state.iteration,
|
|
})
|
|
}
|