From 917ae4dfc37c8dc55a429533d58e717a56c31d2d Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Wed, 8 Apr 2026 17:17:14 +0900 Subject: [PATCH] fix(keyword-detector): narrow ULW auto-start to leading keyword position only Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../keyword-detector/hook-ralph-loop.test.ts | 38 +++++++++++++++++++ src/hooks/keyword-detector/hook.ts | 15 ++++++++ 2 files changed, 53 insertions(+) diff --git a/src/hooks/keyword-detector/hook-ralph-loop.test.ts b/src/hooks/keyword-detector/hook-ralph-loop.test.ts index 0ba0a6d27..ce0a6f066 100644 --- a/src/hooks/keyword-detector/hook-ralph-loop.test.ts +++ b/src/hooks/keyword-detector/hook-ralph-loop.test.ts @@ -86,6 +86,44 @@ describe("keyword-detector ralph-loop activation", () => { 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 () => { + // given + setMainSession("main-session") + const startLoopCalls: StartLoopCall[] = [] + const ralphLoop = createMockRalphLoop(startLoopCalls) + const hook = createKeywordDetectorHook(createMockPluginInput(), undefined, ralphLoop) + const output = { + message: {} as Record, + parts: [{ type: "text", text: "I think ulw is cool" }], + } + + // when + await hook["chat.message"]({ sessionID: "main-session", agent: "sisyphus" }, output) + + // then + expect(startLoopCalls).toHaveLength(0) + expect(output.parts[0]?.text).toBe("I think ulw is cool") + }) + + test("#given question about ultrawork #when chat.message fires #then ralph-loop startLoop is not invoked", async () => { + // given + setMainSession("main-session") + const startLoopCalls: StartLoopCall[] = [] + const ralphLoop = createMockRalphLoop(startLoopCalls) + const hook = createKeywordDetectorHook(createMockPluginInput(), undefined, ralphLoop) + const output = { + message: {} as Record, + parts: [{ type: "text", text: "what is ultrawork?" }], + } + + // when + await hook["chat.message"]({ sessionID: "main-session", agent: "sisyphus" }, output) + + // then + expect(startLoopCalls).toHaveLength(0) + expect(output.parts[0]?.text).toBe("what is ultrawork?") + }) + test("#given non-ulw message #when chat.message fires #then ralph-loop startLoop is not invoked", async () => { // given setMainSession("main-session") diff --git a/src/hooks/keyword-detector/hook.ts b/src/hooks/keyword-detector/hook.ts index 1d35bbe3f..ea6348419 100644 --- a/src/hooks/keyword-detector/hook.ts +++ b/src/hooks/keyword-detector/hook.ts @@ -16,11 +16,16 @@ import type { RalphLoopHook } from "../ralph-loop" import { parseRalphLoopArguments } from "../ralph-loop/command-arguments" const ULTRAWORK_KEYWORD_PATTERN = /\b(ultrawork|ulw)\b/i +const LEADING_ULTRAWORK_PATTERN = /^\s*(ultrawork|ulw)\b/i function extractUltraworkTask(cleanText: string): string { return cleanText.replace(ULTRAWORK_KEYWORD_PATTERN, "").trim() } +function hasLeadingUltraworkKeyword(cleanText: string): boolean { + return LEADING_ULTRAWORK_PATTERN.test(cleanText) +} + export function createKeywordDetectorHook( ctx: PluginInput, _collector?: ContextCollector, @@ -76,6 +81,16 @@ export function createKeywordDetectorHook( } } + if (!hasLeadingUltraworkKeyword(cleanText)) { + const preFilterCount = detectedKeywords.length + detectedKeywords = detectedKeywords.filter((k) => k.type !== "ultrawork") + if (preFilterCount > detectedKeywords.length) { + log(`[keyword-detector] Filtered non-leading ultrawork keyword`, { + sessionID: input.sessionID, + }) + } + } + if (detectedKeywords.length === 0) { return }