diff --git a/src/shared/internal-initiator-marker.test.ts b/src/shared/internal-initiator-marker.test.ts new file mode 100644 index 000000000..cc1035dd8 --- /dev/null +++ b/src/shared/internal-initiator-marker.test.ts @@ -0,0 +1,119 @@ +import { describe, expect, test } from "bun:test" +import { + OMO_INTERNAL_INITIATOR_MARKER, + createInternalAgentTextPart, + stripInternalInitiatorMarkers, +} from "./internal-initiator-marker" + +describe("internal-initiator-marker", () => { + describe("createInternalAgentTextPart", () => { + test("#given clean text #when creating an internal agent text part #then appends exactly one marker", () => { + // given + const text = "Hello world" + + // when + const part = createInternalAgentTextPart(text) + + // then + expect(part.type).toBe("text") + expect(part.text).toBe(`Hello world\n${OMO_INTERNAL_INITIATOR_MARKER}`) + }) + + test("#given text already ending with the marker #when creating a text part #then does not duplicate the marker", () => { + // given + const text = `Already marked\n${OMO_INTERNAL_INITIATOR_MARKER}` + + // when + const part = createInternalAgentTextPart(text) + + // then + const markerCount = part.text.split(OMO_INTERNAL_INITIATOR_MARKER).length - 1 + expect(markerCount).toBe(1) + expect(part.text).toBe(`Already marked\n${OMO_INTERNAL_INITIATOR_MARKER}`) + }) + + test("#given text containing multiple embedded markers #when creating a text part #then collapses to a single trailing marker", () => { + // given + const text = `First\n${OMO_INTERNAL_INITIATOR_MARKER}\nSecond\n${OMO_INTERNAL_INITIATOR_MARKER}\nThird\n${OMO_INTERNAL_INITIATOR_MARKER}` + + // when + const part = createInternalAgentTextPart(text) + + // then + const markerCount = part.text.split(OMO_INTERNAL_INITIATOR_MARKER).length - 1 + expect(markerCount).toBe(1) + expect(part.text.endsWith(OMO_INTERNAL_INITIATOR_MARKER)).toBe(true) + }) + + test("#given text with embedded markers between content #when creating a text part #then strips embedded markers and keeps content", () => { + // given + const text = `Line one\n${OMO_INTERNAL_INITIATOR_MARKER}\nLine two\n${OMO_INTERNAL_INITIATOR_MARKER}` + + // when + const part = createInternalAgentTextPart(text) + + // then + expect(part.text).toContain("Line one") + expect(part.text).toContain("Line two") + const markerCount = part.text.split(OMO_INTERNAL_INITIATOR_MARKER).length - 1 + expect(markerCount).toBe(1) + }) + + test("#given empty text #when creating a text part #then still appends a single marker", () => { + // given + const text = "" + + // when + const part = createInternalAgentTextPart(text) + + // then + expect(part.text).toBe(`\n${OMO_INTERNAL_INITIATOR_MARKER}`) + }) + }) + + describe("stripInternalInitiatorMarkers", () => { + test("#given text with no markers #when stripping #then returns text trimmed at the end", () => { + // given + const text = "No markers here" + + // when + const result = stripInternalInitiatorMarkers(text) + + // then + expect(result).toBe("No markers here") + }) + + test("#given text with one trailing marker #when stripping #then removes the marker", () => { + // given + const text = `Content\n${OMO_INTERNAL_INITIATOR_MARKER}` + + // when + const result = stripInternalInitiatorMarkers(text) + + // then + expect(result).toBe("Content") + }) + + test("#given text with multiple stacked markers #when stripping #then removes all of them", () => { + // given + const text = `Content\n${OMO_INTERNAL_INITIATOR_MARKER}\n${OMO_INTERNAL_INITIATOR_MARKER}\n${OMO_INTERNAL_INITIATOR_MARKER}` + + // when + const result = stripInternalInitiatorMarkers(text) + + // then + expect(result).toBe("Content") + }) + + test("#given text with markers on consecutive lines without separators #when stripping #then removes all markers", () => { + // given + const text = `${OMO_INTERNAL_INITIATOR_MARKER}${OMO_INTERNAL_INITIATOR_MARKER}${OMO_INTERNAL_INITIATOR_MARKER}` + + // when + const result = stripInternalInitiatorMarkers(text) + + // then + expect(result).toBe("") + }) + }) +}) diff --git a/src/shared/internal-initiator-marker.ts b/src/shared/internal-initiator-marker.ts index 3e19c5819..7e810a15e 100644 --- a/src/shared/internal-initiator-marker.ts +++ b/src/shared/internal-initiator-marker.ts @@ -1,11 +1,18 @@ export const OMO_INTERNAL_INITIATOR_MARKER = "" +const INTERNAL_INITIATOR_MARKER_PATTERN = /\n*\s*/g + +export function stripInternalInitiatorMarkers(text: string): string { + return text.replace(INTERNAL_INITIATOR_MARKER_PATTERN, "").trimEnd() +} + export function createInternalAgentTextPart(text: string): { type: "text" text: string } { + const cleanText = stripInternalInitiatorMarkers(text) return { type: "text", - text: `${text}\n${OMO_INTERNAL_INITIATOR_MARKER}`, + text: `${cleanText}\n${OMO_INTERNAL_INITIATOR_MARKER}`, } } diff --git a/src/shared/system-directive.test.ts b/src/shared/system-directive.test.ts index 9da4c9563..2626bb771 100644 --- a/src/shared/system-directive.test.ts +++ b/src/shared/system-directive.test.ts @@ -144,6 +144,50 @@ const x = 1; const directive = ` ${createSystemDirective("TEST")}` expect(isSystemDirective(directive)).toBe(true) }) + + test("#given a ralph-loop ULW continuation prefixed with 'ultrawork ' #when checking system directive #then returns true", () => { + // given + const directive = `ultrawork ${createSystemDirective("RALPH LOOP 2/500")}\n\nYour previous attempt did not output the completion promise.` + + // when + const result = isSystemDirective(directive) + + // then + expect(result).toBe(true) + }) + + test("#given a continuation prefixed with 'ulw ' shorthand #when checking system directive #then returns true", () => { + // given + const directive = `ulw ${createSystemDirective("ULTRAWORK LOOP VERIFICATION 1/500")}\n\nYou already emitted DONE.` + + // when + const result = isSystemDirective(directive) + + // then + expect(result).toBe(true) + }) + + test("#given a continuation prefixed with uppercase 'ULTRAWORK ' #when checking system directive #then returns true", () => { + // given + const directive = `ULTRAWORK ${createSystemDirective("RALPH LOOP 5/500")}` + + // when + const result = isSystemDirective(directive) + + // then + expect(result).toBe(true) + }) + + test("#given user text that legitimately starts with 'ultrawork' word #when no directive follows #then returns false", () => { + // given + const text = "ultrawork is a great mode but I have a question about it" + + // when + const result = isSystemDirective(text) + + // then + expect(result).toBe(false) + }) }) describe("integration with keyword detection", () => { diff --git a/src/shared/system-directive.ts b/src/shared/system-directive.ts index f2ae8c602..001017aa5 100644 --- a/src/shared/system-directive.ts +++ b/src/shared/system-directive.ts @@ -7,6 +7,8 @@ export const SYSTEM_DIRECTIVE_PREFIX = "[SYSTEM DIRECTIVE: OH-MY-OPENCODE" +const SYSTEM_DIRECTIVE_LEADING_KEYWORD_PATTERN = /^\s*(?:ultrawork|ulw)\s+/i + /** * Creates a system directive header with the given type. * @param type - The directive type (e.g., "TODO CONTINUATION", "RALPH LOOP") @@ -23,7 +25,12 @@ export function createSystemDirective(type: string): string { * @returns true if the message is a system directive */ export function isSystemDirective(text: string): boolean { - return text.trimStart().startsWith(SYSTEM_DIRECTIVE_PREFIX) + const trimmed = text.trimStart() + if (trimmed.startsWith(SYSTEM_DIRECTIVE_PREFIX)) { + return true + } + const withoutLeadingKeyword = trimmed.replace(SYSTEM_DIRECTIVE_LEADING_KEYWORD_PATTERN, "") + return withoutLeadingKeyword.startsWith(SYSTEM_DIRECTIVE_PREFIX) } /**