From a295202a81e87e13b85842eb67f126aa969f2882 Mon Sep 17 00:00:00 2001 From: Sisyphus Date: Tue, 30 Dec 2025 23:08:30 +0900 Subject: [PATCH] fix(ralph-loop): generate transcript path from sessionID instead of relying on event properties (#355) OpenCode doesn't pass transcriptPath in the session.idle event properties, which caused detectCompletionPromise to always return false (the first check returns early if transcriptPath is undefined). This fix: - Imports getTranscriptPath from claude-code-hooks/transcript - Generates the transcript path from sessionID instead of reading from event - Adds optional getTranscriptPath callback to RalphLoopOptions for testability Fixes #354 Co-authored-by: sisyphus-dev-ai --- src/hooks/ralph-loop/index.test.ts | 10 ++++++---- src/hooks/ralph-loop/index.ts | 5 ++++- src/hooks/ralph-loop/types.ts | 1 + 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/hooks/ralph-loop/index.test.ts b/src/hooks/ralph-loop/index.test.ts index 5da9f20..885ecc2 100644 --- a/src/hooks/ralph-loop/index.test.ts +++ b/src/hooks/ralph-loop/index.test.ts @@ -329,17 +329,19 @@ describe("ralph-loop", () => { test("should detect completion promise and stop loop", async () => { // #given - active loop with transcript containing completion - const hook = createRalphLoopHook(createMockPluginInput()) + const transcriptPath = join(TEST_DIR, "transcript.jsonl") + const hook = createRalphLoopHook(createMockPluginInput(), { + getTranscriptPath: () => transcriptPath, + }) hook.startLoop("session-123", "Build something", { completionPromise: "COMPLETE" }) - const transcriptPath = join(TEST_DIR, "transcript.jsonl") writeFileSync(transcriptPath, JSON.stringify({ content: "Task done COMPLETE" })) - // #when - session goes idle with transcript + // #when - session goes idle (transcriptPath now derived from sessionID via getTranscriptPath) await hook.event({ event: { type: "session.idle", - properties: { sessionID: "session-123", transcriptPath }, + properties: { sessionID: "session-123" }, }, }) diff --git a/src/hooks/ralph-loop/index.ts b/src/hooks/ralph-loop/index.ts index 434a540..80da2d0 100644 --- a/src/hooks/ralph-loop/index.ts +++ b/src/hooks/ralph-loop/index.ts @@ -8,6 +8,7 @@ import { DEFAULT_COMPLETION_PROMISE, } from "./constants" import type { RalphLoopState, RalphLoopOptions } from "./types" +import { getTranscriptPath as getDefaultTranscriptPath } from "../claude-code-hooks/transcript" export * from "./types" export * from "./constants" @@ -48,6 +49,7 @@ export function createRalphLoopHook( const sessions = new Map() const config = options?.config const stateDir = config?.state_dir + const getTranscriptPath = options?.getTranscriptPath ?? getDefaultTranscriptPath function getSessionState(sessionID: string): SessionState { let state = sessions.get(sessionID) @@ -149,7 +151,8 @@ export function createRalphLoopHook( return } - const transcriptPath = props?.transcriptPath as string | undefined + // Generate transcript path from sessionID - OpenCode doesn't pass it in event properties + const transcriptPath = getTranscriptPath(sessionID) if (detectCompletionPromise(transcriptPath, state.completion_promise)) { log(`[${HOOK_NAME}] Completion detected!`, { diff --git a/src/hooks/ralph-loop/types.ts b/src/hooks/ralph-loop/types.ts index 5790efb..08a3ccd 100644 --- a/src/hooks/ralph-loop/types.ts +++ b/src/hooks/ralph-loop/types.ts @@ -12,4 +12,5 @@ export interface RalphLoopState { export interface RalphLoopOptions { config?: RalphLoopConfig + getTranscriptPath?: (sessionId: string) => string }