feat(session-query): checkpoint build round 1
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user