HTML comment format offered no benefit over XML tags since LLMs see raw
text, not rendered output. Revert to original <dcp-id>m001</dcp-id> and
<dcp-block-id>bN</dcp-block-id> format which is more token-efficient.
Add strip-before-inject in injectMessageIds: each context event strips any
existing dcp-id tags before appending the fresh one. For clean messages
this is idempotent (no cache bust). For model-echoed tags it removes the
duplicate on the next context event, breaking the accumulation loop that
reinforced echoing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
XML-style <dcp-id>m001</dcp-id> tags get echoed by models because they
look like semantic content. HTML comments <!-- dcp-id: m001 --> are
invisible in rendered markdown and models are trained to skip them.
Agents were generating visible messages like 'Almost there, will check again on next trigger' when nudged but deciding not to compress. This is because the nudges tell the agent to evaluate and act, but when it decides not to act, it narrates that decision instead of continuing silently.
When a compress call overlaps existing blocks, the error message now
includes the exact mNNN..mNNN ranges that are available for compression
(before, after, and between existing blocks). This lets the model
immediately retry with valid ranges instead of guessing.
- Fix Infinity anchorTimestamp repair on JSON round-trip (blocks extending
to end-of-conversation now restored correctly instead of discarded)
- Fix nextBlockId calculation to use max(id)+1 instead of array length
- Rename internal prompt tag <dcp-message-id> to <dcp-id>
- Add regression tests for corrupted-block resilience (PR #3, @wassname)
- Update README with Contributors section
- Update CHANGELOG for 1.0.7
- Bump package.json to 1.0.7
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 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
The root cause of the compression spiral (101 failures, 2 hours):
resolveAnchorTimestamp returned Infinity when no message followed the
compression range. Infinity corrupted JSON serialization (became null),
and null timestamps in JS overlap checks coerced to 0, making every
range appear to overlap the ghost block b7.
Fixes:
- resolveAnchorTimestamp returns endTimestamp + 1 instead of Infinity
- Validate all timestamps are finite before creating a block (fail fast)
- Skip blocks with non-finite timestamps in overlap checks and compression
- Include existing block range in overlap error messages (diagnostic)
- Filter out corrupted blocks on session restore
- Guard synthetic message timestamp creation against non-finite values
- Add regression tests for Infinity anchor and null-timestamp blocks
Backward and forward expansion now skip PI-internal passthrough roles
(compaction, branch_summary, custom_message) when scanning for paired
assistant↔toolResult messages, ensuring atomic removal. Added a
post-compression repair safety net and deep-cloning to prevent content
mutation across context events.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bug 1 (400 API error): applyCompressionBlocks could remove toolResult
messages while leaving their paired assistant(tool_use) message intact.
This produced invalid API sequences that Claude rejected with:
messages.N: tool_use ids found without tool_result blocks immediately after
The fix adds two boundary expansions before the splice:
- Expand lo backward: if messages[lo-1] is an assistant whose toolCall
ids appear as toolResult.toolCallId values inside [lo..hi], pull the
assistant into the range
- Expand hi forward: for assistants inside [lo..hi], extend hi to include
any consecutive toolResult messages that immediately follow hi
This ensures tool_use/tool_result pairs are always removed together.
Bug 2 (autocomplete crash): harden getArgumentCompletions to return an
explicitly typed AutocompleteItem[] | null, filter on value (not label,
matching pi-tui's internal contract), and add a typeof guard ensuring no
item with a non-string value can reach getBestAutocompleteMatchIndex.