feat(council-archive): add shared extraction utility and background task type extensions

- Create council-response-extractor.ts with ported extractCouncilResponse()
- Add writeOutputToFile, outputFilePath, _isCompleting to BackgroundTask
- Add writeOutputToFile to LaunchInput
- 8/8 extraction tests passing
This commit is contained in:
ismeth
2026-02-27 15:03:15 +01:00
committed by YeonGyu-Kim
parent 5a819d0914
commit b7552f13ac
3 changed files with 137 additions and 0 deletions
+8
View File
@@ -63,6 +63,12 @@ export interface BackgroundTask {
isUnstableAgent?: boolean
/** Category used for this task (e.g., 'quick', 'visual-engineering') */
category?: string
/** Flag to write raw output to file on completion */
writeOutputToFile?: boolean
/** Path to the output file after successful write */
outputFilePath?: string
/** Race condition guard: prevents concurrent re-entry during async completion */
_isCompleting?: boolean
/** Last message count for stability detection */
lastMsgCount?: number
@@ -89,6 +95,8 @@ export interface LaunchInput {
skillContent?: string
category?: string
sessionPermission?: SessionPermissionRule[]
/** Flag to write raw output to file on completion */
writeOutputToFile?: boolean
}
export interface ResumeInput {
@@ -0,0 +1,103 @@
import { describe, expect, it } from "bun:test"
import { extractCouncilResponse } from "./council-response-extractor"
describe("extractCouncilResponse", () => {
describe("#given complete COUNCIL_MEMBER_RESPONSE tags", () => {
it("#then returns has_response true, response_complete true, and the content", () => {
const result = extractCouncilResponse("<COUNCIL_MEMBER_RESPONSE>analysis here</COUNCIL_MEMBER_RESPONSE>")
expect(result).toEqual({
has_response: true,
response_complete: true,
result: "analysis here",
})
})
})
describe("#given incomplete tags (opening but no closing)", () => {
it("#then returns has_response true, response_complete false, and partial content", () => {
const result = extractCouncilResponse("<COUNCIL_MEMBER_RESPONSE>partial analysis")
expect(result).toEqual({
has_response: true,
response_complete: false,
result: "partial analysis",
})
})
})
describe("#given missing tags (no opening tag)", () => {
it("#then returns has_response false, response_complete false, and null result", () => {
const result = extractCouncilResponse("Just some plain text without any tags.")
expect(result).toEqual({
has_response: false,
response_complete: false,
result: null,
})
})
})
describe("#given empty content between tags", () => {
it("#then returns has_response true, response_complete true, and empty string result", () => {
const result = extractCouncilResponse("<COUNCIL_MEMBER_RESPONSE></COUNCIL_MEMBER_RESPONSE>")
expect(result).toEqual({
has_response: true,
response_complete: true,
result: "",
})
})
})
describe("#given multiple tag pairs", () => {
it("#then returns content from the last opening tag", () => {
const text =
"<COUNCIL_MEMBER_RESPONSE>first analysis</COUNCIL_MEMBER_RESPONSE>\nSome interim text\n<COUNCIL_MEMBER_RESPONSE>final analysis</COUNCIL_MEMBER_RESPONSE>"
const result = extractCouncilResponse(text)
expect(result).toEqual({
has_response: true,
response_complete: true,
result: "final analysis",
})
})
})
describe("#given an empty string", () => {
it("#then returns has_response false and null result", () => {
const result = extractCouncilResponse("")
expect(result).toEqual({
has_response: false,
response_complete: false,
result: null,
})
})
})
describe("#given whitespace-only content between tags", () => {
it("#then returns has_response true, response_complete true, and empty string result", () => {
const result = extractCouncilResponse("<COUNCIL_MEMBER_RESPONSE> </COUNCIL_MEMBER_RESPONSE>")
expect(result).toEqual({
has_response: true,
response_complete: true,
result: "",
})
})
})
describe("#given content with surrounding text before the opening tag", () => {
it("#then returns only the tagged content", () => {
const text = "Some preamble text\n<COUNCIL_MEMBER_RESPONSE>the actual response</COUNCIL_MEMBER_RESPONSE>"
const result = extractCouncilResponse(text)
expect(result).toEqual({
has_response: true,
response_complete: true,
result: "the actual response",
})
})
})
})
@@ -0,0 +1,26 @@
export const OPENING_TAG = "<COUNCIL_MEMBER_RESPONSE>"
export const CLOSING_TAG = "</COUNCIL_MEMBER_RESPONSE>"
export interface CouncilResponseExtraction {
has_response: boolean
response_complete: boolean
result: string | null
}
export function extractCouncilResponse(fullText: string): CouncilResponseExtraction {
const lastOpenIdx = fullText.lastIndexOf(OPENING_TAG)
if (lastOpenIdx === -1) {
return { has_response: false, response_complete: false, result: null }
}
const contentStart = lastOpenIdx + OPENING_TAG.length
const closingAfterLastOpen = fullText.indexOf(CLOSING_TAG, contentStart)
if (closingAfterLastOpen === -1) {
const partial = fullText.slice(contentStart).trim()
return { has_response: true, response_complete: false, result: partial || null }
}
const content = fullText.slice(contentStart, closingAfterLastOpen).trim()
return { has_response: true, response_complete: true, result: content }
}