fix(plugin): handle raw /ulw-loop commands appearing after injected messages

Fix parseRawLoopSlashCommand to correctly extract and parse loop commands
that appear after injected background task messages (e.g., "[BACKGROUND
TASK COMPLETED]"). Previously, the function only checked if the entire
message started with a slash command, failing when injected content
preceded the command.

🤖 Generated with assistance of OhMyOpenCode (https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
YeonGyu-Kim
2026-04-03 22:13:02 +09:00
parent df7dc2f716
commit 6624803a52
2 changed files with 55 additions and 3 deletions
+45
View File
@@ -136,6 +136,51 @@ describe("createChatMessageHandler - /ulw-loop raw slash fallback", () => {
},
])
})
test("starts ultrawork loop when injected messages appear before the raw /ulw-loop command", async () => {
// given
const startLoopCalls: Array<{
sessionID: string
prompt: string
options: Record<string, unknown>
}> = []
const args = createMockHandlerArgs()
args.hooks.ralphLoop = {
startLoop: (sessionID: string, prompt: string, options?: Record<string, unknown>) => {
startLoopCalls.push({ sessionID, prompt, options: options ?? {} })
return true
},
cancelLoop: () => true,
}
const handler = createChatMessageHandler(args)
const input = createMockInput("sisyphus")
const output: ChatMessageHandlerOutput = {
message: {},
parts: [
{
type: "text",
text: "[BACKGROUND TASK COMPLETED]\nPlan finished.\n\n---\n\n/ulw-loop \"Ship feature\" --strategy=continue",
},
],
}
// when
await handler(input, output)
// then
expect(startLoopCalls).toEqual([
{
sessionID: "test-session",
prompt: "Ship feature",
options: {
ultrawork: true,
maxIterations: undefined,
completionPromise: undefined,
strategy: "continue",
},
},
])
})
})
function createMockInput(agent?: string, model?: { providerID: string; modelID: string }) {
+10 -3
View File
@@ -90,17 +90,24 @@ function getStoredMainSessionModel(
function parseRawLoopSlashCommand(promptText: string): RawLoopCommand | null {
const trimmed = promptText.trim()
const commandText = trimmed.startsWith("/")
? trimmed
: trimmed
.split("\n")
.map((line) => line.trim())
.filter((line) => /^\/(?:ralph-loop|ulw-loop|cancel-ralph)\b/i.test(line))
.at(-1)
if (!trimmed.startsWith("/")) {
if (!commandText) {
return null
}
const cancelMatch = trimmed.match(/^\/cancel-ralph(?:\s+.*)?$/i)
const cancelMatch = commandText.match(/^\/cancel-ralph(?:\s+.*)?$/i)
if (cancelMatch) {
return { command: "cancel-ralph", args: "" }
}
const loopMatch = trimmed.match(/^\/(ralph-loop|ulw-loop)\s*([\s\S]*)$/i)
const loopMatch = commandText.match(/^\/(ralph-loop|ulw-loop)\s*([\s\S]*)$/i)
if (!loopMatch) {
return null
}