diff --git a/src/hooks/ralph-loop/continuation-prompt-injector.ts b/src/hooks/ralph-loop/continuation-prompt-injector.ts index dec80c5c6..8c75dc99b 100644 --- a/src/hooks/ralph-loop/continuation-prompt-injector.ts +++ b/src/hooks/ralph-loop/continuation-prompt-injector.ts @@ -10,6 +10,7 @@ import { resolveInheritedPromptTools, } from "../../shared" import { normalizeAgentForPromptKey } from "../../shared/agent-display-names" +import { promptAsyncAfterSessionIdle } from "../shared/prompt-async-gate" type MessageInfo = { agent?: string @@ -66,6 +67,7 @@ export async function injectContinuationPrompt( directory: string apiTimeoutMs: number inheritFromSessionID?: string + idleSettleMs?: number }, ): Promise { let agent: string | undefined @@ -119,17 +121,33 @@ export async function injectContinuationPrompt( let response: unknown try { - response = await ctx.client.session.promptAsync({ - path: { id: options.sessionID }, - body: { - ...(cleanAgent !== undefined ? { agent: cleanAgent } : {}), - ...(launchModel ? { model: launchModel } : {}), - ...(launchVariant ? { variant: launchVariant } : {}), - ...(inheritedTools ? { tools: inheritedTools } : {}), - parts: [createInternalAgentContinuationTextPart(options.prompt)], + const promptResult = await promptAsyncAfterSessionIdle({ + client: ctx.client, + sessionID: options.sessionID, + source: "ralph-loop", + settleMs: options.idleSettleMs, + input: { + path: { id: options.sessionID }, + body: { + ...(cleanAgent !== undefined ? { agent: cleanAgent } : {}), + ...(launchModel ? { model: launchModel } : {}), + ...(launchVariant ? { variant: launchVariant } : {}), + ...(inheritedTools ? { tools: inheritedTools } : {}), + parts: [createInternalAgentContinuationTextPart(options.prompt)], + }, + query: { directory: options.directory }, }, - query: { directory: options.directory }, }) + if (promptResult.status === "failed") { + throw promptResult.error + } + if (promptResult.status !== "dispatched") { + return { + status: "rejected", + error: createPromptAsyncError(`promptAsync skipped: ${promptResult.status}`, promptResult), + } + } + response = promptResult.response } catch (error) { const promptError = error instanceof Error ? error diff --git a/src/hooks/ralph-loop/iteration-continuation.ts b/src/hooks/ralph-loop/iteration-continuation.ts index fd3d2741d..aeca27a16 100644 --- a/src/hooks/ralph-loop/iteration-continuation.ts +++ b/src/hooks/ralph-loop/iteration-continuation.ts @@ -9,6 +9,7 @@ import { createIterationSession, selectSessionInTui } from "./session-reset-stra type ContinuationOptions = { directory: string apiTimeoutMs: number + idleSettleMs: number previousSessionID: string loopState: { setSessionID: (sessionID: string) => RalphLoopState | null @@ -45,6 +46,7 @@ export async function continueIteration( prompt: continuationPrompt, directory: options.directory, apiTimeoutMs: options.apiTimeoutMs, + idleSettleMs: options.idleSettleMs, }) if (promptResult.status === "rejected") { return { status: "dispatch_rejected", error: promptResult.error } @@ -73,6 +75,7 @@ export async function continueIteration( prompt: continuationPrompt, directory: options.directory, apiTimeoutMs: options.apiTimeoutMs, + idleSettleMs: options.idleSettleMs, }) if (promptResult.status === "rejected") { return { status: "dispatch_rejected", error: promptResult.error } diff --git a/src/hooks/ralph-loop/ralph-loop-event-handler.ts b/src/hooks/ralph-loop/ralph-loop-event-handler.ts index 0e832a9ef..02c4c3d83 100644 --- a/src/hooks/ralph-loop/ralph-loop-event-handler.ts +++ b/src/hooks/ralph-loop/ralph-loop-event-handler.ts @@ -358,6 +358,7 @@ export function createRalphLoopEventHandler( previousSessionID: sessionID, directory: options.directory, apiTimeoutMs: options.apiTimeoutMs, + idleSettleMs: options.idleSettleMs, loopState: options.loopState, }) @@ -523,6 +524,7 @@ export function createRalphLoopEventHandler( previousSessionID: sessionID, directory: options.directory, apiTimeoutMs: options.apiTimeoutMs, + idleSettleMs: options.idleSettleMs, loopState: options.loopState, })