fix(keyword-detector): trigger ulw shorthand anywhere

This commit is contained in:
YeonGyu-Kim
2026-04-10 11:12:56 +09:00
parent 5f90b238e0
commit f36231a8a6
3 changed files with 29 additions and 16 deletions
@@ -86,7 +86,7 @@ describe("keyword-detector ralph-loop activation", () => {
expect(startLoopCalls[0].options.ultrawork).toBe(true) expect(startLoopCalls[0].options.ultrawork).toBe(true)
}) })
test("#given ulw mentioned mid-sentence #when chat.message fires #then ralph-loop startLoop is not invoked", async () => { test("#given ulw mentioned mid-sentence #when chat.message fires #then ralph-loop startLoop is invoked", async () => {
// given // given
setMainSession("main-session") setMainSession("main-session")
const startLoopCalls: StartLoopCall[] = [] const startLoopCalls: StartLoopCall[] = []
@@ -94,15 +94,16 @@ describe("keyword-detector ralph-loop activation", () => {
const hook = createKeywordDetectorHook(createMockPluginInput(), undefined, ralphLoop) const hook = createKeywordDetectorHook(createMockPluginInput(), undefined, ralphLoop)
const output = { const output = {
message: {} as Record<string, unknown>, message: {} as Record<string, unknown>,
parts: [{ type: "text", text: "I think ulw is cool" }], parts: [{ type: "text", text: "please ulw fix the flaky keyword tests" }],
} }
// when // when
await hook["chat.message"]({ sessionID: "main-session", agent: "sisyphus" }, output) await hook["chat.message"]({ sessionID: "main-session", agent: "sisyphus" }, output)
// then // then
expect(startLoopCalls).toHaveLength(0) expect(startLoopCalls).toHaveLength(1)
expect(output.parts[0]?.text).toBe("I think ulw is cool") expect(startLoopCalls[0].prompt).toBe("please fix the flaky keyword tests")
expect(output.parts[0]?.text).toContain("please ulw fix the flaky keyword tests")
}) })
test("#given question about ultrawork #when chat.message fires #then ralph-loop startLoop is not invoked", async () => { test("#given question about ultrawork #when chat.message fires #then ralph-loop startLoop is not invoked", async () => {
+18 -7
View File
@@ -15,22 +15,33 @@ import type { ContextCollector } from "../../features/context-injector"
import type { RalphLoopHook } from "../ralph-loop" import type { RalphLoopHook } from "../ralph-loop"
import { parseRalphLoopArguments } from "../ralph-loop/command-arguments" import { parseRalphLoopArguments } from "../ralph-loop/command-arguments"
const ULTRAWORK_KEYWORD_PATTERN = /\b(ultrawork|ulw)\b/i const ULTRAWORK_LONGHAND_PATTERN = /\bultrawork\b/i
const ULW_SHORTHAND_PATTERN = /\bulw\b/i
const LEADING_ULTRAWORK_PATTERN = /^\s*(ultrawork|ulw)\b/i const LEADING_ULTRAWORK_PATTERN = /^\s*(ultrawork|ulw)\b/i
const GREETING_PREFIX_ULTRAWORK_PATTERN = /^\s*(?:hi|hello|hey|hiya|greetings)(?:\s+there)?(?:[!,.:;-]+\s*|\s+)(ultrawork|ulw)\b/i const GREETING_PREFIX_ULTRAWORK_PATTERN = /^\s*(?:hi|hello|hey|hiya|greetings)(?:\s+there)?(?:[!,.:;-]+\s*|\s+)(ultrawork|ulw)\b/i
function normalizeUltraworkTask(taskText: string): string {
return taskText.replace(/\s+/g, " ").trim()
}
function extractUltraworkTask(cleanText: string): string { function extractUltraworkTask(cleanText: string): string {
const greetingPrefixedMatch = cleanText.match(GREETING_PREFIX_ULTRAWORK_PATTERN) const greetingPrefixedMatch = cleanText.match(GREETING_PREFIX_ULTRAWORK_PATTERN)
if (greetingPrefixedMatch) { if (greetingPrefixedMatch) {
return cleanText.slice(greetingPrefixedMatch[0].length).trim() return normalizeUltraworkTask(cleanText.slice(greetingPrefixedMatch[0].length))
} }
return cleanText.replace(ULTRAWORK_KEYWORD_PATTERN, "").trim() if (ULW_SHORTHAND_PATTERN.test(cleanText)) {
return normalizeUltraworkTask(cleanText.replace(ULW_SHORTHAND_PATTERN, " "))
}
return normalizeUltraworkTask(cleanText.replace(ULTRAWORK_LONGHAND_PATTERN, " "))
} }
function hasEdgeUltraworkKeyword(cleanText: string): boolean { function shouldAllowUltraworkKeyword(cleanText: string): boolean {
return LEADING_ULTRAWORK_PATTERN.test(cleanText) || GREETING_PREFIX_ULTRAWORK_PATTERN.test(cleanText) return ULW_SHORTHAND_PATTERN.test(cleanText)
|| LEADING_ULTRAWORK_PATTERN.test(cleanText)
|| GREETING_PREFIX_ULTRAWORK_PATTERN.test(cleanText)
} }
export function createKeywordDetectorHook( export function createKeywordDetectorHook(
@@ -88,11 +99,11 @@ export function createKeywordDetectorHook(
} }
} }
if (!hasEdgeUltraworkKeyword(cleanText)) { if (!shouldAllowUltraworkKeyword(cleanText)) {
const preFilterCount = detectedKeywords.length const preFilterCount = detectedKeywords.length
detectedKeywords = detectedKeywords.filter((k) => k.type !== "ultrawork") detectedKeywords = detectedKeywords.filter((k) => k.type !== "ultrawork")
if (preFilterCount > detectedKeywords.length) { if (preFilterCount > detectedKeywords.length) {
log(`[keyword-detector] Filtered non-edge ultrawork keyword`, { log(`[keyword-detector] Filtered disallowed ultrawork keyword`, {
sessionID: input.sessionID, sessionID: input.sessionID,
}) })
} }
@@ -108,7 +108,7 @@ describe("keyword-detector ultrawork edge trigger", () => {
expect(output.parts[0]?.text).toContain("hey ulw fix the flaky keyword tests") expect(output.parts[0]?.text).toContain("hey ulw fix the flaky keyword tests")
}) })
test("#given ulw mentioned in the middle of a sentence #when chat.message fires #then ultrawork stays disabled", async () => { test("#given ulw mentioned in the middle of a sentence #when chat.message fires #then ultrawork still activates", async () => {
// given // given
const toastCalls: string[] = [] const toastCalls: string[] = []
const startLoopCalls: StartLoopCall[] = [] const startLoopCalls: StartLoopCall[] = []
@@ -119,16 +119,17 @@ describe("keyword-detector ultrawork edge trigger", () => {
) )
const output = { const output = {
message: {} as Record<string, unknown>, message: {} as Record<string, unknown>,
parts: [{ type: "text", text: "I think ulw is cool" }], parts: [{ type: "text", text: "please ulw fix the flaky keyword tests" }],
} }
// when // when
await hook["chat.message"]({ sessionID: "main-session", agent: "sisyphus" }, output) await hook["chat.message"]({ sessionID: "main-session", agent: "sisyphus" }, output)
// then // then
expect(toastCalls).not.toContain("Ultrawork Mode Activated") expect(toastCalls).toContain("Ultrawork Mode Activated")
expect(startLoopCalls).toHaveLength(0) expect(startLoopCalls).toHaveLength(1)
expect(output.parts[0]?.text).toBe("I think ulw is cool") expect(startLoopCalls[0]?.prompt).toBe("please fix the flaky keyword tests")
expect(output.parts[0]?.text).toContain("please ulw fix the flaky keyword tests")
}) })
test("#given trailing ultrawork reference without punctuation #when chat.message fires #then ultrawork stays disabled", async () => { test("#given trailing ultrawork reference without punctuation #when chat.message fires #then ultrawork stays disabled", async () => {