fix(ralph-loop): revalidate ownership and harden commit/session-creation failures
Three correctness fixes on top of the dispatch-before-commit invariant: - ralph-loop-event-handler.ts: after idleSettleMs, also require state ownership and non-verification-pending to match the event source before dispatching. Applied to both the session.idle and session.error retry paths. - verification-failure-handler.ts: if incrementIteration fails after a successful continuation injection, clear the loop state and emit a warning toast instead of returning success. - session-reset-strategy.ts: catch thrown session.create errors so they route through the typed session_creation_rejected path instead of surfacing as an unhandled rejection.
This commit is contained in:
@@ -278,6 +278,17 @@ export function createRalphLoopEventHandler(
|
|||||||
if (!stateAfterSettle || !stateAfterSettle.active) {
|
if (!stateAfterSettle || !stateAfterSettle.active) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (stateAfterSettle.session_id !== undefined && stateAfterSettle.session_id !== sessionID) {
|
||||||
|
log(`[${HOOK_NAME}] Skipped: state rebound during settle window`, {
|
||||||
|
sessionID,
|
||||||
|
currentOwner: stateAfterSettle.session_id,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (stateAfterSettle.verification_pending) {
|
||||||
|
log(`[${HOOK_NAME}] Skipped: state entered verification_pending during settle window`, { sessionID })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const nextIteration = stateAfterSettle.iteration + 1
|
const nextIteration = stateAfterSettle.iteration + 1
|
||||||
const previewState: RalphLoopState = { ...stateAfterSettle, iteration: nextIteration }
|
const previewState: RalphLoopState = { ...stateAfterSettle, iteration: nextIteration }
|
||||||
@@ -401,6 +412,17 @@ export function createRalphLoopEventHandler(
|
|||||||
if (!stateAfterSettle || !stateAfterSettle.active) {
|
if (!stateAfterSettle || !stateAfterSettle.active) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (stateAfterSettle.session_id !== undefined && stateAfterSettle.session_id !== sessionID) {
|
||||||
|
log(`[${HOOK_NAME}] Skipped: state rebound during settle window`, {
|
||||||
|
sessionID,
|
||||||
|
currentOwner: stateAfterSettle.session_id,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (stateAfterSettle.verification_pending) {
|
||||||
|
log(`[${HOOK_NAME}] Skipped: state entered verification_pending during settle window`, { sessionID })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const nextIteration = stateAfterSettle.iteration + 1
|
const nextIteration = stateAfterSettle.iteration + 1
|
||||||
const previewState: RalphLoopState = { ...stateAfterSettle, iteration: nextIteration }
|
const previewState: RalphLoopState = { ...stateAfterSettle, iteration: nextIteration }
|
||||||
|
|||||||
@@ -7,23 +7,31 @@ export async function createIterationSession(
|
|||||||
parentSessionID: string,
|
parentSessionID: string,
|
||||||
directory: string,
|
directory: string,
|
||||||
): Promise<string | null> {
|
): Promise<string | null> {
|
||||||
const createResult = await ctx.client.session.create({
|
try {
|
||||||
body: {
|
const createResult = await ctx.client.session.create({
|
||||||
parentID: parentSessionID,
|
body: {
|
||||||
title: "Ralph Loop Iteration",
|
parentID: parentSessionID,
|
||||||
},
|
title: "Ralph Loop Iteration",
|
||||||
query: { directory },
|
},
|
||||||
})
|
query: { directory },
|
||||||
|
})
|
||||||
|
|
||||||
if (createResult.error || !createResult.data?.id) {
|
if (createResult.error || !createResult.data?.id) {
|
||||||
log("[ralph-loop] Failed to create iteration session", {
|
log("[ralph-loop] Failed to create iteration session", {
|
||||||
|
parentSessionID,
|
||||||
|
error: String(createResult.error ?? "No session ID returned"),
|
||||||
|
})
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return createResult.data.id
|
||||||
|
} catch (error: unknown) {
|
||||||
|
log("[ralph-loop] session.create threw during iteration session creation", {
|
||||||
parentSessionID,
|
parentSessionID,
|
||||||
error: String(createResult.error ?? "No session ID returned"),
|
error: String(error),
|
||||||
})
|
})
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return createResult.data.id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function selectSessionInTui(
|
export async function selectSessionInTui(
|
||||||
|
|||||||
@@ -122,6 +122,14 @@ export async function handleFailedVerification(
|
|||||||
const committed = loopState.incrementIteration()
|
const committed = loopState.incrementIteration()
|
||||||
if (!committed) {
|
if (!committed) {
|
||||||
log(`[${HOOK_NAME}] Failed to commit iteration after verification restart`, { parentSessionID })
|
log(`[${HOOK_NAME}] Failed to commit iteration after verification restart`, { parentSessionID })
|
||||||
|
loopState.clear()
|
||||||
|
showToastBestEffort(ctx, {
|
||||||
|
title: "Ralph Loop Failed",
|
||||||
|
message: "Verification continuation dispatched but iteration commit failed",
|
||||||
|
variant: "warning",
|
||||||
|
duration: 5000,
|
||||||
|
})
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
await ctx.client.tui?.showToast?.({
|
await ctx.client.tui?.showToast?.({
|
||||||
|
|||||||
Reference in New Issue
Block a user