diff --git a/packages/core/src/agents/state-validator.ts b/packages/core/src/agents/state-validator.ts index 50d48a71..01571dd1 100644 --- a/packages/core/src/agents/state-validator.ts +++ b/packages/core/src/agents/state-validator.ts @@ -174,6 +174,7 @@ function extractBalancedJsonObject(text: string, start: number): string | null { let depth = 0; let inString = false; let escaped = false; + let endIndex = -1; for (let index = start; index < text.length; index += 1) { const char = text[index]!; @@ -206,7 +207,8 @@ function extractBalancedJsonObject(text: string, start: number): string | null { if (char === "}") { depth -= 1; if (depth === 0) { - return text.slice(start, index + 1); + endIndex = index; + break; } if (depth < 0) { return null; @@ -214,5 +216,24 @@ function extractBalancedJsonObject(text: string, start: number): string | null { } } - return null; + if (endIndex < 0) return null; + + // Only accept the candidate if what follows the closing brace is + // nothing, whitespace, or a structural JSON terminator. + // This rejects trailing content like "{...} more text here" + const followingChar = text[endIndex + 1]; + if ( + followingChar !== undefined && + followingChar !== "\n" && + followingChar !== "\r" && + followingChar !== "\t" && + followingChar !== " " && + followingChar !== "," && + followingChar !== "]" && + followingChar !== "}" + ) { + return null; + } + + return text.slice(start, endIndex + 1); } diff --git a/packages/core/src/pipeline/runner.ts b/packages/core/src/pipeline/runner.ts index 48910617..d05482d9 100644 --- a/packages/core/src/pipeline/runner.ts +++ b/packages/core/src/pipeline/runner.ts @@ -1828,11 +1828,10 @@ ${matrix}`, this.buildImportReplayStateSeed(language), "utf-8", ), - writeFile( - join(storyDir, "pending_hooks.md"), - this.buildImportReplayHooksSeed(language), - "utf-8", - ), + // NOTE: pending_hooks.md intentionally NOT reset here — hooks are + // chapter-content-specific and user may have invested significant + // effort in tuning them. The replay will incrementally add new hooks + // detected from the imported chapters without destroying existing ones. rm(join(storyDir, "chapter_summaries.md"), { force: true }), rm(join(storyDir, "subplot_board.md"), { force: true }), rm(join(storyDir, "emotional_arcs.md"), { force: true }),