From 3d36f6c5d81b03a92b851dfd01d426db2ba94606 Mon Sep 17 00:00:00 2001 From: ismeth Date: Mon, 2 Mar 2026 00:33:35 +0100 Subject: [PATCH] fix(council): handle whitespace around COUNCIL_MEMBER_RESPONSE tags Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus --- .../council-response-extractor.test.ts | 57 +++++++++++++++++++ .../council-response-extractor.ts | 12 +++- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/tools/council-archive/council-response-extractor.test.ts b/src/tools/council-archive/council-response-extractor.test.ts index d436d2a0b..65441343b 100644 --- a/src/tools/council-archive/council-response-extractor.test.ts +++ b/src/tools/council-archive/council-response-extractor.test.ts @@ -275,5 +275,62 @@ describe("extractCouncilResponse", () => { }) }) }) + + describe("#given leading spaces before opening tag", () => { + it("#then recognizes the tag as structural and extracts content", () => { + const content = "a".repeat(100) + const text = ` \n${content}\n` + const result = extractCouncilResponse(text) + + expect(result).toEqual({ + has_response: true, + response_complete: true, + result: content, + }) + }) + }) + + describe("#given leading tab before opening tag", () => { + it("#then recognizes the tag as structural and extracts content", () => { + const content = "a".repeat(100) + const text = `\t\n${content}\n` + const result = extractCouncilResponse(text) + + expect(result).toEqual({ + has_response: true, + response_complete: true, + result: content, + }) + }) + }) + + describe("#given trailing spaces after closing tag", () => { + it("#then recognizes the closing tag as structural and extracts content", () => { + const content = "a".repeat(100) + const text = `\n${content}\n ` + const result = extractCouncilResponse(text) + + expect(result).toEqual({ + has_response: true, + response_complete: true, + result: content, + }) + }) + }) + + describe("#given inline opening tag (text before tag on same line)", () => { + it("#then rejects the inline tag and returns has_response false", () => { + const content = "a".repeat(100) + const text = `text \n${content}\n` + const result = extractCouncilResponse(text) + + expect(result).toEqual({ + has_response: false, + response_complete: false, + result: null, + }) + }) + }) + }) diff --git a/src/tools/council-archive/council-response-extractor.ts b/src/tools/council-archive/council-response-extractor.ts index f0be4d8f4..a3fd44fe1 100644 --- a/src/tools/council-archive/council-response-extractor.ts +++ b/src/tools/council-archive/council-response-extractor.ts @@ -55,12 +55,20 @@ export function hasCouncilResponseTag(sessionMessages: Array<{ info?: { role?: s } function isStructuralOpen(text: string, idx: number): boolean { - return idx === 0 || text[idx - 1] === "\n" + if (idx === 0) return true + const prevNewline = text.lastIndexOf("\n", idx - 1) + const lineStart = prevNewline === -1 ? 0 : prevNewline + 1 + const between = text.slice(lineStart, idx) + return between.split("").every((ch) => ch === " " || ch === "\t") } function isStructuralClose(text: string, idx: number): boolean { const afterIdx = idx + CLOSING_TAG.length - return afterIdx === text.length || text[afterIdx] === "\n" || text[afterIdx] === "\r" + if (afterIdx === text.length) return true + const nextNewline = text.indexOf("\n", afterIdx) + const lineEnd = nextNewline === -1 ? text.length : nextNewline + const between = text.slice(afterIdx, lineEnd) + return between.split("").every((ch) => ch === " " || ch === "\t" || ch === "\r") } function findLastStructuralClose(text: string): number {