round 2: pin checkpoint recognition at compile time

Move COMPACT_CHECKPOINT_SOURCE and isCompactCheckpointSource into a
cordis-free src/checkpoint.ts leaf, re-exported from the root so every
host-side consumer keeps its import. The client can then type-import the
leaf without reaching dsh-session's root, whose Context merge declares the
host sessions service and collides with the client's -- the dsh-commands/brand
shape. Renaming the plugin id now fails the client typecheck.

Also: keep recoverable summary text when a compact/summary mixes text with
other block types, and capture the seeded-history provenance seqs from the
pushes that produce them instead of deriving them by arithmetic.
This commit is contained in:
Hypatia May
2026-07-30 14:07:08 +08:00
parent 8e5c792b50
commit 91ee264e16
21 changed files with 197 additions and 108 deletions
@@ -225,7 +225,7 @@ describe('TranscriptAdapter', () => {
it.each([
['absent provenance', undefined],
['malformed summary blocks', compactSummary(1, [{ type: 'image', data: 'nope' }])],
['text-less summary blocks', compactSummary(1, [{ type: 'image', data: 'nope' }])],
['a whitespace-only summary', compactSummary(1, [{ type: 'text', text: ' ' }])],
['an empty summary array', compactSummary(1, [])],
['a non-array summary', compactSummary(1, 'plain string')],
@@ -240,6 +240,19 @@ describe('TranscriptAdapter', () => {
])
})
it('keeps the text of a mixed-block summary, skipping the blocks it cannot render', () => {
// ContentBlock is merge-extensible and the payload type is ContentBlock[],
// so a non-text block must not discard recoverable text beside it.
const adapter = new TranscriptAdapter()
adapter.reset([
compactSummary(1, [{ type: 'text', text: '可用摘要' }, { type: 'image', data: 'nope' }]),
checkpoint(2, 1, { start: 0, end: 0, sourceEventSeqs: [1, 0] }),
])
expect(adapter.nodes()).toEqual([
{ kind: 'compaction', seq: 2, time: 1_700_000_000_002, summary: '可用摘要' },
])
})
it('leaves the summary null when the checkpoint records no provenance at all', () => {
const adapter = new TranscriptAdapter()
adapter.reset([at(2, {