From a75f6b7ff722baf82bb39a1b45b51c62b717cd70 Mon Sep 17 00:00:00 2001 From: wassname <1103714+wassname@users.noreply.github.com> Date: Fri, 10 Apr 2026 20:41:47 +0000 Subject: [PATCH] fix: Infinity anchorTimestamp ghost block, prompt tag name, test cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - resolveAnchorTimestamp returns endTimestamp + 1 instead of Infinity - Validate timestamps are finite before creating blocks - Skip corrupted blocks in overlap checks and compression application - Include block timestamp range in overlap error messages - Filter out corrupted blocks on session restore - Fix prompt tag name: dcp-message-id → dcp-id to match injected tags - Remove duplicate test, keep corrupted-block resilience test --- prompts.ts | 6 +++--- pruner.test.ts | 55 +++++++++++++++++--------------------------------- 2 files changed, 21 insertions(+), 40 deletions(-) diff --git a/prompts.ts b/prompts.ts index 630f09e..e106e8b 100644 --- a/prompts.ts +++ b/prompts.ts @@ -13,7 +13,7 @@ You operate in a context-constrained environment. Manage context continuously to The ONLY tool you have for context management is \`compress\`. It replaces older conversation content with technical summaries you produce. -\`\` and \`\` tags are environment-injected metadata. Do not output them. +\`\` and \`\` tags are environment-injected metadata. Do not output them. THE PHILOSOPHY OF COMPRESS \`compress\` transforms conversation content into dense, high-fidelity summaries. This is not cleanup — it is crystallization. Your summary becomes the authoritative record of what transpired. @@ -114,7 +114,7 @@ You specify boundaries by ID using the injected IDs visible in the conversation: - \`mNNN\` IDs identify raw messages (3 digits, zero-padded, e.g. \`m001\`, \`m042\`) - \`bN\` IDs identify previously compressed blocks -Each message has an ID inside XML metadata tags like \`...\`. +Each message has an ID inside XML metadata tags like \`...\`. The ID tag appears at the end of the message it belongs to — it identifies the message above it, not the one below it. Treat these tags as boundary metadata only, not as tool result content. @@ -207,7 +207,7 @@ Prefer multiple short, closed ranges over one large range when several independe export const MANUAL_MODE_SYSTEM_PROMPT = ` You are operating in DCP manual mode for context management. -\`\` and \`\` tags are environment-injected metadata. Do not output them. +\`\` and \`\` tags are environment-injected metadata. Do not output them. In manual mode you do NOT proactively compress conversation content. Compression is a deliberate, user-directed action. diff --git a/pruner.test.ts b/pruner.test.ts index 6bbdebd..3448269 100644 --- a/pruner.test.ts +++ b/pruner.test.ts @@ -709,38 +709,30 @@ function findOrphanedToolUse(result: any[]): string | null { } // --------------------------------------------------------------------------- -// Test 10 — INFINITY ANCHOR BUG (regression test) +// Test 10 — CORRUPTED BLOCK WITH NULL/INFINITY TIMESTAMPS (resilience) // -// Previously, when a compression block's range extended to the end of the -// conversation, resolveAnchorTimestamp returned Infinity. This caused: -// 1. JSON serialization turned Infinity into null, corrupting saved state -// 2. Null timestamps in overlap checks caused false positives (every range -// appeared to overlap the ghost block) -// 3. The model entered a compression spiral, unable to consolidate blocks -// -// Fix: resolveAnchorTimestamp returns endTimestamp + 1 instead of Infinity. +// Blocks from older sessions may have null/Infinity timestamps due to JSON +// round-trip corruption. These blocks should be skipped during compression +// application and should not block new compress operations. // --------------------------------------------------------------------------- { - console.log("TEST 10: Infinity anchor timestamp regression"); + console.log("TEST 10: corrupted block with null/Infinity timestamps is skipped"); - // Conversation where the last message is at timestamp 4000. - // Compression block covers up to the end, so anchor should be 4001, not Infinity. const messages: any[] = [ - { role: "user", content: [{ type: "text", text: "read file" }], timestamp: 1000 }, - { role: "assistant", content: [{ type: "toolCall", id: "toolu_1", name: "read", arguments: {} }], timestamp: 2000 }, - { role: "toolResult", toolCallId: "toolu_1", toolName: "read", isError: false, content: [{ type: "text", text: "file data" }], timestamp: 3000 }, - { role: "user", content: [{ type: "text", text: "thanks" }], timestamp: 4000 }, + { role: "user", content: [{ type: "text", text: "hello" }], timestamp: 1000 }, + { role: "assistant", content: [{ type: "text", text: "hi" }], timestamp: 2000 }, + { role: "user", content: [{ type: "text", text: "bye" }], timestamp: 3000 }, ]; - // Block that extends to the end of conversation + // Block with corrupted timestamps (null from JSON round-trip) const state = makeState([ { id: 1, - topic: "file read", - summary: "File was read.", - startTimestamp: 1000, - endTimestamp: 4000, - anchorTimestamp: 4001, // Fixed: was Infinity before the bugfix + topic: "ghost block", + summary: "This block has corrupted timestamps.", + startTimestamp: null as any, // null from JSON deserialization of Infinity + endTimestamp: null as any, + anchorTimestamp: null as any, active: true, summaryTokenEstimate: 5, createdAt: Date.now(), @@ -757,23 +749,12 @@ function findOrphanedToolUse(result: any[]): string | null { console.log(` role="${m.role}" ts=${m.timestamp} content="${preview}"`); } - // The synthetic message timestamp must be finite (not Infinity) - const synthetic = result.find( - (m: any) => m.role === "user" && typeof m.content?.[0]?.text === "string" && m.content[0].text.includes("Compressed section") - ); - assert.ok(synthetic, "FAIL — no synthetic compressed message found"); - assert.ok( - Number.isFinite(synthetic.timestamp), - `FAIL — synthetic message has non-finite timestamp: ${synthetic.timestamp}` - ); - console.log(` PASS: synthetic message has finite timestamp (${synthetic.timestamp})`); + // All 3 original messages should survive (ghost block was skipped) + assert.strictEqual(result.length, 3, `FAIL — expected 3 messages, got ${result.length}`); + console.log(" PASS: corrupted block skipped, all original messages preserved"); console.log("TEST 10 PASSED\n"); -} - -// --------------------------------------------------------------------------- -// Test 11 — CORRUPTED BLOCK WITH NULL/INFINITY TIMESTAMPS (resilience) -// +}// // Blocks from older sessions may have null/Infinity timestamps due to JSON // round-trip corruption. These blocks should be skipped during compression // application and should not block new compress operations.