diff --git a/src/hooks/ralph-loop/completion-promise-detector.test.ts b/src/hooks/ralph-loop/completion-promise-detector.test.ts index 6e2dae816..b63640457 100644 --- a/src/hooks/ralph-loop/completion-promise-detector.test.ts +++ b/src/hooks/ralph-loop/completion-promise-detector.test.ts @@ -108,4 +108,80 @@ describe("detectCompletionInSessionMessages", () => { expect(detected).toBe(true) }) }) + + describe("#given promise appears in tool_result part (not text part)", () => { + test("#when Oracle returns VERIFIED via task() tool_result #then should detect completion", async () => { + const messages: SessionMessage[] = [ + { + info: { role: "assistant" }, + parts: [ + { type: "text", text: "Consulting Oracle for verification." }, + { type: "tool_use", text: '{"subagent_type":"oracle"}' }, + ], + }, + { + info: { role: "assistant" }, + parts: [ + { type: "tool_result", text: 'Task completed.\n\nAgent: oracle\n\nVERIFIED\n\n\nsession_id: ses_abc123\n' }, + { type: "text", text: "Oracle verified the task." }, + ], + }, + ] + const ctx = createPluginInput(messages) + + const detected = await detectCompletionInSessionMessages(ctx, { + sessionID: "session-123", + promise: "VERIFIED", + apiTimeoutMs: 1000, + directory: "/tmp", + sinceMessageIndex: 0, + }) + + expect(detected).toBe(true) + }) + + test("#when DONE appears only in tool_result part #then should detect completion", async () => { + const messages: SessionMessage[] = [ + { + info: { role: "assistant" }, + parts: [ + { type: "tool_result", text: 'Background task output DONE' }, + { type: "text", text: "Task completed successfully." }, + ], + }, + ] + const ctx = createPluginInput(messages) + + const detected = await detectCompletionInSessionMessages(ctx, { + sessionID: "session-123", + promise: "DONE", + apiTimeoutMs: 1000, + directory: "/tmp", + }) + + expect(detected).toBe(true) + }) + + test("#when promise appears in tool_use part (not tool_result) #then should NOT detect completion", async () => { + const messages: SessionMessage[] = [ + { + info: { role: "assistant" }, + parts: [ + { type: "tool_use", text: 'prompt containing VERIFIED as instruction' }, + { type: "text", text: "Calling Oracle." }, + ], + }, + ] + const ctx = createPluginInput(messages) + + const detected = await detectCompletionInSessionMessages(ctx, { + sessionID: "session-123", + promise: "VERIFIED", + apiTimeoutMs: 1000, + directory: "/tmp", + }) + + expect(detected).toBe(false) + }) + }) }) diff --git a/src/hooks/ralph-loop/completion-promise-detector.ts b/src/hooks/ralph-loop/completion-promise-detector.ts index 81f061bad..40e9e1af7 100644 --- a/src/hooks/ralph-loop/completion-promise-detector.ts +++ b/src/hooks/ralph-loop/completion-promise-detector.ts @@ -93,7 +93,7 @@ export async function detectCompletionInSessionMessages( let responseText = "" for (const part of assistant.parts) { - if (part.type !== "text") continue + if (part.type !== "text" && part.type !== "tool_result") continue responseText += `${responseText ? "\n" : ""}${part.text ?? ""}` } diff --git a/src/hooks/ralph-loop/pending-verification-handler.ts b/src/hooks/ralph-loop/pending-verification-handler.ts index 31dc7295c..00878ca91 100644 --- a/src/hooks/ralph-loop/pending-verification-handler.ts +++ b/src/hooks/ralph-loop/pending-verification-handler.ts @@ -25,7 +25,7 @@ function collectAssistantText(message: OpenCodeSessionMessage): string { let text = "" for (const part of message.parts) { - if (part.type !== "text") { + if (part.type !== "text" && part.type !== "tool_result") { continue } text += `${text ? "\n" : ""}${part.text ?? ""}`