fix(ralph-loop): detect completion promise from session messages API (#413)
* fix(ralph-loop): detect completion promise from session messages API The completion promise (e.g., <promise>DONE</promise>) was not being detected because assistant text messages were never recorded to the transcript file. Only user messages, tool uses, and tool results were recorded. This fix adds a new detection method that fetches session messages via the OpenCode API and checks assistant text messages for the completion promise. The transcript file check is kept as a fallback. Fixes #412 * refactor(ralph-loop): address review feedback - Simplify response parsing to use response.data consistently (greptile) - Add session ID tracking to messages mock for better test coverage (cubic) - Add assertion to verify correct session ID is passed to API --------- Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
This commit is contained in:
@@ -18,6 +18,17 @@ interface SessionState {
|
||||
isRecovering?: boolean
|
||||
}
|
||||
|
||||
interface OpenCodeSessionMessage {
|
||||
info?: {
|
||||
role?: string
|
||||
}
|
||||
parts?: Array<{
|
||||
type: string
|
||||
text?: string
|
||||
[key: string]: unknown
|
||||
}>
|
||||
}
|
||||
|
||||
const CONTINUATION_PROMPT = `[RALPH LOOP - ITERATION {{ITERATION}}/{{MAX}}]
|
||||
|
||||
Your previous attempt did not output the completion promise. Continue working on the task.
|
||||
@@ -81,6 +92,41 @@ export function createRalphLoopHook(
|
||||
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
||||
}
|
||||
|
||||
async function detectCompletionInSessionMessages(
|
||||
sessionID: string,
|
||||
promise: string
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const response = await ctx.client.session.messages({
|
||||
path: { id: sessionID },
|
||||
query: { directory: ctx.directory },
|
||||
})
|
||||
|
||||
const messages = (response as { data?: unknown[] }).data ?? []
|
||||
|
||||
if (!Array.isArray(messages)) return false
|
||||
|
||||
const pattern = new RegExp(`<promise>\\s*${escapeRegex(promise)}\\s*</promise>`, "is")
|
||||
|
||||
for (const msg of messages as OpenCodeSessionMessage[]) {
|
||||
if (msg.info?.role !== "assistant") continue
|
||||
|
||||
for (const part of msg.parts || []) {
|
||||
if (part.type === "text" && part.text) {
|
||||
if (pattern.test(part.text)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
} catch (err) {
|
||||
log(`[${HOOK_NAME}] Failed to fetch session messages`, { sessionID, error: String(err) })
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const startLoop = (
|
||||
sessionID: string,
|
||||
prompt: string,
|
||||
@@ -151,14 +197,20 @@ export function createRalphLoopHook(
|
||||
return
|
||||
}
|
||||
|
||||
// Generate transcript path from sessionID - OpenCode doesn't pass it in event properties
|
||||
const transcriptPath = getTranscriptPath(sessionID)
|
||||
const completionDetectedViaApi = await detectCompletionInSessionMessages(
|
||||
sessionID,
|
||||
state.completion_promise
|
||||
)
|
||||
|
||||
if (detectCompletionPromise(transcriptPath, state.completion_promise)) {
|
||||
const transcriptPath = getTranscriptPath(sessionID)
|
||||
const completionDetectedViaTranscript = detectCompletionPromise(transcriptPath, state.completion_promise)
|
||||
|
||||
if (completionDetectedViaApi || completionDetectedViaTranscript) {
|
||||
log(`[${HOOK_NAME}] Completion detected!`, {
|
||||
sessionID,
|
||||
iteration: state.iteration,
|
||||
promise: state.completion_promise,
|
||||
detectedVia: completionDetectedViaApi ? "session_messages_api" : "transcript_file",
|
||||
})
|
||||
clearState(ctx.directory, stateDir)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user