feat(session-query): checkpoint build round 1

This commit is contained in:
Hypatia May
2026-07-10 16:51:19 +08:00
parent 42ebbfdf8f
commit aa1dc0e2c7
40 changed files with 3174 additions and 102 deletions

View File

@@ -350,6 +350,43 @@ describe('SessionStore', () => {
expect(observed).toBe(0)
})
it('announces a cloned header only after the session leaves the store', async () => {
const ctx = new Context()
await ctx.plugin(SessionStore)
const observations: Array<{ id: string; live: boolean }> = []
ctx.on('session/removed', (header) => {
observations.push({ id: header.id, live: ctx.sessions.get(header.id) !== undefined })
header.createdAt = -1
})
const session = ctx.sessions.prepare(SessionId('removed'), { meta: { createdAt: 7 } })
const detach = ctx.sessions.enter(session)
detach()
await Promise.resolve()
await Promise.resolve()
expect(observations).toEqual([{ id: 'removed', live: false }])
expect(session.header.createdAt).toBe(7)
// A repeated disposer cannot remove or announce a later same-id owner.
const replacement = ctx.sessions.create(SessionId('removed'))
detach()
expect(ctx.sessions.get(replacement.id)).toBe(replacement)
expect(observations).toHaveLength(1)
})
it('contains rejected session/removed listeners during teardown', async () => {
const ctx = new Context()
await ctx.plugin(SessionStore)
ctx.on('session/removed', () => Promise.reject(new Error('observer failed')))
const session = ctx.sessions.prepare(SessionId('contained'))
const detach = ctx.sessions.enter(session)
expect(detach).not.toThrow()
await Promise.resolve()
await Promise.resolve()
expect(ctx.sessions.get(session.id)).toBeUndefined()
})
it('rolls back the session (and onAppend) when a session/created listener throws (P1-1)', async () => {
const ctx = new Context()
await ctx.plugin(SessionStore)

View File

@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
import type { SessionEvent, SurfaceEvent, SurfaceEventType } from '@deepseek-ai/dsh-session'
import { Session, SessionId, isSurfaceEligibleType, isSurfaceEvent } from '@deepseek-ai/dsh-session'
import { Session, SessionId, foldSurface, isSurfaceEligibleType, isSurfaceEvent } from '@deepseek-ai/dsh-session'
import { CallId } from '@deepseek-ai/dsh-llm'
/** Build a minimal session with turn boundaries and a single user message. */
@@ -14,6 +14,34 @@ function surfaceSession(): Session {
}
describe('SurfaceManager', () => {
it('shares exact nodes and nested replacement ranges with foldSurface', () => {
const s = new Session(SessionId('shared-fold'))
s.append('user/message', { content: [{ type: 'text', text: 'a' }], source: { kind: 'user' } }, { surfaceOp: 'append' })
s.append('user/message', { content: [{ type: 'text', text: 'b' }], source: { kind: 'user' } }, { surfaceOp: 'append' })
s.append('assistant/message', { turn: 1, step: 1, content: [{ type: 'text', text: 'summary' }] }, { surfaceOp: { op: 'replace', start: 0, end: 0 }, sourceEventSeqs: [0] })
s.append('assistant/message', { turn: 1, step: 2, content: [{ type: 'text', text: 'summary 2' }] }, { surfaceOp: { op: 'replace', start: 2, end: 1 }, sourceEventSeqs: [2, 1] })
const folded = foldSurface(s.events)
expect(folded.nodes).toEqual(s.surface.nodes)
expect(folded.replacements).toEqual([
{ seq: 2, start: 0, end: 0, shadowedSeqs: [0] },
{ seq: 3, start: 2, end: 1, shadowedSeqs: [2, 1] },
])
folded.nodes[0]!.next = 99
folded.replacements[0]!.shadowedSeqs.push(99)
expect(s.surface.nodes).toEqual([{ seq: 3, prev: null, next: null }])
expect(foldSurface(s.events).replacements[0]!.shadowedSeqs).toEqual([0])
})
it('foldSurface reports the same invalid replacement failures as the incremental manager', () => {
const s = new Session(SessionId('shared-fold-invalid'))
s.append('user/message', { content: [{ type: 'text', text: 'a' }], source: { kind: 'user' } }, { surfaceOp: 'append' })
s.append('assistant/message', { turn: 1, step: 1, content: [] }, { surfaceOp: { op: 'replace', start: 42, end: 0 }, sourceEventSeqs: [0] })
expect(() => foldSurface(s.events)).toThrow(/start seq 42 not found/)
expect(() => s.surface.nodes).toThrow(/start seq 42 not found/)
})
it('rebuilds a linked list from surfaceOp: append markers', () => {
const s = surfaceSession()
const nodes = s.surface.nodes