diff --git a/src/plugin/tool-execute-before.ts b/src/plugin/tool-execute-before.ts index df1f930fd..e7585b7b3 100644 --- a/src/plugin/tool-execute-before.ts +++ b/src/plugin/tool-execute-before.ts @@ -11,6 +11,16 @@ import { readState, writeState } from "../hooks/ralph-loop/storage" import type { CreatedHooks } from "../create-hooks" +function getLoopCommandArguments(args: Record, command: "ralph-loop" | "ulw-loop"): string { + const rawUserMessage = typeof args.user_message === "string" ? args.user_message.trim() : "" + if (rawUserMessage) { + return rawUserMessage + } + + const rawName = typeof args.name === "string" ? args.name : "" + return rawName.replace(new RegExp(`^/?(${command})\\s*`, "i"), "") +} + export function createToolExecuteBeforeHandler(args: { ctx: PluginContext hooks: CreatedHooks @@ -137,7 +147,7 @@ export function createToolExecuteBeforeHandler(args: { const sessionID = input.sessionID || getMainSessionID() if (command === "ralph-loop" && sessionID) { - const rawArgs = rawName?.replace(/^\/?(ralph-loop)\s*/i, "") || "" + const rawArgs = getLoopCommandArguments(output.args, "ralph-loop") const parsedArguments = parseRalphLoopArguments(rawArgs) hooks.ralphLoop.startLoop(sessionID, parsedArguments.prompt, { @@ -148,7 +158,7 @@ export function createToolExecuteBeforeHandler(args: { } else if (command === "cancel-ralph" && sessionID) { hooks.ralphLoop.cancelLoop(sessionID) } else if (command === "ulw-loop" && sessionID) { - const rawArgs = rawName?.replace(/^\/?(ulw-loop)\s*/i, "") || "" + const rawArgs = getLoopCommandArguments(output.args, "ulw-loop") const parsedArguments = parseRalphLoopArguments(rawArgs) hooks.ralphLoop.startLoop(sessionID, parsedArguments.prompt, { diff --git a/src/plugin/tool-execute-before.ulw-loop.test.ts b/src/plugin/tool-execute-before.ulw-loop.test.ts index 50e29ca05..d4283c044 100644 --- a/src/plugin/tool-execute-before.ulw-loop.test.ts +++ b/src/plugin/tool-execute-before.ulw-loop.test.ts @@ -91,6 +91,47 @@ describe("tool.execute.before ultrawork oracle verification", () => { rmSync(directory, { recursive: true, force: true }) }) + test("#given ulw-loop skill invocation carries user_message #when tool.execute.before runs #then the loop starts with that prompt", async () => { + const directory = join(tmpdir(), `tool-before-ulw-skill-${Date.now()}`) + mkdirSync(directory, { recursive: true }) + const startLoopCalls: Array<{ sessionID: string; prompt: string; options: Record }> = [] + const handler = createToolExecuteBeforeHandler({ + ctx: createCtx(directory) as unknown as Parameters[0]["ctx"], + hooks: { + ralphLoop: { + startLoop: (sessionID: string, prompt: string, options?: Record) => { + startLoopCalls.push({ sessionID, prompt, options: options ?? {} }) + return true + }, + cancelLoop: () => true, + getState: () => null, + }, + } as unknown as Parameters[0]["hooks"], + }) + const output = { + args: { + name: "ulw-loop", + user_message: '"Ship feature" --strategy=continue', + }, + } + + await handler({ tool: "skill", sessionID: "ses-main", callID: "call-skill-ulw" }, output) + + expect(startLoopCalls).toHaveLength(1) + expect(startLoopCalls[0]).toEqual({ + sessionID: "ses-main", + prompt: "Ship feature", + options: { + ultrawork: true, + maxIterations: undefined, + completionPromise: undefined, + strategy: "continue", + }, + }) + + rmSync(directory, { recursive: true, force: true }) + }) + test("#given ulw loop is awaiting verification #when oracle sync task metadata is persisted #then oracle session id is stored", async () => { const directory = join(tmpdir(), `tool-after-ulw-${Date.now()}`) mkdirSync(directory, { recursive: true })