diff --git a/open-sse/services/compression/preservation.ts b/open-sse/services/compression/preservation.ts index 3f5e6f3bfa..a55f6549a4 100644 --- a/open-sse/services/compression/preservation.ts +++ b/open-sse/services/compression/preservation.ts @@ -87,6 +87,23 @@ export function extractPreservedBlocks( let result = text; result = extractFrontmatter(result, addBlock); + + // Whole-region patterns run before fenced code and the inline built-ins: those leave + // sentinels inside a region, and replacePattern skips any match that already holds one. + // #13453: the instruction envelopes agentic CLIs inject into user messages — + // compressing them inverts negations, drops emphasis and breaks the XML tags. + const regionPatterns: CompiledPattern[] = [ + { pattern: /[\s\S]*?<\/system-reminder>/g, kind: "system_instruction" }, + { pattern: /[\s\S]*?<\/instructions?>/g, kind: "system_instruction" }, + { + pattern: /[\s\S]*?<\/project[- ]instructions?>/g, + kind: "system_instruction", + }, + ]; + for (const { pattern, kind } of regionPatterns) { + result = replacePattern(result, ensureGlobal(pattern), kind, addBlock); + } + result = extractFencedCodeBlocks(result, (content) => addBlock(content, "fenced_code")); const builtIns: CompiledPattern[] = [ diff --git a/tests/unit/compression/preserve-system-reminder.test.ts b/tests/unit/compression/preserve-system-reminder.test.ts new file mode 100644 index 0000000000..97ab9a38b7 --- /dev/null +++ b/tests/unit/compression/preserve-system-reminder.test.ts @@ -0,0 +1,114 @@ +/** + * Tests for #13453: preserve blocks from lossy compression. + * + * Agentic coding CLIs (Claude Code, Codex, etc.) inject project instructions + * into user-role messages wrapped in envelopes. + * Lossy compression engines (ultra, aggressive, caveman, etc.) were rewriting + * these instruction blocks as prose, dropping negations and breaking XML tags. + * + * The fix adds to the preservation patterns so they survive + * compression byte-identical. + */ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { extractPreservedBlocks } from "../../../open-sse/services/compression/preservation.ts"; + +const INSTRUCTION_BLOCK = ` +Project instructions (auto-injected by the coding agent CLI, role=user): + +# Deploy rules + +- NEVER run \`rm -rf\` on the target host. Always ask first. +- Do not push to \`main\` directly; open a PR. +- The backup files \`.app-prev-*\` must never be deleted. +- Never store the SSH password on disk. +- Always run \`npm test\` before \`npm run build\`. +- Do NOT edit files under \`/etc\` by hand. + +## Restart procedure + +\`\`\`bash +systemctl --user restart app.service +curl -sf http://127.0.0.1:20128/health +\`\`\` + +Docs: https://example.com/runbook +`; + +test("extractPreservedBlocks captures blocks verbatim", () => { + const userMessage = `Here is my request:\n${INSTRUCTION_BLOCK}\n\nPlease deploy the fix.`; + + const { text: tombstoned, blocks } = extractPreservedBlocks(userMessage); + + // The instruction block should be tombstoned (replaced with placeholder) + assert.ok( + !tombstoned.includes("NEVER run"), + "Original instruction text should be replaced with a placeholder" + ); + assert.ok(tombstoned.includes("Here is my request"), "Non-instruction text should remain"); + assert.ok(tombstoned.includes("Please deploy the fix"), "Trailing text should remain"); + + // The preserved block should contain the full instruction text + const instructionBlock = blocks.find((b) => b.kind === "system_instruction"); + assert.ok(instructionBlock, "Should find a preserved system_instruction block"); + assert.ok( + instructionBlock!.content.includes("NEVER run"), + "Preserved block should contain the full instruction text" + ); + assert.ok( + instructionBlock!.content.includes(""), + "Preserved block should include the opening tag" + ); + assert.ok( + instructionBlock!.content.includes(""), + "Preserved block should include the closing tag" + ); +}); + +test("extractPreservedBlocks captures blocks", () => { + const text = `Before\n\nDo NOT touch production.\n\nAfter`; + + const { text: tombstoned, blocks } = extractPreservedBlocks(text); + + const instructionBlock = blocks.find((b) => b.kind === "system_instruction"); + assert.ok(instructionBlock, "Should find a preserved system_instruction block"); + assert.ok( + instructionBlock!.content.includes("Do NOT touch production"), + "Preserved block should contain instruction text" + ); + assert.ok( + !tombstoned.includes("Do NOT touch production"), + "Instruction text should be tombstoned" + ); +}); + +test("extractPreservedBlocks captures blocks", () => { + const text = `Before\n\nNEVER delete the database.\n\nAfter`; + + const { blocks } = extractPreservedBlocks(text); + + const instructionBlock = blocks.find((b) => b.kind === "system_instruction"); + assert.ok(instructionBlock, "Should find a preserved system_instruction block"); + assert.ok( + instructionBlock!.content.includes("NEVER delete the database"), + "Preserved block should contain instruction text" + ); +}); + +test("non-instruction text outside is still compressible", () => { + const text = `Normal prose that can be compressed.\nDo NOT do X\nMore normal prose.`; + + const { text: tombstoned } = extractPreservedBlocks(text); + + // The prose around the instruction block should still be tombstoned + // (i.e. the prose can be compressed, but the instruction block is protected) + assert.ok( + tombstoned.includes("Normal prose that can be compressed"), + "Non-instruction prose should remain in the tombstoned text" + ); + assert.ok(tombstoned.includes("More normal prose"), "Trailing prose should remain"); + assert.ok( + !tombstoned.includes("Do NOT do X"), + "Instruction text should be replaced with placeholder" + ); +});