fix(ulw-loop): track Oracle verification sessions explicitly
🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
import { consumeToolMetadata } from "../features/tool-metadata-store"
|
||||
import type { CreatedHooks } from "../create-hooks"
|
||||
import type { PluginContext } from "./types"
|
||||
import { readState, writeState } from "../hooks/ralph-loop/storage"
|
||||
|
||||
const VERIFICATION_ATTEMPT_PATTERN = /<ulw_verification_attempt_id>(.*?)<\/ulw_verification_attempt_id>/i
|
||||
|
||||
export function createToolExecuteAfterHandler(args: {
|
||||
ctx: PluginContext
|
||||
hooks: CreatedHooks
|
||||
}): (
|
||||
input: { tool: string; sessionID: string; callID: string },
|
||||
@@ -9,7 +14,7 @@ export function createToolExecuteAfterHandler(args: {
|
||||
| { title: string; output: string; metadata: Record<string, unknown> }
|
||||
| undefined,
|
||||
) => Promise<void> {
|
||||
const { hooks } = args
|
||||
const { ctx, hooks } = args
|
||||
|
||||
return async (
|
||||
input: { tool: string; sessionID: string; callID: string },
|
||||
@@ -27,6 +32,30 @@ export function createToolExecuteAfterHandler(args: {
|
||||
}
|
||||
}
|
||||
|
||||
if (input.tool === "task") {
|
||||
const sessionId = typeof output.metadata?.sessionId === "string" ? output.metadata.sessionId : undefined
|
||||
const agent = typeof output.metadata?.agent === "string" ? output.metadata.agent : undefined
|
||||
const prompt = typeof output.metadata?.prompt === "string" ? output.metadata.prompt : undefined
|
||||
const verificationAttemptId = prompt?.match(VERIFICATION_ATTEMPT_PATTERN)?.[1]?.trim()
|
||||
const loopState = readState(ctx.directory)
|
||||
|
||||
if (
|
||||
agent === "oracle"
|
||||
&& sessionId
|
||||
&& verificationAttemptId
|
||||
&& loopState?.active === true
|
||||
&& loopState.ultrawork === true
|
||||
&& loopState.verification_pending === true
|
||||
&& loopState.session_id === input.sessionID
|
||||
&& loopState.verification_attempt_id === verificationAttemptId
|
||||
) {
|
||||
writeState(ctx.directory, {
|
||||
...loopState,
|
||||
verification_session_id: sessionId,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
await hooks.claudeCodeHooks?.["tool.execute.after"]?.(input, output)
|
||||
await hooks.toolOutputTruncator?.["tool.execute.after"]?.(input, output)
|
||||
await hooks.preemptiveCompaction?.["tool.execute.after"]?.(input, output)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { PluginContext } from "./types"
|
||||
import { randomUUID } from "node:crypto"
|
||||
|
||||
import { getMainSessionID } from "../features/claude-code-session-state"
|
||||
import { clearBoulderState } from "../features/boulder-state"
|
||||
@@ -6,7 +7,7 @@ import { log } from "../shared"
|
||||
import { resolveSessionAgent } from "./session-agent-resolver"
|
||||
import { parseRalphLoopArguments } from "../hooks/ralph-loop/command-arguments"
|
||||
import { ULTRAWORK_VERIFICATION_PROMISE } from "../hooks/ralph-loop/constants"
|
||||
import { readState } from "../hooks/ralph-loop/storage"
|
||||
import { readState, writeState } from "../hooks/ralph-loop/storage"
|
||||
|
||||
import type { CreatedHooks } from "../create-hooks"
|
||||
|
||||
@@ -77,8 +78,14 @@ export function createToolExecuteBeforeHandler(args: {
|
||||
&& loopState.session_id === input.sessionID
|
||||
|
||||
if (shouldInjectOracleVerification) {
|
||||
const verificationAttemptId = randomUUID()
|
||||
writeState(ctx.directory, {
|
||||
...loopState,
|
||||
verification_attempt_id: verificationAttemptId,
|
||||
verification_session_id: undefined,
|
||||
})
|
||||
argsObject.run_in_background = false
|
||||
argsObject.prompt = `${prompt ? `${prompt}\n\n` : ""}You are verifying the active ULTRAWORK loop result for this session. Review whether the original task is truly complete: ${loopState.prompt}\n\nIf the work is fully complete, end your response with <promise>${ULTRAWORK_VERIFICATION_PROMISE}</promise>. If the work is not complete, explain the blocking issues clearly and DO NOT emit that promise.`
|
||||
argsObject.prompt = `${prompt ? `${prompt}\n\n` : ""}You are verifying the active ULTRAWORK loop result for this session. Review whether the original task is truly complete: ${loopState.prompt}\n\nIf the work is fully complete, end your response with <promise>${ULTRAWORK_VERIFICATION_PROMISE}</promise>. If the work is not complete, explain the blocking issues clearly and DO NOT emit that promise.\n\n<ulw_verification_attempt_id>${verificationAttemptId}</ulw_verification_attempt_id>`
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@ import { describe, expect, test } from "bun:test"
|
||||
import { mkdirSync, rmSync } from "node:fs"
|
||||
import { tmpdir } from "node:os"
|
||||
import { join } from "node:path"
|
||||
import { createToolExecuteAfterHandler } from "./tool-execute-after"
|
||||
import { createToolExecuteBeforeHandler } from "./tool-execute-before"
|
||||
import { ULTRAWORK_VERIFICATION_PROMISE } from "../hooks/ralph-loop/constants"
|
||||
import { clearState, writeState } from "../hooks/ralph-loop/storage"
|
||||
import { clearState, readState, writeState } from "../hooks/ralph-loop/storage"
|
||||
|
||||
describe("tool.execute.before ultrawork oracle verification", () => {
|
||||
function createCtx(directory: string) {
|
||||
@@ -47,6 +48,7 @@ describe("tool.execute.before ultrawork oracle verification", () => {
|
||||
|
||||
await handler({ tool: "task", sessionID: "ses-main", callID: "call-1" }, output)
|
||||
|
||||
expect(readState(directory)?.verification_attempt_id).toBeTruthy()
|
||||
expect(output.args.run_in_background).toBe(false)
|
||||
expect(output.args.prompt).toContain("Ship feature")
|
||||
expect(output.args.prompt).toContain(`<promise>${ULTRAWORK_VERIFICATION_PROMISE}</promise>`)
|
||||
@@ -77,4 +79,138 @@ describe("tool.execute.before ultrawork oracle verification", () => {
|
||||
|
||||
rmSync(directory, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
test("#given ulw loop is awaiting verification #when oracle task finishes #then oracle session id is stored", async () => {
|
||||
const directory = join(tmpdir(), `tool-after-ulw-${Date.now()}`)
|
||||
mkdirSync(directory, { recursive: true })
|
||||
writeState(directory, {
|
||||
active: true,
|
||||
iteration: 3,
|
||||
completion_promise: ULTRAWORK_VERIFICATION_PROMISE,
|
||||
initial_completion_promise: "DONE",
|
||||
started_at: new Date().toISOString(),
|
||||
prompt: "Ship feature",
|
||||
session_id: "ses-main",
|
||||
ultrawork: true,
|
||||
verification_pending: true,
|
||||
})
|
||||
|
||||
const beforeHandler = createToolExecuteBeforeHandler({
|
||||
ctx: createCtx(directory) as unknown as Parameters<typeof createToolExecuteBeforeHandler>[0]["ctx"],
|
||||
hooks: {} as Parameters<typeof createToolExecuteBeforeHandler>[0]["hooks"],
|
||||
})
|
||||
const beforeOutput = {
|
||||
args: {
|
||||
subagent_type: "oracle",
|
||||
run_in_background: true,
|
||||
prompt: "Check it",
|
||||
} as Record<string, unknown>,
|
||||
}
|
||||
await beforeHandler({ tool: "task", sessionID: "ses-main", callID: "call-1" }, beforeOutput)
|
||||
|
||||
const handler = createToolExecuteAfterHandler({
|
||||
ctx: createCtx(directory) as unknown as Parameters<typeof createToolExecuteAfterHandler>[0]["ctx"],
|
||||
hooks: {} as Parameters<typeof createToolExecuteAfterHandler>[0]["hooks"],
|
||||
})
|
||||
|
||||
await handler(
|
||||
{ tool: "task", sessionID: "ses-main", callID: "call-1" },
|
||||
{
|
||||
title: "oracle task",
|
||||
output: "done",
|
||||
metadata: {
|
||||
agent: "oracle",
|
||||
prompt: String(beforeOutput.args.prompt),
|
||||
sessionId: "ses-oracle",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
expect(readState(directory)?.verification_session_id).toBe("ses-oracle")
|
||||
|
||||
clearState(directory)
|
||||
rmSync(directory, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
test("#given newer oracle attempt exists #when older oracle task finishes #then old session does not overwrite active verification", async () => {
|
||||
const directory = join(tmpdir(), `tool-race-ulw-${Date.now()}`)
|
||||
mkdirSync(directory, { recursive: true })
|
||||
writeState(directory, {
|
||||
active: true,
|
||||
iteration: 3,
|
||||
completion_promise: ULTRAWORK_VERIFICATION_PROMISE,
|
||||
initial_completion_promise: "DONE",
|
||||
started_at: new Date().toISOString(),
|
||||
prompt: "Ship feature",
|
||||
session_id: "ses-main",
|
||||
ultrawork: true,
|
||||
verification_pending: true,
|
||||
})
|
||||
|
||||
const beforeHandler = createToolExecuteBeforeHandler({
|
||||
ctx: createCtx(directory) as unknown as Parameters<typeof createToolExecuteBeforeHandler>[0]["ctx"],
|
||||
hooks: {} as Parameters<typeof createToolExecuteBeforeHandler>[0]["hooks"],
|
||||
})
|
||||
const afterHandler = createToolExecuteAfterHandler({
|
||||
ctx: createCtx(directory) as unknown as Parameters<typeof createToolExecuteAfterHandler>[0]["ctx"],
|
||||
hooks: {} as Parameters<typeof createToolExecuteAfterHandler>[0]["hooks"],
|
||||
})
|
||||
|
||||
const firstOutput = {
|
||||
args: {
|
||||
subagent_type: "oracle",
|
||||
run_in_background: true,
|
||||
prompt: "Check it",
|
||||
} as Record<string, unknown>,
|
||||
}
|
||||
await beforeHandler({ tool: "task", sessionID: "ses-main", callID: "call-1" }, firstOutput)
|
||||
const firstAttemptId = readState(directory)?.verification_attempt_id
|
||||
|
||||
const secondOutput = {
|
||||
args: {
|
||||
subagent_type: "oracle",
|
||||
run_in_background: true,
|
||||
prompt: "Check it again",
|
||||
} as Record<string, unknown>,
|
||||
}
|
||||
await beforeHandler({ tool: "task", sessionID: "ses-main", callID: "call-2" }, secondOutput)
|
||||
const secondAttemptId = readState(directory)?.verification_attempt_id
|
||||
|
||||
expect(firstAttemptId).toBeTruthy()
|
||||
expect(secondAttemptId).toBeTruthy()
|
||||
expect(secondAttemptId).not.toBe(firstAttemptId)
|
||||
|
||||
await afterHandler(
|
||||
{ tool: "task", sessionID: "ses-main", callID: "call-1" },
|
||||
{
|
||||
title: "oracle task",
|
||||
output: "done",
|
||||
metadata: {
|
||||
agent: "oracle",
|
||||
prompt: String(firstOutput.args.prompt),
|
||||
sessionId: "ses-oracle-old",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
expect(readState(directory)?.verification_session_id).toBeUndefined()
|
||||
|
||||
await afterHandler(
|
||||
{ tool: "task", sessionID: "ses-main", callID: "call-2" },
|
||||
{
|
||||
title: "oracle task",
|
||||
output: "done",
|
||||
metadata: {
|
||||
agent: "oracle",
|
||||
prompt: String(secondOutput.args.prompt),
|
||||
sessionId: "ses-oracle-new",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
expect(readState(directory)?.verification_session_id).toBe("ses-oracle-new")
|
||||
|
||||
clearState(directory)
|
||||
rmSync(directory, { recursive: true, force: true })
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user